浅拷贝与深拷贝及实现

浅拷贝与深拷贝

参考: 掘金文章

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, 原来的不变

完美,面试一大考点解决

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值