属性的简洁表示法
-
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
const foo = 'bar'; const baz = {foo}; baz // {foo: "bar"} // 等同于 const baz = {foo: foo};
-
除了属性简写,方法也可以简写。
const o = { method() { return "Hello!"; } }; // 等同于 const o = { method: function() { return "Hello!"; } };
-
CommonJS 模块输出一组变量,就非常合适使用简洁写法。
let ms = {}; function getItem (key) { return key in ms ? ms[key] : null; } function setItem (key, value) { ms[key] = value; } function clear () { ms = {}; } module.exports = { getItem, setItem, clear }; // 等同于 module.exports = { getItem: getItem, setItem: setItem, clear: clear };
-
注意,简写的对象方法不能用作构造函数,会报错。
const obj = { f() { this.foo = 'bar'; } }; new obj.f() // 报错
属性名表达式
-
ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。
let propKey = 'foo'; let obj = { [propKey]: true, ['a' + 'bc']: 123 };
-
表达式还可以用于定义方法名。
let obj = { ['h' + 'ello']() { return 'hi'; } }; obj.hello() // hi
-
注意,属性名表达式与简洁表示法,不能同时使用,会报错。
// 报错 const foo = 'bar'; const bar = 'abc'; const baz = { [foo] }; // 正确 const foo = 'bar'; const baz = { [foo]: 'abc'};
方法的 name 属性
-
函数的
name
属性,返回函数名。对象方法也是函数,因此也有name
属性。const person = { sayName() { console.log('hello!'); }, }; person.sayName.name // "sayName"
上面代码中,方法的
name
属性返回函数名(即方法名)。 -
如果对象的方法使用了取值函数(
getter
)和存值函数(setter
),则name
属性不是在该方法上面,而是该方法的属性的描述对象的get
和set
属性上面,返回值是方法名前加上get
和set
。const obj = { get foo() {}, set foo(x) {} }; obj.foo.name // TypeError: Cannot read property 'name' of undefined const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo'); descriptor.get.name // "get foo" descriptor.set.name // "set foo"
属性的可枚举性和遍历
-
可枚举性
-
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。
Object.getOwnPropertyDescriptor
方法可以获取该属性的描述对象。let obj = { foo: 123 }; Object.getOwnPropertyDescriptor(obj, 'foo') // { // value: 123, // writable: true, // enumerable: true, // configurable: true // }
描述对象的
enumerable
属性,称为“可枚举性”,如果该属性为false
,就表示某些操作会忽略当前属性。 -
目前,有四个操作会忽略
enumerable
为false
的属性。for...in
循环:只遍历对象自身的和继承的可枚举的属性。Object.keys()
:返回对象自身的所有可枚举的属性的键名。JSON.stringify()
:只串行化对象自身的可枚举的属性。Object.assign()
: 忽略enumerable
为false
的属性,只拷贝对象自身的可枚举的属性。
-
-
属性的遍历
-
ES6 一共有 5 种方法可以遍历对象的属性。
(1)for…in
for...in
循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。(2)Object.keys(obj)
Object.keys
返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames
返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols
返回一个数组,包含对象自身的所有 Symbol 属性的键名。(5)Reflect.ownKeys(obj)
Reflect.ownKeys
返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。 -
以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
- 首先遍历所有数值键,按照数值升序排列。
- 其次遍历所有字符串键,按照加入时间升序排列。
- 最后遍历所有 Symbol 键,按照加入时间升序排列。
-
super 关键字
-
ES6 又新增了另一个类似的关键字
super
,指向当前对象的原型对象。const proto = { foo: 'hello' }; const obj = { foo: 'world', find() { return super.foo; } }; Object.setPrototypeOf(obj, proto); obj.find() // "hello"
-
注意,
super
关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。// 报错 const obj = { foo: super.foo } // 报错 const obj = { foo: () => super.foo } // 报错 const obj = { foo: function () { return super.foo } }
上面三种
super
的用法都会报错,因为对于 JavaScript 引擎来说,这里的super
都没有用在对象的方法之中。第一种写法是super
用在属性里面,第二种和第三种写法是super
用在一个函数里面,然后赋值给foo
属性。目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法。
对象的扩展运算符
-
解构赋值
-
对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的(enumerable)、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; x // 1 y // 2 z // { a: 3, b: 4 }
-
由于解构赋值要求等号右边是一个对象,所以如果等号右边是
undefined
或null
,就会报错,因为它们无法转为对象。let { ...z } = null; // 运行时错误 let { ...z } = undefined; // 运行时错误
-
解构赋值必须是最后一个参数,否则会报错。
let { ...x, y, z } = someObject; // 句法错误 let { x, ...y, ...z } = someObject; // 句法错误
-
注意,解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。
let obj = { a: { b: 1 } }; let { ...x } = obj; obj.a.b = 2; x.a.b // 2
-
另外,扩展运算符的解构赋值,不能复制继承自原型对象的属性。
let o1 = { a: 1 }; let o2 = { b: 2 }; o2.__proto__ = o1; let { ...o3 } = o2; o3 // { b: 2 } o3.a // undefined
-
-
扩展运算符
-
对象的扩展运算符(
...
)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。let z = { a: 3, b: 4 }; let n = { ...z }; n // { a: 3, b: 4 }
-
由于数组是特殊的对象,所以对象的扩展运算符也可以用于数组。
let foo = { ...['a', 'b', 'c'] }; foo // {0: "a", 1: "b", 2: "c"}
-
如果扩展运算符后面是一个空对象,则没有任何效果。
{...{}, a: 1} // { a: 1 }
-
如果扩展运算符后面不是对象,则会自动将其转为对象。
// 等同于 {...Object(1)} {...1} // {}
-
但是,如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象。
{...'hello'} // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
-
对象的扩展运算符等同于使用
Object.assign()
方法。let aClone = { ...a }; // 等同于 let aClone = Object.assign({}, a);
-
扩展运算符可以用于合并两个对象。
let ab = { ...a, ...b }; // 等同于 let ab = Object.assign({}, a, b);
-
链判断运算符
- ES2020 引入了“链判断运算符”(optional chaining operator)
?.
,简化上面的写法。
const firstName = message?.body?.user?.firstName || 'default';
const fooValue = myForm.querySelector('input[name=foo]')?.value
-
下面是判断对象方法是否存在,如果存在就立即执行的例子。
iterator.return?.()
-
对于那些可能没有实现的方法,这个运算符尤其有用。
if (myForm.checkValidity?.() === false) { // 表单校验失败 return; }
-
链判断运算符有三种用法。
obj?.prop
// 对象属性obj?.[expr]
// 同上func?.(...args)
// 函数或对象方法的调用
-
使用这个运算符,有几个注意点。
(1)短路机制
?.
运算符相当于一种短路机制,只要不满足条件,就不再往下执行。a?.[++x] // 等同于 a == null ? undefined : a[++x]
上面代码中,如果
a
是undefined
或null
,那么x
不会进行递增运算。也就是说,链判断运算符一旦为真,右侧的表达式就不再求值。(2)delete 运算符
delete a?.b // 等同于 a == null ? undefined : delete a.b
上面代码中,如果
a
是undefined
或null
,会直接返回undefined
,而不会进行delete
运算。(3)括号的影响
如果属性链有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。
(a?.b).c // 等价于 (a == null ? undefined : a.b).c
上面代码中,
?.
对圆括号外部没有影响,不管a
对象是否存在,圆括号后面的.c
总是会执行。一般来说,使用
?.
运算符的场合,不应该使用圆括号。(4)报错场合
以下写法是禁止的,会报错。
// 构造函数 new a?.() new a?.b() // 链判断运算符的右侧有模板字符串 a?.`{b}` a?.b`{c}` // 链判断运算符的左侧是 super super?.() super?.foo // 链运算符用于赋值运算符左侧 a?.b = c
(5)右侧不得为十进制数值
为了保证兼容以前的代码,允许
foo?.3:0
被解析成foo ? .3 : 0
,因此规定如果?.
后面紧跟一个十进制数字,那么?.
不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。
Null 判断运算符
-
读取对象属性的时候,如果某个属性的值是
null
或undefined
,有时候需要为它们指定默认值。常见做法是通过||
运算符指定默认值。const headerText = response.settings.headerText || 'Hello, world!'; const animationDuration = response.settings.animationDuration || 300; const showSplashScreen = response.settings.showSplashScreen || true;
上面的三行代码都通过
||
运算符指定默认值,但是这样写是错的。开发者的原意是,只要属性的值为null
或undefined
,默认值就会生效,但是属性的值如果为空字符串或false
或0
,默认值也会生效。 -
为了避免这种情况,ES2020 引入了一个新的 Null 判断运算符
??
。它的行为类似||
,但是只有运算符左侧的值为null
或undefined
时,才会返回右侧的值。const headerText = response.settings.headerText ?? 'Hello, world!'; const animationDuration = response.settings.animationDuration ?? 300; const showSplashScreen = response.settings.showSplashScreen ?? true;
上面代码中,默认值只有在左侧属性值为
null
或undefined
时,才会生效。 -
这个运算符的一个目的,就是跟链判断运算符
?.
配合使用,为null
或undefined
的值设置默认值。const animationDuration = response.settings?.animationDuration ?? 300;