浅析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中对DOM节点的访问、创建、修改、删除
Nov 16 Javascript
AngularJS directive返回对象属性详解
Mar 28 Javascript
AngularJs Dependency Injection(DI,依赖注入)
Sep 02 Javascript
正则表达式替换html元素属性的方法
Nov 26 Javascript
用 js 的 selection range 操作选择区域内容和图片
Apr 18 Javascript
JavaScript设计模式之单例模式详解
Jun 09 Javascript
jQuery Layer弹出层传值到父页面的实现代码
Aug 17 jQuery
PHP 实现一种多文件上传的方法
Sep 20 Javascript
Vue实现一个无限加载列表功能
Nov 13 Javascript
优雅的elementUI table单元格可编辑实现方法详解
Dec 23 Javascript
Vue中使用Lodop插件实现打印功能的简单方法
Dec 19 Javascript
js实现登录时记住密码的方法分析
Apr 05 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网上商城购物车设计代码分享
2012/02/15 PHP
PHP的mysqli_select_db()函数讲解
2019/01/23 PHP
php中try catch捕获异常实例详解
2020/08/06 PHP
JS类定义原型方法的两种实现的区别评论很多
2007/09/12 Javascript
比较简单实用的使用正则三种版本的js去空格处理方法
2007/11/18 Javascript
jquery插件制作 表单验证实现代码
2012/08/17 Javascript
JS控件的生命周期介绍
2012/10/22 Javascript
巧用局部变量提升javascript性能
2014/02/24 Javascript
AngularJS在IE8的不支持的解决方法
2016/05/13 Javascript
BootStrap 轮播插件(carousel)支持左右手势滑动的方法(三种)
2016/07/07 Javascript
原生JS实现风箱式demo,并封装了一个运动框架(实例代码)
2016/07/22 Javascript
微信小程序遇到修改数据后页面不渲染的问题解决
2017/03/09 Javascript
整理关于Bootstrap列表组的慕课笔记
2017/03/29 Javascript
JS实现动态添加DOM节点和事件的方法示例
2017/04/28 Javascript
解决淘宝cnpm 安装后cnpm不是内部或外部命令的问题
2018/05/17 Javascript
vue实现PC端录音功能的实例代码
2019/06/05 Javascript
vue 使用 vue-pdf 实现pdf在线预览的示例代码
2020/04/26 Javascript
介绍Python的Django框架中的静态资源管理器django-pipeline
2015/04/25 Python
python使用WMI检测windows系统信息、硬盘信息、网卡信息的方法
2015/05/15 Python
python开发简易版在线音乐播放器
2017/03/03 Python
使用 Python 实现微信公众号粉丝迁移流程
2018/01/03 Python
Python 获得命令行参数的方法(推荐)
2018/01/24 Python
Django添加sitemap的方法示例
2018/08/06 Python
更新升级python和pip版本后不生效的问题解决
2020/04/17 Python
amazeui模态框弹出后立马消失并刷新页面
2020/08/19 HTML / CSS
美国林业供应商:Forestry Suppliers
2019/05/01 全球购物
JD Sports西班牙:英国领先的运动服装公司
2020/01/06 全球购物
几个Shell Script面试题
2012/08/31 面试题
市场营销职业生涯规划书范文
2014/01/12 职场文书
《雷雨》教学反思
2014/02/20 职场文书
事务机电主管工作职责
2014/02/25 职场文书
婚前协议书范本
2014/04/15 职场文书
幼儿园清明节活动总结
2014/07/04 职场文书
房屋租赁协议书(标准版)
2014/10/02 职场文书
仓库管理员岗位职责
2015/02/03 职场文书
化工生产实习心得体会
2016/01/22 职场文书