纯牛奶645
纯牛奶645
  • 发布:2018-01-05 13:17
  • 更新:2018-01-05 13:17
  • 阅读:1892

js实现深拷贝

分类:Native.js

type函数 首先我们要实现一个getType函数对元素进行类型判断,关于元素的类型判断, js中typeof和instanceof详解 ,这里用一个更简便的方法,直接调用Object.prototype.toString 方法。

  function getType(obj){  
       //tostring会返回对应不同的标签的构造函数  
       var toString = Object.prototype.toString;  
       var map = {  
          '[object Boolean]'  : 'boolean',   
          '[object Number]'   : 'number',   
          '[object String]'   : 'string',   
          '[object Function]' : 'function',   
          '[object Array]'    : 'array',   
          '[object Date]'     : 'date',   
          '[object RegExp]'   : 'regExp',   
          '[object Undefined]': 'undefined',  
          '[object Null]'     : 'null',   
          '[object Object]'   : 'object'  
      };  
      if(obj instanceof Element) {  
           return 'element';  
      }  
      return map[toString.call(obj)];  
   }  

深拷贝(deepClone)

对于一个引用类型,如果直接将它赋值给另一个变量,由于这两个引用指向同一个地址,这时改变其中任何一个引用,另一个都会受到影响。当我们想复制一个对象并且切断与这个对象的联系,就要使用深拷贝。对于一个对象来说,由于可能有多层结构,所以我们可以使用递归来解决这个问题

  function deepClone(data){  
       var type = getType(data);  
       var obj;  
       if(type === 'array'){  
           obj = [];  
       } else if(type === 'object'){  
           obj = {};  
       } else {  
           //不再具有下一层次  
           return data;  
       }  
       if(type === 'array'){  
           for(var i = 0, len = data.length; i < len; i++){  
               obj.push(deepClone(data[i]));  
           }  
       } else if(type === 'object'){  
           for(var key in data){  
               obj[key] = deepClone(data[key]);  
           }  
       }  
       return obj;  
   }

对于function类型,这里是直接赋值的,还是共享一个内存值。这是因为函数更多的是完成某些功能,有个输入值和返回值,而且对于上层业务而言更多的是完成业务功能,并不需要真正将函数深拷贝。

广度优先遍历

上面是使用递归来进行深拷贝,显然我们可以使用树的广度优先遍历来实现

//这里为了阅读方便,只深拷贝对象,关于数组的判断参照上面的例子

   function deepClone(data){  
       var obj = {};  
       var originQueue = [data];  
       var copyQueue = [obj];  
       //以下两个队列用来保存复制过程中访问过的对象,以此来避免对象环的问题(对象的某个属性值是对象本身)  
       var visitQueue = [];  
       var copyVisitQueue = [];  
       while(originQueue.length > 0){  
           var _data = originQueue.shift();  
           var _obj = copyQueue.shift();  
           visitQueue.push(_data);  
           copyVisitQueue.push(_obj);  
           for(var key in _data){  
               var _value = _data[key]  
               if(typeof _value !== 'object'){  
                   _obj[key] = _value;  
               } else {  
                   //使用indexOf可以发现数组中是否存在相同的对象(实现indexOf的难点就在于对象比较)  
                   var index = visitQueue.indexOf(_value);  
                   if(index >= 0){  
                       _obj[key] = copyVisitQueue[index];  
                   }  
                   originQueue.push(_value);  
                   _obj[key] = {};  
                   copyQueue.push(_obj[key]);  
               }  
           }  
       }  
       return obj;  
   }

JSON

深拷贝对象还有另一个解决方法,在对象中不含有函数的时候,使用JSON解析反解析就可以得到一个深拷贝对象

来源:http://blog.csdn.net/sysuzhyupeng/article/details/70340598

js中对象的复制,浅复制(浅拷贝)和深复制(深拷贝)
在js中,我们经常复制一个对象,复制数据,那么就会有人问了,怎么复制,今天鹏哥就带来js中的复制方法。

JS中对象分为基本类型和复合(引用)类型,基本类型存放在栈内存,复合(引用)类型存放在堆内存。

堆内存用于存放由new创建的对象,栈内存存放一些基本类型的变量和对象的引用变量。

