Understanding Prototypes in JavaScript.md

Understanding Prototypes in JavaScript

Prototype是一个怪异并且我们极力避免的一个话题,但是对于顶级的js开发者来说尤为重要.虽然类更容易理解,并且在学习面向对象编程时通常是许多人的起点,但原型是构建JavaScript的基础.

它看起来像类, 闻起来也像, 但是它不是类。因此,忘记你知道的关于class和面向对象语言的概念, 如果像真正的理解prototype的概念。

不, 我是个骗子。来, 让我们快速的回顾一下传统的类以及面向对象编程是如何工作的。

Classes-the master blueprint

我的想法是,所有东西都是某种对象,所有你需要做的就是为那个对象创建“蓝图”,并在每次需要它的唯一实例时实例化新对象。

类往往是举例。然而, 一个类是仅仅是一个对象创建的模式。其他语言也存在原型,但是在js中使用的原型和java等语言的原型不同。这种语言只是在名称上类似, 但是本质上是完全不同的语言。

类(不是特定于语言的)通常具有构造函数,其中的方法被调用以执行某种操作。它们是以这种方式构造的,因为它为对象提供了清晰的界限和环形围栏 - 这个想法是所有对象都是’事物’而所有’事物’都具有属性和功能。

然而,现实并不总是那么明确,定义对象往往很困难。你能共享一个方法, 创建一个超类或者子类但这是另一个不同的故事了。有时, 但你创建一个class, 它能复制和别的class类似的特性, 但并不是相同的class.

Prototype-Do you have a pen?

别误会我的意思, class是一套很好的编程范式。然而js建立于Prototype之上。

一个prototype像一个结构的class, 它被加总在一起通过引用的方式。如果一个class像一个蓝图, 那么prototype就像是在问你-do you have a pen? 如果答案是不。那么你会去问你朋友这个问题。如果他的回答也是不, 那么你的朋友回去问你朋友的朋友。这个过程会持续到没朋友的那个人。

当你在js创造了一个函数, js引擎会自动创建一个空的容器并且命名为prototype, 为了挂载你的方法。

因此, 如果你写了一个函数funciton x() {}, 并且把它打印出来你可能会看到下面的内容:

这意味着, 你能增加一些东西到prototype, 通过一些像这样的方式:

1
2
3
4
function x () {};
x.prototype = {
sayHello: 'Hello'
};

这些将会展示在prototype下并且让我们调用,当我们通过这个函数实例化一个对象的时候。

原型模式的工作原理是函数/方法只编写一次,并且原型对象可以通过原型链引用和使用。

有些困惑是吗?来看个图表。

连接函数时,原型中设置的方法从左到右可用。x和y中的任何内容都可用于z,但不是相反。希望之前的那笔比喻现在更有意义了。

New Initialization Pattern

这有四种方式初始化并且创建一个prototype链,但是在这篇文章里, 我做一个例子就是新初始化模式。我发现这个模式更简单对于演示原型继承以及原型链,特别是那些仍在努力吸收和理解这个想法的人。

让我们开始,从一个空的car函数。你可以在里面有东西但是为了这个演示的目的,我们将把它留空。

1
2
3
function Car() {

}

我们现在往Car的prototype里增加以下属性.

1
2
3
4
Car.prototype = {
washCar: 'wash',
doors: true
}

现在,当我们用console.dir打印出Car的时候, 我们可以看到这两个属性出现在prototype里。

让我们创建另一个函数以便可以链接他们并且获得继承的效果。

1
2
3
function Honda () {

}

我们有Honda函数.现在我们去创建一个引用并且链接到Car, 通过实例化这个Car函数(使用new关键字)

1
let HondaProto = new Car;

你也可以设置你自己的prototype方法在HondaProto上, 像这样

1
2
3
HondaProto.limit = function() {
console.log('limit');
}

为了链接你的Honda函数与我们定制的prototype蓝图, 只需要把Honda.prototype设置为HondaProto

1
Honda.prototype = HondaProto;

OK, 现在, 如果你使用console.dir(Honda), 你将可以看见这个函数Honda()现在已经有了limit函数, 同时,当你访问prototype的时候你可以看到Car的两个属性washCar/door在proto下.

让我们增加更多的函数在这条继承链上, 因此你可以看到这条链在console.log打印的内容里。

1
2
3
4
5
6
7
8
9
10
11
function Electric () {

}

let ElectricProto = new Honda;

ElectricProto.electric = function () {
console.log('electric');
}

Electric.prototype = ElectricProto;

相同的方式实例化Electric对象, 得到的console信息如下。这将增加一个新的proto在原有的proto下方嵌套。

现在, 如果你使用Electric初始化一个新对象并且调用wash方法, 该对象将可以访问所有Car对象的方法和内部设置。子级域链也会继承任何父级链进一步发生变化的变化。

1
2
let LX4Honda = new Electric();
console.log(LX4Honda.washCar);

console.log将打印出 “wash”.

尽管LX4没有叫washCar的方法, 它会沿着原型链往下直到找到这个方法.如果没有匹配, 将返回undefined.

您还可以将一个原型函数附加到另一个链,从而共享该函数。 创建这种关系的方法与上面说明的完全相同。 从逻辑上讲,如果我们继续使用到目前为止的示例,它应该看起来像这样。

Final words

原型以及原型链的继承最有效的地方是,你没必要创建一个全新的桶来容纳里面的所有方法。相反,你只需要创建一个指向这个函数的引用仅当你需要的时候去调用就好。对于类,这些方法坐在桶中等待被调用。

希望上面这个例子对你有些意义, 并且可以帮助你更好的理解原型。这还有一些其他的方式初始化并且创建一个原型继承链, 但是要控制好链的长度,防止他们混淆你。我已经从这里省略了它们,并向您展示了如何使用新的初始化模式创建继承链。但是,你应该去看看Object.create,Object.setPrototypeOf和proto

这三个列出的方法同样有价值,是创建原型继承链的绝佳选择。

让我们保持联系,加入我每周很棒的网页摘要通讯列表。

谢谢你的阅读

(完)