outline:

  1. 为什么要说JS中深拷贝与浅拷贝
  2. JS对类型的分类
  3. immutable与mutable
  4. 简单类型检测
  5. 浅拷贝VS深拷贝

为什么要说JS中深拷贝与浅拷贝

近来在研读underscore的源码,发现其中一小段代码

_.mixin = function(obj) {_.each(_.functions(obj), function(name) {var func = _[name] = obj[name];_.prototype[name] = function() {var args = [this._wrapped];push.apply(args, arguments);return result(this, func.apply(_, args));};});
};
....
_.mixin(_);

这段代码就是要把我们在_上绑的很多方法浅拷贝一份到_.prototype.这里的浅拷贝引发一些思考.那么什么是浅拷贝、什么是深拷贝?在了解深、浅拷贝之前,我们需要了解下JS中对类型的分类,因为对于不同的类型,我们选择拷贝的方式也是不一样的.

JS对类型的分类

stackoverflow有人提了一个问题:
Stoyan Stefanov in his excellent book 'Object-Oriented JavaScript' says:

Any value that doesn't belong to one of the five primitive types listed above is an object.

Stoyan Stefanov说的这句话,在JS中要么就是primitive类型,要们就是object类型.

Primitive
A primitive (primitive value, primitive data type) is data that is not an object and has no methods. In >JavaScript, there are 6 primitive data types: string, number, boolean, null, undefined, symbol (new in ECMAScript 2015).
Most of the time, a primitive value is represented directly at the lowest level of the language implementation.
All primitives are immutable (cannot be changed).

MDN上指出了JS中的primitive类型一共就是string number boolean null undefined symbol(ES2015)6中类型,其余的都是object类型.MDN还说了primitive类型not an object以及has no methods.但是我们平时的使用都是这样的var str = "hello world";console.log(str.charAt(0)).这段代码中明显str是primitive的变量,按照MDN的说法,str变量应该是not an object并且has no methods的,这里我们明显调用了str.charAt方法.是我们错了还是MDN错了!!!!那我们再测试下str是不是一个object.Object.prototype.toString.call(str)这段代码执行的结果居然是[object String].就是说str不仅是object同时还has methods.但是str确实是primitive类型的.
在MDN给出primitive type定义的同时,还给出了Primitive wrapper objects的定义

Except for null and undefined, all primitive values have object equivalents that wrap around the >primitive values:

String for the string primitive.
Number for the number primitive.
Boolean for the Boolean primitive.
Symbol for the Symbol primitive.

The wrapper's valueOf() method returns the primitive value.

也就是说对于这些primitive的类型,确实不是object,并且也没有methods.执行str.charAt的时候是把string(primitive)类型转成了String(object)类型.ES5规范中这样解释:

这里虽然对于一些内部方法的调用我们并不清楚,但是基本也明确当我们在调用str.charAt的时候,JS执行引擎把str变成了String对象,可以执行String上的方法.了解了JS中的类型分类,我们在说一说JS中mutableimmutable.

immutable与mutable

在上一段我们讲了JS中的类型分类,总体来说就两类就是object和primitive,判断依据就是只有string、number、boolean、null、undefined、symbol(ES2015)才是primitive的,其余均为object的.在我们引用MDN的一段话中,还提到了All primitives are immutable (cannot be changed).那么这句话是什么意思.所有的primitive都是immutable(不可变的).这句话可能大家看完很不理解.var a = 1;a = 2; a= "hello world";,这里a就是primitive的类型,不是可以修改么,那MDN的这句All primitives are immutable是什么意思呢.
MDN的这句话其实是没错误的.碰到这种问题,查内存地址是最好的办法,可惜查内存地址难度太大,在chrome和nodejs上我都尝试了,都没有找个有一个比较直观的方式去看内存地址,如果有读者了解如何看内存可以和我联系.这里我们借用JS中的原型链来做一个小实验,也可以间接达到查看内存地址的目的.

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>immutalbe&mutable</title>
</head>
<body><script>
/*
* 这里的关键还是这个立即执行函数.立即执行函数与function的定义夹出了一个不可回收的区域,也就是var id = 0;
* (不明白的可以参考我的http://warjiang.github.io/devcat/2016/04/16/JSLecture/关于闭包的文章)
* 然后我们定义一个函数generateId,负责给id自增.
* 下面是关键
* 我们在Object.prototype上扩展了一个id的方法.
* 由于JS中的原型链,给Object.prototype扩展方法等于说给所有的对象都扩展了id的这个方法.
* 当某个对象调用id的方法会自动顺着原型链回溯到Object.prototype上的id方法.
* 调用这个方法的时候,方法中的this指向调用这个方法的对象.也就是给这个调用者扩展了id这个方法.
* 由于id在立即执行函数内,generateId和Object.prototype.id外,
* 所以id在执行过程中并不会被释放,而是从0开始不断加1
* 参考自http://stackoverflow.com/questions/2020670/javascript-object-id
*/(function() {var id = 0;function generateId() { return id++; console.log(id)};Object.prototype.id = function() {var newId = generateId();this.id = function() { return newId; };return newId;};})();var a = 1;console.log(a.id());//0a = 2;console.log(a.id());//1a = "hello world";console.log(a.id());//2</script>
</body>
</html>

