Object.assign 是什么?
The object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object.
翻译一下也就是:
Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它会返回目标对象。
为了便于理解,此处贴出mdn的对Object.assign的polyfill
if (typeof Object.assign != 'function') { // Must be writable: true,enumerable: false,configurable: true Object.defineProperty(Object,"assign",{ value: function assign(target,varArgs) { // .length of function is 2 'use strict'; if (target == null) { // TypeError if undefined or null throw new TypeError('Cannot convert undefined or null to object'); } var to = Object(target); for (var index = 1; index < arguments.length; index++) { var nextSource = arguments[index]; if (nextSource != null) { // Skip over if undefined or null for (var nextKey in nextSource) { // Avoid bugs when hasOwnProperty is shadowed if (Object.prototype.hasOwnProperty.call(nextSource,nextKey)) { to[nextKey] = nextSource[nextKey]; } } } } return to; },writable: true,configurable: true }); }
其中的Object构造函数为给定的值创建一个对象包裹器。如果值为null或undefined,它将创建并返回一个空对象,否则,它将返回一个Type对应于给定值的对象。如果该值已经是一个对象,它将返回该值。
举个栗子?
Object(1) // Number {1} Object('') // String {"",length: 0} Object(false) // Boolean {false}
从polyfill的代码不难看出,Object.assign 就是将所传参数当中的对象的可枚举属性的值覆盖到第一个对象上,那么由于js当中的object,array是引用类型,所以对与对象,数组的覆盖其实只是覆盖了对数组,对象的引用,也即 浅copy
mdn栗子来一枚
var o1 = { a: 1,b: 1,c: 1 }; var o2 = { b: 2,c: 2 }; var o3 = { c: 3 }; var obj = Object.assign({},o1,o2,o3); console.log(obj); // { a: 1,b: 2,c: 3 }
如何实现深copy?
来个redux 当中reducer 嵌套数据更新的栗子?
add(state,{ payload: todo }) { const todos = state.a.b.todos.concat(todo); const b = Object.assign({},state.a.b,{todos}) const a = Object.assign({},state.a,{b}); return Object({},state,{a}); },
上面的栗子当中concat()方法用于合并两个或多个数组。此方法不会更改现有的数组,而是返回一个新的数组。可以理解为利用concat方法创建了一个新的todos数组,这样就可以避免对数据的修改影响到了旧的todos数组,然后将新的todos数组使用Object.assign 给新的b,以此,仅仅实现了sate对象中将深层次的todos的一个‘深copy’。
但是如果state还有其他的属性的值为对象或者数组,简单的使用Object.assign 只是复制了一个引用。所以在写reducer的时候需要尽量避免state嵌套的太深,为了安全,我们可以使用 updeep来更新数据,或者直接使用不可变数据,此处不再多说,继续探讨Object.assign.
实现一个deepCopy?
前面探讨了对象当中单个属性值的深copy,但是如果有多个值,怎么办呢?一个一个手动找出来?当然不行啊,这样一点儿也不好玩儿 为了实现一个deepCopy,我们先简单了解一下js的数据类型:
值类型:数值、布尔值、null、undefined。
基本类型值是指在栈内存保存的简单数据段,在复制基本类型值的时候,会开辟出一个新的内存空间,将值复制到新的内存空间,举个栗子:
var a = 1; var b = a; a = 2; console.log(a);//输出2; console.log(b);//输出1;
引用类型:对象、数组、函数等。
用类型值是保存在堆内存中的对象,变量保存的只是指向该内存的地址,在复制引用类型值的时候,其实只复制了指向该内存的地址,举个栗子:
var a={b:1} var a2 = a; a2.b = 2; console.log(a) // 输出 {b: 2}
deepCopy
了解了js的数组类型之后,那么实现一个深copy其实主要就是解决引用类型的copy,数组和对象无限往下拆,最终其属性也是值类型组成的。所以我们可以使用递归实现一个deepCopy
,下面就直接贴代码了,略啰嗦,欢迎?拍砖
function detectType(source) { return Object.prototype.toString .call(source) .split(/[\[,\s,\]]/)[2] .toLowerCase(); } function deepCopy(source,copyDeep) { var type = detectType(source); if (!(type === 'object' || type === 'array')) { return source; } var newObject = type === 'array' ? source.slice() :Object.assign({},source); if (!copyDeep) { return newObject; } Object.keys(newObject).forEach(function (key) { newObject[key] = deepCopy(newObject[key],true); }); return newObject; }