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"}