这里id从0变为1、2就是说,我们的a赋值的过程并不是给a指向的内存赋值,而是说a重新指向了一个新的值.基于此,MDN所谓的primitive是immutable的,说的是primitive类型的value是immutable的,而variable是mutable的.所以说,对于primitive类型的变量,为其赋值,本质上就是让变量指向新的内存.
那么对于object类型的变量呢.我们也来做一个实验:

(function() {var id = 0;function generateId() { return id++; console.log(id)};Object.prototype.id = function() {var newId = generateId();this.id = function() { return newId; };return newId;};
})();
var o1 = {name:'o1'};
console.log(o1.id());//0
var o2 = o1;
console.log(o2.id());//0
o2.name = "o2";
console.log('o1',o1);//o1 Object {name: "o2"}
console.log('o2',o2);//o2 Object {name: "o2"}
o2 = {name:'xx'}
console.log(o2.id())//1
console.log('o1',o1);//o1 Object {name: "o2"}
console.log('o2',o2);//o2 Object {name: "xx"}

从这个例子我们可以看出,对于Object类型的变量,直接赋值过程等于说让变量指向右值内存地址.如var o2 = o1,o2就是指向o1指向的内存空间.但是当我们修改对象的属性的时候,就会修改原来内存中对象的属性值.如果o2.name = "o2"会令o1.name =="o2".这里就会引发一个深拷贝、浅拷贝的问题.比如这里的o2 = o1就是一次浅拷贝.浅拷贝的时候,由于指向的内存地址是一样的,如果直接给对象赋值是不存在任何问题的比如var o2 = o1;o2 = {name:'xx'}此时o1.id()返回0,o2.id()返回1.但是如果修改对象上的属性时,就会触发对象指向的内存中的对象的属性修改.
我们在来看另外一个例子:

(function() {var id = 0;function generateId() { return id++; console.log(id)};Object.prototype.id = function() {var newId = generateId();this.id = function() { return newId; };return newId;};
})();
var o1 = {name:'o1'};
console.log(o1.id());//0
var o2 = {}
o2.name = o1.name;
console.log(o2.id());//1
o2.name = "o2";
console.log('o1',o1);//o1 Object {name: "o1"}
console.log('o2',o2);//o2 Object {name: "o2"}

在这个例子中我们对于o2的赋值没有采用o2 = o1;而是采用了o2={},o2.name = o1.name.那么这样够不够.结合我么之前说的immutable和mutable,由于name对应的值是string类型的,是immutable的,所以这里我们拷贝到name是完全够的,是属于深拷贝.
看到这里,相信大家后面我们要做的深、浅拷贝可能有一定的想法了.浅拷贝就是直接赋值,或者说不完全的赋值(对于对象而言,后面我们会举例),浅拷贝对于primitive类型的或者说不会直接修改属性的对象而言比如Function是无害的,但是对于浅拷贝{k1:v1}或者说是[v1,v2]的对象,会出现严重的问题,即由于指向同一个内存对象,修改属性等于修改了所有指向该内存对象的属性.
那么下面我们就需要做类型检测,对于做深拷贝需要检测的情况很简单,如果检测出来是浅拷贝有害的,我们就做深拷贝,否则直接浅拷贝.

