JAVASCRIPT
VUE
REACT
NODE
ES6
TYPESCRIPT

Js浅拷贝与深拷贝

2017. 05. 29    

JS浅拷贝与深拷贝

传值与传址

在进行赋值操作的时候,基本数据类型的赋值(=)是在内存中新开辟一段栈内存,然后再把再将值赋值到新的栈中。例如:

var i = 8;
var j = i;

i ++ ;
console.log(i); // 8
console.log(j); // 9

但是引用类型的赋值是传址。只是改变指针的指向,例如,也就是说引用类型的赋值是对象保存在栈中的地址的赋值,这样的话两个变量就指向同一个对象,因此两者之间操作互相有影响。例如:

var a = {}; // a保存了一个空对象的实例
var b = a;  // a和b都指向了这个空对象

a.name = 'jozo';
console.log(a.name); // 'jozo'
console.log(b.name); // 'jozo'

b.age = 22;
console.log(b.age);// 22
console.log(a.age);// 22

console.log(a == b);// true

浅拷贝

浅复制只会将对象的各个属性进行依次复制,并不会进行递归复制

var obj1 = {
  'name' : 'aaa',
  'age' :  66,
  'arr': [2,3]
};

var obj2 = obj1;
var obj3 = shallowCopy(obj1);

function shallowCopy(src) {
  var dst = {};
  for (var prop in src) {
    if (src.hasOwnProperty(prop)) {
      dst[prop] = src[prop];
    }
  }
  return dst;
}

和原数据是否指向同一对象 第一层数据为基本数据类型 原数据中包含子对象
赋值 改变会使原数据一同改变 改变会使原数据一同改变
浅拷贝 改变不会使原数据一同改变 改变会使原数据一同改变
深拷贝 改变不会使原数据一同改变 改变不会使原数据一同改变

ES6深拷贝

let obj = { a: { a: "hello" }, b: 33 };
let newObj = JSON.parse(JSON.stringify(obj));
newObj.a = 5;
console.log('obj', obj, 'newObj', newObj);
let arr = [1, 2, 3, 4, 5, 6];
let arr1 = JSON.parse(JSON.stringify(arr));
arr1[2]=  8858
console.log('arr1', arr1, 'arr', arr);

JS深拷贝

function clone (obj){
  let res = Object.prototype.toString.call(obj).slice(8,-1)==='Array'?[]:{};
  for(let item in obj){
    if(typeof obj[item] === 'object'){
      res[item] = clone(obj[item]);
    }else{
      res[item] = obj[item];
    }
  }
  return res;
};

数组的浅拷贝

let a = [1,2,3]

let b = a.slice()

b[0] = 50;

console.log(a,b)
//[1,2,3]
//[50,2,3]

jQuery.extend 实现深拷贝和浅拷贝

第一个参数可以是布尔值,用来设置是否深度拷贝的

jQuery.extend(true, { a : { a : "a" } }, { a : { b : "b" } } );
jQuery.extend( { a : { a : "a" } }, { a : { b : "b" } } );

jq.extend源码

jQuery.extend = jQuery.fn.extend = function() {
	var src, copyIsArray, copy, name, options, clone,
		target = arguments[0] || {},
		i = 1,
		length = arguments.length,
		deep = false;
	// 处理深层复制情况
	if ( typeof target === "boolean" ) {
		deep = target;
		//跳过布尔值和目标
		target = arguments[ i ] || {};
		i++;
	}
	// 处理目标是字符串或其他内容时的情况(可能是深层复制)
	if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
		target = {};
	}
	// 如果只传递一个参数,则扩展jQuery本身
	if ( i === length ) {
		target = this;
		i--;
	}
	for ( ; i < length; i++ ) {
		//仅处理非null /未定义的值
		if ( (options = arguments[ i ]) != null ) {
			// 扩展基础对象
			for ( name in options ) {
				src = target[ name ];
				copy = options[ name ];

				// 防止永无止境的循环
				if ( target === copy ) {
					continue;
				}

				// 如果我们合并普通对象或数组,则递归
				if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
					if ( copyIsArray ) {
						copyIsArray = false;
						clone = src && jQuery.isArray(src) ? src : [];

					} else {
						clone = src && jQuery.isPlainObject(src) ? src : {};
					}

					// 切勿移动原始对象,克隆它们
					target[ name ] = jQuery.extend( deep, clone, copy );

				// 不要引入未定义的值
				} else if ( copy !== undefined ) {
					target[ name ] = copy;
				}
			}
		}
	}

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

Zepto 中的 $.extend

$.extend 方法判断的第一个参数传入的是一个布尔值,判断是否进行深拷贝。

// 内部方法:用户合并一个或多个对象到第一个对象
// 参数:
// target 目标对象  对象都合并到target里
// source 合并对象
// deep 是否执行深度合并
function extend(target, source, deep) {
  for (key in source)
    if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
      // source[key] 是对象,而 target[key] 不是对象, 则 target[key] = {} 初始化一下,否则递归会出错的
      if (isPlainObject(source[key]) && !isPlainObject(target[key]))
        target[key] = {}

      // source[key] 是数组,而 target[key] 不是数组,则 target[key] = [] 初始化一下,否则递归会出错的
      if (isArray(source[key]) && !isArray(target[key]))
        target[key] = []
        // 执行递归
      	extend(target[key], source[key], deep)
    }
    // 不满足以上条件,说明 source[key] 是一般的值类型,直接赋值给 target 就是了
    else if (source[key] !== undefined) target[key] = source[key]
}

// Copy all but undefined properties from one or more
// objects to the `target` object.
$.extend = function(target){
  var deep, args = slice.call(arguments, 1);

  //第一个参数为boolean值时,表示是否深度合并
  if (typeof target == 'boolean') {
    deep = target;
    //target取第二个参数
    target = args.shift()
  }
  // 遍历后面的参数,都合并到target上
  args.forEach(function(arg){ extend(target, arg, deep) })
  return target
}

来源:掘金