对象的扩展
对象的简洁表示法
ES6允许在大括号里面直接写入变量和函数,作为对象的属性和方法,从而使书写更加简洁。
const foo = 'bar';
const baz = {foo};
console.log(baz); // {foo:'bar'}
将变量foo
直接写在大括号中,此时的属性名就是变量名,属性值就是变量值。
function f(x,y){
return {x,y};
}
// 等同于
function f(x,y){
return {x:x,y:y};
}
f(2,3); // {x:2,y:3}
方法的简写:
const o = {
method(){
return 'Hello!';
}
}
// 等同于
const o = {
method: function(){
return 'Hello!';
}
}
属性名表达式
JavaScript 定义对象的属性:
// 方法一
obj.foo = true;
// 方法二
obj['a' + 'bc'] = 123;
方法一是直接用表示符作为属性名,方法二是用表达式作为属性名,这时要将表达式放在方括号内。
但是使用字面量的当时定义对象(使用大括号),在ES5中只能使用方法一。
在ES6中,允许把表达式放在括号里
let obj = {
['propKey']: true,
['a' + 'b']: 123,
}
console.log(obj); // {propKey: true,ab: 123}
表达式也可以用于定义方法名:
let obj = {
['h' + 'ello'](){
return 'hi';
}
}
需要注意:
- 属性名表达式与简洁表示法不能同时使用
const foo = 'bar';
const bar = 'abc';
const baz = { [foo] }; // 报错
const foo = 'bar';
const baz = { [foo]: 'abc' }; // 不报错
- 当属性名表达式为一个对象,默认情况下会自动将对象转为字符串
[object Object]
方法的name属性
函数的name
属性返回函数名,对象方法也有name
属性。
- 对于普通方法返回函数名
const person = {
sayName(){
console.log('name');
},
}
console.log(person.sayName.name); // 'sayName'
- 对于取值函数(
getter
)和存值函数(setter
),name
属性是这个方法的属性的描述对象的get
和set
属性上面,返回值前面加上get
和set
.
const obj = {
get foo() {},
set foo(x) {},
}
console.log(obj.foo.name); // TypeError: Cannot read property 'name' of undefined
const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
console.log(descriptor.get.name);// 'get foo'
console.log(descriptor.set.name);// 'set foo'
bind()
方法创建的函数,name
属性返回bound
加上原函数名Function
构造函数创造的函数,name
属性返回anonymous
.- 当对象的方法是
Symbol
值,那么name
属性返回的是这个Symbol
的描述。
const key1 = Symbol('description');
const key2 = Symbol();
let obj = {
[key1](){},
[key2](){},
}
console.log(obj.key1.name); // 'description'
console.log(obj.key2.name); // ''
属性的可枚举性和遍历
可枚举性
对于每一个属性都有一个描述对象(Descriptor
),用来控制这个属性的行为。通过Object.getOwnPropertyDescriptor()
方法获取这个属性的描述对象。
const obj = {foo: 123};
console.log(Object.getOwnPropertyDescriptor(obj,'foo'));
//
对于Descriptor
对象中的enumerable
属性,称为可枚举性。
当 enumerable
属性为false
时,下面的操作会将这个属性忽略
for...in
循环:只遍历对象自身和集成的可枚举的属性Object.keys()
:返回对象自身的所有可枚举的属性的键名JSON.stringify()
:只串行化对象自身的可枚举的属性Object.assign()
:忽略enumerable
为false
的属性,只拷贝对象自身可枚举的属性
引入“可枚举”目的是为了某些属性可以规避掉for...in
的操作。
- 在ES6中,所有Class的原型的方法都是不可枚举的。
属性的遍历
for...in
循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)Object.keys(obj)
返回一个数组,包括对象自身(不含继承的)所有可枚举属性(不含Symbol属性)的键名Object.getOwnPropertyName(obj)
返回一个数组,包括对象自身的所有属性(不含Symbol属性,但是有不可枚举属性)的键名Object.getOwnPropertySymbols(obj)
返回一个数组,包含对象自身的所有Symbol属性的键名Reflect.ownKeys(obj)
返回一个包含对象自身的所有键名,不论是Symbol、字符串或是否可以枚举
属性遍历的次序规则
- 遍历所有数值键,按照升序排列
- 遍历所有字符串键,按照加入时间升序排列
- 遍历所有Symbol键,按照加入时间升序排列
super关键字
关键字this
总是指向函数所在的当前对象,在ES6中新增加了关键字super
,指向当前对象的原型对象
const proto = {
foo: 'hello'
};
const obj = {
foo: 'world',
find(){
return super.foo;
}
};
Object.setPrototypeOf(obj,proto);
console.log(obj.find());
注意:super
关键字表示原型对象时,只能用在对象的方法中,用在其他地方会报错
JavaScript引擎内部,super.foo
等同于Object.getPrototypeOf(this).foo
(属性)或Object.getPrototypeOf(this).foo.call(this)
(方法)。
对象的扩展运算符
在ES2018中,将扩展运算符(...
)引入了对象中
解构赋值
对象的解构赋值用于从一个对象取值,相当于将目标对象自己的所有可遍历但是没有被读取的属性分配到指定的对象上面。所有的键和它们的值都会拷贝到新对象上面。
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
console.log(x); // 1
console.log(y); // 2
console.log(z); // {a: 3, b: 4}
变量z
是解构赋值所在的对象,它取等号右边还没有被读取的键(a
和b
).
需要注意:
- 由于解构赋值要求等号右边是一个对象,因为
undefined
和null
不能转换成对象,所以等号右边不能是undefined
或null
. - 解构赋值必须是最后一个参数,否则会报错
- 解构赋值的拷贝是浅拷贝
- 解构赋值可以扩展某个函数的参数,引入其他操作
扩展运算符
扩展运算符(...
)用于取出参数对象的所有可遍历的属性,拷贝到当前对象中
let z = { a: 3, b: 4 };
let n = { ...z };
console.log(n); // { a: 3, b: 4 }
- 对象的扩展运算符也可用于数组
let foo = { ...['a', 'b', 'c'] };
console.log(foo); // { 0: 'a', 1: 'b', 2: 'c' }
- 扩展运算符后面是一个空对象,就没有任何效果
- 当扩展运算符后面不是对象,就会自动转换成对象
- 对象的扩展运算度等同于使用
Object.assign()
方法
完整的克隆一个对象,还拷贝对象原型的属性
// 第一种方式
const clone1 = {
_proto_: Object.getPrototypeOf(obj),
...obj
};
// 第二种方式
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)),
obj
)
// 第三种方式
const clone3 = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
注意,第一种写法在非浏览器环境不一定适用,推荐使用第二种或者第三种。
- 扩展运算符合并两个对象
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);
-
当自定义的属性放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖
-
扩展运算符的参数对象之中,如果有去值函数
get
,这个函数会执行。
let f = {
...a,
get x(){
throw new Error('not throw yet');
}
}
// 此时不会抛出异常,因为x属性并没有被执行
let f = {
...a,
...{
get x(){
throw new Error('not throw yet');
}
}
}
// 此时会抛出异常,因为x属性被执行
备注:本文是自己学习阮一峰老师的《ECMAScript 6 入门》所做的笔记,大部分例子来源于此书。