JavaScript 规定,每个函数都有一个prototype
属性,指向一个对象。
function f() {} typeof f.prototype // "object"
上面代码中,函数f
默认具有prototype
属性,指向一个对象。
对于普通函数来说,该属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型。
构造函数的一个方法,或者一个属性
function Animal(name) { this.name = name; } Animal.prototype.color = 'white'; var cat1 = new Animal('大毛'); var cat2 = new Animal('二毛'); cat1.color // 'white' cat2.color // 'white'
上面代码中,构造函数Animal
的prototype
属性,就是实例对象cat1
和cat2
的原型对象。原型对象上添加一个color
属性,结果,实例对象都共享了该属性。
Animal.prototype.color = 'yellow'; cat1.color // "yellow" cat2.color // "yellow"
如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。
总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象。
总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象。
如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype
,即Object
构造函数的prototype
属性。也就是说,所有对象都继承了Object.prototype
的属性。这就是所有对象都有valueOf
和toString
方法的原因,因为这是从Object.prototype
继承的。
那么,Object.prototype
对象有没有它的原型呢?回答是Object.prototype
的原型是null
。null
没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null
。
读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype
还是找不到,则返回undefined
。如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。
prototype
对象有一个constructor
属性,默认指向prototype
对象所在的构造函数。
function P() {} P.prototype.constructor === P // true
由于constructor
属性定义在prototype
对象上面,意味着可以被所有实例对象继承。
function P() {} var p = new P(); //应该有这个属性吗 p.constructor === P // true p.constructor === P.prototype.constructor // true p.hasOwnProperty('constructor') // false p小自身没有constructor属性
上面代码中,p
是构造函数P
的实例对象,但是p
自身没有constructor
属性,该属性其实是读取原型链上面的P.prototype.constructor
属性。
constructor
属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。
另一方面,有了constructor
属性,就可以从一个实例对象新建另一个实例。
function Constr() {} var x = new Constr(); var y = new x.constructor(); y instanceof Constr // true
上面代码中,x
是构造函数Constr
的实例,可以从x.constructor
间接调用构造函数。这使得在实例方法中,调用自身的构造函数成为可能。
instanceof
运算符返回一个布尔值,表示对象是否为某个构造函数的实例。
var v = new Vehicle(); v instanceof Vehicle // true
上面代码中,对象v
是构造函数Vehicle
的实例,所以返回true
。
instanceof
运算符的左边是实例对象,右边是构造函数。它会检查右边构造函数的原型对象(prototype),是否在左边对象的原型链上。因此,下面两种写法是等价的。
v instanceof Vehicle // 等同于 Vehicle.prototype.isPrototypeOf(v)
上面代码中,Vehicle
是对象v
的构造函数,它的原型对象是Vehicle.prototype
,isPrototypeOf()
方法是 JavaScript 提供的原生方法,用于检查某个对象是否为另一个对象的原型,详细解释见后文。
由于instanceof
检查整个原型链,因此同一个实例对象,可能会对多个构造函数都返回true
。
var d = new Date(); d instanceof Date // true d instanceof Object // true
上面代码中,d
同时是Date
和Object
的实例,因此对这两个构造函数都返回true
。
由于任意对象(除了null
)都是Object
的实例,所以instanceof
运算符可以判断一个值是否为非null
的对象。
var obj = { foo: 123 }; obj instanceof Object // true null instanceof Object // false
上面代码中,除了null
,其他对象的instanceOf Object
的运算结果都是true
。
instanceof
的原理是检查右边构造函数的prototype
属性,是否在左边对象的原型链上。有一种特殊情况,就是左边对象的原型链上,只有null
对象。这时,instanceof
判断会失真。
var obj = Object.create(null); typeof obj // "object" obj instanceof Object // false
上面代码中,Object.create(null)
返回一个新对象obj
,它的原型是null
(Object.create()
的详细介绍见后文)。右边的构造函数Object
的prototype
属性,不在左边的原型链上,因此instanceof
就认为obj
不是Object
的实例。这是唯一的instanceof
运算符判断会失真的情况(一个对象的原型是null
)。