8

如何深度克隆一个对象

在我们日常工作中经常会遇到需要去克隆一个对象比如多个地方用到的公共的图表基本参数的配置

相信很多人会想到用 Object.assign, JSON.stringifyJSON.parse 方法去克隆一个对象,这个可以明确告诉大家这些都是些不靠谱的浅度克隆。

我们先来试一下 Object.assign 在控制台执行下列操作

clipboard.png

大家有没有发现联动了。关于此方法具体请参考文档
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

接下来我们看下 JSON.stringifyJSON.parse 克隆对象,同样在控制输入

clipboard.png

大家有没有发现什么异常?虽然 JSON.stringify(value[, replacer[, space]]) 可以处理但是太麻烦了,这个方法我就不多说了具体还是参考文档
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify

下面咋们来看一种稍微靠谱的一种方式。在本站搜的前几条中发现的。

function isArray (arr) {
    return Object.prototype.toString.call(arr) === '[object Array]';  
}
// 深度克隆
function deepClone (obj) {  
    if(typeof obj !== "object" && typeof obj !== 'function') {
        return obj;        //原始类型直接返回
    }
    var o = isArray(obj) ? [] : {}; 
    for(i in obj) {  
        if(obj.hasOwnProperty(i)){ 
            o[i] = typeof obj[i] === "object" ? deepClone(obj[i]) : obj[i]; 
        } 
    } 
    return o;
}

看是靠谱是真是假我们来验证一把 还是在控制台输入

clipboard.png

大家发现什么异常的了吗?

这个没有区分具体的对象,在此问下大家js的对象有哪些呢?相信一般人答不出来4个
[object Object], [object Array], [object Null], [object RegExp], [object Date], [object HTMLXXElement], [object Map],[object Set],... 等等一系列

检测类型使用 Object.prototype.toString.call(xxx)typeof

我们分析下上面对象中哪些是引用类型需要特殊处理呢?相信大家都不陌生了。[object Object][object Array]

好!详细大家思路有了,咋们用递归来实现一把吧!

const deepClone = function(obj) {
  // 先检测是不是数组和Object
  // let isMap = Object.prototype.toString.call(obj) === '[object Map];
  // let isSet = Object.prototype.toString.call(obj) === '[object Set];
  // let isArr = Object.prototype.toString.call(obj) === '[object Array]';
  let isArr = Array.isArray(obj);
  let isJson = Object.prototype.toString.call(obj) === '[object Object]';
  if (isArr) {
    // 克隆数组
    let newObj = [];
    for (let i = 0; i < obj.length; i++) {
      newObj[i] = deepClone(obj[i]);
    }
    return newObj;
  } else if (isJson) {
    // 克隆Object
    let newObj = {};
    for (let i in obj) {
      newObj[i] = deepClone(obj[i]);
    }
    return newObj;
  }
  // 不是引用类型直接返回
  return obj;
};

Object.prototype.deepClone = function() {
  return deepClone(this);
};
咋们先不考虑Map Set Arguments [object XXArrayBuffer] 对象了原理都是一样

各种情况分析完了才说算是真克隆
我们在控制台看下

  • 注意先要把方法在控制台输进去,在调试

clipboard.png

是不是解决了? 在此并没有结束。 专注的伙伴们相信发现了对象中包含了个 deepClone 方法,具体细节我们在此就不多说了,我们给 Object 添加了个 Object.prototype.deepClone方法导致了每个对象都有了此方法。

原则上我们不允许在原型链上添加方法的,因为在循环中 for in, Object.entries, Object.values, Object.keys 等方法会出现自定义的方法。

相信熟悉 Object 文档的伙伴人已经知道解决方案了,

Object.defineProperty 这个方法给大家带来了福音 具体参考 Object 文档。我们使用一个enumerable (不可枚举)属性就可以解决了。

在原来基础上添加以下代码即可。

Object.defineProperty(Object.prototype, 'deepClone', {enumerable: false});

在看控制台

clipboard.png

同样上面方法中也是无法克隆一个不可枚举的属性。

完整代码如下

const deepClone = function(obj) {
  // 先检测是不是数组和Object
  // let isArr = Object.prototype.toString.call(obj) === '[object Array]';
  let isArr = Array.isArray(obj);
  let isJson = Object.prototype.toString.call(obj) === '[object Object]';
  if (isArr) {
    // 克隆数组
    let newObj = [];
    for (let i = 0; i < obj.length; i++) {
      newObj[i] = deepClone(obj[i]);
    }
    return newObj;
  } else if (isJson) {
    // 克隆Object
    let newObj = {};
    for (let i in obj) {
      newObj[i] = deepClone(obj[i]);
    }
    return newObj;
  }
  // 不是引用类型直接返回
  return obj;
};

Object.prototype.deepClone = function() {
  return deepClone(this);
};
Object.defineProperty(Object.prototype, 'deepClone', {enumerable: false});
为了兼容低版本浏览器需要借助 babel-polyfill;

最后还是推荐lodash的cloneDeep方法。

好了,深度克隆介绍到此。


路边县
31 声望1 粉丝