二、对象
A lot of ECMAScript 6 focused on improving the utility of objects. The focus makes sense given that nearly every value in JavaScript is represented by some type of object. Additionally, the number of objects used in an average JavaScript program continues to increase, meaning that developers are writing more objects all the time. With more objects comes the necessity to use them more effectively.
ECMAScript6很多方面都注重于改变对象是功能。这种重视是有道理的,因为几乎javascript中的每一个值都是一种对象类型。此外,对象在javascript程序中的使用数量也在增长,意味着程序员一直在写很多的对象。对象越来越多,让对象变得更加有效也就越必要。
ECMAScript 6 improves objects in a number of ways, from simple syntax to new ways of manipulating and interacting with objects.
ECMAScript通过一系列的方法来改变对象,从简单的语法到操作和与对象交互的新的方法。
2.1对象类别
The ECMAScript 6 specification introduced some new terminology to help distinguish between categories of objects. JavaScript has long been littered with a mix of terminology used to describe objects found in the standard as opposed to those that are added by execution environments such as the browser. ECMAScript 6 takes the time to clearly define each category of object, and it’s important to understand this terminology to have a good understanding of the language as a whole. The object categories are:
ECMAScript6规范引入了一些新的术语来分辨categories和objects。长久以来,javascript对于描述标准中的对象和描述浏览器等执行环境中增加的对象的术语一直模糊不清。ECMAScript 6花时间清楚的定义了每一个类别的对象,理解这个术语对于理解整个语言都很重要。对象的类别有:
- Ordinary objects are objects that have all of the default internal behaviors for objects in JavaScript.
- 普通对象-是指那些拥有javascript所有内部默认行为的对象。
- Exotic objects are objects whose internal behavior is different than the default in some way.
- 异常对象-是指那些内部行为与默认行为在某些方面不相同的对象
- Standard objects are objects defined by ECMAScript 6, such as
Array
,Date
, etc. Standard objects may be ordinary or exotic. - 标准对象 - ECMAScript中定义的对象,例如Array、Date、等等。标准对象可能是普通对象也可能是异常对象。
- Built-in objects are objects that are present in a JavaScript execution environment when a script begins to execute. All standard objects are built-in objects.
- 内置对象-当脚本开始执行,对象的父亲在javascript执行环境中。所有的标准对象都是内置对象。
These terms are used throughout the book to explain the various objects defined by ECMAScript 6.
本书中都用这些术语来解释ECMAScript中定义的对象。
2.2、对象字面量扩展
One of the most popular patterns in JavaScript is the object literal. It’s the syntax upon which JSON is built and can be seen in nearly every JavaScript file on the Internet. The reason for the popularity is clear: a succinct syntax for creating objects that otherwise would take several lines of code to accomplish. ECMAScript 6 recognized the popularity of the object literal and extends the syntax in several ways to make object literals more powerful and even more succinct.
javascript中最流行的一种形式就是对象字面量。它通过JSON的语法撞见,在互联网上的几乎每一个js文件中都能看到。这种形式流行的原因:创建对象的语法形式简单,其他的形式可能需要更多的行来完成。ECMAScript6意识到了对象字面量这种形式的流行,所以在几个方面扩展了它的语法使得对象字面量更加强大更加简洁。
2.3、属性初始化速写
In ECMAScript 5 and earlier, object literals were simply collections of name-value pairs. That meant there could be some duplication when property values are being initialized. For example:
在ECMAScript5 和 之前的版本中,对象字面量只是键-值对的集合。这意味着,在属性初始化的时候可能会存在一些重复工作。例如:
function createPerson(name, age) { return { name: name, age: age };}
The createPerson()
function creates an object whose property names are the same as the function parameter names. The result is what appears to be duplication of name
and age
even though each represents a different aspect of the process.
createPerson函数创建了一个对象,这个对象的属性名字和函数的参数名字相同。结果是name和value出现了两次,尽管它们代表不同方面的过程。
In ECMAScript 6, you can eliminate the duplication that exists around property names and local variables by using the property initializer shorthand. When the property name is going to be the same as the local variable name, you can simply include the name without a colon and value. For example, createPerson()
can be rewritten as follows:
在ECMAScript6中,通过使用属性初始化的速写功能可以消除属性命和变量名重复的情况。当属性名字和变量名字相同的时候,你可以简单的使用名字不需要冒号和值。例如,createPerson可以重写成以下形式:
function createPerson(name, age) { return { name, age };}
When a property in an object literal only has a name and no value, the JavaScript engine looks into the surrounding scope for a variable of the same name. If found, that value is assigned to the same name on the object literal. So in this example, the object literal property name
is assigned the value of the local variable name
.
当对象字面量的属性只有名字没有值的时候,javascript引擎在包裹的作用域寻找相同名字的变量。如果找到了,就把变量的值赋给相同名字的对象属性。所以在本例中,对象属性name的值是由变量name赋给的。
The purpose of this extension is to make object literal initialization even more succinct than it already was. Assigning a property with the same name as a local variable is a very common pattern in JavaScript and so this extension is a welcome addition.
这种扩展的目的是让对象字面量的初始化比现在更加简洁。在javascript中,名字相同的变量给同名字的属性赋值很普遍,所以这种扩展非常受欢迎。
2.4、方法初始化速写
ECMAScript 6 also improves syntax for assigning methods to object literals. In ECMAScript 5 and earlier, you must specify a name and then the full function definition to add a method to an object. For example:
ES6也改进了对象字面量方法的赋值语法。在ES5和之前的版本中,必须指定为对象的方法指定名字和完成的函数定义。例如:
var person = { name: "Nicholas", sayName: function() { console.log(this.name); }};
In ECMAScript 6, the syntax is made more succinct by eliminating the colon and the function
keyword. You can then rewrite the previous example as:
在ES6中,通过删除冒号和function关键字,语法更加的简洁。前一个例子可以重写如下;
var person = { name: "Nicholas", sayName() { console.log(this.name); }};
This shorthand syntax creates a method on the person
object just as the previous example did. There is no difference aside from saving you some keystrokes, so sayName()
is assigned an anonymous function expression and has all of the same characteristics as the function defined in the previous example.
使用速写语法给person对象创建了一个方法。除了节省了敲击键盘的次数没有任何区别,所以sayName被赋予了一个匿名函数表达式,和之前例子中定义的函数有着完全一样的特性。
The name
property of a method created using this shorthand is the name used before the parentheses. In the previous example, the name
property for person.sayName()
is "sayName"
.
注意:使用速写形式创建的方法的name属性是圆括号之前的那个名字。在前一个例子中,person.sayName的name属性是“sayName”。
2.5、可计算的属性名字
JavaScript objects have long had computed property names through the use of square brackets instead of dot notation. The square brackets allow you to specify property names using variables and string literals that may contain characters that would be a syntax error if used in an identifier. For example:
通过使用方括号代替点符号,javascript对象可以使用计算出的属性的名字。方括号允许你使用变量指定一个属性的名字,或者字符串中包含了一些特殊的会导致错误的标识符来指定属性的名字。例如:
var person = {}, lastName = "last name";person["first name"] = "Nicholas";person[lastName] = "Zakas";console.log(person["first name"]); // "Nicholas"console.log(person[lastName]); // "Zakas"
Both of the property names in this example have a space, making it impossible to reference those names using dot notation. However, bracket notation allows any string value to be used as a property name.
本例中的两个属性名字中间都有一个空格,无法使用点符号来引用这些名字。然而,方括号允许任何形式的字符串作为属性名字使用。
In ECMAScript 5, you could use string literals as property names in object literals, such as:
在ES5中,在对象字面量中,你可以使用字符串作为属性名字,例如:
var person = { "first name": "Nicholas"};console.log(person["first name"]); // "Nicholas"
If you could provide the string literal inside of the object literal property definition then you were all set. If, however, the property name was contained in a variable or had to be calculated, then there was no way to define that property using an object literal.
如果你能为对象字面量中的每一个属性提供一个字符串定义,那么你都设置。然而,如果属性的名字包含变量或者需要计算,那么没有办法通过对象字面量来定义这个属性。
ECMAScript 6 adds computed property names to object literal syntax by using the same square bracket notation that has been used to reference computed property names in object instances. For example:
ES6中为对象字面量增加了计算的属性名字语法-通过方括号,与对象实例中使用方括号来引用计算的属性名字的方式一样。例如:
var lastName = "last name";var person = { "first name": "Nicholas", [lastName]: "Zakas"};console.log(person["first name"]); // "Nicholas"console.log(person[lastName]); // "Zakas"
The square brackets inside of the object literal indicate that the property name is computed, so its contents are evaluated as a string. That means you can also include expressions such as:
对象字面量中的方括号里面的值表示属性命是通过计算得来的,所以它得内容是字符串。这意味着你也可以包含表达式,例如:
var suffix = " name";var person = { ["first" + suffix]: "Nicholas", ["last" + suffix]: "Zakas"};console.log(person["first name"]); // "Nicholas"console.log(person["last name"]); // "Zakas"
Anything you would put inside of square brackets while using bracket notation on object instances will also work for computed property names inside of object literals.
任何能放入对象实例中方括号得内容你都可以放入对象字面量的方括号里面。
2.6、Object.assign()
One of the most popular patterns for object composition is mixins, in which one object receives properties and methods from another object. Many JavaScript libraries have a mixin method similar to this:
对象最受欢迎的模式是多态,对象从另一个对象中接受属性和方法。很多javascript库都有类似的多态方法:
function mixin(receiver, supplier) { Object.keys(supplier).forEach(function(key) { receiver[key] = supplier[key]; }); return receiver;}
The mixin()
function iterates over the own properties of supplier
and copies them onto receiver
. This allows the receiver
to gain new behaviors without inheritance. For example:
mixin函数遍历supplier中的每一个属性,复制这些属性给receiver。这样receiver就不需要继承就增加了新的行为。例如:
function EventTarget() { /*...*/ }EventTarget.prototype = { constructor: EventTarget, emit: function() { /*...*/ }, on: function() { /*...*/ }};var myObject = {};mixin(myObject, EventTarget.prototype);myObject.emit("somethingChanged");
In this example, myObject
receives behavior from EventTarget.prototype
. This gives myObject
the ability to publish events and let others subscribe to them using emit()
and on()
, respectively.
在这个例子中,myObject从EventTarget.prototype中获取行为。这样myObject发布events的能力,其他的用户可以分别使用emit和on方法。
This pattern became popular enough that ECMAScript 6 added Object.assign()
, which behaves the same way. The difference in name is to reflect the actual operation that occurs. Since the mixin()
method uses the assignment operator (=
), it cannot copy accessor properties to the receiver as accessor properties. The name Object.assign()
was chosen to reflect this distinction.
ES6增加了Object.assign()方法和这种形式的行为一样。名字的差异反应了实际的操作。mixin函数使用了赋值操作符(=),这不能为receiver为属性复制一个访问器。Object.assign()的名字反应了这种区别。
Similar methods in various libraries may have other names. Some popular alternate names for the same basic functionality are extend()
and mix()
. There was also, briefly, an Object.mixin()
method in ECMAScript 6 in addition to Object.assign()
. The primary difference was that Object.mixin()
also copied over accessor properties, but the method was removed due to concerns over the use of super
(discussed later in this chapter).
注意:同样的方法在不同的库中可能会使用不同的名字。类似功能的函数名字有mix和extend。同样ES6除了Object.assign(),也增加了Object.mixin方法。主要的区别在于mixin同样是复制访问器,但是这个因为考虑到super的使用而移除了(在本章稍后讨论)
You can use Object.assign()
anywhere the mixin()
function would have been used:
你可以在任何能使用minin函数的地方使用Object.assign():
function EventTarget() { /*...*/ }EventTarget.prototype = { constructor: EventTarget, emit: function() { /*...*/ }, on: function() { /*...*/ }}var myObject = {}Object.assign(myObject, EventTarget.prototype);myObject.emit("somethingChanged");
The Object.assign()
method accepts any number of suppliers, and the receiver receives the properties in the order in which the suppliers are specified. That means the second supplier might overwrite a value from the first supplier on the receiver. For example:
Object.assign()方法接收任意数量的supplier,receiver按照顺序接收suppliers指定的属性。意味着第二个supplier可能会重写第一个supplier提供的值,例如:
var receiver = {};Object.assign(receiver, { type: "js", name: "file.js" }, { type: "css" });console.log(receiver.type); // "css"console.log(receiver.name); // "file.js"
The value of receiver.type
is "css"
because the second supplier overwrote the value of the first.
receiver.type的值是css,因为第二个supplie重写了第一个。
The Object.assign()
method isn’t a big addition to ECMAScript 6, but it does formalize a common function that is found in many JavaScript libraries.
Object.assign()方法在ES6中并不是一个重大的改进,但是它规范了大多数js库中的普遍使用的函数。
需要记住使用Object.getPrototypeOf()和.call(this)来获得原型中的方法有一点复杂,所以ES6引入了super。
At it’s simplest, super
acts as a pointer to the current object’s prototype, effectively acting like Object.getPrototypeOf(this)
. So you can simplify the getGreeting()
method by rewriting it as:
最简单的,super实际上是当前原型的一个指针,有效的模拟Object.getPrototypeOf(this)。所以上面的例子可以重写如下:
let friend = { __proto__: person, getGreeting() { // same as Object.getPrototypeOf(this).getGreeting.call(this) // or this.__proto__.getGreeting.call(this) return super.getGreeting() + ", hi!"; }};
The call to super.getGreeting()
is the same as Object.getPrototypeOf(this).getGreeting.call(this)
or this.__proto__.getGreeting.call(this)
. Similarly, you can call any method on an object’s prototype by using a super
reference.
调用super.getGreeting和调用Object.getPrototypeOf(this).getGreeting.call(this)或者this.__proto__.getGreeting.call(this)的功能一样。同样的,你可以使用super调用对象原型上的任何方法。
If you’re calling a prototype method with the exact same name, then you can also call super
as a function, for example:
如果你正在调用原型上的同名方法,你可以像调用函数那样使用super,例如:
let friend = { __proto__: person, getGreeting() { // same as Object.getPrototypeOf(this).getGreeting.call(this) // or this.__proto__.getGreeting.call(this) // or super.getGreeting() return super() + ", hi!"; }};
Calling super
in this manner tells the JavaScript engine that you want to use the prototype method with the same name as the current method. So super()
actually does a lookup using the containing function’s name
property (discussed in Chapter 2) to find the correct method.
以这种方式调用super是告诉js引擎你希望调用与当前方法同名的原型方法。所以super()实际上在包裹的函数中寻找函数的name属性来找到正确的方法。
super
references can only be used inside of functions and cannot be used in the global scope. Attempting to use super
in the global scope results in a syntax error.
注意:super引用只能在函数内部使用,不能在全局作用域中使用。在全局作用域中使用会导致错误。
2.10 方法
Prior to ECMAScript 6, there was no formal definition of a “method” - methods were just object properties that contained functions instead of data. ECMAScript 6 formally defines a method as a function that has an internal [[HomeObject]]
property containing the object to which the method belongs. Consider the following:
ES6版本之前,关于“方法”没有正式的定义-方法是对象的属性,只不过包含的是函数而不是数据。ES6正式的定义了方法-一个函数拥有内置属性[[HomeObject]]包含了这个方法所从属于的对象。思考下面的例子:
let person = { // method getGreeting() { return "Hello"; }};// not a methodfunction shareGreeting() { return "Hi!";}
This example defines person
with a single method called getGreeting()
. The [[HomeObject]]
for getGreeting()
is person
by virtue of assigning the function directly to an object. The shareGreeting()
function, on the other hand, has no [[HomeObject]]
specified because it wasn’t assigned to an object when it was created. In most cases this difference isn’t important, but it becomes very important when using super
.
这个例子定义了person只有一个getGreeting方法。getGreeting的[[HomeObjexct]] 属性是person直接分配给对象的函数。另一方面,shareGreeting函数没有[[HomeObject]]属性,因为它创建的时候没有分配给对象,在大多数情况下,这些区别不是太重要,当使用super的时候就变得很重要了。
Any reference to super
uses the [[HomeObject]]
to determine what to do. The first step is to call Object.getPrototypeOf()
on the [[HomeObject]]
to retrieve a reference to the prototype. Then, the prototype is searched for a function with the same name as the executing function. Last, the this
-binding is set and the method is called. If a function has no [[HomeObject]]
, or has a different one than expected, then this process won’t work. For example:
任何对super的引用都使用了[[HomeObject]]来决定做什么。第一步,在[[HomeObject]]上调用Object.getPrototypeOf()来找到原型的引用。然后,原型搜索与当前执行函数同名的方法。最后this绑定方法被调用。如果一个函数没有[[HomeObject]],或者与期望的值不同,那么上述过程将不再执行,例如:
let person = { getGreeting() { return "Hello"; }};// prototype is personlet friend = { __proto__: person, getGreeting() { return super() + ", hi!"; }};function getGlobalGreeting() { return super.getGreeting() + ", yo!";}console.log(friend.getGreeting()); // "Hello, hi!"getGlobalGreeting(); // throws error
Calling friend.getGreeting()
returns a string while calling getGlobalGreeting()
throws an error for improper use of super
. Since the getGlobalGreeting()
function has no [[HomeObject]]
, it’s not possible to perform a lookup. Interestingly, the situation doesn’t change if getGlobalGreeting()
is later assigned as a method on friend
:
调用 friend.getGreeting()返回一个zifuchuan而调用getGlobalGreeting则是抛出了一个错误因为对super不正确的使用。因为getGlobalGreeting函数美哦与[[HomeObject]],不能执行查找。如果getGlobalGreeting在之后赋给friend作为一个方法,这种情况也不会改变。
// prototype is personlet friend = { __proto__: person, getGreeting() { return super() + ", hi!"; }};function getGlobalGreeting() { return super.getGreeting() + ", yo!";}console.log(friend.getGreeting()); // "Hello, hi!"// assign getGreeting to the global functionfriend.getGreeting = getGlobalGreeting;friend.getGreeting(); // throws error
Here the global getGlobalGreeting()
function is used to overwrite the previously-defined getGreeting()
method on friend
. Calling friend.getGreeting()
at that point results in an error as well. The value of [[HomeObject]]
is only set when the function is first created, so even assigning onto an object doesn’t fix the problem.
这个全局函数getGlobalGreeting重写了之前在friend上定义的getGreeting方法。调用friend.getGreeting()方法也会抛出异常。因为[[HomeObject]]只有在函数第一次创建的时候才会设置,所以尽管赋给了对象当时并没有解决这个问题。