浅析javaScript中的浅拷贝和深拷贝


Posted in Javascript onFebruary 15, 2017

1、javaScript的变量类型

(1)基本类型:

5种基本数据类型Undefined、Null、Boolean、Number 和 String,变量是直接按值存放的,存放在栈内存中的简单数据段,可以直接访问。

(2)引用类型:

存放在堆内存中的对象,变量保存的是一个指针,这个指针指向另一个位置。当需要访问引用类型(如对象,数组等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。

JavaScript存储对象都是存地址的,所以浅拷贝会导致 obj1 和obj2 指向同一块内存地址。改变了其中一方的内容,都是在原来的内存上做修改会导致拷贝对象和源对象都发生改变,而深拷贝是开辟一块新的内存地址,将原对象的各个属性逐个复制进去。对拷贝对象和源对象各自的操作互不影响。

例如:数组拷贝

//浅拷贝,双向改变,指向同一片内存空间
var arr1 = [1, 2, 3];
var arr2 = arr1;
arr1[0] = 'change';
console.log('shallow copy: ' + arr1 + " ); //shallow copy: change,2,3
console.log('shallow copy: ' + arr2 + " ); //shallow copy: change,2,3

2、浅拷贝的实现

2.1、简单的引用复制

function shallowClone(copyObj) {
 var obj = {};
 for ( var i in copyObj) {
 obj[i] = copyObj[i];
 }
 return obj;
}
var x = {
 a: 1,
 b: { f: { g: 1 } },
 c: [ 1, 2, 3 ]
};
var y = shallowClone(x);
console.log(y.b.f === x.b.f);  // true

2.2、Object.assign()

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。Object.assign会跳过那些值为 null 或 undefined 的源对象。

var x = {
 a: 1,
 b: { f: { g: 1 } },
 c: [ 1, 2, 3 ]
};
var y = Object.assign({}, x);
console.log(y.b.f === x.b.f);  // true

3、深拷贝的实现

3.1、Array的slice和concat方法

Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。之所以把它放在深拷贝里,是因为它看起来像是深拷贝。而实际上它是浅拷贝。原数组的元素会按照下述规则拷贝:

  • 如果该元素是个对象引用 (不是实际的对象),slice 会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。
  • 对于字符串、数字及布尔值来说(不是 String、Number 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。

如果向两个数组任一中添加了新元素,则另一个不会受到影响。例子如下:

var array = [1,2,3]; 
var array_shallow = array; 
var array_concat = array.concat(); 
var array_slice = array.slice(0); 
console.log(array === array_shallow); //true 
console.log(array === array_slice); //false,“看起来”像深拷贝
console.log(array === array_concat); //false,“看起来”像深拷贝

可以看出,concat和slice返回的不同的数组实例,这与直接的引用复制是不同的。而从另一个例子可以看出Array的concat和slice并不是真正的深复制,数组中的对象元素(Object,Array等)只是复制了引用。如下:

var array = [1, [1,2,3], {name:"array"}]; 
var array_concat = array.concat();
var array_slice = array.slice(0);
array_concat[1][0] = 5; //改变array_concat中数组元素的值 
console.log(array[1]); //[5,2,3] 
console.log(array_slice[1]); //[5,2,3] 
array_slice[2].name = "array_slice"; //改变array_slice中对象元素的值 
console.log(array[2].name); //array_slice
console.log(array_concat[2].name); //array_slice

3.2、JSON对象的parse和stringify

JSON对象是ES5中引入的新的类型(支持的浏览器为IE8+),JSON对象parse方法可以将JSON字符串反序列化成JS对象,stringify方法可以将JS对象序列化成JSON字符串,借助这两个方法,也可以实现对象的深拷贝。

//例1
var source = { name:"source", child:{ name:"child" } } 
var target = JSON.parse(JSON.stringify(source));
target.name = "target"; //改变target的name属性
console.log(source.name); //source 
console.log(target.name); //target
target.child.name = "target child"; //改变target的child 
console.log(source.child.name); //child 
console.log(target.child.name); //target child
//例2
var source = { name:function(){console.log(1);}, child:{ name:"child" } } 
var target = JSON.parse(JSON.stringify(source));
console.log(target.name); //undefined
//例3
var source = { name:function(){console.log(1);}, child:new RegExp("e") }
var target = JSON.parse(JSON.stringify(source));
console.log(target.name); //undefined
console.log(target.child); //Object {}

这种方法使用较为简单,可以满足基本的深拷贝需求,而且能够处理JSON格式能表示的所有数据类型,但是对于正则表达式类型、函数类型等无法进行深拷贝(而且会直接丢失相应的值)。还有一点不好的地方是它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。同时如果对象中存在循环引用的情况也无法正确处理。

4、jQuery.extend()方法源码实现

jQuery的源码 - src/core.js #L121源码及分析如下:

jQuery.extend = jQuery.fn.extend = function() { //给jQuery对象和jQuery原型对象都添加了extend扩展方法
var options, name, src, copy, copyIsArray, clone,
 target = arguments[0] || {},
 i = 1,
 length = arguments.length,
 deep = false;
 //以上其中的变量:options是一个缓存变量,用来缓存arguments[i],name是用来接收将要被扩展对象的key,src改变之前target对象上每个key对应的value。
 //copy传入对象上每个key对应的value,copyIsArray判定copy是否为一个数组,clone深拷贝中用来临时存对象或数组的src。
// 处理深拷贝的情况
if ( typeof target === "boolean" ) {
 deep = target;
 target = arguments[1] || {};
 //跳过布尔值和目标 
 i++;
}

 
// 控制当target不是object或者function的情况
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
 target = {};
}

// 当参数列表长度等于i的时候,扩展jQuery对象自身。
if ( length === i ) {
 target = this;
 --i;
}

for ( ; i < length; i++ ) {
 if ( (options = arguments[ i ]) != null ) {
  // 扩展基础对象
  for ( name in options ) {
   src = target[ name ];
   copy = options[ name ];

   // 防止永无止境的循环,这里举个例子,如var i = {};i.a = i;$.extend(true,{},i);如果没有这个判断变成死循环了
   if ( target === copy ) {
    continue;
   }
   if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
    if ( copyIsArray ) {
     copyIsArray = false;
     clone = src && jQuery.isArray(src) ? src : [];// 如果src存在且是数组的话就让clone副本等于src否则等于空数组。
    } else {
     clone = src && jQuery.isPlainObject(src) ? src : {};// 如果src存在且是对象的话就让clone副本等于src否则等于空数组。
    }

    // 递归拷贝
    target[ name ] = jQuery.extend( deep, clone, copy );

   } else if ( copy !== undefined ) {
    target[ name ] = copy;// 若原对象存在name属性,则直接覆盖掉;若不存在,则创建新的属性。
   }
  }
 }
}

// 返回修改的对象
return target;
};

jQuery的extend方法使用基本的递归思路实现了浅拷贝和深拷贝,但是这个方法也无法处理源对象内部循环引用,例如:

var a={"name":"aaa"};
var b={"name":"bbb"};
a.child=b;
b.parent=a;
$.extend(true,{},a);//直接报了栈溢出。Uncaught RangeError: Maximum call stack size exceeded

5、自己动手实现一个拷贝方法

var $ = (function(){
 var types = 'Array Object String Date RegExp Function Boolean Number Null Undefined'.split(' ');
 function type() {
 return Object.prototype.toString.call(this).slice(8, -1);
 }
 for (var i = types.length; i--;) {
  $['is' + types[i]] = (function (self) {
  return function (elem) {
   return type.call(elem) === self;
  };
 })(types[i]);
 }
 return $;
})();//类型判断

function copy(obj,deep){ 
 if(obj === null || typeof obj !== "object"){ 
  return obj; 
 } 
 
 var name, target = $.isArray(obj) ? [] : {}, value; 
 for(name in obj){ 
  value = obj[name]; 
  if(value === obj) {
  continue;
  }
  if(deep && ($.isArray(value) || $.isObject(value))){
  target[name] = copy(value,deep);
  }else{
  target[name] = value;
  } 
 } 
 return target;
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
WEB高性能开发之疯狂的HTML压缩
Jun 19 Javascript
js下写一个事件队列操作函数
Jul 19 Javascript
JS实现弹出浮动窗口(支持鼠标拖动和关闭)实例详解
Aug 06 Javascript
JavaScript 继承详解(六)
Oct 11 Javascript
js的OOP继承实现(必看篇)
Feb 18 Javascript
JavaScript正则获取地址栏中参数的方法
Mar 02 Javascript
深入学习 JavaScript中的函数调用
Mar 23 Javascript
如何使用angularJs
May 08 Javascript
js编写简单的聊天室功能
Aug 17 Javascript
webpack热模块替换(HMR)/热更新的方法
Apr 05 Javascript
解决angular2 获取到的数据无法实时更新的问题
Aug 31 Javascript
jQuery中DOM操作原则实例分析
Aug 01 jQuery
微信小程序之MaterialDesign--input组件详解
Feb 15 #Javascript
Bootstrap table简单使用总结
Feb 15 #Javascript
原生js轮播(仿慕课网)
Feb 15 #Javascript
微信小程序 image组件binderror使用例子与js中的onerror区别
Feb 15 #Javascript
Canvas实现放射线动画效果
Feb 15 #Javascript
微信小程序 开发经验整理
Feb 15 #Javascript
bootstrap table操作技巧分享
Feb 15 #Javascript
You might like
PHP 判断变量类型实现代码
2009/10/23 PHP
PHP的范围解析操作符(::)的含义分析说明
2011/07/03 PHP
Codeigniter出现错误提示Error with CACHE directory的解决方案
2014/06/12 PHP
ThinkPHP中redirect用法分析
2014/12/05 PHP
PHP封装的Twitter访问类实例
2015/07/18 PHP
php图片水印添加、压缩、剪切的封装类实现
2020/04/18 PHP
JS中操作JSON总结
2020/12/06 Javascript
JavaScript实现按Ctrl键打开新页面
2014/09/04 Javascript
javascript中parseInt()函数的定义和用法分析
2014/12/20 Javascript
javascript操作符&quot;!~&quot;详解
2015/02/10 Javascript
jQuery操作属性和样式详解
2016/04/13 Javascript
AngularJS入门教程之Select(选择框)详解
2016/07/27 Javascript
Javascript之面向对象--方法
2016/12/02 Javascript
vue如何使用 Slot 分发内容实例详解
2017/09/05 Javascript
JS实现table表格内针对某列内容进行即时搜索筛选功能
2018/05/11 Javascript
vue+element-ui动态生成多级表头的方法
2018/08/28 Javascript
详解如何解决vue开发请求数据跨域的问题(基于浏览器的配置解决)
2018/11/12 Javascript
vue 组件之间事件触发($emit)与event Bus($on)的用法说明
2020/07/28 Javascript
js实现盒子移动动画效果
2020/08/09 Javascript
python time模块用法实例详解
2014/09/11 Python
python操作gmail实例
2015/01/14 Python
浅谈Python浅拷贝、深拷贝及引用机制
2016/12/15 Python
解决python3 urllib 链接中有中文的问题
2018/07/16 Python
使用Python在Windows下获取USB PID&amp;VID的方法
2019/07/02 Python
Python图像处理库PIL的ImageFilter模块使用介绍
2020/02/26 Python
Python xml、字典、json、类四种数据类型如何实现互相转换
2020/05/27 Python
Python3 + Appium + 安卓模拟器实现APP自动化测试并生成测试报告
2021/01/27 Python
利用简洁的图片预加载组件提升html5移动页面的用户体验
2016/03/11 HTML / CSS
德国珠宝和手表在线商店:VALMANO
2019/03/24 全球购物
质量标语大全
2014/06/12 职场文书
公司开会通知
2015/04/20 职场文书
2016创先争优活动党员公开承诺书
2016/03/24 职场文书
2019西餐厅创业计划书范文!
2019/07/12 职场文书
导游词之太行山青龙峡
2020/01/14 职场文书
如何利用opencv判断两张图片是否相同详解
2021/07/07 Python
MySQL如何解决幻读问题
2021/08/07 MySQL