至于堆内存和栈内存的区别介绍,你们可以百度看看。

下面开始讲解复制:

这种只是简单的变量,内存小,我们直接复制不会发生引用。

var a=123;  
var b=a;  
a=123456;  
alert(a); //123456  
alert(b); //123  

//或者是  

var a='afafas';  
var b=a;  
a='fgfdsdsgs';  
alert(a); //fgfdsdsgs  
alert(b); //afafas

而对于对象这种内存占用比较大的来说,直接让复制的东西等于要复制的,那么就会发生引用,因为这种复制,只是将复制出来的东西的指向指向了要复制的那个东西,简单的说,就是两个都同时指向了一个空间,如果改变其中一个,另一个也会发生变化。这就发生了引用。

引用只发生在对象的身上:

var arr1=[1,2,3];  
var arr2=arr1;  
arr1.push(4);  
alert(arr1); //1234  
alert(arr2); //1234  
arr2.push(5);  
alert(arr1); //12345  
alert(arr2); //12345

那么对于数组,ES6我们复制有新的两种方法,不会发生引用。

第一种:Array.from(要复制的数组);

var arr1=[1,2,3];  
var arr2=Array.from(arr1);  
arr1.push(4);  
alert(arr1);  //1234  
alert(arr2);  //123  
arr2.push(5);  
alert(arr1);  //1234  
alert(arr2);  //1235

第二种:...

var arr1=[1,2,3];  
var arr2=[...arr1];  
arr1.push(4);  
alert(arr1);  //1234  
alert(arr2);  //123  
arr2.push(5);  
alert(arr1);  //1234  
alert(arr2);  //1235

第二种这个方法也可以用在函数的行参上面。

function show(...arr1){  //直接来复制arguments这个伪数组,让它变成真正的数组,从而拥有数组的方法。  
  alert(arr1); //1234  
  arr1.push(5);  
  alert(arr1); //12345  
}  
show(1,2,3,4)  
或者是通过循环来复制:  

var arr1=[1,2,3,4];  
var arr2=[];  
for(var i=0; i<arr1.length; i++){  
  arr2[i]=arr1[i];  
}  
arr1.push(5);  
arr2.push(6);  
alert(arr1); //12345  
alert(arr2); //12346  

//或者是json  

var json1={"name":"鹏哥","age":24,"job":"前端开发"};  
var json2={};  
for(var name in json1){  
  json2[name]=json1[name];  
}  
alert(JSON.stringify(json1)); //{"name":"鹏哥","age":24,"job":"前端开发"}  
alert(JSON.stringify(json2)); //{"name":"鹏哥","age":24,"job":"前端开发"}  
json1.a=1;  
json2.b=2;  
alert(JSON.stringify(json1)); //{"name":"鹏哥","age":24,"job":"前端开发","a":1}  
alert(JSON.stringify(json2)); //{"name":"鹏哥","age":24,"job":"前端开发","b":2}

深复制和浅复制最根本的区别在于是否是真正获取了一个对象的复制实体,而不是引用,

1)深复制在计算机中开辟了一块内存地址用于存放复制的对象,
  2)而浅复制仅仅是指向被复制的内存地址,如果原地址中对象被改变了,那么浅复制出来的对象也会相应改变。

所谓的浅复制,只是拷贝了基本类型的数据,而引用类型数据,复制后也是会发生引用,我们把这种拷贝叫做“(浅复制)浅拷贝”。

看例子:

var json1 = {"a":"李鹏","arr1":[1,2,3]}  
function copy(obj1) {  
    var obj2 = {};  
    for (var i in obj1) {  
      obj2[i] = obj1[i];  
    }  
    return obj2;  
}  
var json2 = copy(json1);  
json1.arr1.push(4);  
alert(json1.arr1);  //1234  
alert(json2.arr1)  //1234

而深复制的话,我们要求复制一个复杂的对象,那么我们就可以利用递归的思想来做,及省性能,又不会发生引用。

看例子:

