原型链是JavaScript实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。实现原型链的基本模式是:让当前构造函数的原型对象等于另一个构造函数的实例。下面通过例子来具体分析:
例子:
function A(name, age) {
this.name = name;
this.age = age;
this.family = ["爸爸", "妈妈"];
if (typeof(this.ageName) != "function") {
A.prototype.getName = function() {
return this.name;
}
}
}
function B() {
this.job = "IT";
if (typeof(this.getJob) != "function") {
B.prototype.getJob = function() {
return this.job;
}
}
}
B.prototype = new A("Tom", 20);
var p = new B();
//实例 p 的属性name、age是继承自原型 B.prototype
console.log(p.name);
console.log(p.age);
console.log(p.hasOwnProperty("name"));
console.log(p.hasOwnProperty("age"));
//输出:Tom,20,false,false
//实例 p 的原型 B.prototype 被重写,所以 p 的构造函数指向 A
console.log(p.constructor == B);
console.log(p.constructor == A);
//输出:false,true
//一个完整的原型链
// p.__proto__ >> B.prototype.__proto__ >> A.prototype.__proto__ >> Object.prototype
console.log(B.prototype.isPrototypeOf(p));
console.log(A.prototype.isPrototypeOf(B.prototype));
console.log(Object.prototype.isPrototypeOf(A.prototype));
//输出:true,true,true
重点分析
通过原型链实现继承在例子中,我们让函数B的原型对象
B.prototype
等于函数A的实例new A()
,那么此时B.prototype
相当于被重写,B.prototype
会拥有new A()
中的所有属性与方法。我们知道,实例对象的内部默认都会有一个指针,也就是__proto__
属性,会指向自己的原型对象。而此时,这个指针也会存在于B.prototype
之中。最后的结果就是:B的实例new B().__proto__
指向B.prototype
,B.prototype.__proto__
指向A.prototype
,最后别忘了所有函数的默认原型都是Object的实例,所以,A.prototype.__proto__
指向Object.prototype
。
最理想的继承方式通过原型链,扩展了原型的搜索机制。当访问一个实例属性时,首先会在实例中搜索该属性。如果没有找到该属性,则会继续搜索实例的原型。在通过原型链实现继承的情况下,搜索过程就得以沿着原型链继续向上。在找不到属性或方法的情况下,搜索过程会一环一环地前行到原型链末端,也就是所有引用类型默认都继承的
Object.prototype
,这也正是所有自定义类型都会继承toString()、valueOf()
等默认方法的根本原因。
借用构造函数 + 原型链 + Object.create()
不了解借用构造函数
和Object.create()
可以参考以下手记:
JavaScript创建对象之Object.create()方法
虽然说构造函数+原型链的模式是最常用的继承方式,但它也有一个不足之处,原先的实例属性变成了现在的原型属性,当所有实例共享一个原型属性的时候,就会互相影响。例如上面的例子,我们加上如下的测试:
p.family.push("爷爷");
console.log(p.family);
//输出:["爸爸", "妈妈", "爷爷"]
var p1 = new B();
console.log(p1.family);
//输出:["爸爸", "妈妈", "爷爷"]
基于此,构造函数+原型链的继承方式并不是最理想的方式。结合前面所学,如果我们通过借用构造函数来继承实例属性,通过原型链+Object.create()来继承方法或共享的属性,就是最理想的继承模式。
例子:
function A(name, age) {
this.name = name;
this.age = age;
this.family = ["爸爸", "妈妈"];
if (typeof(this.ageName) != "function") {
A.prototype.getName = function() {
return this.name;
}
}
}
function B(name, age, job) {
//复制函数A的实例属性
A.call(this, name, age);
this.job = job;
}
//以A的原型对象为原型创建对象并赋值给B的原型对象,从而继承A原型上的方法
B.prototype = Object.create(A.prototype);
//B的原型对象被重写,需要重新指定B的原型的构造函数
B.prototype.constructor = B;
//测试1
var p1 = new B("Tom", 40, "Doctor");
console.log(p1.name);
console.log(p1.age);
console.log(p1.job);
console.log(p1.getName());
p1.family.push("爷爷");
console.log(p1.family);
console.log(p1.constructor == B);
//输出:Tom,40,Doctor,Tom,["爸爸", "妈妈", "爷爷"],true
//测试2
var p2 = new B("Amy", 30, "Teacher");
console.log(p2.name);
console.log(p2.age);
console.log(p2.job);
console.log(p2.getName());
p2.family.push("姥姥");
console.log(p2.family);
console.log(p2.constructor == B);
//输出:Amy,30,Teacher,Amy,["爸爸", "妈妈", "姥姥"],true
文中的代码部分,带有“例子”和“测试代码”字样的,只是用来学习或测试某一功能用的代码,不可以直接用于项目的开发中。带有“代码如下”字样的,都是经过本人测试,简单修改即可用于项目开发中的代码,如有错误,欢迎指出。