原型链
所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所有它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型......
一层层追溯的话,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。也就是说,所有对象都继承了Object.prototype的属性。Object.prototype的原型是null。因此,原型链的尽头是null。
读取对象的某个属性时,Javascript引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到顶层的Object.prototype还是找不到,则返回undefined。如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。
注意,一级级向上,在整个原型链上寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。
举例来说,如果让构造函数的prototype属性指向一个数组,就意味着实例对象可以调用数组方法。
var MyArray = function () { }
MyArray.prototype = new Array()
MyArray.prototype.constructor = MyArray
var mine = new MyArray()
mine.push(1, 2, 3)
console.log(mine.length) // 3
console.log(mine instanceof Array) // true
上面代码中,mine是构造函数MyArray的实例对象,由于MyArray.prototype指向一个数组实例,使得mine可以调用数组方法(这些方法定义在数组实例的prototype对象上面)。最后那行instanceof表达式,用来比较一个对象是否为某个构造函数的实例,结果就是证明mine为Array的实例
constructor属性
prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。
function P(){}
console.log(P.prototype.constructor === P) // true
由于constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承。
function P(){}
var p = new P()
console.log(p.constructor === P) // true
console.log(p.constructor === P.prototype.constructor) // true
console.log(p.hasOwnProperty('constructor')) // false
上面代码中,p是构造函数P的实例对象,但是p自身没有constructor属性,该属性其实是读取原型链上面的P.prototype.constructor属性。
constructor属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。
function F(){}
var f = new F()
console.log(f.constructor === F) // true
console.log(f.constructor === Array) // false
上面代码中,constructor属性确定了实例对象f的构造函数是F,而不是Array。
另一方面,有了constructor属性,就可以从一个实例对象新建另一个实例。
function Constr() { }
var x = new Constr()
var y = new x.constructor()
console.log(y instanceof Constr) // true
上面代码中,x是构造函数Constr的实例,可以从x.constructor间接调用构造函数。这使得在实例方法中,调用自身的构造函数成为可能。
Constr.prototype.createCopy = function(){
return new this.constructor()
}
上面代码中,creatrCopy方法调用构造函数,新建另一个实例。
constructor属性表示原型对象与构造函数之间的关联关系,如果修改了原型对象,一般会同时修改constructor属性,防止引用的时候出错
function Person(name) {
this.name = name
}
console.log(Person.prototype.constructor === Person) // true
Person.prototype = {
method: function () {
}
}
console.log(Person.prototype.constructor === Person) // false
console.log(Person.prototype.constructor === Object) // true
上面代码中,构造函数Person的原型对象改掉了,但是没有修改constructor属性,导致这个属性不再指向Person。由于Person的新原型是一个普通对象,而普通对象的constructor属性指向Object构造函数,导致Person.prototype.constructor变成了Object。
所以,修改原型对象时,一般要同时修改constructor属性的指向。
function Person(name) {
this.name = name
}
Person.prototype.getName = function () {
console.log('2222')
}
console.log(Person.prototype.constructor === Person) // true
Person.prototype = {
constructor: Person,
method: function () {
}
}
var p1 = new Person()
console.log(p1.getName) // undefined
console.log(p1.method) // f(){}
console.log(Person.prototype.constructor === Person) // true
console.log(Person.prototype.constructor === Object) // false
// 坏的写法
C.prototype = {
method1: function (...) {... },
// ...
}
// 好的写法
C.prototype = {
constructor: C,
method1:function(...){...},
// ...
}
//更好的写法
C.prototype.method1 = function(...){...}
上面代码中,要么将constructor属性重新指向原来的构造函数,要么只在原型对象上添加方法,这样可以保证instanceof运算符不会失真。
如果不能确定constructor属性是什么函数,还有一个办法:通过name属性,从实例得到构造函数的名称
function Foo(){}
var f = new Foo()
console.log(f.constructor.name) // Foo