前言

在JavaScript中没”子类”和“父类”的概念,进一步地也没有“类”和“实例”的的区分。它靠一种看上去十分古怪的”原型链“(prototype chain)模式来实现继承。学过JAVA等编程语言的人可能会认为这是对Java等语言的继承实现方式的一种拙劣并且失败的模仿,然而事实并非如此,原型链模式和我们常见的Class模式分别是两种编程范式prototype_base和class_base的实现,前者在动态语言中似乎十分常见,而后者主要在静态语言领域流行。下面是维基百科关于prototype_base模式的介绍:

Prototype-based programming is a style of object-oriented programming in which behaviour reuse (known as inheritance) is performed via a process of reusing existing objects via delegation that serve as prototypes. This model can also be known as prototypal, prototype-oriented, classless, or instance-based programming. Delegation is the language feature that supports prototype-based programming.
Prototype object oriented programming uses generalized objects, which can then be cloned and extended. Using fruit as an example, a “fruit” object would represent the properties and functionality of fruit in general. A “banana” object would be cloned from the “fruit” object, and would also be extended to include general properties specific to bananas. Each individual “banana” object would be cloned from the generic “banana” object. Compare to the class-based paradigm, where a “fruit” class (not object) would be extended by a “banana” class

维基原文

如何理解原型链

我们以一个名字叫Foo()的函数为例。我们定义:

1
2
3
4
function Foo(){
}

然后再var f1 = new Foo(),var f2 = new Foo(),这期间都有什么事情发生呢?我们通过一张图来看一下:
prototype.jpg

先介绍两个概念:_proto_prototype:

  • _proto_:引用《JavaScript权威指南》中的说明:

    Every JavaScript object has a second JavaScript object (or null ,
    but this is rare) associated with it. This second object is known as a prototype, and the first
    object inherits properties from the prototype.
    就是说就是每个JS对象一定对应一个原型对象,并从原型对象继承属性和方法。既然有这么一个原型对象,那么对象怎么和它对应的?如何描述这种对应关系?答案就是通过_proto_,对象__proto__属性的值就是它所对应的原型对象。

  • prototype: 与_proto_不同,prototype是函数才有的属性。当你创建函数时,JS会为这个函数自动添加prototype属性,值是空对象。而一旦你把这个函数当作构造函数(constructor)调用(即通过new关键字调用),那么JS就会帮你创建该构造函数的实例,实例继承构造函数prototype的所有属性和方法(实例通过设置自己的__proto__指向承构造函数的prototype来实现这种继承)。它的存在主要是为了存放共享的数据和实现继承。

  下面结合上面的图示来分析,我们可以看到function Foo()对应一个Foo.prototype的原型,那么function FooFoo.prototype之间的关系是什么尼?
  图里其实已经展示得很清楚了,functon Foo()Foo.prototype的构造函数,Foo.prototypefunction Foo()的原型实例。当我们使用new关键字创建var f1 = new Foo(),var f2 = new Foo()后,f1、f2中会有一个_proto_字段指向Foo.prototype,这种xxx._proto_._proto....的指向就代表了原型链的结构(应该是个森林)。同时每个函数function xxx()其实都是通过function Function()来创造的,所以function Foo()_proto_应该指向Function.prototype,并且function Function()自身的_proto_也指向Function.prototype
  事实上,所有function xxx()_proto_最终都会指向Function.prototype,而所有的xxx.prototype最后都会指向Object.prototype,最终指向null。关于function Object()这个函数其实有点像Java中的Object对象,所有原型都会继承自它的原型。这里有个有意思的问题,function Function()也是个函数,所以function Function()_proto_属性的值为Function.prototype,这也就意味着它自己创造了自己。这样的结果就是function Object()._proto_ = Function.prototype、而function Function()._proto._proto_ = Object.prototype,即Object instanceof Function == trueFunction instanceof Object == true翻译过来就是ObjectFunction的实例,FunctionObject的实例,这是一种类似先有鸡还是先有蛋的蜜汁尴尬局面。

总结:

  • 所有对象的_proto_字段都指向创建它的构造函数的原型, 最终都指向Object.prototype,类似xxx.prototype._proto_._proto_..._proto_ = Object.prototype = null就是原型链。
  • 所有函数都由function Function()创建,所以所有函数的(包括它本身)_proto_字段都会指向Function.prototype,最后才指向Object.prototype

使用原型链实现继承

定义父函数:

1
2
3
4
5
6
function Father() {
this.age = "56"; }
Father.prototype.say = function () {
alert("my age is "+this.age);
}

定义子函数:

1
2
3
4
5
6
7
function Son() {
this.age = '26';
this.play = "football"; }
Son.prototype.play = function () {
alert("I like play "+this.play);
}

实现继承后的原型链应该是:Son.prototype._proto_ = Father.prototype
实现方式:借用第三个函数过渡

1
2
3
4
5
6
7
8
9
function extends(Child,Father){
var F = function(){};
F.prototype = Father.prototype;
//Child.prototype._proto_ = F.prototype = Father.prototype
Child.prototype = new F();
//原本Child.prototype.constructor = F,修改为Child
Child.prototype.constructor = Child;
}

测试验证:Son的实例可以调用say()则说明继承成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Father() {
this.age = "56"; }
Father.prototype.say = function () {
alert("my age is "+this.age); }
function Son() {
this.age = '26';
this.play = "football"; }
Son.prototype.play = function () {
alert("I like play "+this.play); }
function excents(Child,Father) {
var F = function () {}
F.prototype = Father.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child; }
excents(Son,Father);
var son = new Son();
son.say();

运行结果:

继承成功!