Wscats / articles

🔖My Learning Notes and Memories - 分享我的学习片段和与你的回忆

Home Page:https://github.com/Wscats/articles

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Javascript深浅拷贝

Wscats opened this issue · comments

Javascript有六种基本数据类型(也就是简单数据类型),它们分别是:Undefined,Null,Boolean,Symbol,Number和String。还含有一种复杂数据类型,就是对象

注意Undefined和Null的区别,Undefined类型只有一个值,就是undefined,Null类型也只有一个值,也就是null
Undefined其实就是已声明未赋值的变量输出的结果
null其实就是一个不存在的对象的结果

var c;
console.log(c)//undefined

console.log(document.getElementById('wsscat'))//没有id为wsscat的节点,输出null

简单的数据类型和复杂的数据类型有以下重要的区别

对于简单数据类型

它们值在占据了内存中固定大小的空间,并被保存在栈内存中。当一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本,还有就是不能给基本数据类型的值添加属性

var a = 1;
var b = a;
a.attr = 'wsscat';
console.log(a.attr)//undefined

上面代码中a就是简单数据类型(Number),b就是a的副本,它们两者都占有不同位置但相等的内存空间

对于复杂的数据类型

复杂的数据类型即引用类型,它的值是对象,保存在堆内存中,包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针。从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象。

var obj = {
    name:'wsscat',
    age:0
}
var obj2 = obj;
obj2['c'] = 5;
console.log(obj);//Object {name: "wsscat", age: 0, c: 5}
console.log(obj2);////Object {name: "wsscat", age: 0, c: 5}

qq20161016-0

我们可以看到obj赋值给obj2后,当我们更改其中一个对象的属性值,两个对象都发生了改变,究其原因局势因为obj和obj2这两个变量都指向同一个指针,赋值只是复制了指针,所以当我们改变其中一个的值就会影响另外一个变量的值

浅拷贝

其实这段代码就是浅拷贝,有时候我们只是想备份数组,但是只是简单让它赋给一个变量,改变其中一个,另外一个就紧跟着改变,但很多时候这不是我们想要的

var obj = {
    name:'wsscat',
    age:0
}
var obj2 = obj;
obj2['c'] = 5;
console.log(obj);//Object {name: "wsscat", age: 0, c: 5}
console.log(obj2);////Object {name: "wsscat", age: 0, c: 5}

深拷贝

数组
对于数组我们可以使用slice()concat()方法来解决上面的问题
slice

var arr = ['wsscat', 'autumns', 'winds'];
var arrCopy = arr.slice(0);
arrCopy[0] = 'tacssw'
console.log(arr)//['wsscat', 'autumns', 'winds']
console.log(arrCopy)//['tacssw', 'autumns', 'winds']

concat

var arr = ['wsscat', 'autumns', 'winds'];
var arrCopy = arr.concat();
arrCopy[0] = 'tacssw'
console.log(arr)//['wsscat', 'autumns', 'winds']
console.log(arrCopy)//['tacssw', 'autumns', 'winds']

对象
对象我们可以定义一个新的对象并遍历新的属性上去实现深拷贝

var obj = {
    name:'wsscat',
    age:0
}

var obj2 = new Object();
obj2.name = obj.name;
obj2.age = obj.age

obj.name = 'autumns';
console.log(obj);//Object {name: "autumns", age: 0}
console.log(obj2);//Object {name: "wsscat", age: 0}

当然我们可以封装好一个方法来处理对象的深拷贝,代码如下

var obj = {
    name: 'wsscat',
    age: 0
}
var deepCopy = function(source) {
    var result = {};
    for(var key in source) {
        if(typeof source[key] === 'object') {
            result[key] = deepCopy(source[key])
        } else {
            result[key] = source[key]
        }
    }
    return result;
}
var obj3 = deepCopy(obj)
obj.name = 'autumns';
console.log(obj);//Object {name: "autumns", age: 0}
console.log(obj3);//Object {name: "wsscat", age: 0}

Javascript数组存放函数

在javascript中函数也是一种数据,能够像操作一个对象对它进行操作。并且javascript不进行数据类型检查,数组可以存放任何东西,在下面代码中我们不但在数组中存放了函数,并且也可以在存放一个执行函数的返回值,所以数组前两个数据存放都是函数执行返回值

var funcA = function() {
    console.log("funcA");
    return "hello funA";
}
var funcB = function() {
    console.log("funcB");
    return "hello funB";
}
var funcC = function() {
    console.log("funcC");
    return "hello funC";
}
var arr = [funcA(), funcB(), funcC];
console.log(arr);
arr[2]();

输出的结果如下
qq20161105-0

commented

对象中不含有函数的话。JSON解析反解析就行了

深拷贝

数组
对于数组我们可以使用slice()和concat()方法来解决上面的问题
silce
...

应该是手滑了吧,silce -> slice

谢谢,已更正~

ES6中有 Object.assign() 方法,应该也可以解决你对于深克隆的需求吧?

commented

image
concat 和slice对对象数组没有效果的?

@GreenMelon Object.assign()方法不适于深拷贝,MDN上有说明

@yeyuqiudeng concat和slice主要是针对基本数据类型数组的深拷贝,牵涉到多层复杂数据类型即对象的就要再对{a:{b:1}进行深拷贝。有兴趣的话,可对Underscore、Lodash 和 JQuery的源码进行学习,相关文章《深入剖析 JavaScript 的深复制

commented

@flynntsc 嗯嗯,谢谢

我这里实现了对象包括数组的深拷贝:

Object.prototype.deepCopy=function(){
    var obj=null;//用于最后返回一个对象,这个对象是深复制的结果
    for(var attr in this){//遍历这个对象的每一个属性
        if(this.hasOwnProperty(attr)){//主要是递归自有属性
            if(typeof (this[attr]==='object')){//如果对象的属性是一个对象,就递归复制它的每一个属性
                if(this[attr]===null){//如果对象为null
                    obj[attr]=null;
                }else if(Object.prototype.toString(this[attr])==='[object Array]'){//如果是个数组
                    obj[attr]=[];
                    for(var i=0;i<this[attr].length;i++){
                        obj[attr].push(this[attr][i].deepCopy());
                    }
                }else{//object
                    obj[attr]=this[attr].deepCopy();
                }
            }else{
                obj[attr]=this[attr];
            }
        }
    }
    return obj;
}

test.html:12 Uncaught RangeError: Maximum call stack size exceeded
报错了 @specialCoder

基本类型在ES6还有一个Symbol,漏掉了

嗯嗯 by @Wscats

不错不错👍🏻

commented

写得挺好的

涨知识啦

没看到对循环引用的处理啊

commented

function deepCopy(jsonData){
return JSON.parse(JSON.stringify(jsonData));
};
这样也可以吧

@plane-hjh 这样有其他问题 具体可以看我 这篇文章

@kscript 循环引用解决方案可以看这个具体可以看这篇文章

slice、concat、assign都是浅拷贝,如果数据中没有函数,可以直接用JSON来实现拷贝,如果有函数,就得用递归来实现深拷贝,递归实现时,函数依然是公用的;
更高级的可以参考楼上的 文章LiuL0703/blog#19

我这里实现了对象包括数组的深拷贝:

Object.prototype.deepCopy=function(){
    var obj=null;//用于最后返回一个对象,这个对象是深复制的结果
    for(var attr in this){//遍历这个对象的每一个属性
        if(this.hasOwnProperty(attr)){//主要是递归自有属性
            if(typeof (this[attr]==='object')){//如果对象的属性是一个对象,就递归复制它的每一个属性
                if(this[attr]===null){//如果对象为null
                    obj[attr]=null;
                }else if(Object.prototype.toString(this[attr])==='[object Array]'){//如果是个数组
                    obj[attr]=[];
                    for(var i=0;i<this[attr].length;i++){
                        obj[attr].push(this[attr][i].deepCopy());
                    }
                }else{//object
                    obj[attr]=this[attr].deepCopy();
                }
            }else{
                obj[attr]=this[attr];
            }
        }
    }
    return obj;
}

typeof 对array和object都返回object,所以没有必要加一层array的判断吧?

function deepCopy(jsonData){
return JSON.parse(JSON.stringify(jsonData));
};
这样也可以吧
只适合对象吧这个

let deep_copy = function f(obj) {
    let new_value;
    if(typeof obj === 'object' && obj != null){
        if(obj instanceof Array){
            new_value = [];
            for(let i=0;i<obj.length;i++){
                if(typeof obj[i] !== 'object' || obj[i] === null){
                    new_value[i] = obj[i];
                }else{
                    new_value.push(f(obj[i]));
                }
            }
        }else{
            new_value = {};
            for(let item in obj){
                if(obj.hasOwnProperty(item)){
                    if(typeof obj[item] !== 'object' || obj[item] === null){
                        new_value[item] = obj[item];
                    }else{
                        Object.assign(new_value, { [item]: f(obj[item])});
                    }
                }
            }
        }
    }else{
        new_value = obj;
    }
 
    return new_value;
};

我觉的你的理解有点问题,对象的赋值操作不能算是浅拷贝。在对对象进行浅拷贝时,对象中的基本数据类型会开辟新的空间,引用类型指向的还是同一个地址。而赋值操作,是将对象B的地址指向对象A。
举个例子

var obj1 = {
      name: 'zhangsan',
      num: [1, 2, 3]
}
// 赋值操作
var obj2 = obj1
obj2.name = 'lisi'
console.log(obj1) // {name: "lisi", num: [1, 2, 3] }
console.log(obj2) // {name: "lisi", num: [1, 2, 3] }
// 浅拷贝
var obj3 = Object.assign({}, obj1)
obj3.name = 'wanger'
console.log(obj1) // {name: "lisi", num: [1, 2, 3] }
console.log(obj3) // {name: "wanger", num: [1, 2, 3] }

如果需要深拷贝 函数和正则 呢

ES6中有 Object.assign() 方法,应该也可以解决你对于深克隆的需求吧?

assign不是深克隆