JavaScript 继承机制详解:原型链、构造函数与寄生组合模式

作者:半夏时间:2026-10-28 18:10:21浏览:287

本文系统梳理了 JavaScript 中实现继承的多种核心方案,包括原型链继承、构造函数继承、混合模式以及寄生组合模式。通过对比分析各方法的优缺点,重点解析了如何解决原型共享引用类型数据的问题,并详细阐述了寄生组合模式作为最佳实践的原理与代码实现,帮助开发者掌握 JS 面向对象编程的关键技术。

JavaScript 继承机制概述

本文详细总结了 JavaScript 中实现继承的多种常用方法,旨在为开发者提供清晰的参考与对比。

以下是具体的实现示例与分析。

代码片段如下:

/**
* 实现子类继承父类,但不会产生多余的属性和方法
* @returns {Function}
*/
define(function(){
return function(subType, superType){
var proto = new Object(superType.prototype);
proto.constructor = subType;
subType.prototype = proto;
};
});
//——————————————————————————
define(function(){
function ostring(s)
{
this.str = s;
this.length = this.str.length;
}
ostring.prototype.show = function(){
alert(this.str);
};
return ostring;
});
//——————————————————————————
define(['inherit', 'ostring'], function(inherit, ostring){
function wstring(s){
//用call实现调用父类构造函数
ostring.call(this, s);
this.chlength = 2 * s.length;
}
//继承其他的属性
inherit(wstring, ostring);
wstring.prototype.add = function(w)
{
alert(this.str + w);
};
return wstring;
});

基于 Function 的继承实现

接下来看另一个例子,首先介绍如何使用 function 关键字来实现继承:

代码如下:

function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}
function Author(name, books) {
this.inherit=person;
this.inherit(name);
this.books = books;
    
}
var au=new Author("dororo","Learn much");
au.name

或者采用同等效力的写法:


代码如下:

function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}
function Author(name, books) {
Person.call(this, name);
this.books = books;
    
}
var au=new Author("dororo","Learn much");
au.getName

上述方法仅将 this 作为参数传递给父类 Person 的构造函数,从而将父类的所有实例属性赋予 Author 子类。然而,这种方式无法继承定义在父类原型(prototype)上的属性和方法。因此,在前面的例子中,调用 au.getName 会返回 undefined,因为 getName 是定义在 Person 的原型对象中,而非实例属性。

此外,子类的构造函数必须在定义自身属性之前调用父类构造函数,否则子类的定义可能会被父类覆盖。也就是说,Author 中定义属性 books 的代码必须放在 Person.call 之后,否则会被覆盖。同时,在子类中也不建议使用 prototype 来定义子类的函数域,因为在子类被 new 实例化后,会先执行 prototype 的定义,随后才调用父类构造函数,这同样可能导致父类属性覆盖子类定义。

基于 Prototype 的原型链继承

第二种方法是利用 prototype 属性来实现继承:

代码如下:

function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}
function Author(name, books) {
this.books = books;
}
Author.prototype=new Person(name);
Author.prototype.constructor=Author;
Author.prototype.getBooks = function() {
return this.books;
}
var au1=new Author("dororo1","Learn much");
var au2=new Author("dororo2","Learn less");
alert(au1.getName());
alert(au2.getName());

这种方法解决了第一种方法中无法继承 prototype 的问题,因为 Author.prototype=new Person(name) 会调用 Person 的构造函数并继承其原型中的所有属性。但其缺点在于,Author.prototype 已经被实例化。当子类再次实例化时,所有非基本数据类型都会以引用拷贝(reference copy)的形式共享。因此,在上面的例子中,无论实例化 au1 还是 au2,调用 getName() 返回的值都是 dororo1,因为 name 属性在原型实例化时已被固定。

混合模式与寄生组合模式

第三种方法是采用“混合”模式来实现:

代码如下:

function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}
function Author(name, books) {
this.base = new Person(name);
for(var key in this.base){
if(!this[key]){
this[key]=this.base[key];
           }
           }
this.book=books;
}
var au1=new Author("dororo1","work");
var au2=new Author("dororo2","play");
alert(au1.getName());
alert(au2.getName());
au1.book;
au2.book;

 

这种模式属于扩展方式,它将父类的所有域都拷贝到子类中。它完全避免了上述两种方法的缺点,既继承了实例属性,又保留了原型链的灵活性。


此外,还有一种更优化的“寄生组合模式”。

属性继承与 New 关键字原理

JS的继承涵盖属性与方法,二者通过不同机制实现。 1. 属性继承

属性的继承通常通过改变函数的执行环境(this)来实现,这可以使用 call() 和 apply() 两种方法来完成。

首先,我们创建一个模拟的“类” Animal(在 JavaScript 中,这实际上是一个 Function 函数对象)。

代码如下:

function Animal(typeName) {
//为当前方法的执行环境(this)添加一个属性typeName
//但是执行环境(this)要执行这个函数的时候才能确定
this.typeName = typeName;
this.colors = ["red","while"];
}
//想函数的原型里 添加 两个(对象共享的)的方法
Animal.prototype.Shout = function () { alert("我是:--" + this.typeName);};
Animal.prototype.Eat = function () { alert("我是:--" + this.typeName) };
//--定义一个狮子--“类”(其实就是一个函数)
function Lion(tn) {
//--执行Animal方法,并通过apply的第一个参数 修改了Animal的执行环境为Lion的this
//同样的,Lion的this,也要在执行的时候才能确定是谁
Animal.apply(this,["狮子"]);//--继承了父类的变量属性,this因为是new了Lion,this是Lion
}
Lion.prototype = Animal.prototype; //继承父类的方法,搞定--但是这写不好,当子类再添加方法时候,父类同样也有此方法,这是指针引用
Lion.prototype.Hunt = function () {
alert("我是:狮子,我要去捕猎~~·~");
}
var aminm = new Animal();
aminm.Hunt(); //---可以访问到子类的方法,这样就不好了
//----那么如何解决这个问题呢》??????
//---解决方案:继承方法时候可以这样写:
Lion.prototype = new Animal();//继承父类的方法,把Animal对象赋给了prototype原型,其实它里面也有属性
var lion = new Lion(); //new 关键字除了创建的,还会修改Lion对象的执行环境为Lion对象本身
// ---换句话说,就是new完了之后,Lion函数里的this就是Lion函数本身了,然后调用Lion函数

下面分析 new 关键字的作用:

new 关键字在上述代码中完成了以下几项关键工作: 1) 开辟堆空间,以准备存储 Lion 对象 2) 修改 Lion 对象本身的执行环境,使得 Lion 函数的 this 指向了 Lion 函数对象本身。 3) 调用 Lion“类”的“构造函数”,创建 Lion 对象 4) 将 Lion 函数对象的堆地址赋值给变量 l,这个时候 l 就指向了这个 Lion 函数对象 lion.Shout(); lion.Eat(); 但是这种简单的继承方式有一个缺点:父类的构造函数被调用了两次,一次是通过 call/apply,另一次是通过 new 关键字实例化原型。

希望本文所述的 JavaScript 程序设计内容能对读者有所帮助。

作者最新文章