JavaScript是一门直译式脚本语言,是一种动态类型、基于原型的语言。 JavaScript的灵活性不亚于C++,你可以使用JavaScript尝试不同的程序设计范型。 比如函数式编程、指令式或过程式编程、以及基于原型的面向对象编程。

不同于Java、C#等面向对象语言,JavaScript采用基于原型的继承方式。 本文便来介绍使用JavaScript进行面向对象编程的核心概念:原型链。

用户定义类型的原型链

如何检查JavaScript变量类型?一文指出,instanceof关键字可以基于原型链来检测变量的类型。 马上来看看instanceof的使用方式:先构造一个原型链,再用instanceof来检测类型:

function Animal(){}
function Cat(){}
Cat.prototype = new Animal
function BadCat(){}
BadCat.prototype = new Cat

BadCat cat
cat instanceof Cat      // true
cat instanceof Animal   // true

Cat instanceof Function         // true 
Function instanceof Object      // true 

由上述的instanceof的结果,可以判断这些类型的继承层级:

Object -> Function -> Animal -> Cat -> BadCat

事实上instanceof是通过原型链来检测类型的,例如L instanceof R: 如果R.prototype出现在了L的原型链上则返回true,否则返回false。 用JavaScript来描述instanceof的实现逻辑是这样的:


function instance_of(L, R) {
    for(L = L.__proto__; L; L = L.__proto__){
        if (L === R.prototype) return true;
    } 
    return false; 
}

两个特殊的内置对象

我们知道JavaScript除了5中基本数据类型(见如何检查JavaScript变量类型?)外, 还提供了一系列的内置对象(如Object, Function, Number, String, Date, RegExp), 虽然同为内置对象,但它们拥有不同的原型链结构,你看:

Cat instanceof Cat              // flase
Number instanceof Number        // false 
String instanceof String        // false 
Date instanceof Date            // false

Object instanceof Object        // true, why??
Function instanceof Function    // true, why??

原因见下文:原型链。

如果你没写过JavaScript,看到这里你可能已经决定远离JavaScript了。。 不过对于写JavaScript读者和我,这一点却是需要理解的: JavaScript类型包括基本数据类型(5种)和对象(无数种),而ObjectFunction是两种特殊的对象

  • Object特殊在Object.prototype是凭空出来的。语法上,所有的{}都会被解释为new Object()
  • Function特殊在__proto__ == prototype。语法上,所有的函数声明都会被解释为new Function()

除了这两个特殊的对象,其他对象和用户定义类型拥有一样的原型链结构。

JavaScript 原型链

我用下图给出JavaScript的原型链结构。悄悄告诉你理解原型链的小技巧: 将__proto__箭头视作泛化(子类到父类)关系!那么图中所有的虚线将构成一个继承层级,而实线表示属性引用。

图中给出了Object.prototype.__proto__ == null,但它还没有标准化,在Chrome、Safari和Node.js下它是不同的东西。 但可以看到JavaScript中所有对象的共同隐式原型为Object.prototype,它的上一级隐式原型是什么已经不重要了, 因为它不会影响所有内置对象以及用户定义类型的原型链结构。

上图其实已经解释了不同内置对象instanceof的行为,我们来看FunctionObject的特殊之处:

  1. Object是由Function创建的:因为Object.__proto__ === Funciton.prototype
  2. 同理,Function.prototype是由Object创建的;
  3. Funciton是由Function自己创建的!
  4. Object.prototype是凭空出来的!

现在我们可以解释特殊对象的instance行为了:

// 因为 Function.__proto__ === Function.prototype
Function instanceof Function == true

// 因为 Object.__proto__.__proto__ === Object.prototype
Object instanceof Object == true

// 因为 Function.prototype Object.prototype同时位于Function和Object的原型链上
Object/Function instanceof Object/Function === true

另外可以看到当你声明一个函数(比如Animal)时,Animal.prototype会自动被赋值为一个继承自Object的对象, 而且该对象的constructor等于Animal。即:

function Animal(){}
Animal.prototype.constructor === Animal     // true

值得注意的是Animal如果被Cat继承,Cat实例(比如cat)的constructor仍然是Animal

function Cat(){}
Cat.prototype = new Animal
var cat = new Cat

cat.constructor 
    === Cat.prototype.constuctor 
    === (new Animal).constructor 
    === Animal.prototype.constructor
    === Animal

本文采用 知识共享署名 4.0 国际许可协议(CC-BY 4.0)进行许可,转载注明来源即可: https://harttle.land/2015/09/21/js-prototype-chain.html。如有疏漏、谬误、侵权请通过评论或 邮件 指出。