简单类型检测

这里我们只需要做Object和Array的类型检测,对于Function、Date等类型的我们都不是很需要.类型检测我们采用Object.prototype.toString方法

var isType = function(type){return function(obj){return Object.prototype.toString.call(obj) === '[object '+ type +']';}
}
var is = {isArray : isType('Array'),isObject : isType('Object'),
}

有了类型检测函数,下面我们就可以开心的做深拷贝了.

浅拷贝VS深拷贝

浅拷贝我们之前也说了,这里直接举个例子,说明其危害.

(function() {var id = 0;function generateId() { return id++; console.log(id)};Object.prototype.id = function() {var newId = generateId();this.id = function() { return newId; };return newId;};
})();
var o1 = {number: 1,                                      string: "I am a string",                     object: {test1: "Old value"                             },arr: [                                        "a string",                                       {                                                  test2: "Try changing me"                }]
};var extend = function(result, source) {for (var key in source)result[key] = source[key];return result;
}var o2 = extend({},o1);
console.log('o1',o1.number.id());//0
console.log('o1',o1.string.id());//1
console.log('o1',o1.object.id());//2
console.log('o1',o1.arr.id());//3console.log('o2',o2.number.id());//4
console.log('o2',o2.string.id());//5
console.log('o2',o2.object.id());//2
console.log('o2',o2.arr.id());//3

从id的值上看,o2和o1的内部属性值,number、string是采用的两个副本,但是object和arr确实采用的同一个副本.这种情况下如果我们修改o2.object = {name:'o2'}是没有问题的,由于直接复制本质上上内存指向修改的问题.但是如果我们修改o2.object.test1 = "New value",此时o1和o2会一起变!!!这种情况是我们不想看到的.对于object、array类型的最好做深拷贝(是否深拷贝看应用场景,读者需要斟酌),结合我们上面的类型检测,我们把extend函数修改一下

