说说JS中的浅拷贝与深拷贝
来源:互联网 发布:上海浦东java培训机构 编辑:程序博客网 时间:2024/06/16 21:13
在JavaScript中对象的浅拷贝和深拷贝有如下区别:
浅拷贝:仅仅复制对象的引用,而不是对象本身。
深拷贝:复制对象所引用的全部对象。
我在平常练习时,常使用的2种浅拷贝和三种深拷贝的方法。
浅拷贝:
1.自定义实现
function simpleClone(obj) { var simpleCloneObj = {}; for (var i in obj) { simpleCloneObj[i] = obj[i]; } return simpleCloneObj; }
2.使用Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
var simpleClone= Object.assign({}, obj);
深拷贝:
1.使用 JSON.parse() 方法
function deepClone(obj) { var deepCloneObj = {}; try { deepCloneObj = JSON.parse(JSON.stringify(obj)); } catch (e) { } return deepCloneObj; }
这种方法虽然简单,但是有如下的问题,它会抛弃对象的constructor,也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。
这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象(自己百度一下),即那些能够被 json 直接表示的数据结构。RegExp对象是无法通过这种方式深拷贝。
- 递归拷贝
第二个参数可以用来实现追加。
function deepClone(initalObj, finalObj) { var deepCloneObj = finalObj || {}; for (var i in initalObj) { var prop = initalObj[i]; // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况 if (prop === deepCloneObj) { continue; } if (typeof prop === 'object') { deepCloneObj[i] = (prop.constructor === Array) ? [] : {}; arguments.callee(prop, deepCloneObj[i]); } else { deepCloneObj[i] = prop; } } return deepCloneObj; }
3.使用Object.create()方法
function deepClone(initalObj, finalObj) { var deepCloneObj = finalObj || {}; for (var i in initalObj) { var prop = initalObj[i]; // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况 if (prop === deepCloneObj) { continue; } if (typeof prop === 'object') { deepCloneObj[i] = (prop.constructor === Array) ? [] : Object.create(prop); } else { deepCloneObj[i] = prop; } } return deepCloneObj; }
当直接使用下面的方法也可以达到深拷贝的效果。
var deepCloneObj= Object.create(oldObj);
那么现在来测试一下浅拷贝的方法。
var cloneObj = simpleClone(obj); console.log(cloneObj.name); console.log(cloneObj.val); cloneObj.name = "simpleCloneTest2"; cloneObj.val = [3, 4, 5]; console.log(cloneObj.val); console.log(obj.name); console.log(obj.val);
执行结果如下:
simpleCloneTest [0, 1, 2] simpleCloneTest2 [3, 4, 5] simpleCloneTest [0, 1, 2]
我们发现在cloneObj 更改name和val时,obj的值并没有更改。这是为什么呢?我们稍后说明。
我们再来另一种测试。
var obj = { name: "simpleCloneTest", val: [0, 1, 2] }; var cloneObj = simpleClone(obj); console.log(cloneObj.name); console.log(cloneObj.val); //cloneObj.name = "simpleCloneTest2"; //cloneObj.val = [3, 4, 5]; //console.log(cloneObj.name); //console.log(cloneObj.val); //console.log(obj.name); //console.log(obj.val); cloneObj.name = "simpleCloneTest3"; cloneObj.val[0] = 3; console.log(cloneObj.name); console.log(cloneObj.val[0]); console.log(obj.name); console.log(obj.val[0]);
输出结果如下:
simpleCloneTest[0, 1, 2]simpleCloneTest33simpleCloneTest3
一下内容来自于:https://yq.aliyun.com/articles/35053
这时我们发现clone和原来的obj的值都更改了。这让我百思不得其解,最终找到一篇很有说服力的文章,进行复制过来(避免文章被删帖)。
outline:
为什么要说JS中深拷贝与浅拷贝
JS对类型的分类
immutable与mutable
简单类型检测
浅拷贝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中mutable和immutable.
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());//0 a = 2; console.log(a.id());//1 a = "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());//0var o2 = o1;console.log(o2.id());//0o2.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())//1console.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());//0var o2 = {}o2.name = o1.name;console.log(o2.id());//1o2.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());//0console.log('o1',o1.string.id());//1console.log('o1',o1.object.id());//2console.log('o1',o1.arr.id());//3console.log('o2',o2.number.id());//4console.log('o2',o2.string.id());//5console.log('o2',o2.object.id());//2console.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 copy result[key] = extend(result[key] || [], copy); }else if(is.isObject(copy)){ //Object deep copy result[key] = extend(result[key] || {}, copy); }else{ result[key] = copy; } } return result;}var o2 = extend({},o1);console.log('o1',o1.number.id());//0console.log('o1',o1.string.id());//1console.log('o1',o1.object.id());//2console.log('o1',o1.arr.id());//3console.log('o2',o2.number.id());//4console.log('o2',o2.string.id());//5console.log('o2',o2.object.id());//6console.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 == "就不改你"
上面内容来自于:
https://yq.aliyun.com/articles/35053
- 说说JS中的浅拷贝与深拷贝
- Js中的深拷贝与浅拷贝
- js中的浅拷贝和深拷贝
- js中的深拷贝和浅拷贝
- js 深拷贝与浅拷贝Demo
- js 浅拷贝与深拷贝
- JS深拷贝与浅拷贝
- js对象浅拷贝与深拷贝
- JS的深拷贝与浅拷贝
- js深拷贝与浅拷贝
- js深拷贝与浅拷贝
- c++中的深拷贝与浅拷贝
- C#中的浅拷贝与深拷贝
- C#中的深拷贝与浅拷贝
- Python中的浅拷贝与深拷贝
- java中的深拷贝与浅拷贝
- IOS中的深拷贝与浅拷贝
- python中的深拷贝与浅拷贝
- 查看表空间,表空间路径,增加表空间 2015-06-11 15:13:46 分类: Oracle select * from dba_tablespaces--查看表空间 select
- 448. Find All Numbers Disappeared in an Array
- hihoCoder 字符消除 (字符串处理)(枚举)
- 路径/\问题
- iOS - Xcode 语言国际化步骤
- 说说JS中的浅拷贝与深拷贝
- Swift初体验-对象和类
- javascript模拟类
- 最受欢迎的深度学习项目
- unity3D 批量导出源代码
- String深入解析
- web引用连接oracle 12C 数据库
- 寻找设计模式-模板方法
- Unity两点之间模拟抛物线运动