摘要:对象拷贝,简而言之就是将对象再复制一份,然而,复制的办法不同将会失去不同的后果。

本文分享自华为云社区《js对象深浅拷贝,来,试试看!》,作者: 北极光之夜。。

一.速识概念:

对象拷贝,简而言之就是将对象再复制一份,然而,复制的办法不同将会失去不同的后果。比方间接给新变量赋值为一个对象:

  // 1.建一个对象  var obj = {    name: "北极光之夜。",    like: "aurora",  };  // 2. 间接将对象赋值给变量 clone  var clone = obj;  // 3.批改obj的like属性  obj.like = "wind";  // 4.输入 clone 对象  console.log(clone);

从输入后果能够看到,我明明扭转的是 obj 对象的属性,然而 clone 对象的属性也扭转了。这是因为,当创立 obj 对象时,它在堆内存中开拓了一块空间存储对象的内容。而当 clone 间接赋值为 obj 时,clone 并不会再从新开拓一块堆内存,而是 obj 跟 clone 说我把我这内存空间存储的对象的地址给你,这个地址存在栈内存中,你通过栈内存的地址找到堆内存里对象的内容,咱们共用就完事了。所以说, obj 和 clone 指向的都是同一块内容,不论谁改了对象的内容,他人再拜访都是改过之后的了。

所以这不是咱们想要的,我不想共用,我想要属于本人的一片天地,我命由我不禁你,所以这就须要浅拷贝和深拷贝了。

简略补充: 像一些根本数据类型的变量(Number Boolean String undefined null)被赋值时会间接在栈内存中开拓出了一个新的存储区域用来存储新的变量,不会如对象那样只是把援用给他人。

二.浅拷贝原理与罕用办法:

简略来说浅拷贝就是只拷贝一层。什么意思呢 ?比方我有一个对象 obj :

 var obj = {        name: "北极光之夜。",        like: "aurora",      };

我要把它拷贝给变量 b ,原理就是我再从新开拓一块内存,而后我间接看 obj 里有什么属性和值就间接复制一份,比方通过如下形式实现:

     // 1.建一个对象      var obj = {        name: "北极光之夜。",        like: "aurora",      };      // 2. 封装一个函数,实现传入一个对象返回一个拷贝后的新对象      function cloneObj(obj) {        let clone = {};        // 3.用 for  in 遍历obj的属性        for (let i in obj) {          clone[i] = obj[i];        }        return clone;      }      // 4.执行函数,将失去一个新对象      var clone = cloneObj(obj);      // 5.更改 obj 属性值      obj.like = "wind";      // 6.输入      console.log(clone);

后果:

能够看到,就是新建一个空对象,还是循环间接赋值给它,这时扭转 obj 的like属性值 ,新建的那个对象也不受影响了。然而,如果 obj 是上面这种模式的呢:

 var obj = {        name: "北极光之夜。",        like: "aurora",        num: {          a: "1",          b: "2",        },      };

此时再用下面那种办法就不行了,如果obj只扭转像 name 这种属性还没问题,然而当 obj 扭转得是像 num 这种援用类型(对象、数组都是援用类型)的数据时,拷贝的对象还是能被影响,因为浅拷贝只能拷贝一层,如果拷贝的对象里还有子对象的话,那子对象拷贝其是也只是失去一个地址指向而已。这通过下面代码也能看出,就一层循环而已。想要真的达到我命由我不禁天的话得用深拷贝,真正的刨根问底。深拷贝见第三大点。上面介绍下浅拷贝罕用的办法,当对象只有一层的时候还是用浅拷贝好。

浅拷贝罕用的办法:

1.第一种是次要利用 for in 遍历原对象的属性。

// 封装一个函数,实现传入一个对象返回一个拷贝后的新对象 function cloneObj(obj) {    let clone = {};    // 用 for  in 遍历obj的属性    for (let i in obj) {      clone[i] = obj[i];    }    return clone;  }

2.能够用Object.keys()办法:

Object.keys() 办法会返回一个由一个给定对象的本身可枚举属性组成的数组。

function cloneObj(obj) {    let clone = {};    for (let i of Object.keys(obj)) {      clone[i] = obj[i];    }    return clone;  }

3.能够用Object.entries()办法:

Object.entries()办法返回一个给定对象本身可枚举属性的键值对数组。

  function cloneObj(obj) {    let clone = {};    for (let [key, value] of Object.entries(obj)) {      clone[key] = value;    }    return clone;  }

4.可用Object.getOwnPropertyNames()配合forEach循环:

Object.getOwnPropertyNames()返回一个由它的属性形成的数组。

 function cloneObj(obj) {    let clone = {};    Object.getOwnPropertyNames(obj).forEach(function (item) {      clone[item] = obj[item];    });    return clone;  }

5.可用Object.defineProperty()办法:

Object.defineProperty(obj, prop, descriptor) 办法会间接在一个对象上定义一个新属性,或者批改一个对象的现有属性,并返回此对象。obj要定义属性的对象。prop要定义或批改的属性的名称或 Symbol。descriptor要定义或批改的属性描述符。

Object.getOwnPropertyDescriptor():返回指定对象上一个自有属性对应的属性描述符。
属性描述符:JS 提供了一个外部数据结构,用来形容对象的值、管制其行为。称为属性描述符。

function cloneObj(obj) {        let clone = {};        Object.getOwnPropertyNames(obj).forEach(function (item) {          // 获取本来obj每个属性修饰符          var des = Object.getOwnPropertyDescriptor(obj, item);          // 把属性修饰符赋值给新对象          Object.defineProperty(clone, item, des);        });        return clone;      }

还有很多办法,就不一一列举了

三.深拷贝常见办法:

深拷贝就不会像浅拷贝那样只拷贝一层,而是有多少层我就拷贝多少层,要真正的做到全部内容都放在本人新开拓的内存里。能够利用递归思维实现深拷贝。

1.能够如下实现,还是用 for in 循环,如果为属性对象则递归:

function cloneObj(obj) {        let clone = {};        for (let i in obj) {          // 如果为对象则递归更进一层去拷贝          if (typeof obj[i] == "object" && obj[i] != null) {            clone[i] = cloneObj(obj[i]);          } else {            clone[i] = obj[i];          }        }        return clone;      }

试一试看:

 // 1.建一个对象 var obj = {        name: "北极光之夜。",        like: "aurora",        age: {          a: 1,          b: 2,        },      };      // 2. 封装一个函数,实现传入一个对象返回一个拷贝后的新对象      function cloneObj(obj) {        let clone = {};        for (let i in obj) {          // 如果为对象则递归更进一层去拷贝          if (typeof obj[i] == "object" && obj[i] != null) {            clone[i] = cloneObj(obj[i]);          } else {            clone[i] = obj[i];          }        }        return clone;      }      // 4.执行函数,将失去一个新对象      var clone = cloneObj(obj);      // 5.更改 obj 属性值      obj.age.a = "666";      // 6.输入      console.log(clone);

后果如下,拷贝胜利,原对象扭转无奈使新对象也扭转:

2.如果对象外面有数组怎么办,数组也跟对象一样是援用类型,那么咱们能够在结尾加个判断它是对象还是数组,数组的话赋空数组,一样遍历拷贝:

 function cloneObj(obj) {        // 通过原型链判断 obj 是否为数组        if (obj instanceof Array) {          var clone = [];        } else {          var clone = {};        }        for (let i in obj) {          // 如果为对象则递归更进一层去拷贝          if (typeof obj[i] == "object" && obj[i] != null) {            clone[i] = cloneObj(obj[i]);          } else {            clone[i] = obj[i];          }        }        return clone;      }

试一试看:

var obj = {        name: "北极光之夜。",        like: "aurora",        age: {          a: [1, 2, 3],          b: 2,        },      };      // 2. 封装一个函数,实现传入一个对象返回一个拷贝后的新对象      function cloneObj(obj) {        // 先判断 obj 是否为数组        if (obj instanceof Array) {          var clone = [];        } else {          var clone = {};        }        for (let i in obj) {          // 如果为对象则递归更进一层去拷贝          if (typeof obj[i] == "object" && obj[i] != null) {            clone[i] = cloneObj(obj[i]);          } else {            clone[i] = obj[i];          }        }        return clone;      }      // 4.执行函数,将失去一个新对象      var clone = cloneObj(obj);      // 5.更改 obj 属性值      obj.age.a[1] = "666";      // 6.输入      console.log(clone);

后果没问题:

当然,也可用Array.isArray(obj)办法用于判断一个对象是否为数组。如果对象是数组返回 true,否则返回 false。

function cloneObj(obj) {        // 判断 obj 是否为数组        if (Array.isArray(obj)) {          var clone = [];        } else {          var clone = {};        }        for (let i in obj) {          // 如果为对象则递归更进一层去拷贝          if (typeof obj[i] == "object" && obj[i] != null) {            clone[i] = cloneObj(obj[i]);          } else {            clone[i] = obj[i];          }        }        return clone;      }

四.总结:

以上就是深浅拷贝的大抵内容啦。因为对象是援用类型,所以间接赋值对象给新变量,那么新变量指向的内存和原对象是一样的。所以咱们通过浅拷贝和深拷贝实现开拓本人的内存空间。而浅拷贝只拷贝一层,深拷贝拷贝全副。如果,文章有什么谬误的,恳请大佬指出。

点击关注,第一工夫理解华为云陈腐技术~