(function() {var id = 0;function generateId() { return id++; console.log(id)};Object.prototype.id = function() {var newId = generateId();this.id = function() { return newId; };return newId;};
})();
var isType = function(type){return function(obj){return Object.prototype.toString.call(obj) === '[object '+ type +']';}
}
var is = {isArray : isType('Array'),isObject : isType('Object'),
}var o1 = {number: 1,                                      string: "I am a string",                     object: {test1: "Old value"                             },arr: [                                        "a string",                                       {                                                  test2: "Try changing me"                }]
};var extend = function(result, source) {for (var key in source){var copy = source[key];if(is.isArray(copy)){//Array deep copyresult[key] = extend(result[key] || [], copy);}else if(is.isObject(copy)){//Object deep copyresult[key] = extend(result[key] || {}, copy);}else{result[key] = copy;}}return result;
}var o2 = extend({},o1);
console.log('o1',o1.number.id());//0
console.log('o1',o1.string.id());//1
console.log('o1',o1.object.id());//2
console.log('o1',o1.arr.id());//3console.log('o2',o2.number.id());//4
console.log('o2',o2.string.id());//5
console.log('o2',o2.object.id());//6
console.log('o2',o2.arr.id());//7o2.object.test1 = "new Value";
console.log(o1,JSON.stringify(o1))//o1.object.test1 == "Old value"
console.log(o2,JSON.stringify(o2))//o2.object.test1 == "new Value"o2.arr[1].test2 = "就不改你";
console.log(o1,JSON.stringify(o1))//o1.object.test1 == "Try changing me"
console.log(o2,JSON.stringify(o2))//o2.arr[1].test2 == "就不改你"

说说JS中的浅拷贝与深拷贝相关推荐

  1. js 浅拷贝直接赋值_JS中实现浅拷贝和深拷贝的代码详解

    (一)JS中基本类型和引用类型 JavaScript的变量中包含两种类型的值:基本类型值 和 引用类型值,在内存中的表现形式在于:前者是存储在栈中的一些简单的数据段,后者则是保存在堆内存中的一个对象. ...

  2. 【转】JAVA中的浅拷贝和深拷贝

    原文网址:http://blog.bd17kaka.net/blog/2013/06/25/java-deep-copy/ JAVA中的浅拷贝和深拷贝(shallow copy and deep co ...

  3. Python中的浅拷贝和深拷贝(一看就懂!!!)

    浅拷贝和深拷贝一直傻傻的分不清,甚至有时候会理解反.今天就简单总结一下python中的浅拷贝和深拷贝. 总结 最直观的理解就是: 1.深拷贝,拷贝的程度深,自己新开辟了一块内存,将被拷贝内容全部拷贝过 ...

  4. js中的浅拷贝深拷贝深入理解

    举个例子来说明一下什么是浅拷贝什么是深拷贝 浅拷贝 var x = {a: 1,b: {f: {g: 1}},c: [1, 2, 3] }; var y = shallow(x); 得出的结果可以看出 ...

  5. Java中的浅拷贝与深拷贝

    一.引用拷贝与对象拷贝 class Person implements Cloneable{private String name;private int age;...省略get和set方法 pro ...

  6. 43 JavaScript中的浅拷贝与深拷贝

    技术交流QQ群:1027579432,欢迎你的加入! 欢迎关注我的微信公众号:CurryCoder的程序人生 1.浅拷贝与深拷贝 浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用. 深拷贝拷贝多层,每 ...

  7. C#中的浅拷贝和深拷贝

    C#中有两种类型变量,一种是值类型变量,一种是引用类型变量.对于前者,copy是属于全盘复制:而对于后者,一般的copy只是浅copy,相当于只传递一个引用指针一样.因此对于后者进行真正copy的时候 ...

  8. python浅拷贝_Python中的浅拷贝和深拷贝

    本文翻译自copy in Python (Deep Copy and Shallow Copy),讲述了在Python语言中浅拷贝与深拷贝的不同用法.全文系作者原创,仅供学习参考使用,转载授权请私信联 ...

  9. clone是深拷贝还是浅拷贝_Python中的浅拷贝和深拷贝

    本文翻译自copy in Python (Deep Copy and Shallow Copy),讲述了在Python语言中浅拷贝与深拷贝的不同用法.全文系作者原创,仅供学习参考使用,转载授权请私信联 ...

最新文章

  1. 如何修改pdf文件的背景色
  2. 百度母婴技术团队—基于Reactjs实现webapp #1
  3. iOS开发之打电话,发短信,发送邮件
  4. 将x的二进制最后一位置为0
  5. Pytorch初学实战(一):基于的CNN的Fashion MNIST图像分类
  6. Android 优化布局层次结构
  7. matlab 2018 ccs,Matlab2018a 与ccs7生成tms320F2812代码调试记录
  8. ftp 服务器创建访问连接抱错_如何用固定IP连接FTP服务器?
  9. selinium如何多线程_求教个selenium+grid+testng多线程运行的问题
  10. 从技术上分析铁路售票系统
  11. 周立功USBCAN-2E-U的驱动安装及上位机安装
  12. Vue系列之-Idea进行Vue前端调试
  13. 【mysql】mysql表分区、索引的性能测试
  14. 51单片机数码管显示数字
  15. 计算机中求声音传输时间公式,计算机常用计算公式汇总
  16. 编译工具各种报错(步骤正确,代码正确,配置正确)均可认为无解,除非...
  17. 微信小程序 某个页面直接返回首页(微信小程序回退到首页)
  18. 基于ssm高校科研成果管理系统 java ideamysql
  19. windows下的ubuntu盘符问题
  20. 考研数据结构(2)笔记

热门文章

  1. 自学python还是报班-学习Python到底是培训还是自学合适呢?
  2. python免费试听-哈尔滨Python人工智能课程免费试听
  3. python培训好学吗-人工智能“速成班”Python好学吗 小心别被忽悠了
  4. python中文教程-中谷python中文视频教程(全38集)
  5. python经典案例-20个Python练手经典案例,能全做对的人确实很少!
  6. LeetCode209 长度最小的子数组(二分法)
  7. vim中搭建与sourceinsight类似功能
  8. phpstorm 控制台运行
  9. 使用dotnet-dump 查找 .net core 3.0 占用CPU 100%的原因
  10. Noip2016换教室