浅析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 相关文章推荐
javascript instanceof,typeof的区别
Mar 24 Javascript
基于jQuery Ajax实现上传文件
Mar 24 Javascript
form+iframe解决跨域上传文件的方法
Nov 18 Javascript
JavaScript中从setTimeout与setInterval到AJAX异步
Feb 13 Javascript
JS打开摄像头并截图上传示例
Feb 18 Javascript
AngularJS之ionic 框架下实现 Localstorage本地存储
Apr 22 Javascript
使用puppeteer破解极验的滑动验证码
Feb 24 Javascript
vue单个组件实现无限层级多选菜单功能
Apr 10 Javascript
详解VUE里子组件如何获取父组件动态变化的值
Dec 26 Javascript
vue移动端实现手机左右滑动入场动画
Jun 17 Javascript
基于vue实现探探滑动组件功能
May 29 Javascript
Vuex实现购物车小功能
Aug 17 Javascript
微信小程序之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中静态类与静态变量用法的区别分析
2015/01/15 PHP
PHP基于接口技术实现简单的多态应用完整实例
2017/04/26 PHP
详解cookie验证的php应用的一种SSO解决办法
2017/10/20 PHP
php用户名的密码加密更安全的方法
2019/06/21 PHP
用js实现的抽象CSS圆角效果!!
2007/05/03 Javascript
jQuery UI Autocomplete 1.8.16 中文输入修正代码
2012/04/16 Javascript
JS获取select的value和text值的简单实例
2014/02/26 Javascript
详解AngularJS中$http缓存以及处理多个$http请求的方法
2016/02/06 Javascript
又一款MVVM组件 构建自己的Vue组件(2)
2017/03/13 Javascript
Node.js 实现简单的接口服务器的实例代码
2017/05/23 Javascript
Node.js操作redis实现添加查询功能
2017/05/25 Javascript
深入探究angular2 UI组件之primeNG用法
2017/07/26 Javascript
ES6学习教程之Map的常用方法总结
2017/08/03 Javascript
微信小程序表单验证功能完整实例
2017/12/01 Javascript
JS 实现微信扫一扫功能
2018/09/14 Javascript
JS 音频可视化插件Wavesurfer.js的使用教程
2018/10/31 Javascript
详解vue-router导航守卫
2019/01/19 Javascript
linux系统使用python监测系统负载脚本分享
2014/01/15 Python
Python创建文件和追加文件内容实例
2014/10/21 Python
用Python编写简单的定时器的方法
2015/05/02 Python
python去除空格和换行符的实现方法(推荐)
2017/01/04 Python
Python图片的横坐标汉字实例
2019/12/04 Python
tensorflow中tf.reduce_mean函数的使用
2020/04/19 Python
HTML5在a标签内放置块级元素示例代码
2013/08/23 HTML / CSS
在校生党员自我评价
2013/09/25 职场文书
建筑毕业生自我鉴定
2013/10/18 职场文书
品牌推广策划方案
2014/05/28 职场文书
街道党工委党的群众路线教育实践活动对照检查材料思想汇报
2014/10/05 职场文书
幼儿园小班见习报告
2014/10/31 职场文书
国庆节慰问信
2015/02/15 职场文书
2014年度个人工作总结范文
2015/03/09 职场文书
2015年秋季运动会前导词
2015/07/20 职场文书
2016形势与政策学习心得体会
2016/01/12 职场文书
《圆明园的毁灭》教学反思
2016/02/16 职场文书
Python如何使用logging为Flask增加logid
2021/03/30 Python
Golang日志包的使用
2022/04/20 Golang