当前位置: 首页 > news >正文

Javascript/ES6+/Typescript重点内容篇——手撕(待总结)

在这里插入图片描述

前端核心知识点梳理与面试题详解

1. Promise

核心知识点

  • Promise 是异步编程的解决方案,用于处理异步操作
  • 三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)
  • 状态一旦改变就不会再变,从 pending 到 fulfilled 或 rejected
  • 常用方法:then()、catch()、finally()、all()、race()、allSettled() 等

面试题:实现 Promise.all

Promise.myAll = function(promises) {return new Promise((resolve, reject) => {// 判断传入的是否是可迭代对象if (!Array.isArray(promises)) {return reject(new TypeError('The input must be an array'));}const results = [];let completedCount = 0;const total = promises.length;if (total === 0) {return resolve(results);}promises.forEach((promise, index) => {// 确保每个元素都是 Promise 对象Promise.resolve(promise).then((value) => {results[index] = value;completedCount++;// 所有 Promise 都成功时才 resolveif (completedCount === total) {resolve(results);}},(reason) => {// 有一个 Promise 失败就立即 rejectreject(reason);});});});
};

面试题:实现 Promise 串行执行

// 实现一个函数,让多个Promise按顺序执行
function promiseSerial(tasks) {// 初始返回一个已 resolved 的 Promisereturn tasks.reduce((prev, current) => {return prev.then((results) => {// 等待当前任务执行完成return current().then((result) => {// 将结果添加到数组中return [...results, result];});});}, Promise.resolve([]));
}// 使用示例
const createTask = (time, value) => {return () => new Promise(resolve => {setTimeout(() => {console.log(value);resolve(value);}, time);});
};const tasks = [createTask(1000, '任务1'),createTask(500, '任务2'),createTask(800, '任务3')
];promiseSerial(tasks).then(results => {console.log('所有任务完成:', results);
});

2. 原型链

核心知识点

  • 每个对象都有 __proto__ 属性,指向其构造函数的 prototype
  • 构造函数有 prototype 属性,是其实例的原型
  • 原型链是由 __proto__ 连接而成的链式结构
  • 当访问对象的属性或方法时,会先在自身查找,找不到则沿原型链向上查找
  • Object.prototype 是原型链的终点,其 __proto__ 为 null

面试题:原型链相关输出题

function Foo() {getName = function() { console.log(1); };return this;
}Foo.getName = function() { console.log(2); };Foo.prototype.getName = function() { console.log(3); };var getName = function() { console.log(4); };function getName() { console.log(5); }// 以下输出结果是什么?
Foo.getName();          // 2 - 访问Foo的静态方法
getName();              // 4 - 函数声明提升后被变量声明覆盖
Foo().getName();        // 1 - Foo()执行后修改了全局getName
getName();              // 1 - 已经被Foo()修改
new Foo.getName();      // 2 - 优先级问题,相当于new (Foo.getName)()
new Foo().getName();    // 3 - 实例化后访问原型上的方法
new new Foo().getName();// 3 - 先实例化Foo,再调用其原型上的getName并实例化

面试题:实现 instanceof

function myInstanceof(left, right) {// 基本类型直接返回falseif (typeof left !== 'object' || left === null) return false;// 获取对象的原型let proto = Object.getPrototypeOf(left);// 遍历原型链while (true) {// 找到尽头仍未匹配,返回falseif (proto === null) return false;// 找到匹配的原型,返回trueif (proto === right.prototype) return true;// 继续向上查找proto = Object.getPrototypeOf(proto);}
}// 测试
function Person() {}
const p = new Person();
console.log(myInstanceof(p, Person)); // true
console.log(myInstanceof(p, Object)); // true
console.log(myInstanceof(p, Array));  // false
console.log(myInstanceof([], Array)); // true

3. 生成器(Generator)

核心知识点

  • 生成器函数使用 function* 声明,内部使用 yield 关键字
  • 调用生成器函数返回迭代器对象,而非直接执行函数体
  • 通过迭代器的 next() 方法控制函数执行,每次遇到 yield 暂停
  • next() 方法返回包含 valuedone 属性的对象
  • 可用于实现异步操作的同步化表达、自定义迭代器等

面试题:使用 Generator 实现异步操作

// 模拟异步操作
function fetchData(url) {return new Promise((resolve) => {setTimeout(() => {resolve(`数据: ${url}`);}, 1000);});
}// 使用Generator处理异步
function* getData() {console.log('开始请求数据1');const data1 = yield fetchData('url1');console.log('获取到数据1:', data1);console.log('开始请求数据2');const data2 = yield fetchData('url2');console.log('获取到数据2:', data2);return '所有数据获取完成';
}// 执行生成器
function runGenerator(gen) {const iterator = gen();function handleResult(result) {if (result.done) {console.log('最终结果:', result.value);return;}// 处理Promiseresult.value.then(data => {handleResult(iterator.next(data));});}handleResult(iterator.next());
}// 运行
runGenerator(getData);

4. 闭包

核心知识点

  • 闭包是函数及其捆绑的周边环境状态的引用的组合
  • 形成条件:函数嵌套、内部函数引用外部函数的变量、内部函数被外部引用
  • 作用:实现私有变量、模块化、柯里化、保存状态等
  • 注意:不当使用可能导致内存泄漏

面试题:实现防抖函数

function debounce(fn, delay, immediate = false) {let timer = null;let isInvoked = false;// 返回闭包函数return function(...args) {const context = this;// 如果存在定时器,清除它if (timer) {clearTimeout(timer);}// 立即执行的情况if (immediate && !isInvoked) {fn.apply(context, args);isInvoked = true;} else {// 延迟执行timer = setTimeout(() => {fn.apply(context, args);isInvoked = false;timer = null;}, delay);}};
}// 使用示例
const handleSearch = (keyword) => {console.log('搜索:', keyword);
};// 防抖处理,延迟500ms执行,不立即执行
const debouncedSearch = debounce(handleSearch, 500);// 模拟频繁调用
debouncedSearch('a');
debouncedSearch('ab');
debouncedSearch('abc'); // 只有最后一次会在500ms后执行

面试题:实现节流函数

function throttle(fn, interval) {let lastTime = 0;let timer = null;return function(...args) {const context = this;const now = Date.now();const remainingTime = interval - (now - lastTime);// 如果时间间隔已到,直接执行if (remainingTime <= 0) {if (timer) {clearTimeout(timer);timer = null;}fn.apply(context, args);lastTime = now;} else if (!timer) {// 否则设置定时器,确保最后一次一定会执行timer = setTimeout(() => {fn.apply(context, args);lastTime = Date.now();timer = null;}, remainingTime);}};
}// 使用示例
const handleScroll = () => {console.log('滚动事件触发');
};// 节流处理,每100ms最多执行一次
const throttledScroll = throttle(handleScroll, 100);// 监听滚动事件
window.addEventListener('scroll', throttledScroll);

5. 异步与事件循环

核心知识点

  • JavaScript 是单线程语言,通过事件循环实现异步
  • 事件循环:调用栈 -> 微任务队列 -> 宏任务队列 -> UI渲染
  • 微任务优先级高于宏任务,常见微任务:Promise.then/catch/finally、process.nextTick、MutationObserver
  • 常见宏任务:setTimeout、setInterval、DOM事件、I/O操作、setImmediate

面试题:事件循环输出题

console.log('1');setTimeout(function() {console.log('2');new Promise(function(resolve) {console.log('3');resolve();}).then(function() {console.log('4');});
}, 0);new Promise(function(resolve) {console.log('5');resolve();
}).then(function() {console.log('6');
});setTimeout(function() {console.log('7');new Promise(function(resolve) {console.log('8');resolve();}).then(function() {console.log('9');});
}, 0);console.log('10');// 输出顺序:1 5 10 6 2 3 4 7 8 9

6. Map

核心知识点

  • Map 是 ES6 新增的键值对集合
  • 与对象相比,Map 的键可以是任意类型,而对象的键只能是字符串或 Symbol
  • 常用方法:set()、get()、has()、delete()、clear()、size 属性
  • 可以通过 for…of 直接迭代,迭代顺序是插入顺序
  • 适合用于频繁添加/删除键值对的场景

面试题:实现 Map 的基本功能

class MyMap {constructor() {// 存储键值对的数组this.items = [];}// 向Map中添加元素set(key, value) {// 检查键是否已存在const index = this.items.findIndex(item => this.isEqual(item.key, key));if (index !== -1) {// 键存在则更新值this.items[index].value = value;} else {// 键不存在则添加新键值对this.items.push({ key, value });}return this;}// 获取指定键的值get(key) {const item = this.items.find(item => this.isEqual(item.key, key));return item ? item.value : undefined;}// 检查是否包含指定键has(key) {return this.items.some(item => this.isEqual(item.key, key));}// 删除指定键delete(key) {const index = this.items.findIndex(item => this.isEqual(item.key, key));if (index !== -1) {this.items.splice(index, 1);return true;}return false;}// 清空Mapclear() {this.items = [];}// 获取Map的大小get size() {return this.items.length;}// 判断两个键是否相等isEqual(a, b) {// 处理NaN的情况,NaN !== NaN但在Map中视为相等if (a !== a && b !== b) return true;// 处理+0和-0的情况,在Map中视为相等if (a === 0 && b === 0) return 1 / a === 1 / b;return a === b;}// 迭代器,用于for...of循环*[Symbol.iterator]() {for (const item of this.items) {yield [item.key, item.value];}}
}// 使用示例
const map = new MyMap();
map.set('name', '张三');
map.set(1, '数字1');
map.set(NaN, 'NaN值');
console.log(map.get('name')); // 张三
console.log(map.size); // 3
console.log(map.has(NaN)); // truefor (const [key, value] of map) {console.log(key, value);
}

7. 数组

核心知识点

  • 数组是有序的元素集合,具有动态长度
  • 常用方法:push()、pop()、shift()、unshift()、slice()、splice() 等
  • 高阶函数:map()、filter()、reduce()、forEach()、find()、some()、every() 等
  • 数组去重、扁平化、排序是常见操作

面试题:数组扁平化

// 方法1:使用递归
function flatten(arr, depth = Infinity) {if (depth <= 0) return arr.slice();return arr.reduce((acc, item) => {if (Array.isArray(item)) {// 递归扁平化,并减少深度return acc.concat(flatten(item, depth - 1));} else {return acc.concat(item);}}, []);
}// 方法2:使用ES6的flat方法
// const flattened = arr.flat(depth);// 测试
const nestedArray = [1, [2, [3, [4]], 5]];
console.log(flatten(nestedArray)); // [1, 2, 3, 4, 5]
console.log(flatten(nestedArray, 1)); // [1, 2, [3, [4]], 5]

面试题:数组去重

// 方法1:使用Set
function unique1(arr) {return [...new Set(arr)];
}// 方法2:使用filter和indexOf
function unique2(arr) {return arr.filter((item, index) => {return arr.indexOf(item) === index;});
}// 方法3:使用对象键值对
function unique3(arr) {const obj = {};return arr.filter(item => {// 处理不同类型但值相同的情况,如1和'1'const key = typeof item + item;if (!obj[key]) {obj[key] = true;return true;}return false;});
}// 方法4:使用Map
function unique4(arr) {const map = new Map();return arr.filter(item => {if (!map.has(item)) {map.set(item, true);return true;}return false;});
}// 测试
const testArray = [1, 2, 2, '3', '3', true, true, false, false, null, null, undefined, undefined, NaN, NaN];
console.log(unique1(testArray)); // Set方法能正确去重NaN
console.log(unique2(testArray)); // indexOf无法识别NaN,会保留重复的NaN
console.log(unique3(testArray)); // 能区分不同类型的值
console.log(unique4(testArray)); // Map方法也能正确去重NaN

以上梳理了前端核心知识点及常见面试题,涵盖了Promise、原型链、生成器、闭包、异步、事件循环、Map和数组等内容。这些知识点不仅是面试高频考点,也是日常开发中经常用到的核心概念,掌握这些内容对于前端工程师至关重要。

前端对象与字符串知识点梳理及面试题详解

一、对象(Object)

核心知识点

  • 对象是键值对的集合,键可以是字符串或Symbol,值可以是任意类型
  • 对象的创建方式:对象字面量{}new Object()、构造函数、Object.create()
  • 对象属性的访问方式:点语法(obj.key)和方括号语法(obj['key']
  • 可枚举属性与不可枚举属性:可枚举属性会被for...in遍历到
  • 对象的特性:writable(可写)、enumerable(可枚举)、configurable(可配置)
  • 常见方法:Object.keys()Object.values()Object.entries()Object.assign()

面试题:实现对象的深拷贝

function deepClone(obj, hash = new WeakMap()) {// 处理null和基本类型if (obj === null || typeof obj !== 'object') {return obj;}// 处理日期if (obj instanceof Date) {return new Date(obj);}// 处理正则if (obj instanceof RegExp) {return new RegExp(obj.source, obj.flags);}// 处理循环引用if (hash.has(obj)) {return hash.get(obj);}// 区分数组和对象const cloneObj = Array.isArray(obj) ? [] : {};hash.set(obj, cloneObj);// 遍历属性(包括Symbol键)Reflect.ownKeys(obj).forEach(key => {cloneObj[key] = deepClone(obj[key], hash);});return cloneObj;
}// 测试
const obj = {a: 1,b: { c: 2 },d: [3, 4],e: new Date(),f: /test/,[Symbol('g')]: 5
};
obj.self = obj; // 循环引用const cloned = deepClone(obj);
console.log(cloned);

面试题:实现对象的扁平化解构

function flattenObject(obj, prefix = '', result = {}) {for (const key in obj) {if (obj.hasOwnProperty(key)) {const value = obj[key];const newKey = prefix ? `${prefix}.${key}` : key;if (typeof value === 'object' && value !== null && !Array.isArray(value)) {// 递归处理嵌套对象flattenObject(value, newKey, result);} else {result[newKey] = value;}}}return result;
}// 测试
const nestedObj = {a: 1,b: {c: 2,d: {e: 3,f: 4}},g: 5
};console.log(flattenObject(nestedObj));
// 输出: { a: 1, 'b.c': 2, 'b.d.e': 3, 'b.d.f': 4, g: 5 }

面试题:实现对象的属性拦截(类似Vue2的响应式原理)

function observe(obj) {if (typeof obj !== 'object' || obj === null) {return;}// 遍历对象属性Object.keys(obj).forEach(key => {let value = obj[key];// 递归处理嵌套对象observe(value);// 重定义属性Object.defineProperty(obj, key, {get() {console.log(`获取属性${key}的值: ${value}`);return value;},set(newValue) {console.log(`设置属性${key}的值: ${newValue}`);// 处理新值为对象的情况observe(newValue);value = newValue;}});});
}// 测试
const data = {name: '张三',age: 20,address: {city: '北京'}
};observe(data);
data.name; // 触发get
data.age = 21; // 触发set
data.address.city = '上海'; // 触发get和set

二、字符串(String)

核心知识点

  • 字符串是字符的有序序列,在JavaScript中是不可变的
  • 常见创建方式:字符串字面量(''"")、模板字符串(` `)、new String()
  • 常用属性:length(长度)
  • 常用方法:
    • 查找:indexOf()lastIndexOf()includes()startsWith()endsWith()
    • 截取:slice()substring()substr()
    • 转换:toUpperCase()toLowerCase()trim()
    • 其他:split()replace()charAt()concat()
  • 模板字符串支持多行文本和变量插值(${}

面试题:实现字符串反转

// 方法1:使用数组方法
function reverseString1(str) {return str.split('').reverse().join('');
}// 方法2:使用for循环
function reverseString2(str) {let result = '';for (let i = str.length - 1; i >= 0; i--) {result += str[i];}return result;
}// 方法3:使用递归
function reverseString3(str) {if (str === '') {return '';} else {return reverseString3(str.substr(1)) + str.charAt(0);}
}// 测试
console.log(reverseString1('hello')); // 'olleh'
console.log(reverseString2('world')); // 'dlrow'
console.log(reverseString3('test'));  // 'tset'

面试题:实现字符串中的单词反转(不改变单词顺序)

function reverseWords(str) {// 分割单词(处理多个空格的情况)const words = str.split(/\s+/);// 反转每个单词const reversedWords = words.map(word => {return word.split('').reverse().join('');});// 拼接回字符串return reversedWords.join(' ');
}// 测试
console.log(reverseWords('Hello World')); // 'olleH dlroW'
console.log(reverseWords('I love JavaScript')); // 'I evol tpircSavaJ'
console.log(reverseWords('   Hello   there   ')); // '   olleH   ereht   '

面试题:实现千位分隔符格式化数字

function formatNumber(num) {// 处理非数字情况if (typeof num !== 'number' || isNaN(num)) {return '0';}// 转换为字符串并分割整数和小数部分const parts = num.toString().split('.');let integerPart = parts[0];const decimalPart = parts[1] || '';// 处理负数let sign = '';if (integerPart[0] === '-') {sign = '-';integerPart = integerPart.slice(1);}// 反转字符串便于处理let reversed = integerPart.split('').reverse().join('');let formatted = '';// 每三位添加一个逗号for (let i = 0; i < reversed.length; i++) {if (i !== 0 && i % 3 === 0) {formatted += ',';}formatted += reversed[i];}// 反转回来并拼接符号和小数部分formatted = sign + formatted.split('').reverse().join('');return decimalPart ? `${formatted}.${decimalPart}` : formatted;
}// 测试
console.log(formatNumber(1234567)); // '1,234,567'
console.log(formatNumber(1234567.89)); // '1,234,567.89'
console.log(formatNumber(-123456)); // '-123,456'
console.log(formatNumber(0)); // '0'

面试题:实现字符串匹配算法(KMP算法)

// 构建部分匹配表
function buildLPS(pattern) {const lps = new Array(pattern.length).fill(0);let len = 0; // 最长前缀后缀的长度for (let i = 1; i < pattern.length; ) {if (pattern[i] === pattern[len]) {len++;lps[i] = len;i++;} else {if (len !== 0) {len = lps[len - 1];} else {lps[i] = 0;i++;}}}return lps;
}// KMP匹配算法
function kmpSearch(text, pattern) {const n = text.length;const m = pattern.length;if (m === 0) return 0; // 空模式匹配开头const lps = buildLPS(pattern);let i = 0; // text的索引let j = 0; // pattern的索引while (i < n) {if (pattern[j] === text[i]) {i++;j++;if (j === m) {return i - j; // 找到匹配,返回起始索引}} else {if (j !== 0) {j = lps[j - 1];} else {i++;}}}return -1; // 未找到匹配
}// 测试
console.log(kmpSearch('ABC ABCDAB ABCDABCDABDE', 'ABCDABD')); // 15
console.log(kmpSearch('hello world', 'world')); // 6
console.log(kmpSearch('test string', 'xyz')); // -1

对象和字符串是JavaScript中最基础也是最常用的数据类型,掌握它们的特性和操作方法对于前端开发至关重要。上述知识点和面试题覆盖了常见的考点和实际开发需求,深入理解这些内容有助于提升代码质量和解决问题的能力。


以下是对前端核心知识点(Promise、原型链、闭包、异步、事件循环、Map、数组)的系统梳理及高频手写题目详解,结合面试考点和实现细节,助你高效备战。


一、核心知识梳理与面试详解

⚡ 1. Promise与异步编程
  • 核心机制
    • 三种状态Pending(等待)、Fulfilled(成功)、Rejected(失败),状态不可逆。
    • 链式调用.then()处理成功状态,.catch()捕获失败,.finally()无论成败均执行。
  • 手写Promise.all
    function myPromiseAll(promises) {return new Promise((resolve, reject) => {// 校验输入是否为可迭代对象if (typeof promises?.[Symbol.iterator] !== 'function') {reject(new TypeError('Argument is not iterable'));return;}const promiseArray = Array.from(promises);const results = new Array(promiseArray.length);let completedCount = 0;// 空数组直接返回if (promiseArray.length === 0) resolve(results);promiseArray.forEach((p, i) => {Promise.resolve(p)  // 包装非Promise值.then(res => {results[i] = res;  // 按索引存储结果if (++completedCount === promiseArray.length) resolve(results);}).catch(err => reject(err));  // 任一失败立即终止});});
    }
    
    得分点
    • 校验可迭代对象、处理空数组、非Promise包装。
    • 结果顺序与输入一致(避免push导致错乱)。

🔗 2. 原型链与继承
  • 核心概念
    • 原型(Prototype):构造函数(如Array)的prototype属性,存放共享方法(如Array.prototype.map)。
    • 原型链:对象通过__proto__向上查找属性,终点为Object.prototype.__proto__ === null
  • 继承实现
    // 寄生组合继承(最优解)
    function Parent(name) { this.name = name; }
    Parent.prototype.say = function() { console.log(this.name); };
    function Child(name, age) {Parent.call(this, name);  // 继承实例属性this.age = age;
    }
    Child.prototype = Object.create(Parent.prototype); // 继承方法
    Child.prototype.constructor = Child;  // 修复构造函数指向
    
    面试重点
    • 避免组合继承的两次调用父类构造函数问题。
    • ES6的class本质是语法糖(如super()调用父类构造)。

🔒 3. 闭包与生成器
  • 闭包(Closure)
    • 原理:函数嵌套,内层函数访问外层作用域的变量(即使外层已销毁)。
    • 应用
      // 自增ID生成器
      function createIdGenerator(init = 0) {let id = init;return () => ++id;  // 闭包保存id状态
      }
      const gen = createIdGenerator();
      gen(); // 1, gen(); // 2
      
      优势:状态隔离(多个生成器互不干扰)。
  • 生成器(Generator)
    • 特性function*定义,yield暂停执行,next()恢复执行。
    • 异步应用:配合co库实现类似async/await的异步控制(已逐渐被替代)。

🔁 4. 事件循环(Event Loop)
  • 执行顺序
    1. 同步代码 → 微任务(Promise.thenMutationObserver) → 宏任务(setTimeoutDOM事件)。
  • 经典面试题
    console.log('1');
    setTimeout(() => console.log('2'), 0);
    Promise.resolve().then(() => console.log('3'));
    console.log('4');
    // 输出:1 → 4 → 3 → 2
    
    原理:微任务优先级高于宏任务,同步代码执行完毕后清空微任务队列。

🗺️ 5. Map与数组高频操作
  • Map vs. Object

    • Map优势:键可为任意类型(对象、函数)、有序迭代、性能更优(频繁增删场景)。
    • APIset(), get(), has(), delete()
  • 数组重点方法

    方法用途是否改变原数组
    map()映射新数组
    filter()过滤满足条件的元素
    reduce()累计计算(如求和、扁平化)
    splice()删除/插入元素
    slice()截取子数组

    手写数组扁平化

    function flatten(arr) {return arr.reduce((acc, cur) => acc.concat(Array.isArray(cur) ? flatten(cur) : cur), []);
    }
    flatten([1, [2, [3]]]); // [1, 2, 3]
    

二、高频手撕题目详解

1. 防抖(Debounce)与节流(Throttle)
  • 防抖:连续触发时只执行最后一次:
    function debounce(fn, delay) {let timer;return (...args) => {clearTimeout(timer);timer = setTimeout(() => fn.apply(this, args), delay);};
    }
    // 场景:搜索框输入联想(停止输入300ms后请求)
    
  • 节流:固定间隔执行一次:
    function throttle(fn, interval) {let lastTime = 0;return (...args) => {const now = Date.now();if (now - lastTime >= interval) {fn.apply(this, args);lastTime = now;}};
    }
    // 场景:滚动事件(每200ms计算位置)
    
2. 手写深拷贝
function deepClone(obj, map = new Map()) {if (typeof obj !== 'object' || obj === null) return obj;if (map.has(obj)) return map.get(obj); // 解决循环引用const clone = Array.isArray(obj) ? [] : {};map.set(obj, clone);for (const key in obj) {if (obj.hasOwnProperty(key)) {clone[key] = deepClone(obj[key], map);}}return clone;
}

边界处理:循环引用(Map记录已拷贝对象)、函数和正则等特殊类型。


三、面试策略与复习建议

  1. 手写题要点
    • 先口述思路(如防抖的“清除定时器+延迟执行”),再写代码。
    • 处理边界:空输入、非预期类型(如Promise.all校验可迭代对象)。
  2. 框架结合
    • React:闭包在useCallback中的应用(依赖项变化才更新函数)。
    • Vue:响应式原理依赖Object.definePropertygetter/setter(类闭包状态保存)。
  3. 性能优化
    • 减少闭包滥用(避免内存泄漏),如事件监听后及时移除。
    • 数组操作优先map/filter(返回新数组,避免forEach副作用)。

本文完整题目及扩展解析参考:https://blog.csdn.net/weixin_48377020/article/details/132241816 | https://blog.csdn.net/T_iAn_T/article/details/145597660。


在 JavaScript 中,对象(引用类型)和字符串(基本类型)的拷贝机制存在本质差异,这直接影响开发中的数据处理逻辑。以下是关键知识点梳理和对比:


📦 一、对象的拷贝

1. 浅拷贝(Shallow Copy)
  • 特点:仅复制对象的第一层属性。嵌套对象仍共享内存地址。
  • 实现方式
    • Object.assign({}, obj)
    • 展开运算符 { ...obj }
    • 数组方法如 slice()concat()
  • 示例
    const obj = { a: 1, b: { c: 2 } };
    const shallowCopy = { ...obj };
    shallowCopy.b.c = 3; // 修改嵌套属性
    console.log(obj.b.c); // 3(原对象被影响)
    
2. 深拷贝(Deep Copy)
  • 特点:递归复制所有层级,新旧对象完全独立。
  • 实现方式
    • JSON.parse(JSON.stringify(obj))
      • ✅ 简单快捷
      • ❌ 无法处理函数、undefinedSymbolDate(转为字符串)、循环引用
    • 递归实现
      function deepClone(obj, hash = new WeakMap()) {if (typeof obj !== 'object' || obj === null) return obj;if (hash.has(obj)) return hash.get(obj); // 解决循环引用const clone = Array.isArray(obj) ? [] : {};hash.set(obj, clone);for (const key in obj) {if (obj.hasOwnProperty(key)) {clone[key] = deepClone(obj[key], hash);}}return clone;
      }
      
    • 特殊类型处理
      • Datenew Date(obj.getTime())
      • RegExpnew RegExp(obj)
      • Map/Set → 递归复制元素
    • 现代 APIstructuredClone()
      • ✅ 支持循环引用、DateMapSet
      • ❌ 不支持函数、DOM 节点
    • 第三方库_.cloneDeep(obj)(Lodash)

🔤 二、字符串的拷贝

  • 字符串是基本类型(Primitive Type),赋值时直接复制值,而非引用地址。
  • 示例
    const str1 = "hello";
    const str2 = str1; // 复制值
    str2 = "world";    // 修改不影响 str1
    console.log(str1); // "hello"
    
  • 特点
    • 无需深拷贝/浅拷贝概念,每次赋值均创建独立副本。
    • 操作(如拼接、切片)均返回新字符串,原字符串不变。

⚠️ 三、关键差异总结

特性对象(引用类型)字符串(基本类型)
拷贝机制赋值传递引用地址赋值直接复制值
修改影响浅拷贝时嵌套属性互相影响修改后原数据不变
深拷贝需求必需(需递归处理嵌套引用)无需
内存占用浅拷贝节省内存,深拷贝开销大每次修改均创建新副本

💡 四、实际应用建议

  1. 对象拷贝场景
    • 简单数据且无特殊类型 → JSON.parse(JSON.stringify())
    • 复杂对象(含循环引用、Map等)→ structuredClone() 或 Lodash 的 _.cloneDeep()
  2. 字符串操作
    • 直接赋值或使用 slice()substring() 等返回新字符串的方法。
  3. 性能优化
    • 避免对大对象频繁深拷贝,改用 不可变数据(Immutable.js)按需拷贝

完整实现代码及边界案例可参考:https://blog.csdn.net/qq_53353440/article/details/148548048、https://developer.mozilla.org/en-US/docs/Web/API/structuredClone。

http://www.xdnf.cn/news/1250299.html

相关文章:

  • W3D引擎游戏开发----从入门到精通【22】
  • 【科研绘图系列】R语言绘制瀑布图
  • sqli-labs靶场less40-less45
  • 012 网络—基础篇
  • 医疗AI中GPU部署的“非对等全节点架构“方案分析(上)
  • 如何创建一个vue项目
  • 5G随身WiFi怎么选?实测延迟/网速/续航,中兴V50适合商务,格行MT700适合短租、户外党~避坑指南+适用场景全解析
  • Git 分支管理:从新开发分支迁移为主分支的完整指南
  • 【数据结构初阶】--排序(四):归并排序
  • Linux基础命令的生产常用命令及其示例简单解释
  • 对接钉钉审批过程记录(C#版本)
  • C++与C语言实现Stack的对比分析
  • 基于 kubeadm 搭建 k8s 集群
  • Go语言数据类型深度解析:位、字节与进制
  • 深度学习(pytorch版)前言:环境安装和书籍框架介绍
  • 【运维进阶】DHCP服务配置和DNS域名解析
  • 基于串口实现可扩展的硬件函数 RPC 框架(附完整 Verilog 源码)
  • iOS混淆工具有哪些?在集成第三方 SDK 时的混淆策略与工具建议
  • docker容器临时文件去除,服务器容量空间
  • 力扣137:只出现一次的数字Ⅱ
  • 企业级Linux服务器安全:防火墙规则配置与Web/SSH服务优化指南
  • 进阶向:Python开发简易QQ聊天机器人
  • 微软的BitLocker加密
  • DM数据库的安全版本SYSDBA无法修改其他用户密码?
  • Go语言 单元测试
  • 企业通讯与营销技术融合创新:定制开发开源AI智能名片S2B2C商城小程序的协同价值研究
  • 【数字图像处理系列笔记】Ch03:图像的变换
  • dify之推送飞书群消息工作流
  • selenium操作指南
  • python中的推导式