1
0
mirror of https://github.com/apachecn/eloquent-js-3e-zh.git synced 2025-05-23 20:02:20 +00:00
This commit is contained in:
wizardforcel 2018-05-04 20:57:14 +08:00
parent a8deced354
commit f304d920b4
3 changed files with 100 additions and 75 deletions

160
6.md
View File

@ -1,9 +1,21 @@
## 六、对象的秘密
> 原文:[The Secret Life of Objects](http://eloquentjavascript.net/06_object.html)
>
> 译者:[飞龙](https://github.com/wizardforcel)
>
> 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)
>
> 自豪地采用[谷歌翻译](https://translate.google.cn/)
>
> 部分参考了[《JavaScript 编程精解(第 2 版)》](https://book.douban.com/subject/26707144/)
> 抽象数据类型是通过编写一种特殊的程序来实现的,该程序根据可在其上执行的操作来定义类型。
>
> Barbara Liskov《Programming with Abstract Data Types》
![](img/6-0.jpg)
第 4 章介绍了 JavaScript 的对象object。 在编程文化中我们有一个名为面向对象编程OOP的东西这是一组技术使用对象和相关概念作为程序组织的中心原则。
虽然没有人真正同意其精确定义,但面向对象编程已经成为了许多编程语言的设计,包括 JavaScript 在内。 本章将描述这些想法在 JavaScript 中的应用方式。
@ -28,7 +40,7 @@
方法不过是持有函数值的属性。 这是一个简单的方法:
```
```js
let rabbit = {};
rabbit.speak = function(line) {
console.log(`The rabbit says '${line}'`);
@ -40,7 +52,7 @@ rabbit.speak("I'm alive.");
方法通常会在对象被调用时执行一些操作。将函数作为对象的方法调用时,会找到对象中对应的属性并直接调用。当函数作为方法调用时,函数体内叫做`this`的绑定自动指向在它上面调用的对象。
```
```js
function speak(line) {
console.log(`The ${this.type} rabbit says '${line}'`);
}
@ -57,18 +69,18 @@ hungryRabbit.speak("I could use a carrot right now.");
你可以把`this`看作是以不同方式传递的额外参数。 如果你想显式传递它,你可以使用函数的`call`方法,它接受`this`值作为第一个参数,并将其它处理为看做普通参数。
```
```js
speak.call(hungryRabbit, "Burp!");
// → The hungry rabbit says 'Burp!'
```
这段代码使用了关键字this来输出正在说话的兔子的种类。我们回想一下apply和bind方法这两个方法接受的第一个参数可以用来模拟对象中方法的调用。这两个方法会把第一个参数复制给this。
这段代码使用了关键字`this`来输出正在说话的兔子的种类。我们回想一下`apply``bind`方法,这两个方法接受的第一个参数可以用来模拟对象中方法的调用。这两个方法会把第一个参数复制给`this`
由于每个函数都有自己的`this`绑定,它的值依赖于它的调用方式,所以在用`function`关键字定义的常规函数中,不能引用外层作用域的`this`
箭头函数是不同的 - 它们不绑定他们自己的`this`,但可以看到他们周围(定义位置)作用域的`this`绑定。 因此,你可以像下面的代码那样,在局部函数中引用`this`
```
```js
function normalize() {
console.log(this.coords.map(n => n / this.length));
}
@ -82,7 +94,7 @@ normalize.call({coords: [0, 2, 3], length: 5});
我们来仔细看看以下这段代码。
```
```js
let empty = {};
console.log(empty.toString);
// → function toString(){…}
@ -94,9 +106,9 @@ console.log(empty.toString());
实际上并非如此。我只是掩盖了一些 JavaScript 对象的内部工作细节罢了。每个对象除了拥有自己的属性外都包含一个原型prototype。原型是另一个对象是对象的一个属性来源。当开发人员访问一个对象不包含的属性时就会从对象原型中搜索属性接着是原型的原型依此类推。
那么空对象的原型是什么呢是Object.prototype它是所有对象中原型的父原型。
那么空对象的原型是什么呢?是`Object.prototype`,它是所有对象中原型的父原型。
```
```js
console.log(Object.getPrototypeOf({}) ==
Object.prototype);
// → true
@ -106,11 +118,11 @@ console.log(Object.getPrototypeOf(Object.prototype));
正如你的猜测,`Object.getPrototypeOf`返回一个对象的原型。
JavaScript对象原型的关系是一种树形结构整个树形结构的根部就是Object.prototype。Object.prototype提供了一些可以在所有对象中使用的方法。比如说toString方法可以将一个对象转换成其字符串表示形式。
JavaScript 对象原型的关系是一种树形结构,整个树形结构的根部就是`Object.prototype``Object.prototype`提供了一些可以在所有对象中使用的方法。比如说,`toString`方法可以将一个对象转换成其字符串表示形式。
许多对象并不直接将Object.prototype作为其原型而会使用另一个原型对象用于提供一系列不同的默认属性。函数继承自Function.prototype而数组继承自Array.prototype。
许多对象并不直接将`Object.prototype`作为其原型,而会使用另一个原型对象,用于提供一系列不同的默认属性。函数继承自`Function.prototype`,而数组继承自`Array.prototype`
```
```js
console.log(Object.getPrototypeOf(Math.max) ==
Function.prototype);
// → true
@ -119,11 +131,11 @@ console.log(Object.getPrototypeOf([]) ==
// → true
```
对于这样的原型对象来说其自身也包含了一个原型对象通常情况下是Object.prototype所以说这些原型对象可以间接提供toString这样的方法。
对于这样的原型对象来说,其自身也包含了一个原型对象,通常情况下是`Object.prototype`,所以说,这些原型对象可以间接提供`toString`这样的方法。
你可以使用`Object.create`来创建一个具有特定原型的对象。
```
```js
let protoRabbit = {
speak(line) {
console.log(`The ${this.type} rabbit says '${line}'`);
@ -137,7 +149,7 @@ killerRabbit.speak("SKREEEE!");
像对象表达式中的`speak(line)`这样的属性是定义方法的简写。 它创建了一个名为`speak`的属性,并向其提供函数作为它的值。
原型对象protoRabbit是一个容器用于包含所有兔子对象的公有属性。每个独立的兔子对象比如killerRabbit可以包含其自身属性比如本例中的type属性也可以派生其原型对象中公有的属性。
原型对象`protoRabbit`是一个容器,用于包含所有兔子对象的公有属性。每个独立的兔子对象(比如`killerRabbit`)可以包含其自身属性(比如本例中的`type`属性),也可以派生其原型对象中公有的属性。
### 类
@ -147,7 +159,7 @@ JavaScript 的原型系统可以解释为对一种面向对象的概念(称为
所以为了创建一个给定类的实例,你必须使对象从正确的原型派生,但是你也必须确保,它本身具有这个类的实例应该具有的属性。 这是构造器constructor函数的作用。
```
```js
function makeRabbit(type) {
let rabbit = Object.create(protoRabbit);
rabbit.type = type;
@ -159,7 +171,7 @@ JavaScript 提供了一种方法,来使得更容易定义这种类型的功能
构造对象时使用的原型对象,可以通过构造器的`prototype`属性来查找。
```
```js
function Rabbit(type) {
this.type = type;
}
@ -170,13 +182,13 @@ Rabbit.prototype.speak = function(line) {
let weirdRabbit = new Rabbit("weird");
```
构造器(实际上是所有函数)都会自动获得一个名为`prototype`的属性,默认情况下它包含一个普通的,来自`Object.prototype`的空对象。 如果需要,可以用新对象覆盖它。 或者,可以将属性添加到现有对象,如示例所示。
构造器(实际上是所有函数)都会自动获得一个名为`prototype`的属性,默认情况下它包含一个普通的,来自`Object.prototype`的空对象。 如果需要,可以用新对象覆盖它。 或者,可以将属性添加到现有对象,如示例所示。
按照惯例,构造器的名字是大写的,这样它们可以很容易地与其他函数区分开来。
重要的是,理解原型与构造器关联的方式(通过其`prototype`属性),与对象拥有原型(可以通过`Object.getPrototypeOf`查找)的方式之间的区别。 构造器的实际原型是`Function.prototype`,因为构造器是函数。 它的`prototype`属性拥有原型,用于通过它创建的实例。
```
```js
console.log(Object.getPrototypeOf(Rabbit) ==
Function.prototype);
// → true
@ -189,7 +201,7 @@ console.log(Object.getPrototypeOf(weirdRabbit) ==
所以 JavaScript 类是带有原型属性的构造器。 这就是他们的工作方式,直到 2015 年,这就是你编写他们的方式。 最近,我们有了一个不太笨拙的表示法。
```
```js
class Rabbit {
constructor(type) {
this.type = type;
@ -205,11 +217,11 @@ let blackRabbit = new Rabbit("black");
`class`关键字是类声明的开始,它允许我们在一个地方定义一个构造器和一组方法。 可以在声明的大括号内写入任意数量的方法。 一个名为`constructor`的对象受到特别处理。 它提供了实际的构造器,它将绑定到名称`"Rabbit"`。 其他函数被打包到该构造器的原型中。 因此,上面的类声明等同于上一节中的构造器定义。 它看起来更好。
类声明目前只允许方法 - 持有函数的属性 - 添加到原型中。 当你想在那里保存一个非函数值时,这可能会有点不方便。 该语言的下一个版本可能会改善这一点。 现在,可以在定义该类后直接操作原型来创建这些属性。
类声明目前只允许方法 - 持有函数的属性 - 添加到原型中。 当你想在那里保存一个非函数值时,这可能会有点不方便。 该语言的下一个版本可能会改善这一点。 现在,可以在定义该类后直接操作原型来创建这些属性。
`function`一样,`class`可以在语句和表达式中使用。 当用作表达式时,它没有定义绑定,而只是将构造函数作为一个值生成。 您可以在类表达式中省略类名称。
`function`一样,`class`可以在语句和表达式中使用。 当用作表达式时,它没有定义绑定,而只是将构造器作为一个值生成。 你可以在类表达式中省略类名称。
```
```js
let object = new class { getWord() { return "hello"; } };
console.log(object.getWord());
// → hello
@ -219,7 +231,7 @@ console.log(object.getWord());
将属性添加到对象时,无论它是否存在于原型中,该属性都会添加到对象本身中。 如果原型中已经有一个同名的属性,该属性将不再影响对象,因为它现在隐藏在对象自己的属性后面。
```
```js
Rabbit.prototype.teeth = "small";
console.log(killerRabbit.teeth);
// → small
@ -232,15 +244,15 @@ console.log(Rabbit.prototype.teeth);
// → small
```
下图简单地描述了代码执行后的情况。其中Rabbit和Object原型画在了killerRabbit之下我们可以从原型中找到对象中没有的属性。
下图简单地描述了代码执行后的情况。其中`Rabbit``Object`原型画在了`killerRabbit`之下,我们可以从原型中找到对象中没有的属性。
![](../Images/00204.jpeg)
![](img/6-1.svg)
覆盖原型中存在的属性是很有用的特性。就像示例展示的那样我们覆盖了killerRabbit的teeth属性这可以用来描述实例对象中更为泛化的类的实例的特殊属性同时又可以让简单对象从原型中获取标准的值。
覆盖原型中存在的属性是很有用的特性。就像示例展示的那样,我们覆盖了`killerRabbit``teeth`属性,这可以用来描述实例(对象中更为泛化的类的实例)的特殊属性,同时又可以让简单对象从原型中获取标准的值。
覆盖也用于向标准函数和数组原型提供`toString`方法,与基本对象的原型不同。
```
```js
console.log(Array.prototype.toString ==
Object.prototype.toString);
// → false
@ -248,9 +260,9 @@ console.log([1, 2].toString());
// → 1,2
```
调用数组的toString方法后得到的结果与调用.join的结果十分类似即在数组的每个值之间插入一个逗号。而直接使用数组调用Object.prototype.toString则会产生一个完全不同的字符串。由于Object原型提供的toString方法并不了解数组结构因此只会简单地输出一对方括号并在方括号中间输出单词“object”和类型的名称。
调用数组的`toString`方法后得到的结果与调用`.join(",")`的结果十分类似,即在数组的每个值之间插入一个逗号。而直接使用数组调用`Object.prototype.toString`则会产生一个完全不同的字符串。由于`Object`原型提供的`toString`方法并不了解数组结构,因此只会简单地输出一对方括号,并在方括号中间输出单词`"object"`和类型的名称。
```
```js
console.log(Object.prototype.toString.call([1, 2]));
// → [object Array]
```
@ -259,9 +271,9 @@ console.log(Object.prototype.toString.call([1, 2]));
我们在上一章中看到了映射map这个词用于一个操作通过对元素应用函数来转换数据结构。 令人困惑的是,在编程时,同一个词也被用于相关而不同的事物。
映射(名词)是将值(键)与其他值相关联的数据结构。 例如,可能想要将姓名映射到年龄。 为此可以使用对象。
映射(名词)是将值(键)与其他值相关联的数据结构。 例如,可能想要将姓名映射到年龄。 为此可以使用对象。
```
```js
let ages = {
Boris: 39,
Liang: 22,
@ -280,7 +292,7 @@ console.log("Is toString's age known?", "toString" in ages);
因此,使用简单对象作为映射是危险的。 有几种可能的方法来避免这个问题。 首先,可以使用`null`原型创建对象。 如果将`null`传递给`Object.create`,那么所得到的对象将不会从`Object.prototype`派生,并且可以安全地用作映射。
```
```js
console.log("toString" in Object.create(null));
// → false
```
@ -289,7 +301,7 @@ console.log("toString" in Object.create(null));
幸运的是JavaScript 带有一个叫做`Map`的类,它正是为了这个目的而编写。 它存储映射并允许任何类型的键。
```
```js
let ages = new Map();
ages.set("Boris", 39);
ages.set("Liang", 22);
@ -304,9 +316,9 @@ console.log(ages.has("toString"));
`set``get``has`方法是`Map`对象的接口的一部分。 编写一个可以快速更新和搜索大量值的数据结构并不容易,但我们不必担心这一点。 其他人为我们实现,我们可以通过这个简单的接口来使用他们的工作。
如果确实有一个简单对象,出于某种原因需要将它视为一个映射,那么了解`Object.keys`只返回对象的自己的键,而不是原型中的那些键,会很有用。 作为`in`运算符的替代方法,可以使用`hasOwnProperty`方法,该方法会忽略对象的原型。
如果确实有一个简单对象,出于某种原因需要将它视为一个映射,那么了解`Object.keys`只返回对象的自己的键,而不是原型中的那些键,会很有用。 作为`in`运算符的替代方法,可以使用`hasOwnProperty`方法,该方法会忽略对象的原型。
```
```js
console.log({x: 1}.hasOwnProperty("x"));
// → true
console.log({x: 1}.hasOwnProperty("toString"));
@ -317,7 +329,7 @@ console.log({x: 1}.hasOwnProperty("toString"));
当你调用一个对象的`String`函数(将一个值转换为一个字符串)时,它会调用该对象的`toString`方法来尝试从它创建一个有意义的字符串。 我提到一些标准原型定义了自己的`toString`版本,因此它们可以创建一个包含比`"[object Object]"`有用信息更多的字符串。 你也可以自己实现。
```
```js
Rabbit.prototype.toString = function() {
return `a ${this.type} rabbit`;
};
@ -338,9 +350,9 @@ console.log(String(blackRabbit));
这是一个坏主意,这个问题并不常见。 大多数 JavaScript 程序员根本就不会去想它。 但是,语言设计师们正在思考这个问题,无论如何都为我们提供了解决方案。
当我声称属性名称是字符串时,这并不完全准确。 他们通常是但他们也可以是符号symbol。 符号是使用`Symbol`函数创建的值。 与字符串不同,新创建的符号是唯一的 - 不能两次创建相同的符号。
当我声称属性名称是字符串时,这并不完全准确。 他们通常是但他们也可以是符号symbol。 符号是使用`Symbol`函数创建的值。 与字符串不同,新创建的符号是唯一的 - 不能两次创建相同的符号。
```
```js
let sym = Symbol("name");
console.log(sym == Symbol("name"));
// → false
@ -353,7 +365,7 @@ console.log(blackRabbit[sym]);
由于符号既独特又可用于属性名称,因此符号适合定义可以和其他属性共生的接口,无论它们的名称是什么。
```
```js
const toStringSymbol = Symbol("toString");
Array.prototype[toStringSymbol] = function() {
return `${this.length} cm of blue yarn`;
@ -366,7 +378,7 @@ console.log([1, 2][toStringSymbol]());
通过在属性名称周围使用方括号,可以在对象表达式和类中包含符号属性。 这会导致属性名称的求值,就像方括号属性访问表示法一样,这允许我们引用一个持有该符号的绑定。
```
```js
let stringObject = {
[toStringSymbol]() { return "a jute rope"; }
};
@ -384,7 +396,7 @@ console.log(stringObject[toStringSymbol]());
我们可以直接使用这个接口。
```
```js
let okIterator = "OK"[Symbol.iterator]();
console.log(okIterator.next());
// → {value: "O", done: false}
@ -396,7 +408,7 @@ console.log(okIterator.next());
我们来实现一个可迭代的数据结构。 我们将构建一个`matrix`类,充当一个二维数组。
```
```js
class Matrix {
constructor(width, height, element = (x, y) => undefined) {
this.width = width;
@ -419,12 +431,12 @@ class Matrix {
该类将其内容存储在`width × height`个元素的单个数组中。 元素是按行存储的,因此,例如,第五行中的第三个元素存储在位置`4 × width + 2`中(使用基于零的索引)。
构造函数需要宽度,高度和一个可选的内容函数,用来填充初始值。 `get``set`方法用于检索和更新矩阵中的元素。
构造需要宽度,高度和一个可选的内容函数,用来填充初始值。 `get``set`方法用于检索和更新矩阵中的元素。
遍历矩阵时,通常对元素的位置以及元素本身感兴趣,所以我们会让迭代器产生具有`x``y``value`属性的对象。
```
```js
class MatrixIterator {
constructor(matrix) {
this.x = 0;
@ -451,7 +463,7 @@ class MatrixIterator {
让我们使`Matrix`类可迭代。 在本书中,我会偶尔使用事后的原型操作来为类添加方法,以便单个代码段保持较小且独立。 在一个正常的程序中,不需要将代码分成小块,而是直接在`class`中声明这些方法。
```
```js
Matrix.prototype[Symbol.iterator] = function() {
return new MatrixIterator(this);
};
@ -459,7 +471,7 @@ Matrix.prototype[Symbol.iterator] = function() {
现在我们可以用`for/of`来遍历一个矩阵。
```
```js
let matrix = new Matrix(2, 2, (x, y) => `value ${x},${y}`);
for (let {x, y, value} of matrix) {
console.log(x, y, value);
@ -476,7 +488,7 @@ for (let {x, y, value} of matrix) {
这样的对象甚至不需要直接在实例中计算和存储这样的属性。 即使直接访问的属性也可能隐藏了方法调用。 这种方法称为读取器getter它们通过在方法名称前面编写`get`来定义。
```
```js
let varyingSize = {
get size() {
return Math.floor(Math.random() * 100);
@ -491,7 +503,7 @@ console.log(varyingSize.size);
每当有人读取此对象的`size`属性时,就会调用相关的方法。 当使用写入器setter写入属性时可以做类似的事情。
```
```js
class Temperature {
constructor(celsius) {
this.celsius = celsius;
@ -515,11 +527,11 @@ console.log(temp.celsius);
// → 30
```
`Temperature`类允许以摄氏度或华氏度读取和写入温度,但内部仅存储摄氏度,并在`fahrenheit`读写器中自动转换为摄氏度。
`Temperature`类允许以摄氏度或华氏度读取和写入温度,但内部仅存储摄氏度,并在`fahrenheit`读写器中自动转换为摄氏度。
有时候你想直接向你的构造函数附加一些属性,而不是原型。 这样的方法将无法访问类实例,但可以用来提供额外方法来创建实例。
有时候你想直接向你的构造附加一些属性,而不是原型。 这样的方法将无法访问类实例,但可以用来提供额外方法来创建实例。
在类声明内部,名称前面写有`static`的方法,存储在构造函数中。 所以`Temperature`类可以让你写出`Temperature.fromFahrenheit(100)`,来使用华氏温度创建一个温度。
在类声明内部,名称前面写有`static`的方法,存储在构造中。 所以`Temperature`类可以让你写出`Temperature.fromFahrenheit(100)`,来使用华氏温度创建一个温度。
## 继承
@ -531,7 +543,7 @@ JavaScript 的原型系统可以创建一个新类,就像旧类一样,但是
在面向对象的编程术语中这称为继承inheritance。 新类继承旧类的属性和行为。
```
```js
class SymmetricMatrix extends Matrix {
constructor(size, element = (x, y) => undefined) {
super(size, size, (x, y) => {
@ -554,19 +566,19 @@ console.log(matrix.get(2, 3));
`extends`这个词用于表示,这个类不应该直接基于默认的`Object`原型,而应该基于其他类。 这被称为超类superclass。 派生类是子类subclass
为了初始化`SymmetricMatrix`实例,构造函数通过`super`关键字调用其超类的构造函数。 这是必要的,因为如果这个新对象的行为(大致)像`Matrix`,它需要矩阵具有的实例属性。 为了确保矩阵是对称的,构造函数包装了`content`方法,来交换对角线以下的值的坐标。
为了初始化`SymmetricMatrix`实例,构造器通过`super`关键字调用其超类的构造器。 这是必要的,因为如果这个新对象的行为(大致)像`Matrix`,它需要矩阵具有的实例属性。 为了确保矩阵是对称的,构造包装了`content`方法,来交换对角线以下的值的坐标。
`set`方法再次使用`super`,但这次不是调用构造函数,而是从超类的一组方法中调用特定的方法。 我们正在重新定义`set`,但是想要使用原来的行为。 因为`this.set`引用新的`set`方法,所以调用这个方法是行不通的。 在类方法内部,`super`提供了一种方法,来调用超类中定义的方法。
`set`方法再次使用`super`,但这次不是调用构造,而是从超类的一组方法中调用特定的方法。 我们正在重新定义`set`,但是想要使用原来的行为。 因为`this.set`引用新的`set`方法,所以调用这个方法是行不通的。 在类方法内部,`super`提供了一种方法,来调用超类中定义的方法。
继承允许我们用相对较少的工作,从现有数据类型构建稍微不同的数据类型。 它是面向对象传统的基础部分,与封装和多态一样。 尽管后两者现在普遍被认为是伟大的想法,但继承更具争议性。
尽管封装和多态可用于将代码彼此分离,从而减少整个程序的耦合,但继承从根本上将类连接在一起,从而产生更多的耦合。 继承一个类时,比起单纯使用它,你通常必须更加了解它如何工作。 继承可能是一个有用的工具,并且我现在在自己的程序中使用它,但它不应该成为你的第一个工具,你可能不应该积极寻找机会来构建类层次结构(类的家族树)。
### 6.12 instanceof运算符
### 6.12 `instanceof`运算符
在有些时候了解某个对象是否继承自某个特定类也是十分有用的。JavaScript 为此提供了一个二元运算符名为instanceof。
在有些时候了解某个对象是否继承自某个特定类也是十分有用的。JavaScript 为此提供了一个二元运算符,名为`instanceof`
```
```js
console.log(
new SymmetricMatrix(2) instanceof SymmetricMatrix);
// → true
@ -578,17 +590,17 @@ console.log([1] instanceof Array);
// → true
```
该运算符会浏览所有继承类型。所以`SymmetricMatrix``Matrix`的一个实例。 该运算符也可以应用于像`Array`这样的标准构造函数。 几乎每个对象都是`Object`的一个实例。
该运算符会浏览所有继承类型。所以`SymmetricMatrix``Matrix`的一个实例。 该运算符也可以应用于像`Array`这样的标准构造。 几乎每个对象都是`Object`的一个实例。
### 本章小结
对象不仅仅持有它们自己的属性。对象中有另一个对象原型只要原型中包含了属性那么根据原型构造出来的对象也就可以看成包含了相应的属性。简单对象直接以Object.prototype作为原型。
对象不仅仅持有它们自己的属性。对象中有另一个对象:原型,只要原型中包含了属性,那么根据原型构造出来的对象也就可以看成包含了相应的属性。简单对象直接以`Object.prototype`作为原型。
构造函数是名称通常以大写字母开头的函数,可以与`new`运算符一起使用来创建新对象。 新对象的原型是构造函数`prototype`属性中的对象。 通过将属性放到它们的原型中,可以充分利用这一点,给定类型的所有值在原型中分享它们的属性。 `class`表示法提供了一个显式方法,来定义一个构造函数及其原型。
构造是名称通常以大写字母开头的函数,可以与`new`运算符一起使用来创建新对象。 新对象的原型是构造`prototype`属性中的对象。 通过将属性放到它们的原型中,可以充分利用这一点,给定类型的所有值在原型中分享它们的属性。 `class`表示法提供了一个显式方法,来定义一个构造及其原型。
可以定义读写器,在每次访问对象的属性时秘密地调用方法。 静态方法是存储在类的构造函数,而不是其原型中的方法。
可以定义读写器,在每次访问对象的属性时秘密地调用方法。 静态方法是存储在类的构造,而不是其原型中的方法。
给定一个对象和一个构造函数,`instanceof`运算符可以告诉你该对象是否是该构造函数的一个实例。
给定一个对象和一个构造器,`instanceof`运算符可以告诉你该对象是否是该构造器的一个实例。
可以使用对象的来做一个有用的事情是,为它们指定一个接口,告诉每个人他们只能通过该接口与对象通信。 构成对象的其余细节,现在被封装在接口后面。
@ -600,13 +612,13 @@ console.log([1] instanceof Array);
#### 6.14.1 向量类型
编写一个构造器Vec在二维空间中表示数组。该函数接受两个数字参数x和y并将其保存到对象的同名属性中。
编写一个构造器`Vec`,在二维空间中表示数组。该函数接受两个数字参数`x``y`,并将其保存到对象的同名属性中。
向Vec原型添加两个方法plus和minus它们接受另一个向量作为参数分别返回两个向量一个是this另一个是参数的和向量与差向量。
`Vec`原型添加两个方法:`plus``minus`,它们接受另一个向量作为参数,分别返回两个向量(一个是`this`,另一个是参数)的和向量与差向量。
向原型添加一个getter属性length用于计算向量长度即点xy与原点00之间的距离。
向原型添加一个`getter`属性`length`,用于计算向量长度,即点`(x,y)`与原点`(0,0)`之间的距离。
```
```js
// Your code here.
console.log(new Vec(1, 2).plus(new Vec(2, 3)));
@ -621,13 +633,13 @@ console.log(new Vec(3, 4).length);
标准的 JavaScript 环境提供了另一个名为`Set`的数据结构。 像`Map`的实例一样,集合包含一组值。 与`Map`不同,它不会将其他值与这些值相关联 - 它只会跟踪哪些值是该集合的一部分。 一个值只能是一个集合的一部分 - 再次添加它没有任何作用。
写一个名为`Group`的类(因为`Set`已被占用)。 像`Set`一样,它具有`add``delete``has`方法。 它的构造函数创建一个空的分组,`add`给分组添加一个值(但仅当它不是成员时),`delete`从组中删除它的参数(如果它是成员),`has` 返回一个布尔值,表明其参数是否为分组的成员。
写一个名为`Group`的类(因为`Set`已被占用)。 像`Set`一样,它具有`add``delete``has`方法。 它的构造创建一个空的分组,`add`给分组添加一个值(但仅当它不是成员时),`delete`从组中删除它的参数(如果它是成员),`has` 返回一个布尔值,表明其参数是否为分组的成员。
使用`===`运算符或类似于`indexOf`的东西来确定两个值是否相同。
为该类提供一个静态的`from`方法,该方法接受一个可迭代的对象作为参数,并创建一个分组,包含遍历它产生的所有值。
```
```js
// Your code here.
class Group {
@ -646,13 +658,13 @@ console.log(group.has(10));
#### 可迭代分组
使上一个练习中的`Group`类可迭代。 如果不清楚接口的确切形式,请参阅本章前面迭代器接口的章节。
使上一个练习中的`Group`类可迭代。 如果不清楚接口的确切形式,请参阅本章前面迭代器接口的章节。
如果使用数组来表示分组的成员,则不要仅仅通过调用数组中的`Symbol.iterator`方法来返回迭代器。 这会起作用,但它会破坏这个练习的目的。
如果使用数组来表示分组的成员,则不要仅仅通过调用数组中的`Symbol.iterator`方法来返回迭代器。 这会起作用,但它会破坏这个练习的目的。
如果分组被修改时,的迭代器在迭代过程中出现奇怪的行为,那也没问题。
如果分组被修改时,的迭代器在迭代过程中出现奇怪的行为,那也没问题。
```
```js
// Your code here (and the code from the previous exercise)
for (let value of Group.from(["a", "b", "c"])) {
@ -665,11 +677,11 @@ for (let value of Group.from(["a", "b", "c"])) {
## 借鉴方法
在本章前面我提到,当你想忽略原型的属性时,对象的`hasOwnProperty`可以用作`in`运算符的更强大的替代方法。 但是如果你的映射需要包含`hasOwnProperty`这个词呢? 将无法再调用该方法,因为对象的属性隐藏了方法值。
在本章前面我提到,当你想忽略原型的属性时,对象的`hasOwnProperty`可以用作`in`运算符的更强大的替代方法。 但是如果你的映射需要包含`hasOwnProperty`这个词呢? 将无法再调用该方法,因为对象的属性隐藏了方法值。
你能想到一种方法,对拥有自己的同名属性的对象,调用`hasOwnProperty`吗?
```
```js
let map = {one: true, two: true, hasOwnProperty: true};
// Fix this call
console.log(map.hasOwnProperty("one"));

BIN
img/6-0.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

13
img/6-1.svg Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="621" height="274" viewBox="-4 -82 621 274"><style>
@font-face {
font-family: 'PT Mono';
font-style: normal;
font-weight: 400;
src: local('PT Mono'), local('PTMono-Regular'), url(http://themes.googleusercontent.com/static/fonts/ptmono/v1/jmle3kzCPnW8O7_gZGRDlQ.woff) format('woff');
}
.objtext { font-family: "PT Mono"; font-size: 14px; stroke: none; }
.objbox { border-radius: 2px; fill: white; stroke: black }
.sep { stroke: #666 }
</style>
<g><g><rect x="210.5" y="133.5" width="202" height="55" class="objbox"></rect><text x="221.5" y="154.5" class="objtext">toString: &lt;function&gt;</text><text x="221.5" y="176.5" class="objtext">...</text></g><g><rect x="121.5" y="83.5" width="178" height="55" class="objbox"></rect><text x="132.5" y="104.5" class="objtext">teeth: "small"</text><text x="132.5" y="126.5" class="objtext">speak: &lt;function&gt;</text></g><g><rect x="0.5" y="0.5" width="242" height="88" class="objbox"></rect><text x="11" y="21" class="objtext">killerRabbit</text><text x="11" y="54" class="objtext">teeth: "long, sharp, ..."</text><text x="11" y="76" class="objtext">type: "killer"</text><path d="M 0.5 33.5 L 242.5 33.5" class="sep"></path></g><g><rect x="282.5" y="-79.5" width="114" height="66" class="objbox"></rect><text x="293.5" y="-58.5" class="objtext">Rabbit</text><text x="293.5" y="-25.5" class="objtext">prototype</text><path d="M 282.5 -46.5 L 396.5 -46.5" class="sep"></path></g><path d="M 329.5 -19.5 L 259.5 83.5" class="sep"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(102, 102, 102)" transform="translate(259.5 83.5) rotate(214.20048413026853) scale(1)"></path><g><rect x="426.5" y="-9.5" width="186" height="110" class="objbox"></rect><text x="437.5" y="11.5" class="objtext">Object</text><text x="437.5" y="44.5" class="objtext">create: &lt;function&gt;</text><text x="437.5" y="66.5" class="objtext">prototype</text><text x="437.5" y="88.5" class="objtext">...</text><path d="M 426.5 23.5 L 612.5 23.5" class="sep"></path></g><path d="M 432.5 70.5 L 331.5 133.5" class="sep"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(102, 102, 102)" transform="translate(331.5 133.5) rotate(238.0456370629486) scale(1)"></path></g></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB