原生js对象的浅拷贝和深拷贝的总结
发布在Jeason的前端之路2016年9月16日view:1771Javscript闭包&作用域JavaScript原型前端工程师
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

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

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

  • 一般来说,访问对象属性时使用的都是点表示法,这也是很多面向对象语言中通用的语法.不过在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的临时变量. 总结: * 深拷贝则是复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制 * 深拷贝则是复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制 * 深拷贝则是复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制

第一次写文章,还是学生党.只是把自己的想法写了出来,希望大家能指出我的不足,也不知道我理解的对不对. 对自己说一声加油~~

评论
发表评论
10个月前
添加了一枚【评注】:44
WRITTEN BY
PUBLISHED IN
Jeason的前端之路

从零开始学习前端 come!

我的收藏