浅拷贝与深拷贝
参考: 掘金文章
JS类型及存储
深拷贝和浅拷贝的主要区别就是其在内存中的存储类型不同:
JS的五大基础类型 (undefined,boolean,number,string,null)
都是存储在栈内存
中,按值存放不可修改,所以拷贝的时候是直接复制值
JS的引用类型 (object, array, function...)
,其真实值存储在堆内存
中,变量存储的实际是指向堆内存地址的指针
,存储在栈内存
,所以比较、赋值的时候是复制指针,修改会影响到堆中的数据。
浅拷贝
浅拷贝即遍历原对象的第一层属性和值,存储到新的对象中。
function shallowCopy(src) {
var dst = {};
for (var prop in src) {
if (src.hasOwnProperty(prop)) {
dst[prop] = src[prop];
}
}
return dst;
}
在ES6中还可以使用对象解构来实现浅拷贝:
const newObj = {...obj}
这样拷贝出来, 第一层的值是基础类型的,没问题;但如果第一层属性的值有引用类型的值,那么在修改拷贝对象这些属性的值,仍会改变原对象
const obj = {a:1, b: { key: 2} }
const newObj = {...obj}
newObj.b.key = 3;
console.log(obj.b.key) // => 3, 被改变了
深拷贝
深拷贝就是希望把这种属性里仍包含引用类型的对象,完整的拷贝,使得拷贝后的值改动,不影响原来的对象。
既然基础类型的值不可变,引用类型的值是指针可变,那思路无非就是:遍历对象的属性与值,如果值为数组或对象,则递归遍历;如果是基础类型,则直接拷贝。
Zepto中的深拷贝源码实现:
/**
* 递归合并属性
* @params {object} target 目标对象
* @params {object} source 原对象
* @params {boolean} deep 是否深拷贝
*/
function extend(target, source, deep) {
for (let key in source) {
if (deep && (isPlainObject(source[key]) || !isArray(source[key]))) {
if (isPlainObject(source[key]) && !isPlainObject(target[key])) {
target[key] = {}
}
if (isArray(source[key]) && !isArray(target[key])) {
target[key] = []
}
// 执行递归
extend(target[key], source[key], deep)
} else if (source[key] !== undefined) {
target[key] = source[key]
}
}
}
$.extend = function (target) {
var deep, args = slice.call(arguments, 1);
// 第一个参数为布尔时,代表是否要深度合并
if (typeof target === 'boolean') {
deep = target;
target = args.shift();
}
args.forEach(function(arg) {
extend(target, arg, deep);
});
return target;
}
按照源码思路自己实现一波,巧妙在这个 for-in 既可遍历对象,又可遍历数组,还有记得根据情况初始化target的属性
function isPlainObject(arg) {return arg instanceof Object}
function isArray(arg) {return Array.isArray(arg)}
// 自己实现
function deepClone(target, source, deep) {
// 既可遍历对象,又可遍历数组
for (let key in source) {
if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
// 因为有数组和对象两种情况,要针对不同情况做初始化,否则递归时赋值会出错
if (isPlainObject(source[key]) && !isPlainObject(target[key])) {
target[key] = {}
}
if (isArray(source[key]) && !isArray(target[key])) {
target[key] = []
}
// 执行递归
deepClone(target[key], source[key], deep)
} else if (source[key] !== undefined) {
target[key] = source[key]
}
}
}
测试一下,使用VS Code调试功能,可以看清楚每一步的过程
let obj = {a: 1, b: {key: 2}, c: [1, 2, {key: 3}], d: {key: {key:4}}}
let newObj = {}
deepClone(newObj, obj, true)
console.log(newObj)
newObj.b.key = 100
console.log(obj.b.key) // 2, 原来的不变
newObj.c[2].key = 100
console.log(obj.c[2].key) // 3, 原来的不变
newObj.d.key.key = 100
console.log(obj.d.key.key) // 4, 原来的不变
完美,面试一大考点解决