理性分析 JavaScript 中的原型

1,086 阅读4分钟

原型

在类继承的语言中,比如 Java ,使用了类来描述实例对象的行为。JavaScript 中没有类,所以也没有使用类继承。采用的是原型继承的方式。

原型继承使用对象来描述实例对象的行为,这个描述行为的对象就是原型对象(prototype)。

prototype

prototype 是所有函数都具有的属性。当一个函数被作为构造函数生成一个实例对象时,prototype 就是这个实例对象的原型对象。

constructor

constructor 表示生成该实例对象的构造函数。 但是实例对象是不存在 constructor 属性的,这个属性被保存在了原型对象中。

来看实例:

function Foo(){}
var foo1 = new Foo()
console.log(foo1) 
console.log(Foo.prototype) 
console.log(foo1.constructor === Foo.prototype.constructor) 

结果如下:

我们可以看到 foo1 对象中是不包含 constructor 属性的,而 Foo.prototype 中存在 constructor 属性。但是 foo1 的 constructor 值和 Foo.prototype 的 constructor 值却是相等的。说明 foo1 的 constructor 属性其实是从 Foo.prototype 继承过来的。这种将属性不保存在自身,却能通过自身访问得到的设计被称为行为委托。

__proto__

在上图中,我们看到在 foo1 对象和 Foo.prototype 对象中都有一个属性 __proto__ 。

那么 __proto__ 是什么呢? 在继承中,我们需要一种向上查找的能力去维持继承关系。对于 JavaScript 来说就是实例对象查找实例原型,子原型对象查找父原型对象,父原型对象继续向上查找直到根原型对象,也就是 Object.prototype,Object.prototype向上查找会得到一个 null 值,指示查找结束。整个查询路径构成了原型链。

在浏览器的实现中,使用了 __proto__ 属性来缓存原型对象,所有的对象都拥有这个属性。这样对象通过查询 __proto__ 属性便能实现向上查找。

来看实例:

function Foo(){}
var foo1 = new Foo()
console.log(foo1.__proto__ === Foo.prototype) // true
console.log(Foo.prototype.__proto__ === Object.prototype) // true

实例对象 foo1 的 __proto__ 属性缓存着原型对象 Foo.prototype 。子原型对象 Foo.prototype 的 __proto__ 属性缓存着父原型对象 Object.prototype。

图示

从 constructor、prototype、__proto__ 这三个角度去思考,我们便能很快的画出整个图示。

先来思考 constructor ,原型对象的构造函数表示生成实例对象的构造函数,由此得出下图:

再来思考 prototype,构造函数的 prototype 就是原型对象。补充得出下图:

最后来思考 __proto__,实例对象的 __proto__属性缓存着原型对象,原型对象的 __proto__缓存着父原型对象。 实例对象是由构造函数使用 new 操作符生成的。补充得出下图:


原型相关

instanceof

instanceof 运算符用来检测一个对象的原型链中是否存在指定构造函数的原型对象。

用法如下:

object instanceof constructor

来看实例:

function Foo(){}
var foo1 = new Foo()
console.log(foo1 instanceof Foo) // true
console.log(foo1 instanceof Object) // true 

通过图示,我们可以清楚地看到 Foo.prototype 和 Object.prototype 都位于原型链中。

Object.getPrototypeOf

ECMAScript 5 提供的 Object.getPrototypeOf 可以用来查看实例对象的原型。

function Foo(){}
var foo1 = new Foo()
console.log(Object.getPrototypeOf(foo1 ) === Foo.prototype) // true
console.log(Object.getPrototypeOf(foo1 ) === Object.prototype) //false

内建对象和内建函数

关于内建对象:

  • 所有的内建对象都是由 Object() 创建而来。

关于内建函数:

  • 所有的内建函数都是由 Function() 创建而来。

理解上面的两个要点,就能理解下面的例子了

Function.__proto__ === Function.prototype // true 
Object.__proto__ === Function.prototype // true

Function 是内建函数是由 Function() 创建而来,所以它的 __proto__ 属性中缓存着 Function.prototype。同理可得Object.__proto__ === Function.prototype

Function.__proto__.__proto__ === Object.prototype // true 
Object.__proto__.__proto__ === Object.prototype // true 
Object.__proto__.__proto__.__proto__ === null // true 

Function.__proto__是一个内建原型对象,是由 Object() 创建而来的。所以它的 __proto__属性中缓存着 Object.prototype。同理可得 Object.__proto__.__proto__ === Object.prototype。 既然 Object.__proto__.__proto__ === Object.prototype,而 Object.prototype.__proto__ === null ,所以Object.__proto__.__proto__.__proto__ === null


总结

  • prototype 是所有函数都具有的属性。
  • __proto__ 是所有对象(包括函数)都具有的属性。
  • JavaScript 采用行为委托的方式,来继承 prototype 对象中的属性。实例对象本身并不包含这些属性,是通过原型链查找来获得这些属性的值。
  • 在浏览器实现中,使用了 __proto__ 属性来缓存 prototype 对象,整个 __proto__ 查询路径构成了原型链。
  • constructor 表示生成该实例对象的构造函数。实例对象没有 constructor 属性,这个属性被保存在了原型对象中。

相关知识点

  • 继承
  • class、extends、super、static
  • this