日期:2014-05-16  浏览次数:20320 次

白话详解Javascript原型

原文:A Plain English Guide to JavaScript Prototypes



当初我刚学Javascript的对象模型的时候,我完全被吓到了,而且觉得这个东西很不靠谱。因为这是我第一次接触基于原型的编程语言,所以我被Javascript的原型特性弄得晕头转向。Javascript对于原型的实现有其自己独特的一面,就是添加了函数构造器(function constructor),不过这并没能简化学习过程。我敢打赌你们中的大多数人也有类似经历。

但是当我用得多了之后,我不仅理解了Javascript的对象模型,我也开始喜欢上其中的某些部分了。多亏了Javascript,它令我发现了原型语言的优雅与灵活之处。我现在可以说非常痴迷于原型语言,因为在对象模型的实现方面,它比基于类的编程语言更加简单和灵活。




Javascript里的原型(prototype)


大多数教程在解释Javascript的对象的时候,往往是从构造函数(constructor functions)入手,我觉得这是错误的做法,因为这样做过早引入了太过于复杂的概念,而令Javascript显得太难于理解。我们不如把这个问题先放在一边,先从原型的一些基础开始讲起。




原型链(prototype chain) ————也称为原型继承(prototype inheritance)


Javascript的每个对象都有一个原型(prototype)。当一个单元信息到达一个对象时,JavaScript首先会在这个对象自身上面寻找这个属性,如果没有找到,那么这个单元信息会被发送到这个对象的原型,而这个原型本身又是另一个对象,所以上面的操作被再次执行,这个过程一直这样持续下去直到找到为止。这跟基于类的编程语言里的单继承是一样的。



你想要原型继承链有多长,它就可以有多长,完全由你决定。可是最好不要弄太长,不然你的代码会很难理解,也不好维护。




__proto__对象


理解Javascript的原型链的最简单的方法莫过于通过__proto__属性。不过很遗憾的是__proto__并不在Javascript实现标准内,至少在ES6之前都不是。所以在你的产品代码里不要使用它,但是不管怎么说作为研究学习用途来说,它可以帮助你很容易地理解原型。


// 先创建一个alien对象
>>> var alien = {
  kind: 'alien'
}


// 再创建一个person对象
>>> var person = {
  kind: 'person'
}


// 创建一个叫“zack”的对象
>>> var zack = {};

// 把alien赋值给zack的原型
>>> zack.__proto__ = alien;

// zack现在与alien关联了起来,它“继承”了alien的所有属性
>>> console.log(zack.kind);
alien

// 现在把zack的原型改为person对象
>>> zack.__proto__ = person;

// 那么现在zack与person关联了起来
>>> console.log(zack.kind);
person

现在你看到了,__proto__的用法非常简单明了。虽然不能在产品代码里直接使用它,不过上面这个例子已经很好地解释了Javascript的对象模型的最基础的部分。


你可以通过下面的代码来验证一个对象是否是另一个对象的原型:
>>> console.log(person.isPrototypeOf(zack));
true


动态原型查找

你可以随时在一个对象的原型上面添加属性,而原型链查找总能按照你所预计的那样找到这个属性。

>>> var person = {}

>>> var zack = {}
>>> zack.__proto__ = person;


// 在这个时候zack上面还没有kind属性
>>> console.log(zack.kind);
undefined

// 现在给person添加kind属性
>>> person.kind = 'person';

// 现在,在zack上面能找到kind属性了,因为它在person上面找到了“kind”
>>> console.log(zack.kind);