理解 JavaScript(ECMAScript 6)—— 扩展的对象

一、对象字面量

对象字面量(object literal)是 JavaScript 中最常见的模式之一,JSON 即基于它的语法。

在 ECMAScript 5 及更早的版本中,对象字面量即一系列简单的键值对的集合:

1
2
3
4
5
6
7
8
9
function createPerson(name, age) {
return {
name: name,
age: age
};
}

console.log(createPerson('Jack', 16))
// { name: 'Jack', age: 16 }

而在 ECMAScript 6 中,则可以通过 property initializer 的简写语法省略上述代码中的部分重复内容:

1
2
3
4
5
6
7
8
9
function createPerson(name, age) {
return {
name,
age
}
}

console.log(createPerson('Jack', 16))
// { name: 'Jack', age: 16 }

如果一个对象字面量只给出名字而没有值,则 JavaScript 引擎会在周围的作用域中寻找同名的变量,将其值赋值给对象字面量中对应的名字。

方法简化

ECMAScript 6 同样简化了将方法关联给某个对象字面量的语法。

ECMAScript 5 及更早版本中为对象添加方法:

1
2
3
4
5
6
7
8
9
var person = {
name: "Nicholas",
sayName: function() {
console.log(this.name)
}
};

person.sayName()
// Nicholas

ECMAScript 6 则支持更简单的语法:

1
2
3
4
5
6
7
8
9
var person = {
name: "Nicholas",
sayName() {
console.log(this.name)
}
};

person.sayName()
// Nicholas

Computed Property Names

在 ECMAScript 5 和早期版本中,允许在对象实例上使用由中括号([])包围的计算属性
相对于由点(.)指示的属性,由中括号包裹的属性可以使用变量或者有可能导致语法错误的字符串作为属性名。

1
2
3
4
5
6
7
8
9
10
11
var person = {}
var lastName = "last name"

person["first name"] = "Nicholas"
person[lastName] = "Zakas"
person.age = 16

console.log(person["first name"])
console.log(person[lastName])
// Nicholas
// Zakas

ECMAScript 6 中计算属性的语法则更加灵活:

1
2
3
4
5
6
7
8
9
10
11
let lastName = "last name"

let person = {
"first name": "Nicholas",
[lastName]: "Zakas"
}

console.log(person["first name"])
console.log(person[lastName])
// Nicholas
// Zakas

甚至可以在中括号中使用表达式作为计算属性名:

1
2
3
4
5
6
7
8
9
10
11
var suffix = " name"

var person = {
["first" + suffix]: "Nicholas",
["last" + suffix]: "Zakas"
}

console.log(person["first name"])
console.log(person["last name"])
// Nicholas
// Zakas

二、新方法

Object.is()

在 JavaScript 中比较两个值,可以使用相等操作符(==)或相同操作符(===)。大部分开发者会倾向于使用后者。
=== 操作符并非完全准确,比如 +0-0 会被认为相同,而 NaN === NaN 会返回 false

ECMAScript 6 中引入了 Object.is() 方法可以解决 === 中存在的不准确的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
console.log(+0 == -0)             // true
console.log(+0 === -0) // true
console.log(Object.is(+0, -0)) // false

console.log(NaN == NaN) // false
console.log(NaN === NaN) // false
console.log(Object.is(NaN, NaN)) // true

console.log(5 == 5) // true
console.log(5 == "5") // true
console.log(5 === 5) // true
console.log(5 === "5") // false
console.log(Object.is(5, 5)) // true
console.log(Object.is(5, "5")) // false

Object.assign()

Mixins 是 JavaScript 中最流行的构建对象的方式之一。通过 mixin,一个对象可以接收另一个对象中定义的属性和方法。其底层原理类似于如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function mixin(receiver, supplier) {
Object.keys(supplier).forEach(function(key) {
receiver[key] = supplier[key]
})
return receiver
}

let person1 = {
name: "Jack",
age: 16
}

let person2 = {
name: "Rose",
gender: "F"
}

mixin(person1, person2)
console.log(person1)
// { name: 'Rose', age: 16, gender: 'F' }

mixin() 函数遍历 supplier 对象的属性,将它们复制给 receiver(注意是浅复制,即当属性值是对象时,复制的是对象的引用)。
这允许 receiver 不通过继承就能获取到新的属性。

鉴于 mixin 模式非常常用,ECMAScript 6 中添加了同样行为的 Object.assign() 方法。

1
2
3
4
5
6
7
8
9
10
11
function EventTarget() { /*...*/ }

EventTarget.prototype = {
constructor: EventTarget,
emit: function() { /*...*/ },
on: function() { /*...*/ }
}

var myObject = {}
Object.assign(myObject, EventTarget.prototype)
myObject.emit("somethingChanged")

三、Prototypes

Prototypes 是 JavaScript 中继承的基础。

修改对象的 Prototype

通常某个对象的 prototype 由构造器或 Object.create() 方法指定。ECMAScript 5 中添加了 Object.getPrototypeOf() 方法可以获取任意对象的 prototype,但 prototype 在对象实例化之后就无法再变更了。

ECMAScript 6 中添加了 Object.setPrototypeOf() 方法可以用来变更任意对象的 prototype。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let person = {
getGreeting() {
return "Hello"
}
}

let dog = {
getGreeting() {
return "Woof"
}
}

// prototype is person
let friend = Object.create(person)
console.log(friend.getGreeting()) // "Hello"
console.log(Object.getPrototypeOf(friend) === person) // true

// set prototype to dog
Object.setPrototypeOf(friend, dog)
console.log(friend.getGreeting()) // "Woof"
console.log(Object.getPrototypeOf(friend) === dog) // true

通过 super 访问 Prototype

super 是一个指向当前对象的 prototype 的指针,即 Object.getPrototypeOf(this) 的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let person = {
getGreeting() {
return "Hello"
}
}

let friend = {
getGreeting() {
return super.getGreeting() + ", hi"
}
}
Object.setPrototypeOf(friend, person)

// prototype is friend
let relative = Object.create(friend)

console.log(person.getGreeting()) // "Hello"
console.log(friend.getGreeting()) // "Hello, hi"
console.log(relative.getGreeting()) // "Hello, hi"

参考资料

Understanding ECMAScript 6