原生js对象的浅拷贝和深拷贝的总结


title: 原生js对象的浅拷贝和深拷贝的总结

这里是说明.


在此之前我们先复习两个知识点.

第一个知识点:对象方括号表示法

  • 一般来说,访问对象属性时使用的都是点表示法,这也是很多面向对象语言中通用的语法.不过在Javascript也可以用方括号表示法来访问对象的属性.在使用方括号语法时,应该将要访问的属性以字符串的形式放在方括号中
var person = {
    name : "gay"
}
alert(person["name"]); // "gay"

alert(person.name); // "gay

优点

可以通过变量来访问属性,例如:

var propertyName = "name";

alert(person[propertyName]);  // "gay"

如果属性名中包含会导致语法错误的字符,或者属性名使用的是关键字火保留字,也可以使用方括号表示法.例如:

person["first name"] = "gay";

由于"first name"中包含一个空格,所以不能使用点表示发来访问它.然而,属性名中是可以包含非字母非数字的,这时候就可以使用方括号表示法来访问它们.

  • 通常,除非必须使用变量来访问属性,否则我们建议使用点表示法.

第二个知识点:函数传递参数:

  • ECMAScript中所有的函数都是按值传递!!!! -----高程第三版P70
  • ECMAScript中所有的函数都是按值传递!!!! -----高程第三版P70
  • ECMAScript中所有的函数都是按值传递!!!! -----高程第三版P70
  • 函数内部声明的变量都是临时变量!在函数执行完之后也会被销毁!!!
  • 函数内部声明的变量都是临时变量!在函数执行完之后也会被销毁!!!
  • 函数内部声明的变量都是临时变量!在函数执行完之后也会被销毁!!!
  • 在JS中,如果一个引用类型赋值给一个变量,那么这个变量装的是这个对象的地址!!!
  • 在JS中,如果一个引用类型赋值给一个变量,那么这个变量装的是这个对象的地址!!!
  • 在JS中,如果一个引用类型赋值给一个变量,那么这个变量装的是这个对象的地址!!!
    其实记住上面三句话,可以理解好多问题
    几个例子解决你疑惑

第一个例子:

function addTen(num){
  num += 10;
}
var count = 20;
addTen(count);
console.log(count);//20
console.log(num);//"ReferenceError: num is not defined

分析一下函数 :
这个函数里面声明了一个临时变量num,,然后这个临时变量会在函数结束后消失.
所以当我们即使把count的值传给num,也不会影响count的值.
而最后输出num的值也会出现未定义错误.

第二个例子

function addTen(num){
  num += 10;
  return num;
}
var count = 20;
var result = addTen(count);
console.log(count);//20
console.log(result);//30

分析:
如果我们想要函数里面的那个临时变量num的值该怎么办呢? 那么我们就要把它return出来,
最后可以看出 count输出20 ,result(也就是临时变量num的值,num变量在函数调用后已经消失,它的唯一作用就是临死前告诉result它的值是多少)是30.

第三个例子

var person = {
  name : "wsy"
}
function setName(obj){
   obj.name = "gay"
}
setName(person);
console.log(person.name);// "gay"
console.log(obj.name);//"ReferenceError: obj is not defined

分析:
有人可能会说,你不是说是值传递么,为什么还会改变原来对象name的值.
可是我还说了一句话

  • 在JS中,如果一个引用类型赋值给一个变量,那么这个变量装的是这个对象的地址!!!
    setName(person);
    进入函数后,我们定义一个临时变量obj,这个obj里装的是person对象的地址.注意是地址.学过c语言的同学肯定好理解,这个obj说白了就是一个指针变量呗.
    所以,当我们obj.name = "gay"改变的就是原来那个对象的name,因为他们共享一个地址.所以console.log(person.name);// "gay".
    又因为obj只是一个临时变量,所以在函数外输出obj.name肯定找不到了.因为obj已经挂了.

第四个例子

