🧑💻秒杀!前端常见手写题!-HowieCong
一、浅拷贝
-
浅拷贝是创建一个新对象,这个新对象有着原始对象属性值的一份精确拷贝
-
如果属性是基本数据类型,拷贝的就是基本数据类型的值
-
如果属性是引用数据类型,拷贝的就是内存地址,如果其中一个对象改变了这个地址,就会影响到另一个对象
-
实现浅拷贝的常见思路有使用扩展运算符、
Object.assign()方法、手动遍历对象属性等
二、实现方法
(1)使用扩展运算符(ES6)
- 扩展运算符可以将一个对象的所有可枚举属性展开到另一个对象中,实现浅拷贝
function shallowCopy(obj){
// 利用扩展运算符将 obj 的属性展开到一个新对象中
return {...obj};
}
// 测试
const originalObj1 = { a: 1, b: { c: 2 } };
const copiedObj1 = shallowCopy(originalObj1);
console.log(copiedObj1);
(2)使用Object.assign()方法
Object.assign()方法用于将一个或多个源对象的所有可枚举属性复制到目标对象,返回目标对象。这里将源对象复制到一个空对象上,实现浅拷贝
function shallowCopy(obj){
// 使用
return Object.assign({},obj);
}
// 测试
const originalObj2 = { a: 1, b: { c: 2 } };
const copiedObj2 = shallowCopy(originalObj2);
console.log(copiedObj2);
(3)手动遍历对象属性
1.Object.keys()实现
-
使用
Object.keys()方法来替代for...in循环结合hasOwnProperty()的方式 -
Object.keys()方法会返回一个由一个给定对象的所有可枚举属性组成的数组,并且只包含对象自身的属性,不包含原型链上的属性,这样就无需再手动检查属性是否为对象自身的属性,能简化代码逻辑
function shallowCopy(obj) {
// 首先判断传入的 obj 是否为对象,如果不是对象(比如是 null 或者基本数据类型),直接返回 obj
if (typeof obj!== 'object' || obj === null) {
return obj;
}
// 创建一个新对象,用于存储浅拷贝后的结果
const newObj = Array.isArray(obj)? [] : {};
// 使用 Object.keys() 方法获取 obj 对象自身的所有可枚举属性组成的数组
const keys = Object.keys(obj);
// 遍历这个属性数组
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
// 将原对象的属性值赋值给新对象对应的属性
newObj[key] = obj[key];
}
return newObj;
}
// 测试示例
const originalObj = { a: 1, b: { c: 2 } };
const copiedObj = shallowCopy(originalObj);
console.log(copiedObj);
2.for...in实现
- 通过
for...in循环遍历对象的所有可枚举属性,将属性和值添加到一个新对象中,实现浅拷贝
function shallowCopy(obj){
const newObj = {}
// 遍历对象的所有可枚举属性
for(let key in obj){
// 检查属性是否为对象自身的属性,而不是原型链上的属性
if(obj.hasOwnProperty(key)){
// 将属性是否为对象自身的属性,而不是原型的属性
newObj[key] = obj[key];
}
}
return newObj;
}
// 测试
const originalObj3 = { a: 1, b: { c: 2 } };
const copiedObj3 = shallowCopy(originalObj3);
console.log(copiedObj3);
(4)数组的浅拷贝
- 对于数组,可以使用
slice()或concat()方法实现浅拷贝
// 使用 slice方法
function shallowCopyArray1(arr){
return arr.slice();
}
// 测试
const originalArr1 = [1, [2, 3]];
const copiedArr1 = shallowCopyArray1(originalArr1);
console.log(copiedArr1);
// 使用concat()方法
function shallowCopyArray2(arr){
// concat方法用于合并两个或多个数组,这里将原数组和一个空数组合并,实现浅拷贝
return [].concat(arr);
}
// 测试
const originalArr2 = [1, [2, 3]];
const copiedArr2 = shallowCopyArray2(originalArr2);
console.log(copiedArr2);
三、讨论
(1)扩展运算符和Object.assign()方法实现浅拷贝的区别
-
语法上,扩展运算符更简洁直观
-
功能上,二者基本相同,但
Object.assign()可以同时合并多个源对象到目标对象 -
在处理
getter和setter时,Object.assign()会调用getter和setter,而扩展运算符不会
(2)手动遍历对象属性和Object.assign()的优点缺点
-
手动遍历的优点是可以精确控制拷贝过程,比如可以对特定属性进行特殊处理;缺点是代码量相对较多,容易出错
-
Object.assign()方法的优点是代码简洁,使用方便;缺点是对于复杂对象的处理可能不够灵活,并且会调用getter和setter
(3)浅拷贝和深拷贝的区别
-
浅拷贝只复制对象的一层属性,如果属性是引用类型,复制的是引用地址,修改原对象或拷贝对象的引用类型属性会相互影响
-
深拷贝会递归地复制对象的所有属性,包括嵌套的对象,创建一个完全独立的对象,修改原对象不会影响拷贝对象
(4)如何选择合适的浅拷贝方法
-
如果是简单的对象拷贝,且项目支持 ES6 语法,优先选择扩展运算符,因为它代码简洁
-
如果需要合并多个对象或在不支持 ES6 的环境中,使用
Object.assign()方法 -
如果需要对拷贝过程进行精细控制,比如过滤某些属性或对特定属性进行特殊处理,使用手动遍历对象属性的方法
-
对于数组,根据习惯选择
slice()或concat()方法
(5)Object.keys() 和 for...in 循环在遍历对象属性时的区别
-
Object.keys()只返回对象自身的可枚举属性组成的数组,不包含原型链上的属性 -
而
for...in循环会遍历对象自身的可枚举属性以及原型链上的可枚举属性 -
使用
Object.keys()可以避免遍历到原型链上的属性,在只需要处理对象自身属性时更合适
(6)对象的属性是 Symbol 类型,Object.keys() 不能获取到这些属性吗
-
Object.keys()方法只能获取对象的字符串类型的可枚举属性,不会获取Symbol类型的属性 -
如果需要获取
Symbol类型的属性,可以使用Object.getOwnPropertySymbols()方法
❓其他
1. 疑问与作者HowieCong声明
-
如有疑问、出错的知识,请及时点击下方链接添加作者HowieCong的其中一种联系方式或发送邮件到下方邮箱告知作者HowieCong
-
若想让作者更新哪些方面的技术文章或补充更多知识在这篇文章,请及时点击下方链接添加里面其中一种联系方式或发送邮件到下方邮箱告知作者HowieCong
-
声明:作者HowieCong目前只是一个前端开发小菜鸟,写文章的初衷只是全面提高自身能力和见识;如果对此篇文章喜欢或能帮助到你,麻烦给作者HowieCong点个关注/给这篇文章点个赞/收藏这篇文章/在评论区留下你的想法吧,欢迎大家来交流!