var json1={"name":"鹏哥","age":18,"arr1":[1,2,3,4,5],"string":'afasfsafa',"arr2":[1,2,3,4,5],"arr3":[{"name1":"李鹏"},{"job":"前端开发"}]};  
var json2={};  
function copy(obj1,obj2){  
  var obj2=obj2||{}; //最初的时候给它一个初始值=它自己或者是一个json  
  for(var name in obj1){  
    if(typeof obj1[name] === "object"){ //先判断一下obj[name]是不是一个对象  
      obj2[name]= (obj1[name].constructor===Array)?[]:{}; //我们让要复制的对象的name项=数组或者是json  
      copy(obj1[name],obj2[name]); //然后来无限调用函数自己 递归思想  
    }else{  
      obj2[name]=obj1[name];  //如果不是对象,直接等于即可,不会发生引用。  
    }  
  }  
  return obj2; //然后在把复制好的对象给return出去  
}  
json2=copy(json1,json2)  
json1.arr1.push(6);  
alert(json1.arr1);  //123456  
alert(json2.arr1);  //12345

作者:虚幻的锈色
链接:https://www.jianshu.com/p/0d7bd31ccf43
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

JavaScript 中对象的深拷贝
在JavaScript中,对对象进行拷贝的场景比较常见。但是简单的复制语句只能对对象进行浅拷贝,即复制的是一份引用,而不是它所引用的对象。而更多的时候,我们希望对对象进行深拷贝,避免原始对象被无意修改
对象的深拷贝与浅拷贝的区别如下:
浅拷贝:仅仅复制对象的引用,而不是对象本身;
深拷贝:把复制的对象所引用的全部对象都复制一遍。
对象的深拷贝与浅拷贝的区别如下:
浅拷贝:仅仅复制对象的引用,而不是对象本身;
深拷贝:把复制的对象所引用的全部对象都复制一遍。
一. 浅拷贝的实现
浅拷贝的实现方法比较简单,只要使用是简单的复制语句即可。
1.1 方法一:简单的复制语句

/* ================ 浅拷贝 ================ */  
function simpleClone(initalObj) {  
    var obj = {};  
    for ( var i in initalObj) {  
        obj[i] = initalObj[i];  
    }  
    return obj;  
}

客户端调用

/* ================ 客户端调用 ================ */  
var obj = {  
    a: "hello",  
    b: {  
        a: "world",  
        b: 21  
    },  
    c: ["Bob", "Tom", "Jenny"],  
    d: function() {  
        alert("hello world");  
    }  
}  
var cloneObj = simpleClone(obj); // 对象拷贝  

console.log(cloneObj.b); // {a: "world", b: 21}  
console.log(cloneObj.c); // ["Bob", "Tom", "Jenny"]  
console.log(cloneObj.d); // function() { alert("hello world"); }  

// 修改拷贝后的对象  
cloneObj.b.a = "changed";  
cloneObj.c = [1, 2, 3];  
cloneObj.d = function() { alert("changed"); };  

console.log(obj.b); // {a: "changed", b: 21} // // 原对象所引用的对象被修改了  

console.log(obj.c); // ["Bob", "Tom", "Jenny"] // 原对象所引用的对象未被修改  
console.log(obj.d); // function() { alert("hello world"); } // 原对象所引用的函数未被修改

1.2 方法二:Object.assign()
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

var obj = { a: {a: "hello", b: 21} };   
var initalObj = Object.assign({}, obj);   
initalObj.a.a = "changed";   
console.log(obj.a.a); // "changed"

二. 深拷贝的实现
要实现深拷贝有很多办法,有最简单的 JSON.parse() 方法,也有常用的递归拷贝方法,和ES5中的 Object.create() 方法。
2.1 方法一:使用 JSON.parse() 方法
在JavaScript权威指南p141序列化对象
o = {x:1,y:{z:[false,null," "]}}; //定义一个测试对象
s = JSON.stringify(o); //s是 '{"x":1,"y":{"z":[false,null," "]}}'
p = JSON.parse(s); //p是o的深拷贝
要实现深拷贝有很多办法,比如最简单的办法是使用 JSON.parse():

/* ================ 深拷贝 ================ */  
function deepClone(initalObj) {  
    var obj = {};  
    try {  
        obj = JSON.parse(JSON.stringify(initalObj));  
    }  
    return obj;  
}  

/* ================ 客户端调用 ================ */  
var obj = {  
    a: {  
        a: "world",  
        b: 21  
    }  
}  
var cloneObj = deepClone(obj);  
cloneObj.a.a = "changed";  

