在弄懂原型和原型链之前,先了解几个概念
1.什么是原型对象
每一个函数对象(fn)都有一个特殊的属性叫做原型对象(prototype) ,它指向另一个对象,这个对象(fn.prototype)被称为原型对象, 原型对象是用来共享属性和方法的,即在构造函数的原型对象上定义的方法,通过这个构造函数创造的实例均可调用该方法
2.constructor的指向
构造函数的原型对象有一个constructor属性指向构造函数本身
fn.prototype.constuctor = fn //True
构造函数的实例对象的constructor指向构造函数本身
let a = new fn()
a.constructor = fn
3.隐式原型_proto_
在js中,每个对象都有一个 _proto_ 属性,这个_proto_就被称为隐式原型。
实例对象当然也是对象,也存在_proto_属性,每个js对象都有一个隐藏的原型对象属性_proto_,它指向创建它的构造函数的原型对象(fn.prototype)
let a= new fn()
a._proto_ = fn.prototype //True
什么是原型链
我们知道,每个对象都有一个_proto_属性指向其构造函数的原型对象,那么又已知函数fn的原型对象也是对象,那fn的原型对象fn.prototype会不会也有存在隐式原型__proto__指向某个构造函数的原型对象?这种情况是存在的,即fn.prototype这一原型对象是另一个构造函数的实例
// 第一个构造函数 A
function A(name) {
this.name = name;
}
// 第一个构造函数 B
function B(age) {
this.age = age;
}
// 让 A 的原型对象是 B 的实例
A.prototype = new B(19);
const obj = new A("John");
console.log(obj.name); // "John" - 来自构造函数 A
console.log(obj.age); // 19 - 来自构造函数 B(通过原型继承)
分析上段代码,构造函数A的原型对象是构造函数B的实例,输出obj.name时,根据构造函数A对实例中的name的定义输出即可,但输出obj.age时,构造函数A中并未对age定义,那么他只能向它的构造函数的原型对象(A.prototype)去找,即obj._proto_.age,已知A.prototype.age=19,所有obj._proto_.age即为obj.age=19,这一成了一条链,即为原型链。
每当构造函数A的实例a中找不到某属性的定义时,他就会通过a._proto向A.prototype中寻找,若仍寻找不到再向上层A.prototype._porto寻找,直至到达原型链的终点,即某个对象的_proto_属性指向了Object.prototype这一原型链最上层的终点,因为它是终点,所以它不再有自己的原型对象,所以Object.prototype.proto 指向null。