zoukankan      html  css  js  c++  java
  • JS 深拷贝与浅拷贝

    写在前面: 

      在了解深浅拷贝之前,我们先来了解一下堆栈

      堆栈是一种数据结构,在JS中

    • 栈:由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。

          读写快,但是存储的内容较少

    • 堆:一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收(垃圾回收机制)

            读写慢,但是可以存储较多的内容

         (!!注意:若堆中已动态分配的内存,在使用完之后由于某种原因没有被释放或者无法释放,就会造成系统内存的浪费,导致程序运行速度降低甚至崩溃,这种情况称为内存泄漏!!)

      JS数据按照在内存中的存储形式可以分为两种: 
    • 基本数据类型(存储在中):string,number,boolean,undefined,null,symbol,以及复杂类型的指针
    • 复杂数据类型(存储在中):object,array,function 等

                    栈内存和堆内存

       当我们创建变量并赋值的时候,如果是基本数据类型会直接存储在栈中,

      而复杂数据类型会存储在堆中, 当我们将复杂数据类型赋值给某个变量时,只是将该数据在堆中的地址,赋值给了这个变量(指针)。这个变量(指针)存储了一个指向堆中数据的地址,一般称之为指针

     浅拷贝

      堆栈中数据的拷贝,如果是基本数据类型,那么拷贝的就是数据;

     1         var a = 10;
     2         var b = a;
     3         console.log('改变前 打印变量 a b');
     4         console.log(a);  // 10
     5         console.log(b);  // 10
     6 
     7         var c = 10;
     8         var d = c;       // 拷贝的是数据
     9         d = 20;          // 修改拷贝之后的数据
    10         console.log("打印变量 c d");
    11         console.log(c);  // 10
    12         console.log(d);  // 20

      

       如果拷贝的书复杂数据类型,以对象为例,当我们想通过简单的赋值方式拷贝一个对对象时,

    例如:

     1         //复杂数据类型的拷贝
     2         var obj1 = { a: 10 };
     3         var obj2 = obj;
     4         console.log(obj1);  // { a: 10 }
     5         console.log(obj2); // { a: 10 }
     6         //到这里 我们可以看到obj1和obj2 的打印结果完全相同  也许我们完成了数据的拷贝,
     7         //但是当我们修改拷贝过来的对象的数据时就会出现一个问题
     8         obj2.a = 20;       // 修改拷贝之后的数据
     9         console.log(obj1);  // { a: 20 }
    10         console.log(obj2); // { a: 20 } 
    11         //此处我们将obj2的a 修改为了20   但是当我们打印这个对象时 , 发现 obj1 中的 a 也被改变了
    12         // 思考: 为什么会发生这种情况??

       

      分析: 文章开头关于堆栈的描述中,有提到过当我们新建对象并赋值给一个变量(var obj1 = { a: 10 })的时候,该变量存储的不是对象的数据,而是该对象在堆中的地址。因此当我们通过这种简单的方式(obj2 = obj;)拷贝复杂数据类型时,只是拷贝了指针中的地址而已,当你通过原引用修改了对象中的数据,另一个也会感知到这个对象的变化。这种行为被称为浅拷贝

      复杂数据类型通过普通方式(obj1=obj2)拷贝的是指针,两个指针引用地址相同,读取操作的都是同一个数据。  

      一般情况下,等号赋值,函数传参,都是浅拷贝,也就是只拷贝了数据的地址。

     1         let foo = {title: "hello obj"}
     2 
     3         // 等号赋值
     4         let now = foo;
     5         now.title = "this is new title";
     6 
     7         console.log(foo.title);       // this is new title
     8         console.log(now.title);       // this is new title
     9 
    10         // 函数传参
    11         function change(o) {
    12             o.title = "this is function change title";
    13         }
    14         change(foo);
    15         console.log(foo.title);       // this is function change title

    如何实现深拷贝?

      所谓对象的拷贝,其实就是基于复杂数据在拷贝时的异常处理,我们将复杂数据的默认拷贝定义为浅拷贝;就是只拷贝复杂数据的地址,而不拷贝值。那么与之对应的就是不拷贝地址,只拷贝值,也被称为深拷贝。

    1.函数递归方式

     1         //代码分析:  形参obj 代表被拷贝目标,  调用函数 传入拷贝目标,  
     2         //      通过Array.isArray(obj)判断obj的类型是否为数组
     3         //      通过 for in 遍历拷贝目标, 
     4         //      使用 typeof 判断其每一个元素或者属性, 是否为obj类型(typeof  Array/Object  返回值皆为object)
     5         //          若该属性/元素, 部位null 并且 typeof返回值为object,  则代表其为复杂数据类型, 递归调用 deepCopy(obj[key]),继续拷贝其内部
     6         //          否则: 代表该元素非 数组  非对象, 为基本数据类型/函数 等  , 直接赋值拷贝即可
     7         //      最后返回拷贝完成的result ,函数执行完毕 
     8         function deepCopy(obj) {
     9             var result = Array.isArray(obj) ? [] : {};
    10             for (var key in obj) {
    11                 if (typeof obj[key] === 'object' && obj[key] !== null) {
    12                     result[key] = deepCopy(obj[key]); //递归复制
    13                 } else {
    14                     result[key] = obj[key];
    15                 }
    16             }
    17             return result;
    18         }

     2.利用JS中对JSON的解析方法

        什么是JSON?   

          JSON( JavaScript Object Notation) 是一种轻量级的存储和传输数据的格式。经常在数据从服务器发送到网页时使用。

        JavaScript   JSON方法

          JSON.stringify(value)  方法用于将 JavaScript 值转换为 JSON 字符串,并返回该字符串。

          JSON.parse(value)      用于将一个 JSON 字符串转换为对象 并返回该对象。

     1         let obj = {
     2             title: {
     3                 sTitle: 0,
    4 list: [1, 2, { a: 3, b: 4 }] 5 } 6 } 7 let obj2 = JSON.parse(JSON.stringify(obj)); 8 //通过JSON的方式对数据进行处理转换时, 不是改变原数据, 而是在内存中开辟一个新空间来存储转换的数据, 9 //这样两次转换后, 返回的数据 ,与原数据内容相同但是存储地址不同, 不存在引用关系 10 console.log(obj,obj2); 11 // 深拷贝成功 12 console.log(foo === now); // false

     

       缺陷 : 受json数据的限制,无法拷贝函数,undefined,NaN属性

     1         let obj={
     2             a:10,
     3             b:[1,2,3,{c:10}],
     4             d:undefined,
     5             e(){
     6                 console.log(this.a);
     7             },
     8             f:NaN
     9         }
    10         let obj2 = JSON.parse(JSON.stringify(obj));
    11         console.log(obj,obj2);

     

     3.利用ES6 提供的 Object.assign()    

       只能可以拷贝一层数据,无法拷贝多层数据,内层依然为浅拷贝

     1 let foo = {
     2     title:{
     3         show:function(){},
     4         num:NaN,
     5         empty:undefined
     6     }
     7 }
     8 
     9 let now = {};
    10 
    11 Object.assign(now, foo);
    12 
    13 console.log(foo);   // {title: {{num: NaN, empty: undefined, show: ƒ}}}
    14 console.log(now);   // {title: {{num: NaN, empty: undefined, show: ƒ}}}
    15 
    16 // 外层对象深拷贝成功
    17 console.log(foo === now);                // false
    18 // 内层对象依然是浅拷贝
    19 console.log(foo.title === now.title);    // true 

    4. 利用ES6 提供的展开运算符:...

     1 let foo = {
     2     title:{
     3         show:function(){},
     4         num:NaN,
     5         empty:undefined
     6     }
     7 }
     8 
     9 let now = {...foo};
    10 
    11 console.log(foo);   // {title: {{num: NaN, empty: undefined, show: ƒ}}}
    12 console.log(now);   // {title: {{num: NaN, empty: undefined, show: ƒ}}}
    13 
    14 // 外层对象深拷贝成功
    15 console.log(foo === now);                // false
    16 // 内层对象依然是浅拷贝
    17 console.log(foo.title === now.title);    // true 

    5.使用函数库lodash中的cloneDeep()方法

    使用方法:

    1.下载模块

    1    cnpm i lodash --save 
    2    yarn add lodash

    2.引入模块

    1    import _ from 'lodash'

    3.使用

    1    let obj1 = loodash.cloneDeep(obj) 

    6. 使用immutable-js

     

    其他(参考用, 仍可完善)

     1 function cloneObj(source, target) {
     2     // 如果目标对象不存在,根据源对象的类型创建一个目标对象
     3     if (!target) target = new source.constructor();
     4     // 获取源对象的所有属性名,包括可枚举和不可枚举
     5     var names = Object.getOwnPropertyNames(source);
     6     // 遍历所有属性名
     7     for (var i = 0; i < names.length; i++) {
     8         // 根据属性名获取对象该属性的描述对象,描述对象中有configurable,enumerable,writable,value
     9         var desc = Object.getOwnPropertyDescriptor(source, names[i]);
    10         // 表述对象的value就是这个属性的值
    11         // 判断属性值是否不是对象类型或者是null类型
    12         if (typeof desc.value !== "object" || desc.value === null) {
    13             // 定义目标对象的属性名是names[i],值是上面获取该属性名的描述对象
    14             // 这样可以将原属性的特征也复制了,比如原属性是不可枚举,不可修改,这里都会定义一样
    15             Object.defineProperty(target, names[i], desc);
    16         } else {
    17             // 新建一个t对象
    18             var t = {};
    19             // desc.value 就是源对象该属性的值
    20             // 判断这个值是什么类型,根据类型创建新对象
    21             switch (desc.value.constructor) {
    22                 // 如果这个类型是数组,创建一个空数组
    23                 case Array:
    24                     t = [];
    25                     break;
    26                 // 如果这个类型是正则表达式,则将原值中正则表达式的source和flags设置进来
    27                 // 这两个属性分别对应正则desc.value.source 正则内容,desc.value.flags对应修饰符
    28                 case RegExp:
    29                     t = new RegExp(desc.value.source, desc.value.flags);
    30                     break;
    31                 // 如果是日期类型,创建日期类型,并且把日期值设置相同
    32                 case Date:
    33                     t = new Date(desc.value);
    34                     break;
    35                 default:
    36                     // 如果这个值是属于HTML标签,根据这个值的nodeName创建该元素
    37                     if (desc.value instanceof HTMLElement)
    38                         t = document.createElement(desc.value.nodeName);
    39                     break;
    40             }
    41             // 将目标元素,设置属性名是names[i],设置value是当前创建的这个对象
    42             Object.defineProperty(target, names[i], {
    43                 enumerable: desc.enumerable,
    44                 writable: desc.writable,
    45                 configurable: desc.configurable,
    46                 value: t
    47             });
    48             // 递归调用该方法将当前对象的值作为源对象,将刚才创建的t作为目标对象
    49             cloneObj(desc.value, t);
    50         }
    51     }
    52     return target;
    53 }
  • 相关阅读:
    ldap集成jenkins
    自动发现实现url+响应时间监控
    5秒跳转
    String的使用
    数字货币转换为中文货币
    字符串的使用(string,StringBuffer,StringBuilder)
    修饰符
    类的继承,抽象类,接口,方法重写和重载
    类的使用
    java中的输入流(Scanner),数据类型,运算符,switch,数组的用法
  • 原文地址:https://www.cnblogs.com/rookieKong/p/13621746.html
Copyright © 2011-2022 走看看