ps做网站,石家庄机票网站建设,惠城区城乡规划建设局网站,厦门it做网站最强基础问答问#xff1a;知道浅拷贝和深拷贝吗#xff1f;为什么要用深拷贝#xff1f;答#xff1a;拷贝#xff0c;可以认为是赋值#xff0c;对于 JavaScript 中的基础类型#xff0c;如 string, number, null, boolean, undefined, symbol 等#xff0c;在赋值给一个…基础问答问知道浅拷贝和深拷贝吗为什么要用深拷贝答拷贝可以认为是赋值对于 JavaScript 中的基础类型如 string, number, null, boolean, undefined, symbol 等在赋值给一个变量的时候是直接拷贝值给变量而对于引用类型如 object, array, function 等则会拷贝其引用地址。使用深拷贝是为了避免操作公共对象的时候影响到其他使用该对象的组件。扩展延伸一个拷贝函数可以直接评估出来你对 JavaScript 基础能力掌握水平。在理解浅拷贝和深拷贝前需先明确拷贝的本质。在 JavaScript 中数据类型分为基本类型string、number、boolean、null、undefined、symbol、bigint和引用类型object、array、function 等这两种类型在内存中存储方式是不一样的基本类型值直接存储在栈内存中赋值时直接拷贝值。引用类型值存储在堆内存中栈内存仅存储指向堆内存的引用地址赋值时仅拷贝引用地址而非实际值。所以根据这两种存储方式很容易想到浅拷贝和深拷贝的区别就在于 是否递归复制嵌套的引用类型。 这里给出一个简单的定义浅拷贝Shallow Copy仅复制对象的表层属性若属性值为引用类型如嵌套对象、数组则拷贝的是引用地址引用地址就是表层属性新旧对象共享嵌套数据。深拷贝Deep Copy递归复制对象的所有属性包括嵌套的引用类型新旧对象完全独立修改拷贝后的对象不会影响原始对象的数据。实现方式浅拷贝浅拷贝适用于无嵌套引用类型或无需独立嵌套数据的场景实现方式简单性能开销小。浅拷贝对象 Object.assign()Object.assign(target, ...sources) 方法将源对象的可枚举属性复制到目标对象最后返回的是目标对象使用这个方法时要注意该方法仅拷贝对象自身属性不包含继承属性嵌套的对象仅拷贝引用示例如下const obj { a: 1, b: { c: 2 } };const shallowCopy Object.assign({}, obj);// 测试基本类型属性修改不影响原对象shallowCopy.a 100;console.log(obj.a); // 输出1原对象不变// 测试嵌套对象修改会影响原对象shallowCopy.b.c 200;console.log(obj.b.c); // 输出200原对象被修改浅拷贝数组 Array.prototype.slice() 和 Array.prototype.concat()这两个方法返回的都是新数组不在原数组上操作示例如下const arr [1, [2, 3]];const shallowCopy1 arr.slice(0); // 方法1sliceconst shallowCopy2 [].concat(arr); // 方法2concat// 测试基本类型元素修改不影响原数组shallowCopy1[0] 100;console.log(arr[0]); // 输出1原数组不变// 测试嵌套数组修改会影响原数组shallowCopy2[1][0] 200;console.log(arr[1][0]); // 输出200原数组被修改扩展运算符 ...这个是 es6 新增的运算符可以用于对象和数组的浅拷贝语法相较于上面两种方式比较简单示例如下// 对象浅拷贝const obj { a: 1, b: { c: 2 } };const shallowObj { ...obj };// 数组浅拷贝const arr [1, [2, 3]];const shallowArr [...arr];深拷贝深拷贝适用于包含嵌套引用类型且需要完全独立副本的场景实现复杂度较高需处理递归、循环引用等边界情况。属于前端八股面试必须准备的一个问题。序列化方式拷贝 JSON.parse(JSON.stringify())利用 JSON 序列化与反序列化实现深拷贝语法简单多数时候够用。const obj { a: 1, b: { c: 2 }, d: [3, 4] };const deepCopy JSON.parse(JSON.stringify(obj));// 测试嵌套对象修改不影响原对象deepCopy.b.c 200;console.log(obj.b.c); // 输出2原对象不变但是这个方式有一定的局限性不能拷贝函数JSON不支持不能拷贝 undefined Symbol 类型不能处理循环引用不支持 BigInt 类型对于日期对象和正则对象有特殊处理解析后可能得不到我们想要的结果自定义实现拷贝函数思路遍历对象每一次遍历过程中判断是否是引用类型对象或数组如果是则递归的调用拷贝函数若不是则直接赋值进行下一步。function deepCopy(target) {// 基本类型直接返回if (target null || typeof target ! object) {return target;}// 区分数组和对象let copy;if (Array.isArray(target)) {copy [];} else {copy {};}// 遍历属性并递归拷贝for (const key in target) {if (target.hasOwnProperty(key)) {// 递归处理引用类型copy[key] deepCopy(target[key]);}}return copy;}// 测试const obj { a: 1, b: { c: 2 }, d: [3, 4] };const copyObj deepCopy(obj);copyObj.b.c 200;console.log(obj, copyObj, obj copyObj, obj.b.c); // 对比输出结果可以发现两个对象是不同的copyObj.d[0] 300;console.log(obj, copyObj, obj copyObj, obj.d[0]); // 同上但是这个没有处理边界情况主要是两种情况循环应用循环引用指对象引用自身如 obj.self obj直接递归会导致无限循环栈溢出。可以用 WeakMap 存储已拷贝的对象避免在递归过程中重复拷贝。特殊对象类似于 DateRegExp 的对象需要我们手动特殊处理根据类型直接 new完整的深拷贝示例function deepCopy(target, hash new WeakMap()) {// 基本类型直接返回if (target null || typeof target ! object) {return target;}// 处理循环引用若已拷贝过直接返回缓存的副本if (hash.has(target)) {return hash.get(target);}let copy;// 处理Dateif (target instanceof Date) {copy new Date(target);hash.set(target, copy);return copy;}// 处理RegExpif (target instanceof RegExp) {copy new RegExp(target.source, target.flags);copy.lastIndex target.lastIndex; // 保留lastIndex属性hash.set(target, copy);return copy;}// 处理数组和对象if (Array.isArray(target)) {copy [];} else {// 处理普通对象包括自定义对象copy new target.constructor(); // 保持原型链}// 缓存已拷贝的对象解决循环引用hash.set(target, copy);// 遍历属性并递归拷贝// 处理Mapif (target instanceof Map) {target.forEach((value, key) {copy.set(key, deepCopy(value, hash));});return copy;}// 处理Setif (target instanceof Set) {target.forEach(value {copy.add(deepCopy(value, hash));});return copy;}// 处理普通对象和数组的属性for (const key in target) {if (target.hasOwnProperty(key)) {copy[key] deepCopy(target[key], hash);}}return copy;}// 测试循环引用const obj { name: test };obj.self obj; // 循环引用const copyObj deepCopy(obj);console.log(copyObj.self copyObj, copyObj obj, obj, copyObj);// 测试特殊对象const date new Date();const copyDate deepCopy(date);console.log(copyDate instanceof Date, copyDate date, date, copyDate);const reg /abc/gim;reg.lastIndex 10;const copyReg deepCopy(reg);console.log(copyReg, reg);差异对比这里我简单总结一个表来让你快速理解二者异同对比方向 浅拷贝 深拷贝拷贝层级 仅拷贝对象表层属性 递归拷贝所有层级包括嵌套的引用类型内存占用 较小共享嵌套对象的内存 较大完全复制所有数据独立占用内存性能开销 低无需递归操作简单 高递归处理需处理边界情况拷贝前后对象的独立性 表层属性独立嵌套引用类型共享 完全独立新旧对象无任何关联适用场景 无嵌套引用类型、性能优先、无需独立嵌套数据的情况简单来说不需要前后独立的都可以直接用浅拷贝 有嵌套引用类型、需完全隔离数据、修改不能相互影响的情况实现复杂度 简单可通过原生方法或简单遍历实现 复杂需处理递归、循环引用、特殊对象类型面试追问直接使用 赋值算浅拷贝还是深拷贝都不是赋值运算符只是将一个值或者引用赋给一个变量对于基本类型赋值运算符是直接复制这个值给变量对于引用类型赋值运算符则是复制引用给变量而非对象本身。这个和浅拷贝的定义略有差异。实现一个浅拷贝函数思路就是直接遍历浅层对象第一层赋给新的对象。function shallowCopy(target) {// 区分目标是数组还是对象if (Array.isArray(target)) {const copy [];for (let i 0; i target.length; i) {copy[i] target[i];}return copy;} else if (target ! null typeof target object) {const copy {};// 仅拷贝自身可枚举属性for (const key in target) {if (target.hasOwnProperty(key)) {copy[key] target[key];}}return copy;} else {// 基本类型直接返回无需拷贝return target;}}// 测试const obj { a: 1, b: { c: 2 }, d: [3, 4] };const copyObj shallowCopy(obj);copyObj.b.c 200;console.log(obj.b.c); // 输出200嵌套对象共享引用深拷贝的时候怎么特殊处理函数类型函数属于引用类型通常不需要深拷贝因为函数体是改不了的通常直接复制引用就行了。如果面试时强烈要求你深拷贝可以直接使用 toString() eval 实现但可能随之而来的会将话题转到 eval 上来问词法作用域、严格模式、安全问题等等一般是来转换个话题。实际开发的时候有经常用这两种模式吗举个场景说明一下前端分页displayData 通常是直接通过 slice 获取原始列表的一部分数据由于不需要操作所以也不需要深拷贝