阮一峰ES6教程读书笔记(五)对象的扩展
1. 属性的简洁表示法
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}
// 等同于
const baz = {foo: foo};
当然,除了属性可以简写,方法也可以简写,比如:
const obj = {
print (param) {
console.log(param)
}
}
2. 属性名表达式
我们知道,读取对象的属性时可通过obj.key
或者obj[key]
这两种方式,不同的是第一种方式key
必须是一个确定的键名,而第二种方法的key
可以是确定的键名也能使一个表达式
let [key, age] = ['name', 23]
let obj = {
[key]: 'bing',
age: age
}
obj // {name: "bing", age: 23}
let obj = {
[key]: 'bing',
[age]: age
}
obj // {name: "bing", 23: 23}
通过上面的代码可以看出来,如果给给对象的键名加上[]
,对象的键名就会变成一个JavaScript
表达式,表达式计算出来的值就是对象的键名,如果上述例子不能很好理解,看下面这个例子:
let lastWord = 'last word';
const a = {
'first word': 'hello',
[lastWord]: 'world'
};
a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"
a[lastWord]
与a['last word']
的值是一样的,因为lastWord
变量的值就是last word
,所以这两个值其实是一个对象的同一个键名对应的值。
需要注意的是如果表达式是一个对象,默认情况下会自动将对象转为字符串[object Object]
const keyA = {a: 1};
const keyB = {b: 2};
const myObject = {
[keyA]: 'valueA',
[keyB]: 'valueB'
};
myObject // Object {[object Object]: "valueB"}
3. 方法的 name 属性
对象的方法也是函数,因此也有name
属性
const obj = {
print () {
console.log('hello world')
}
}
obj.print.name // print
如果对象的方法使用了取值函数(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"
有两种特殊情况:bind
方法创造的函数,name
属性返回bound
加上原函数的名字;Function
构造函数创造的函数,name
属性返回anonymous
。
(new Function()).name // "anonymous"
var doSomething = function() {
// ...
};
f = doSomething.bind(this)
f.name // "bound doSomething"
4. 属性的可枚举性
对象的每个属性都有一个描述对象,用来控制该属性的行为。Object.getOwnPropertyDescriptor
方法可以获取该对象的属性描述对象,描述对象的enumerable
竖向称为可枚举性,如果该属性值为false
,那么就说明该属性不可枚举,即某些操作或忽略该属性,目前有四个操作会忽略不可枚举的属性。
-
for...in
循环:只遍历对象自身的和继承的可枚举的属性。 -
Object.keys()
:返回对象自身的所有可枚举的属性的键名。 -
JSON.stringify()
:只串行化对象自身的可枚举的属性。 -
Object.assign()
: 忽略enumerable
为false
的属性,只拷贝对象自身的可枚举的属性。
引入可枚举属性的目的就在于让某些属性可以规避某些遍历操作。
由此引发思考,当拷贝对象时,我们如果仅仅使用for...in
遍历了对象的可枚举属性,那么不可枚举属性就会被遗漏,那么对象拷贝就变得不严谨,所以我使用Object.getOwnPropertyNames
方法来替代```for...in``直接遍历对象:
function deepCopy(obj) {
if (obj instanceof Date) { return new Date(obj) }
if (obj instanceof RegExp) { return new RegExp(obj)}
let result = new obj.__proto__.constructor()
for (let key of Object.getOwnPropertyNames(obg) {
if (obj.hasOwnProperty(key) && obj[key] !== obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
result[key] = deepCopy(obj[key]);
} else {
result[key] = obj[key];
}
}
}
return result;
}
5. 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
引擎确认,定义的是对象的方法。
6. 对象的扩展运算符
之前在数组的扩展中介绍了扩展运算符(...
),ES2018将这个运算符引入了对象,因为数组是一种特殊的对象,所以理论上,扩展运算符本就应该应用于对象中。
6.1 使用扩展运算符解构赋值
let obj = {
name: 'bing',
age: 23,
id: 007
}
let {name, ...rest} = obj
name // 'bing'
rest // {age: 23, id: 7}
6.2 使用扩展运算符浅拷贝对象
对象的扩展运算符用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
let foo = { ...['a', 'b', 'c'] };
foo
// {0: "a", 1: "b", 2: "c"}
如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象。
{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
跟数组一样,扩展运算符还可以合并两个对象
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);
如果对象中有重复的键值,那么后面的会覆盖前面的,相当于重复赋值。
let obj1 = {name:'bing'}
let obj2 = {name: 'yan'}
let obj = {...obj1, ...obj2}
obj // {name: "yan"}