console.log(obj.a.a); // "world"

这种方法简单易用。
但是这种方法也有不少坏处,譬如它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。
这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构。RegExp对象是无法通过这种方式深拷贝。
2.2 方法二:递归拷贝
代码如下:

/* ================ 深拷贝 ================ */  
function deepClone(initalObj, finalObj) {  
    var obj = finalObj || {};  
    for (var i in initalObj) {  
        if (typeof initalObj[i] === 'object') {  
            obj[i] = (initalObj[i].constructor === Array) ? [] : {};  
            arguments.callee(initalObj[i], obj[i]);  
        } else {  
            obj[i] = initalObj[i];  
        }  
    }  
    return obj;  
}

上述代码确实可以实现深拷贝。但是当遇到两个互相引用的对象,会出现死循环的情况。
为了避免相互引用的对象导致死循环的情况,则应该在遍历的时候判断是否相互引用对象,如果是则退出循环。
改进版代码如下:

/* ================ 深拷贝 ================ */  
function deepClone(initalObj, finalObj) {  
    var obj = finalObj || {};  
    for (var i in initalObj) {  
        var prop = initalObj[i];  

        // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况  
        if(prop === obj) {  
            continue;  
        }  

        if (typeof prop === 'object') {  
            obj[i] = (prop.constructor === Array) ? [] : {};  
            arguments.callee(prop, obj[i]);  
        } else {  
            obj[i] = prop;  
        }  
    }  
    return obj;  
}

2.3 方法三:使用Object.create()方法
直接使用var newObj = Object.create(oldObj),可以达到深拷贝的效果。

/* ================ 深拷贝 ================ */  
function deepClone(initalObj, finalObj) {  
    var obj = finalObj || {};  
    for (var i in initalObj) {  
        var prop = initalObj[i];  

        // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况  
        if(prop === obj) {  
            continue;  
        }  

        if (typeof prop === 'object') {  
            obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);  
        } else {  
            obj[i] = prop;  
        }  
    }  
    return obj;  
}

三. 参考:jQuery.extend()方法的实现
jQuery.js的jQuery.extend()也实现了对象的深拷贝。下面将官方代码贴出来,以供参考。
官方链接地址:https://github.com/jquery/jquery/blob/master/src/core.js。

jQuery.extend = jQuery.fn.extend = function() {  
    var options, name, src, copy, copyIsArray, clone,  
        target = arguments[ 0 ] || {},  
        i = 1,  
        length = arguments.length,  
        deep = false;  

    // Handle a deep copy situation  
    if ( typeof target === "boolean" ) {  
        deep = target;  

        // Skip the boolean and the target  
        target = arguments[ i ] || {};  
        i++;  
    }  

    // Handle case when target is a string or something (possible in deep copy)  
    if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {  
        target = {};  
    }  

    // Extend jQuery itself if only one argument is passed  
    if ( i === length ) {  
        target = this;  
        i--;  
    }  

    for ( ; i < length; i++ ) {  

        // Only deal with non-null/undefined values  
        if ( ( options = arguments[ i ] ) != null ) {  

            // Extend the base object  
            for ( name in options ) {  
                src = target[ name ];  
                copy = options[ name ];  

                // Prevent never-ending loop  
                if ( target === copy ) {  
                    continue;  
                }  

                // Recurse if we're merging plain objects or arrays  
                if ( deep && copy && ( jQuery.isPlainObject( copy ) ||  
                    ( copyIsArray = jQuery.isArray( copy ) ) ) ) {  

                    if ( copyIsArray ) {  
                        copyIsArray = false;  
                        clone = src && jQuery.isArray( src ) ? src : [];  

                    } else {  
                        clone = src && jQuery.isPlainObject( src ) ? src : {};  
                    }  

                    // Never move original objects, clone them  
                    target[ name ] = jQuery.extend( deep, clone, copy );  

                // Don't bring in undefined values  
                } else if ( copy !== undefined ) {  
                    target[ name ] = copy;  
                }  
            }  
        }  
    }  

    // Return the modified object  
    return target;  
};
0 关注 分享

要回复文章请先登录注册