属性的简洁表示法
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。
- 属性名就是变量名, 属性值就是变量值。
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}
// 等同于
const baz = {foo: foo};
function f(x, y) {
return {x, y};
}
f(1, 2) // Object {x: 1, y: 2}
- 方法也可以简写
let birth = '2000/01/01';
const Person = {
name: '张三',
//等同于birth: birth
birth,
// 等同于hello: function ()...
hello() { console.log('我的名字是', this.name); }
};
- 简写的对象方法不能用作构造函数,会报错。
const obj = {
f() {
this.foo = 'bar';
}
};
new obj.f() // 报错
class中的方法new本身就会报错,但是对象中的方法(非简写)new不会报错
class obj {
f() {
this.foo = 'bar';
}
};
new obj.f() // 报错
const o = {
f: function() {
this.foo = 'bar';
}
};
new o.f() // {foo: "bar"}
属性名表达式
其实就是使用[]作为属性名,方括号中可以放基本数据类型变量或者表达式
将变量的内容或者表达式的结果作为对象的属性名
let a = 'foo'
let o = {
[a]: 1,
['s' + 'tr']: 'string'
}
console.log(o) // {foo: 1, str: "string"}
- 属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object],这一点要特别小心。
const keyA = {a: 1};
const keyB = {b: 2};
const myObject = {
[keyA]: 'valueA',
[keyB]: 'valueB'
};
myObject // {[object Object]: "valueB"}
属性的可枚举性和遍历
- 有四个操作会忽略enumerable为false的属性。
for…in循环:只遍历对象自身的和继承的可枚举的属性。
Object.keys():返回对象自身的所有可枚举的属性的键名。
JSON.stringify():只串行化对象自身的可枚举的属性。
Object.assign(): 忽略enumerable为false的属性,只拷贝(浅拷贝)对象自身的可枚举的属性。此方法将源对象中的可枚举属性添加到目标对象中,并返回目标对象
- 其中,只有for…in会返回继承的属性,其他三个方法都会忽略继承的属性,只处理对象自身的属性。
- ES6 规定,所有 Class 的原型的方法都是不可枚举的。
属性的遍历
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 键,按照加入时间升序排列。
Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]
super 关键字
super,指向当前对象的原型对象。
- super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。
- 目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法。
const proto = {
x: 'hello',
foo() {
console.log(this.x);
},
};
const obj = {
x: 'world',
foo() {
super.foo();
}
}
Object.setPrototypeOf(obj, proto);
obj.foo() // "world"
上面代码中,super.foo指向原型对象proto的foo方法,但是绑定的this却还是当前对象obj,因此输出的就是world。
对象的扩展运算符
解构赋值
对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的(enumerable)、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
- 解构赋值要求等号右边是一个对象
- 解构赋值的拷贝是浅拷贝
- 扩展运算符的解构赋值,不能复制继承自原型对象的属性。
扩展运算符
对象的扩展运算符(…)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
- 对象的扩展运算符等同于使用Object.assign()方法。
链判断运算符
使用了?.
运算符,直接在链式调用的时候判断,左侧的对象是否为null或undefined。如果是的,就不再往下运算,而是返回undefined。
const firstName = message?.body?.user?.firstName || 'default';
const fooValue = myForm.querySelector('input[name=foo]')?.value
链判断运算符有三种用法。
- obj?.prop // 对象属性
- obj?.[expr] // 同上
- func?.(…args) // 函数或对象方法的调用
//判断obj中是否有foo这个方法,如果有就调用,没有返回undefined
obj.foo?.()
- 如果a?.b()里面的a.b不是函数,不可调用,那么a?.b()是会报错的。a?.()也是如此,如果a不是null或undefined,但也不是函数,那么a?.()会报错。
- 右侧不得为十进制数值
为了保证兼容以前的代码,允许foo?.3:0
被解析成foo ? .3 : 0
,因此规定如果?.后面紧跟一个十进制数字,那么?.不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。
Null 判断运算符
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时,才会生效。
- ??有一个运算优先级问题,它与&&和||的优先级孰高孰低。现在的规则是,如果多个逻辑运算符一起使用,必须用括号表明优先级,否则会报错。