JavaScript 面向对象编程:原型继承与调用继承详解

作者:闭包旅人时间:2026-10-25 01:47:40浏览:482

本文深入解析 JavaScript 面向对象编程中的两种核心继承模式:原型继承与调用继承。通过对比分析,详细阐述原型链如何共享方法以及调用继承如何利用 call 实现成员复用。此外,文章还探讨了 JavaScript 独特的多态实现,包括基于 arguments 的重载技巧及通过私有变量模拟的父类方法调用机制。

原型继承机制解析

实现原型继承的第一步,是实例化一个父类对象,并将其赋值给子类的 prototype 属性。


这一操作使得父类中所有的公有实例成员均被子类继承。此时,使用 instanceof 运算符进行检测,子类的实例既属于子类,也属于父类。



随后,需将子类构造函数本身赋值给其 prototype 的 constructor 属性(注意此处无需括号)。


此步骤旨在确保通过子类的实例访问 constructor 属性时,能够正确指向子类自身的定义,而非父类。



通过调用 o.method1() 的结果可知,若子类继承的公有实例方法内部调用了私有字段或方法,这些私有成员实际归属于父类。



观察 o.method2() 的调用结果,若子类自身定义的实例方法调用了私有字段或方法,则这些私有成员归属于子类。



通过 o.method() 的调用结果可见,定义在父类原型上的方法能够被子类正常继承。


通过 o.method3() 的调用结果可知,子类中定义的实例方法无法访问父类中定义的私有实例成员。


最后,通过 subClass.staticMethod() 的调用结果可知,静态成员不会被继承。



调用继承法实现

2.4 调用继承法


调用继承的核心机制在于:在子类构造函数内部,通过父类构造器方法在子类的执行上下文中执行。父类构造器中所有通过 this 操作的内容,实际上都是对子类实例的操作。这种模式的主要目的是减少重复代码的编写。


代码如下:

 
function parentClass() {
// private field
var x = "I'm a parentClass field!";
// private method
function method1() {
alert(x);
alert("I'm a parentClass method!");
} 
// public field
this.x = "I'm a parentClass object field!";
// public method
this.method1 = function() {
alert(x);
alert(this.x);
method1();
} 
} 
parentClass.prototype.method = function () {
alert("I'm a parentClass prototype method!");
} 
parentClass.staticMethod = function () {
alert("I'm a parentClass static method!");
} 
function subClass() {
// inherit
parentClass.call(this);
// private field
var x = "I'm a subClass field!";
// private method
function method2() {
alert(x);
alert("I'm a subClass method!");
} 
// public field
this.x = "I'm a subClass object field!";
// public method
this.method2 = function() {
alert(x);
alert(this.x);
method2();
} 
this.method3 = function() {
method1();
} 
} 
// test
var o = new subClass();
alert(o instanceof parentClass); // false
alert(o instanceof subClass); // true
alert(o.constructor); // function subClass() {...}
o.method1(); // I'm a parentClass field!
// I'm a subClass object field!
// I'm a parentClass field!
// I'm a parentClass method!
o.method2(); // I'm a subClass field!
// I'm a subClass object field!
// I'm a subClass field!
// I'm a subClass method!
o.method(); // Error!!!
o.method3(); // Error!!!
subClass.staticMethod(); // Error!!!

上述示例清晰地展示了如何利用调用继承法来实现继承。


利用调用继承的关键仅涉及一步操作:


即在子类定义时,利用父类的 call 方法,将子类的 this 指针传入,从而使得父类方法在子类的上下文中执行。


如此,父类中所有通过 this 方式定义的公有实例成员均被子类继承。


使用 instanceof 运算符判断时,子类的实例仅属于子类,不属于父类。


查看子类实例的 constructor 属性时,显示的是子类的定义,而非父类。


接下来,通过 o.method1() 和 o.method2() 的调用结果与原型继承法的结果相同,所说明的问题一致,此处不再赘述。


通过 o.method() 的调用结果可知,定义在父类原型上的方法不会被子类继承。


通过 o.method3() 的调用结果可知,子类中定义的实例方法同样无法访问父类中定义的私有实例成员。


最后,通过 subClass.staticMethod() 的调用结果可知,静态成员同样不会被继承。


此外,还有一个未在上述示例中体现的特点:调用继承法支持多继承。即一个子类可以从多个父类中,继承所有通过 this 方式定义在父类内部的公有实例成员。


作为一种弱类型语言,JavaScript 提供了丰富的多态性,其多态性是其他强类型面向对象语言所无法比拟的。


JavaScript 多态:重载与覆盖

多态


重载和覆盖


首先说明重载(overload)与覆盖(override)的区别。网络上许多人将 override 误认为重载,这是错误的。两者存在明确区别。


重载指的是:同名函数(包括方法)拥有多个实现,依靠参数的类型和/或个数来区分识别。


覆盖指的是:子类中定义与父类同名、且参数类型和个数相同的方法。定义后,在子类实例中,父类继承的同名方法将被隐藏。


重载


JavaScript 中函数的参数没有类型,且参数个数任意。例如,尽管定义了:


代码如下:

 
function add(a, b) {
return a + b;
} 

这样的函数,调用时仍可传入任意多个参数,且类型任意。是否出错取决于函数执行的内容,JavaScript 并不根据指定的参数个数和类型来判断调用哪个函数。


因此,无法像强类型语言那样定义重载方法,但仍可实现重载。具体做法是利用函数的 arguments 属性。例如:


代码如下:

 
function add() {
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
sum += arguments[i];
} 
return sum;
} 

这样便实现了任意多个参数加法函数的重载。


当然,还可以在函数中通过 instanceof 或 constructor 判断每个参数的类型,以决定后续执行的操作,实现更复杂的函数或方法重载。总之,JavaScript 的重载由用户通过操作 arguments 属性在函数内部实现。


覆盖


实现覆盖也很简单,例如:


代码如下:

 
function parentClass() {
this.method = function() {
alert("parentClass method");
} 
} 
function subClass() {
this.method = function() {
alert("subClass method");
} 
} 
subClass.prototype = new parentClass();
subClass.prototype.constructor = subClass;
var o = new subClass();
o.method();

这样,子类中定义的 method 便覆盖了从父类继承来的 method 方法。


有人可能会问,虽然这样覆盖不错,但在 Java 中,覆盖的方法可以调用被覆盖的方法(父类方法),这里如何实现?同样容易,且比 Java 更灵活。Java 限制只能在覆盖被覆盖方法的方法中使用 super 调用次被覆盖的方法。我们不仅能实现这点,还能让子类中所有方法调用父类中被覆盖的方法。示例如下:


代码如下:

 
function parentClass() {
this.method = function() {
alert("parentClass method");
} 
} 
function subClass() {
var method = this.method;
this.method = function() {
method.call(this);
alert("subClass method");
} 
} 
subClass.prototype = new parentClass();
subClass.prototype.constructor = subClass;
var o = new subClass();
o.method();

你会发现实现非常简单:只要在定义覆盖方法前,定义一个私有变量,将父类中将要被覆盖的方法赋值给它,随后即可继续调用它。由于该方法是私有的,对子类对象不可见,这与其它高级语言实现的覆盖一致。



最后需注意,在覆盖方法中调用该方法时,需使用 call 方法将执行上下文改为 this(尽管本例中非必须)。若直接调用,执行上下文将变为全局对象。

作者最新文章