原型对象
在JavaScript中实现继承的核心就是原型与原型链。
在JavaScript中使用构造函数来创建一个对象时,这个构造函数内有一个prototype属性指向一个对象,这个对象包含了由该构造函数所创建出来的所有实例共享的属性和方法,这个对象就是原型对象。当构造函数创建一个实例时,这个实例中有一个指针存放在__proto__属性,实例可以通过这个指针(即__ptoto__属性)来访问原型对象,这个指针就是通常所说的对象的原型。他们之间的关系如下:
// Person为构造函数,p1是由Person构造函数所创建的实例
p1.__proto__ === Person.prototype // true
注意:
- 只有函数有
prototype属性,指向原型对象; - 只有对象有
__proto__、constructor属性,__proto__指向原型对象,constructor指向构造函数。
原型链
当我们访问一个对象的属性或方法时,如果在对象自身不存在这个属性或方法,那么它就会到它的原型对象上面去查找,若它的原型对象上也不存在这个属性或方法,而这个原型对象又有自己的原型,那么这个原型对象又会到它自己的原型对象上去查找,就这样一级一级向上查找,直到找到或到达原型链的尽头,这样就形成了一条原型链。原型链的尽头为Object.prototype即null,这也是为什么新创建的对象能使用Object的一些方法的原因。
注意:因为所以实例共享同一个原型对象,所以但有实例对原型对象上的属性或方法进行修改时,会影响到其他的实例,如:
function Peroson(name) {
this.name = name
}
Person.prototype.friends = ['mike', 'jackson']
let p1 = new Person('zhangsan')
let p2 = new Person('lisi')
p1.name // zhangsan
p2.name // lisi
p1.name = 'wangwu' // 修改p1自身的name属性
p1.name // wangwu // p1的name属性修改成功
p2.name // lisi // 不影响p2的name属性,因为name属性位于实例自身
p1.friends // ['mike', 'jackson'],p1自身没有,是在原型对象上找到的
p2.friends // ['mike', 'jackson'],p2自身没有,是在原型对象上找到的
p1.friends.push('lily') // 修改原型对象上的属性
p1.friends // ['mike', 'jackson', 'lily']
p2.friends // ['mike', 'jackson', 'lily'],p2受到影响
原型链的指向关系
p1.__proto__ // Person.prototype
Person.prototype.__proto__ // Object.prototype
p1.__proto__.__proto__ //Object.prototype
p1.__proto__.constructor.prototype.__proto__ // Object.prototype
Person.prototype.constructor.prototype.__proto__ // Object.prototype
p1.__proto__.constructor // Person
Person.prototype.constructor // Person
p1.__proto__ === Person.prototype // true,实例的原型指向原型对象
p1.constructor === Person // true,实例的constructor指向构造函数,这里其实是在原型链上通过原型对象找到的,实例对象自身不手动设置的话是没有constructor属性的
Person.prototype === Person.prototype // true,构造函数的prototype指向原型对象
Person.prototype.__proto__ === Object.prototype // 原型对象的原型指向Object.prototype
Person.prototype.constructor === Person // 原型对象的constructor指向构造函数
特别注意的是,实例自身上并没有constructor属性,之所有能使用是因为实例通过原型链,在原型对象身上找到了constructor属性。
修改、重写原型
修改原型对象身上的方法或属性不会影响原型对象的构造函数,但若直接使用{}字面量来重写原型对象的话,会导致新创建的实例的constructor属性指向Object,如下:
function Person(name) {
this.name = name
}
// 修改原型
Person.prototype.sayHi = function() { console.log('hi~') }
let p1 = new Person('mike')
p1.constructor === Person // true
// 重写原型
Person.prototype = {
sayHi: function() {console.log('hi')}
}
let p2 = new Person('jackson')
p2.constructor // Object
因此需要显示指定构造函数:
Person.prototype = {
constructor: Person
sayHi: function() {}
}