ES6标准入门(六):对象的扩展
属性的简洁表示法
ES6 允许直接写入变量和函数作为对象的属性和方法,让书写更加简洁
1 | const foo = 'bar' |
上面代码中,变量foo
直接写在大括号里面。这时,属性名就是变量名, 属性值就是变量值。除了属性简写,方法也可以简写。
1 | const o = { |
这种写法用于函数的返回值,将会非常方便。
1 | function getPoint() { |
CommonJS 模块输出一组变量,就非常合适使用简洁写法。
1 | let ms = {} |
注意,简写的对象方法不能用作构造函数,会报错。
1 | const obj = { |
属性名表达式
JavaScript 定义对象的属性,有两种方法。
1 | // 方法一 |
上面代码的方法一是直接用标识符作为属性名,方法二是用表达式作为属性名,这时要将表达式放在方括号之内。
但是,如果使用字面量方式定义对象(使用大括号),在 ES5 中只能使用方法一(标识符)定义属性。
1 | var obj = { |
ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。
1 | let propKey = 'foo' |
表达式还可以用于定义方法名
1 | let obj = { |
注意,属性名表达式与简洁表示法,不能同时使用,会报错。
1 | // 报错 |
注意,属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]
,这一点要特别小心。
1 | const keyA = { a: 1 } |
上面代码中,[keyA]
和[keyB]
得到的都是[object Object]
,所以[keyB]
会把[keyA]
覆盖掉,而myObject
最后只有一个[object Object]
属性。
方法的 name 属性
函数的name
属性,返回函数名。对象方法也是函数,因此也有name
属性。
1 | const person = { |
上面代码中,方法的name
属性返回函数名(即方法名)。
如果对象的方法使用了取值函数(getter
)和存值函数(setter
),则name
属性不是在该方法上面,而是该方法的属性的描述对象的get
和set
属性上面,返回值是方法名前加上get
和set
。
1 | const obj = { |
有两种特殊情况:bind
方法创造的函数,name
属性返回bound
加上原函数的名字;Function
构造函数创造的函数,name
属性返回anonymous
。
1 | new Function().name // "anonymous" |
如果对象的方法是一个 Symbol 值,那么name
属性返回的是这个 Symbol 值的描述。
1 | const key1 = Symbol('description') |
上面代码中,key1
对应的 Symbol 值有描述,key2
没有。
对象的可枚举性和遍历
1 | let obj = { a: 1 } |
enumerable
属性成为可枚举性
,如果该属性为 false,有一些操作会省略当前属性。
有四个操作会忽略 enumerable 为 false 的属性。
for...in
循环:只遍历对象自身
和继承
的可枚举属性。Object.keys()
:返回对象自身
的所有可枚举的属性的键名
。JSON.stringify()
:只串行化对象自身
的可枚举属性。Object.assign()
:忽略enumerable
为 false 的属性,只拷贝对象自身
的可枚举的属性。
属性的遍历
for...in
遍历对象自身
和继承
的可枚举属性。(不包括 Symbol 属性)Object.keys()
返回一个数组,包括对象自身
的(不包括继承的)所有可枚举属性的键名。(不包括 Symbol 属性)Object.getOwnPropertyNames(obj)
, 返回一个数组,包含对象自身
的所有属性的键名。(不包括 Symbol 属性,但是包含不可枚举的属性)Object.getOwnPropertySymbols(obj)
,返回一个数组,包含对象自身
的所有 Symbol 属性的键名。Reflect.ownKeys(obj)
,返回一个数组,包括对象自身
的所有键名。(不管键名是否为 Symbol 值,不管是否可枚举)。
Object 的方法扩展
Object()
方法可以把参数变成对象
1 | Object(1) // Number |
Object.is()
它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
1 | Object.is('foo', 'foo') |
不同之处只有两个:一是+0
不等于-0
,二是NaN
等于自身。
1 | ;+0 === -0 //true |
ES5 可以通过下面的代码,部署Object.is
。
1 | Object.defineProperty(Object, 'is', { |
Object.assign()
Object.assign
方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)
1 | const target = { a: 1 } |
Object.assign
方法的第一个参数是目标对象,后面的参数都是源对象。
注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性
1 | const target = { a: 1, b: 1 } |
如果只有一个参数,Object.assign
会直接返回该参数。
1 | const obj = { a: 1 } |
如果该参数不是对象,则会先转成对象,然后返回。
1 | typeof Object.assign(2) // "object" |
由于undefined
和null
无法转成对象,所以如果它们作为参数,就会报错
1 | Object.assign(undefined) // 报错 |
但是,如果undefined
和null
不在首参数,就不会报错。
1 | let obj = { a: 1 } |
除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果:
1 | const v1 = 'abc' |
上述代码中数值和布尔值都会被忽略。这是因为只有字符串的包装对象,会产生可枚举属性。
1 | Object(true) // {[[PrimitiveValue]]: true} |
上面代码中,布尔值、数值、字符串分别转成对应的包装对象,可以看到它们的原始值都在包装对象的内部属性[[PrimitiveValue]]
上面,这个属性是不会被Object.assign
拷贝的。只有字符串的包装对象,会产生可枚举的实义属性,那些属性则会被拷贝。
Object.assign
拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false
)。
1 | Object.assign({b: 'c'}, |
上述需要拷贝的对象只有一个不可枚举属性 invisible,所以这个属性并没有被拷贝进去。
属性名为 Symbol
值的属性,也会被拷贝:
1 | Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' }) |
注意点
浅拷贝
如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
1 | const obj1 = {a: {b: 1}}; |
同名属性的替换
1 | const target = { a: { b: 'c', d: 'e' } } |
上述代码中,a
被整个替换,不会得到{ a: { b: 'hello', d: 'e' } }
这样的结果。
数组的处理
可以用来处理数组,但是会把数组视为对象。
1 | Object.assign([1, 2, 3], [4, 5]) |
上述代码中,以后一个数组的值替换了目标数组中对应下标的值。
取值函数的处理
Object.assign
只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制:
1 | const source = { |
上面代码中,source
对象的foo
属性是一个取值函数,Object.assign
不会复制这个取值函数,只会拿到值以后,将这个值复制过去。
常见用途
为对象添加属性
1 | class Point { |
为对象添加方法
1 | Object.assign(SomeClass.prototype, { |
克隆对象
1 | function clone(origin) { |
合并多个对象
1 | const merge = (target, ...sources) => Object.assign(target, ...sources) |
为属性指定默认值
1 | const DEFAULTS = { |
上面代码中,DEFAULTS
对象是默认值,options
对象是用户提供的参数。Object.assign
方法将DEFAULTS
和options
合并成一个新对象,如果两者有同名属性,则options
的属性值会覆盖DEFAULTS
的属性值。
Object.getOwnPropertyDescriptors()
ES5 的Object.getOwnPropertyDescriptor()
获取一个对象中某个属性的描述
1 | Object.getOwnPropertyDescriptor('123', length) |
ES2017 引入了Object.getOwnPropertyDescriptors()
方法,返回指定对象所有自身属性(非继承属性)的描述对象
1 | const obj = { |
OBject.keys()
ES5 引入了Object.keys
方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
1 | var obj = { foo: 'bar', baz: 42 } |
Object.values()
Object.values
方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
1 | const obj = { foo: 'bar', baz: 42 } |
Object.entries()
Object.entries()
方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
1 | const obj = { foo: 'bar', baz: 42 } |
Object.fromEntries()
Object.fromEntries()
方法是Object.entries()
的逆操作,用于将一个键值对数组转为对象。
1 | Object.fromEntries([ |
Object.getPrototypeOf()
Object.getPrototypeOf()
用于读取一个对象的原型对象,如果参数不是对象,会被自动转为对象。
1 | function Rectangle() { |
如果参数是 undefined 或 null,它们无法转为对象,所以会报错:
1 | Object.getPrototypeOf(null) |
Object.setPrototypeOf()
Object.setPrototypeOf
方法的作用与__proto__
相同,用来设置一个对象的prototype
对象,返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法,如果第一个参数不是对象,会自动转为对象。
1 | // 格式 |
由于undefined
和null
无法转为对象,所以如果第一个参数是undefined
或null
,就会报错。
1 | Object.setPrototypeOf(undefined, {}) |
参考资料:
《ES6 标准入门》(第 3 版) 阮一峰著