Object.assign 是什么?
此处直接复制 mdn 文档的内容如下:
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 的数据类型:
值类型: 数值布尔值 nullundefined
基本类型值是指在栈内存保存的简单数据段, 在复制基本类型值的时候, 会开辟出一个新的内存空间, 将值复制到新的内存空间, 举个栗子:
- 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;
- }
来源: https://segmentfault.com/a/1190000013202537