本文详细解析JavaScript基于原型链的继承机制。通过构造函数、原型对象与实例之间的关系,阐述如何利用原型实现属性与方法共享。重点讨论原型链构建过程中的constructor指向问题、isPrototypeOf与instanceof的使用,以及引用类型属性共享和无法向超类构造函数传参等核心局限性,帮助开发者掌握正确的原型链使用方式。
JavaScript原型继承概述
JavaScript并非传统意义上的面向对象语言,未提供常规的继承机制,而是通过原型继承的方式,利用其内置的原型属性来达成继承效果。
原型链构成了JavaScript中实现继承的核心手段。
其基本逻辑在于:借助原型机制,使一个引用类型能够继承另一个引用类型的属性与方法。
构造函数、原型与实例的关系
需明确构造函数、原型与实例三者间的关联:每个构造函数均拥有一个原型对象,该原型对象包含一个指向构造函数的指针;同时,实例内部包含一个指向原型对象的指针。
若将原型对象设置为另一个对象的实例,则该原型对象将包含指向另一个原型的指针;相应地,另一个原型中也会包含指向另一个构造函数的指针。
原型链的基本实现模式
原型链的基础实现模式如下:
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
};
function SubType() {
this.subproperty = false;
}
// 继承SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); // true
SubType通过创建SuperType的实例并将其赋值给SubType.prototype来实现继承。此过程的本质是重写原型对象,将其替换为新类型的实例。由此,SuperType实例中的属性与方法便存在于SubType.prototype中。随后,在SubType.prototype上添加新方法,即可在继承SuperType属性与方法的基础上扩展功能。
上述示例中的实例关系结构如下:

此处未使用SubType默认的原型,而是为其替换了一个新原型,该新原型即为SuperType的实例。新原型内部还包含一个指向SuperType原型的指针。最终结构表现为:instance指向SubType的原型,而SubType的原型又指向SuperType的原型。getValue()方法仍保留在SuperType.prototype中,但prototype属性位于SubType.prototype中。由于property是实例属性,而getSuperValue()是原型方法,当SubType.prototype成为SuperType的实例时,property自然位于该实例中。
需注意:此时instance.constructor指向SuperType,因为SubType的原型指向了另一个对象——即SuperType的原型,而该原型对象的constructor属性指向SuperType。
在读取模式下访问属性时,系统首先搜索实例本身;若未找到,则继续搜索实例的原型。通过原型链实现继承时,搜索过程会沿原型链向上进行。
默认原型与完整原型链
默认原型机制
所有引用类型默认继承自Object,这一继承同样通过原型链实现。所有函数的默认原型均为Object的实例,因此默认原型包含一个指向Object.prototype的内部指针。这也是自定义类型能够继承toString()、valueOf()等方法的原因。
完整的原型链结构如下:

在上述继承体系中,SubType继承SuperType,SuperType继承Object。当调用instance.toString()时,实际执行的是保存在Object.prototype中的方法。
确定实例和原型关系的方法
确定实例与原型关系的方法
可通过以下两种方式确认原型与实例之间的关系:
使用instanceof操作符
alert(instance instanceof Object);
alert(instance instanceof SuperType);
alert(instance instanceof SubType);
基于原型链的关系,上述所有判断均返回true。
使用isPrototypeOf()方法
alert(Object.prototype.isPrototypeOf(instance));
alert(SuperType.prototype.isPrototypeOf(instance));
alert(SubType.prototype.isPrototypeOf(instance));
原型链实现中的注意事项
谨慎定义方法
向原型添加方法的代码必须置于替换原型的语句之后。
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
};
function SubType() {
this.subproperty = false;
}
SuperType.prototype = new SuperType();
// 添加方法
SubType.prototype.getSubValue = function () {
return this.subproperty;
};
// 覆盖超类中的方法
SubType.prototype.getSuperValue = function () {
return false;
};
var instance = new SubType();
alert(instance.getSuperValue()); // false
前述示例中需特别注意:在使用SuperType的实例替换原型后,再定义那两个方法。
此外,通过原型链实现继承时,禁止使用对象字面量创建原型方法,因为这会导致原型链被重写:
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
};
function SubType() {
this.subproperty = false;
}
// 继承SuperType
SubType.prototype = new SuperType();
// 使用字面量添加新方法,导致上一行代码无效
SubType.prototype = {
getSubValue :function() {
return this.subproperty;
},
someOtherMethod: function () {
return false;
}
};
var instance = new SubType();
alert(instance.getSuperValue()); // error
上述示例将SuperType的实例赋值给原型后,紧接着用对象字面量替换原型,从而引发问题。此时原型包含的是Object的实例而非SuperType的实例,SubType与SuperType之间已无关联。
原型链的局限性
原型链的局限性
前文已提及,包含引用类型值的原型属性会被所有实例共享;这也是为何应在构造函数而非原型对象中定义属性的原因。
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {
}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); // "red", "blue", "green", "black"
var instance2 = new SubType();
alert(instance2.colors); // "red", "blue", "green", "black"
在上述示例中,SuperType构造函数定义了一个包含数组的colors属性,每个SuperType实例拥有各自独立的colors数组。当SubType通过原型链继承SuperType后,SubType.prototype成为SuperType的一个实例,因此它也拥有了自己的colors属性。然而,SubType的所有实例将共享这一个colors属性。
另一个问题是,无法在不影响所有对象实例的前提下,向超类的构造函数传递参数。
以上内容为JavaScript基于原型链继承的相关知识分享,旨在提供帮助。如有任何疑问,欢迎留言,小编会及时回复。感谢大家的支持!