一、了解自有属性
hasOwnProperty
下面的实例,Bird 构造函数定义了两个属性:name 和 numLegs:
|
|
name 和 numLegs 被叫做 自身属性,因为它们是直接在实例对象上定义的。
这就意味着 duck 和 canary 这两个对象分别拥有这些属性的独立副本
。
事实上,Bird 的所有实例都将拥有这些属性的独立副本。
下面的代码将 duck 的所有自身属性都存到一个叫作 ownProps 的数组里面:
|
|
二、使用原型属性来减少重复代码
所有 Bird 实例可能会有相同的 numLegs 值,
所以在每一个 Bird 的实例中本质上都有一个重复的变量 numLegs。
当只有两个实例时可能并不是什么问题,但想象一下如果有数百万个实例。 这将会产生许许多多重复的变量。
更好的方法是使用 Bird 的 prototype
。
prototype 是一个可以在所有 Bird 实例之间共享的对象。
以下是一个在 Bird prototype 中添加 numLegs 属性的示例:
|
|
现在所有的 Bird 实例都拥有了共同的 numLegs 属性值。
|
|
由于所有的实例都可以继承 prototype 上的属性,所以可以把 prototype 看作是创建对象的 “配方”。
请注意:duck 和 canary 的 prototype 属于 Bird 的构造函数,即 Bird 的原型 Bird.prototype。
JavaScript 中几乎所有的对象都有一个 prototype 属性,这个属性是属于它所在的构造函数。
三、迭代所有属性
现在已经了解了两种属性: 自身属性
和 prototype
属性。
自身属性是直接在对象上定义的。 而原型属性在 prototype 上定义。
|
|
这个示例会告诉你如何将 duck 的 自身属性 和 prototype 属性分别添加到 ownProps 数组和 prototypeProps 数组里面:
|
|
四、了解构造函数属性
上例创建的实例对象 duck 和 beagle 都有一个特殊
的 constructor
属性:
|
|
需要注意到的是这个 constructor 属性是对创建这个实例的构造函数的一个引用。
constructor 属性的一个好处是可以通过检查这个属性来找出它是一个什么对象。
下面是一个例子,来看看是怎么使用的:
|
|
注意: 由于 constructor
属性可以被重写,所以最好使用 instanceof
方法来检查对象的类型。
五、将原型更改为新对象
单独给 prototype 添加属性:
|
|
需要添加多个属性的,这未免会显得拖沓
。
|
|
一种更有效的方法就是给对象的 prototype
设置为一个已经包含了属性的新对象
。
这样一来,所有属性都可以一次性添加进来:
|
|
六、更改原型时,记得设置构造函数属性
手动设置一个新对象的原型有一个重要的副作用。
它清除了 constructor 属性!
此属性可以用来检查是哪个构造函数创建了实例,但由于该属性已被覆盖,它现在给出了错误的结果:
|
|
为了解决这个问题,凡是手动给新对象重新设置过原型对象的,都别忘记在原型对象中定义一个 constructor 属性
:
|
|
七、了解对象的原型来自哪里
就像人们从父母那里继承基因一样,对象也可直接从创建它的构造函数那里继承其 prototype。
请看下面的例子:Bird 构造函数创建了一个 duck 对象:
|
|
duck 从 Bird 构造函数那里继承了它的 prototype。
你可以使用 isPrototypeOf
方法来验证他们之间的关系:
|
|
八、了解原型链
JavaScript 中所有的对象(除了少数例外)都有自己的 prototype。
而且,对象的 prototype 本身也是一个对象。
|
|
正因为 prototype 是一个对象,所以 prototype 对象也有它自己的 prototype!
这样看来的话,Bird.prototype 的 prototype 就是 Object.prototype:
|
|
这有什么作用呢?
|
|
hasOwnProperty
是定义在 Object.prototype
上的一个方法
,
尽管在 Bird.prototype 和 duck上并没有定义该方法,但是我们依然可以在这两个对象上访问到。
这就是 prototype 链的一个例子。
在这个 prototype
链中,Bird 是 duck 的 supertype,而 duck 是 subtype。
Object 则是 Bird 和 duck 实例共同的 supertype。
Object
是 JavaScript 中所有对象
的 supertype
,也就是原型链的最顶层
。
因此,所有对象都可以访问 hasOwnProperty 方法。
十、使用继承避免重复
有一条原则叫做:Don’t Repeat Yourself。
常以缩写形式 DRY
出现,意思是“不要自己重复”。
编写重复代码会产生的问题是:任何改变都需要去多个地方修复所有重复的代码。
这通常意味着我们需要做更多的工作,会产生更高的出错率。
请观察下面的示例,Bird 和 Dog 共享 describe 方法:
|
|
我们可以看到 describe 方法在两个地方重复定义了。
根据以上所说的 DRY 原则
,我们可以通过创建一个 Animal supertype(或者父类)来重写这段代码:
|
|
Animal 构造函数中定义了 describe 方法,可将 Bird 和 Dog 这两个构造函数的方法删除掉:
|
|
十一、从超类继承行为
创建一个超类 supertype(或者叫父类)的实例
|
|
|
|
此语法用于继承时
会存在一些缺点,太复杂了animal.prototype = {constructor: animal}
。
相反,另外一种没有这些缺点的方法来替代 new 操作:
|
|
Object.create(obj)
创建了一个新对象,并指定了 obj 作为新对象的 prototype。
回忆一下,之前说过 prototype 就像是创建对象的“配方”。
如果把 animal 的 prototype 设置为与 Animal 构造函数的 prototype 一样,
那么就相当于让 animal 这个实例具有与 Animal 的其他实例相同的“配方”了。
|
|
十二、将子辈的原型设置为父辈的实例
给子类型(或者子类)设置 prototype。 这样一来,Bird 就是 Animal 的一个实例了
|
|
请记住,prototype 类似于创建对象的“配方”。 从某种意义上来说,Bird 对象的配方包含了 Animal 的所有关键“成分”。
|
|
duck 继承了Animal 的所有属性,其中包括了 eat 方法。
十三、重置一个继承的构造函数属性
当一个对象从另一个对象那里继承了其 prototype 时,那它也继承了父类的 constructor 属性。
|
|
但是 duck 和其他所有 Bird 的实例都应该表明它们是由 Bird 创建的,而不是由 Animal 创建的。
为此,可以手动将 Bird 的构造函数属性设置为 Bird 对象:
|
|
十四、继承后添加方法
从超类构造函数继承其 prototype 对象的构造函数,除了继承的方法外,还可以拥有自己的方法。
Bird 是一个构造函数,它继承了 Animal 的 prototype:
|
|
除了从 Animal 构造函数继承的行为之外,还需要给 Bird 对象添加它独有的行为。
这里,我们给 Bird 对象添加一个 fly() 函数。
函数会以一种与其他构造函数相同的方式添加到 Bird’s 的 prototype 中:
|
|
现在 Bird 的实例中就有了 eat() 和 fly() 这两个方法:
|
|
十五、重写继承方法
通过使用一个与需要重写的方法相同的方法名
,向 ChildObject.prototype 中添加方法。
请看下面的举例:Bird 重写了从 Animal 继承来的 eat() 方法:
|
|
如果你有一个实例:let duck = new Bird();,然后你调用了 duck.eat(),以下就是 JavaScript 在 duck 的 prototype 链上寻找方法的过程:
- duck => eat() 是定义在这里吗? 不是。
- Bird => eat() 是定义在这里吗? => 是的。 执行它并停止往上搜索。
- Animal => 这里也定义了 eat() 方法,但是 JavaScript 在到达这层原型链之前已停止了搜索。
- Object => JavaScript 在到达这层原型链之前也已经停止了搜索。
十六、使用 Mixin 在不相关对象之间添加共同行为
行为是可以通过继承来共享的。
然而,在有些情况下,继承不是最好的解决方案。
继承不适用于不相关的对象,比如 Bird 和 Airplane。
虽然它们都可以飞行,但是 Bird 并不是一种 Airplane,反之亦然。
对于不相关的对象
,更好的方法是使用 mixins
。
mixin 允许其他对象使用函数集合。
|
|
flyMixin 能接受任何对象,并为其提供 fly 方法。
|
|
这里的 flyMixin 接收了bird 和 plane 对象,然后将 fly 方法分配给了每一个对象。 现在 bird 和 plane 都可以飞行了:
|
|
十七、使用闭包保护对象内的属性不被外部修改
bird 有一个公共属性 name。 公共属性的定义就是:它可以在 bird 的定义范围之外被访问和更改。
|
|
因此,代码的任何地方都可以轻松地将 bird 的 name 属性更改为任意值。
想想密码和银行账户之类的东西,如果代码库的任何部分都可以轻易改变他们。 那么将会引起很多问题。
使属性私有化最简单的方法就是在构造函数中创建变量。
可以将该变量范围限定在构造函数中,而不是全局可用。
这样,属性只能由构造函数中的方法访问和更改。
|
|
这里的 getHatchedEggCount 是一种特权方法,因为它可以访问私有属性 hatchedEgg。
这是因为 hatchedEgg 是在与 getHatchedEggCount 相同的上下文中声明的。
在 JavaScript 中,函数总是可以访问创建它的上下文。 这就叫做 闭包 closure
。
知识点
-
hasOwnProperty 获取自身属性的方法
-
prototype 原型属性
-
isPrototypeOf 验证原型的关系的方法
-
如果不用传参:
Object.create(object) 优于 new 关键字 -
constructor
-
closure 闭包