function setName(obj){
   obj.name = "gay"
   obj = new Object();
   obj.name = "les"
}
var person = new Object();
setName(person);
console.log(person.name);//"gay"

分析:
我们定义一个person对象.
setName(person);
然后进入函数,首先给临时变量obj给一个值(person的地址).然后obj.name = "gay",因为obj和person共享一个地址,所以person的name属性也变成了"gay".
然后 obj = new Object(); .注意这里 重新new了一个对象,(也就是重新在堆内存里分配了一块地址)给临时变量obj.此时,obj里装的地址和person的地址并不是一个值.也就
是说改变obj.name并不会影响到person.

最后一个例子

function setName(obj){
   obj.name = "gay"
   obj = new Object();
   obj.name = "les"
   return obj;
}
var person = new Object();
var person1 = setName(person);
console.log(person.name);//"gay"
console.log(person1.name);//"les"

这个例子无非就是想把这个新开辟的obj返回出来.so easy~

总结

其实我们发现,红皮书说的真好,js函数传递就是值传递.可为什么传递引用类型时会改变原来的值呢?是因为传引用对象时,其实传递的是他的地址.所以他们共享地址了.
这就是传说中的 call by shareing!
OK,让我们进入正题!

浅拷贝:

简单讲,浅拷贝就是复制一份引用,所有引用对象都指向一份数据,并且都可以修改这份数据

var person = {

  name : "wsy"

}

var person1 = person;

person1.name = "yxy";

console.log(person.name); // yxy

从上述代码中我们可以发现,改变person1的name值然后person的值也跟着改变.
内存分析图:

再看一段代码:

var Chinese = {
    name : "China"
}
function extendCopy(p) {
    var c = {};
    for (var i in p) {
        c[i] = p[i];
    }
    return c;
}
var Doctor = extendCopy(Chinese);
alert(Doctor.name);//China
Doctor.name = "USA"
alert(Chinese.name);//China

解释下这个函数,创建一个c对象,然后 c["name"] = p["name"];
但是 改变c的name属性并不影响p的属性.
再往下看

Chinese.birthPlaces = ['北京','上海','香港'];
var Doctor = extendCopy(Chinese);
Doctor.birthPlaces.push('厦门');
alert(Doctor.birthPlaces); //北京, 上海, 香港, 厦门
alert(Chinese.birthPlaces); //北京, 上海, 香港, 厦门

假如我们给Chinese添加一个属性,这个属性为一个数组对象.
然后再进行extendCopy函数赋值给Doctor.我们会发现改变Doctor的值会影响Chinese的值.
也就是说Doctor.birthPlaces 和 ChinesePlaces指向了同一块内存.

经实验,我们发现在extendCopy(p)函数中:
如果参数p的某一个属性为基本类型.则为值传递(也就是仅仅简简单单的赋值)
如果参数p的某一个属性为引用类型(对象),则为引用传递(这俩个对象的这个属性指向同一块内存)
所以,extendCopy() 只是拷贝了基本类型的数据,我们把这种拷贝叫做“浅拷贝”。

知乎用户MickeyHong : Javascript 对于复制的问题其实有些模糊 不过总的来说 你只要记住Object在Javascript里是pass by reference的 其余的都是pass一个复制值 (我知道有人会吵>javascript都是pass by value的 而obj的value就是reference什么什么的)

深拷贝

  • 所谓”深拷贝”,就是能够实现真正意义上的数组和对象的拷贝。它的实现并不难.深拷贝则是复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制
  • 所谓”深拷贝”,就是能够实现真正意义上的数组和对象的拷贝。它的实现并不难.深拷贝则是复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制
  • 所谓”深拷贝”,就是能够实现真正意义上的数组和对象的拷贝。它的实现并不难.深拷贝则是复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制
    直接撸代码:
var Chinese = {
    birthPlaces : ['北京','上海','香港']
}
function deepCopy(p,c) {
    var c = c || {};
    for (var i in p) {
        if (typeof p[i] === 'object') {
            c[i] = (p[i].constructor === Array) ? [] : {};
            //alert(i);  // i = birthPlace
            //alert( c[i]);//空对象
            //alert(p[i]);//['北京','上海','香港'];
            deepCopy(p[i], c[i]);
        } else {
            c[i] = p[i];
        }
    }
    return c;
}
var Doctor = deepCopy(Chinese);
Doctor.birthPlaces.push('厦门');
alert(Doctor.birthPlaces); //北京, 上海, 香港, 厦门
alert(Chinese.birthPlaces); //北京, 上海, 香港

这里我们实现了就算这个对象的某一个属性为Object类型的,我们可以让这两个对象的这个属性指向不同的内存.

  • 所谓”深拷贝”,就是能够实现真正意义上的数组和对象的拷贝。它的实现并不难.深拷贝则是复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制

ok,分析一下函数:

function deepCopy(p,c) {
    var c = c || {};
    for (var i in p) {
        if (typeof p[i] === 'object') {
            c[i] = (p[i].constructor === Array) ? [] : {};
            //alert(i);  // i = "birthPlace"
            //alert( c[i]);//空对象
            //alert(p[i]);//['北京','上海','香港'];
            deepCopy(p[i], c[i]);
        } else {
            c[i] = p[i];
        }
    }
    return c;
}

分析一下运行过程:
var Chinese = {
birthPlaces : ['北京','上海','香港']
}

现在Chinese只有一个属性,这个属性的值是一个数组(Array)
一步一步分析:

var Doctor = deepCopy(Chinese);
给deepCopy传进入一个参数Chineese.

var c = c || {};
定义一个变量 c, 这个c的值是怎么计算的呢? 其实这里用了||的特性,如果传进来的c不为null那么新定义的c的值就是传进来的c的值,否则新定义的c等于一个空对象({ })
.而我们第一次调用deepCopy时,只传进来一个一个参数,所以. 这里 var c = {}; 也就是定义c是一个空对象.\

for (var i in p)
这里由于p是一个对象,所以这里面的i值是循环p的属性.由于Chinese只有birthPlaces一个属性,所以只循环一次,i的值就是 "birthPlaces"(string类型).

if (typeof p[i] === 'object')
判断p[i]是不是Object类型的.这里面p[i]就是p["birthPlaces"] 那么肯定是Object啊(这里用到的前面复习的第一个知识点::对象方括号表示法)

c[i] = (p[i].constructor === Array) ? [] : {};
判断p[i]到底是哪个Object类型的,如果是数组那么;c["birthPlaces"]为空数组,如果是对象那么:c["birthPlaces"]为空对象.

deepCopy(p[i], c[i]);
也就是deepCopy(p["birthPlaces"],c["birthPlaces"])
也就是deepCopy(['北京','上海','香港'],[])

ok,我们再进入一遍这个函数
注意,刚才我们传进去俩个参数,p = ['北京','上海','香港'],c = [];

var c = c || {};
然后 c = [];

for (var i in p)
这里p一个数组,所以i是这个数组的三个索引为 "0", "1" "2"所以进行三次循环 (注意这个索引是string类型的 )

if (typeof p[i] === 'object')
当i = "0"时,p["0"] = "北京", 而北京是一个string类型的.依次类推,这三次循环永远不会进入这个if语句里.

else {

            c[i] = p[i];
        }

循环三次后,因为c本来就是一个数组.所以最后 c = ['北京','上海','香港']因为这个c和c["birthPlaces"]共享地址,所以c["birthPlaces"] = ['北京','上海','香港'];

return c;
在函数外var Doctor = deepCopy(Chinese);来接受这个我们在函数内新var的临时变量.
总结:

  • 深拷贝则是复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制
  • 深拷贝则是复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制
  • 深拷贝则是复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制

你可能感兴趣的:(原生js对象的浅拷贝和深拷贝的总结)