一、什么是浅拷贝?
如果拷贝的值是基本数据类型,拷贝的是基本类型的值。如果是引用类型拷贝的是内存地址。
浅拷贝只解决了第一层的问题,拷贝第一层的基本类型值,以及第一层的引用类型地址。
也就是说:只能保证第一层数据为基本数据类型时,不会随原数据改变。原数据中包含子对象时,随原数据变化。
1.1 浅拷贝的使用场景
1. Object.assign()
该方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| let a = { name: 'owenli', book: { title:'前端进阶之路', price: '66' } } let b = Object.assign({}, a) console.log('b原始值') console.log(b) a.name = '啦啦啦' a.book.price = '88' console.log('修改 name 和 price 后') console.log(a) console.log(b)
|
注意:Object.assign() 不是深拷贝。
2. 展开运算符
1 2 3 4 5
| let c = {...a}
console.log('展开运算符...') console.log(c) console.log(a)
|
展开运算符的效果和 Object.assign 相同。
另外,slice 、concat 等也是浅拷贝。
二、深拷贝
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。拷贝前后两个对象互不影响。
2.1 使用场景
1. JSON.parse(JSON.stringify(object))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| let a = { name: 'owenli', book: { title: '啦啦啦', price: '44' } } let b = JSON.parse(JSON.stringify(a)) console.log(a)
a.name = '啦啦啦' a.book.price = '66'
console.log(a) console.log(b)
|
注意:undefined、Symbol 和 函数三种情况会直接忽略。不能处理 new Date() 和正则表达式。
三、浅拷贝 Object.assigin 的原理及实现
1
| Object.assign(target, ...sources)
|
其中 target 是目标对象,sources 是源对象,可以有多个,返回修改后的目标对象 target。
如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后来的源对象的属性将类似地覆盖早先的属性。
3.1 Object.assign 模拟实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| if (typeof Object.assign2 != 'function') { Object.defineProperty(Object, 'assign2', { value: function (target) { 'use strict' if (target == 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) { for (var nextKey in nextSource) { if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { to[nextKey] = nextSource[nextKey] } } } } return to }, writable: true, configurable: true }) }
let a = { name: '1111', age: 19 } let b = { name: 'owenli', book: { title: 'lalalal', price: 20 } } let c = Object.assign2(a, b) console.log(c)
console.log(a === c)
|
3.2 可枚举性
原生的Object中的属性是不可以枚举的。Object.assign
是否可以被枚举。
1 2 3 4
| Object.getOwnPropertyDescriptor(Object, 'assign')
Object.propertyIsEnumerable('assign')
|
直接向 Object 上挂载属性是可以枚举的。
1 2 3 4 5 6
| Object.a = function () { } Object.getOwnPropertyDescriptor(Object, 'a')
|
所以要想实现 Object.assign 就需要使用 Object.defineProperty,并设置 writable: true, enumerable: false, configurable: true。默认都是 false。
1 2 3 4 5 6 7
| Object.defineProperty(Object, 'b', { value: function() { console.log('b') } }) Object.getOwnPropertyDescriptor(Object, 'b')
|
综上:Object.assign2
实现是需要设置 writable: true, configurable: true
。因为默认是 enumerable
是 false。
3.3 参数判断
在使用判断的时候直接用 target == null
。
3.4 原始类型包装成对象
1 2 3 4 5 6 7 8 9 10
| var v1 = 'abc' var v2 = true var v3 = 10 var v4 = Symbol('foo')
var obj = Object.assign({}, v1, null, v2, undefined, v3, v4)
console.log(obj)
|
v2, v3, v4 被忽略,原因是自身没有可枚举的属性。
通过 Object.keys() 和 Object.getOwnPropertyNames() 可以查看枚举属性和所有的属性。
1 2 3 4 5 6 7 8 9
| console.log(Object.keys(v1)) console.log(Object.keys(v2)) console.log(Object.keys(v3))
console.log(Object.getOwnPropertyNames(v1)) console.log(Object.getOwnPropertyNames(v2))
|
那没 Object.assign 是如何实现:
1 2 3 4 5 6 7 8 9 10 11 12
| var a = 'abc' var b = { v1: 'def', v2: true, v3: 10, v4: Symbol('foot'), v5: null, v6: undefined } var objc = Object.assign(a, b) console.log(objc)
|
原因:因为 undefined , true 不是作为对象,而是作为对象 b 的属性值,对象 b 是可以枚举的。
1
| console.log(Object.keys(b))
|
这里其实又可以看出一个问题来,那就是目标对象是原始类型,会包装成对象,对应上面的代码就是目标对象 a 会被包装成 [String: ‘abc’],那模拟实现时应该如何处理呢?很简单,使用 Object(..) 就可以了。
另外,在严格模式下向不可修改的对象属性赋值会报错。默认情况静默失败。
3.5 存在性
如何在不访问对象属性的情况下判断属性是否存在。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| var obj = { a: 1 }
var newObj = Object.create(obj) newObj.b = 10
console.log(newObj)
console.log('a' in newObj) console.log('b' in newObj)
console.log(newObj.hasOwnProperty('a')) console.log(newObj.hasOwnProperty('b'))
|
两种方法的区别:
- in 操作符检查 对象 和 原型链。
- hasOwnProperty 值检查 对象,不检查原型链。
模拟实现 Object.assign 只需检测对象中属性,不需要检查原型链。
1 2 3 4 5 6
| var obj1 = Object.create(null) obj1.b = 2
console.log(obj1.hasOwnProperty('b')) console.log(Object.prototype.hasOwnProperty.call(obj1, 'b'))
|
参考
木易杨前端进阶