我们已经来到了现代ECMAScript的时代,以前的正确的方法,现在看来似乎有点考虑不全。比如在ES6之前要实现对象的浅拷贝,比较简单,因为当时对象的属性只有String类型,ES6之后对象的属性有String和Symbol类型。
由于文章上下文关系,本文将按照’属性描述符’->’对象的属性遍历方法介绍’->’现代ECMAScript对象的浅拷贝’进行介绍,现代ECMAScript对象的深拷贝比较复杂,有时间在分析。
属性描述符
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一,不能同时是两者。
数据描述符
configurable
当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
enumerable
当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为 false。
数据描述符同时具有以下可选键值:
value
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。
writable
当且仅当该属性的writable为true时,value才能被赋值运算符改变。默认为 false。
存取描述符
get
一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined。
set
一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined。
属性描述符的读写操作
我们可以通过Object.getOwnPropertyDescriptor(o,name)、Object.getOwnPropertyDescriptors(obj)来查看属性描述符,
通过Object.defineProperty(o,name,desc)、Object.defineProperties(o,descriptors)、Object.create(proto,descriptors)等方法来更改属性描述符。
对象的属性遍历方法介绍
上面,我们了解了属性描述符,其中枚举这个属性描述符,在不同方法对对象属性的遍历过程产生的作用差异很大,下面我开始介绍如何遍历对象的属性。
本小结的测试代码,我就不贴出来了,都很简单,我这里直接给出结论,感兴趣的朋友可以自己尝试。
for…in
定义:for…in语句以任意顺序遍历一个对象的可枚举属性。对于每个不同的属性,语句都会被执行。
语法:
PS:这里我们需要记住该方法遍历属性的特点:
1.可以遍历对象自身和原型链上可枚举的属性
2.任意顺序,说明遍历的属性先后顺序不定(不同运行环境顺序不同)。不建议对数组使用for…in来遍历主要原因就是这个,另一方面为性能考虑for…in还会遍历到原型链上的可枚举属性。
Object.keys()
定义:Object.keys() 方法会返回一个由该对象的自身的可枚举属性组成的数组。
语法:
PS:这里我们需要记住该方法遍历属性的特点:
1.属性是自身的
2.属性是可枚举的
3.任意顺序,枚举属性的顺序和for…in/Object.getOwnPropertyNames(obj)一致
4.返回一个所有元素为字符串(不包括Symbol)的数组
Object.getOwnPropertyNames()
定义:Object.getOwnPropertyNames()方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。
语法:
PS:这里我们需要记住该方法遍历属性的特点:
1.属性是自身的
2.属性了枚举和不可枚举都可以遍历
3.任意顺序,枚举属性的顺序和for…in/Object.keys(obj)一致
Object.getOwnPropertySymbols()
定义:Object.getOwnPropertySymbols() 方法返回一个给定对象自身的所有 Symbol 属性的数组。
语法:
PS:
1.属性是自身的
2.属性为Symbol类型
Reflect.ownKeys()
定义:Reflect.ownKeys() 返回一个由目标对象自身的属性键组成的数组。
语法:
PS:
1.属性是自身的
2.属性可以是字符串或Symbol
3.属性是可枚举或不可枚举
这里看,似乎能遍历出自身的所有属性,还差原型连上的属性。
总结下上面的方法
为了好归纳,我这里将对象的属性分为以下类别:1.自身可枚举的属性,2.自身不可枚举的属性,3.Symbol类型的属性,4.原型链上的可枚举属性,5.原型链上的不可枚举属性,6.原型链上的Symbol属性2018-03-16更新
方法 | 可遍历的属性类别 |
---|---|
for…in | 1,4 |
Object.keys() | 1 |
Object.getOwnPropertyNames() | 1,2 |
Object.getOwnPropertySymbols() | 3 |
Reflect.ownKeys() | 1,2,3 |
从上表分析,还没有一个方法能完美解决,我们只能组合使用了。
PS:除了for…in其余方法均返回数组。
现代ECMAScript对象的浅拷贝
本小节会我们会实现各种浅拷贝,并分析各自的劣势,最终我们将实现一种比较完美的方法(暂不考虑兼容性)。
现看一个以前实现的方法
|
|
这中方法对于ES6之前的确可行,毕竟我也用过这样的方法,有了Symbol之后这个就不再正确了。
JSON.parse(JSON.stringify())
该方式在遇到不安全的JSON值会自动将其忽略,在数组中则会返回null(以保证单元位置不变)。
不安全的 JSON 值: undefined 、 function 、 symbol (ES6+)和包含循环引用(对象之间相互引用,形成一个无限循环)的 对象 都不符合 JSON 结构标准,支持 JSON 的语言无法处理它们
Object.assign 和 展开运算符(…)
|
|
这个函数的定义:Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。
特点:自身的可枚举的包括Symbol类型的,不包括不可枚举的属性和原型链上的属性,不完美。
Object.create() + Object.getPrototypeOf() + Object.getOwnPropertyDescriptors()
首先需要介绍一下相关的方法,ES7的Object.getOwnPropertyDescriptors()。
Object.getOwnPropertyDescriptors()
定义:Object.getOwnPropertyDescriptors() 方法用来获取一个对象的所有自身属性的描述符。
语法:
PS:
1.属性是自身的
2.属性可以是字符串或Symbol
3.属性是可枚举或不可枚举
4.包括了集体属性的描述符(value)
到这里似乎我们已经找到了,比较完美的解决方案了,我们来组合一下这3个方法。
PS:obj1对象通过Object.create()方法指定了自身的原型链(从原型链继承了相关属性),然后在通过Object.getOwnPropertyDescriptors()方法把自身的(包括可枚举的、不可枚举的、Symbol类型的)全部添加obj1上,这样是实现了我们的真正意义上的浅拷贝。
扩展
数组的浅拷贝ary.slice()、 ary.concat()、[…ary]、JSON.parse(JSON.stringify(ary))
期望加入一个技术氛围nice的团队-成都
参考文档:
Object.create
Object.assign
Object.getOwnPropertySymbols
ES6时代,你真的会克隆对象吗?