JavaScript 三种继承方式详解:原型链、apply与call+prototype

作者:青柠

时间:2026-10-30

浏览:166

本文深入解析JavaScript的三种主要继承模式:基于prototype的原型链继承、利用apply方法继承以及结合call与prototype的混合继承。通过代码示例对比各方式在参数传递、属性复制及instanceof类型判断上的表现,指出原型链无法传参、apply导致类型丢失等局限,并论证call+prototype组合在保持类型关系与功能复用上的优势,同时提及更完善的继承方案以解决重复初始化问题。

文章目录

原型链继承方式

第一种实现方式是基于prototype属性的原型链继承:


//父类 
function person(){ 
  this.hair = 'black'; 
  this.eye = 'black'; 
  this.skin = 'yellow'; 
  this.view = function(){ 
    return this.hair + ',' + this.eye + ',' + this.skin; 
  } 
} 

//子类 
function man(){ 
  this.feature = ['beard','strong']; 
} 

man.prototype = new person(); 
var one = new man(); 

console.log(one.feature); //['beard','strong'] 
console.log(one.hair); //black 
console.log(one.eye); //black 
console.log(one.skin); //yellow 
console.log(one.view()); //black,black,yellow 

该方式实现最为直观,只需将子类的prototype属性指向父类的一个实例即可。完成此赋值后,子类实例便可直接调用父类中定义的方法。

prototype属性的含义是什么?prototype即原型,由function定义的对象默认拥有一个名为prototype的属性,其值是一个对象类型。

该默认属性用于实现属性的链式向上查找机制。具体而言,当访问某个对象的属性不存在时,JavaScript引擎会通过该对象的prototype属性所属对象继续查找该属性。若在此层级仍未找到呢?

JavaScript会自动继续查找prototype属性所指向对象的prototype属性,从而沿着原型链一直向上索引查找,直到找到目标属性或prototype最终为null(即undefined)。

以示例中one.view()方法的调用为例,JavaScript首先在一one实例中查找view()方法,由于未找到,转而查找man.prototype属性。

由于man.prototype的值是person的一个实例,该实例中包含view()方法,因此调用成功。

apply方法继承

第二种实现方式是利用apply方法:


//父类 
function person(){ 
  this.hair = 'black'; 
  this.eye = 'black'; 
  this.skin = 'yellow'; 
  this.view = function(){ 
    return this.hair + ',' + this.eye + ',' + this.skin; 
  } 
} 

//子类 
function man(){ 
  // person.apply(this,new Array()); 
  person.apply(this,[]); 
  this.feature = ['beard','strong']; 
} 

var one = new man(); 

console.log(one.feature); //['beard','strong'] 
console.log(one.hair); //black 
console.log(one.eye); //black 
console.log(one.skin); //yellow 
console.log(one.view()); //black,black,yellow 

注意:如果apply方法的参数为空,即没有传递具体参数,则必须通过new Array()或[]来传递空数组,直接传递null是无效的。

call与prototype结合继承

第三种实现方式是结合call与prototype的方式:


//父类 
function person(){ 
  this.hair = 'black'; 
  this.eye = 'black'; 
  this.skin = 'yellow'; 
  this.view = function(){ 
    return this.hair + ',' + this.eye + ',' + this.skin; 
  } 
} 

//子类 
function man(){ 
  // person.apply(this,new Array()); 
  person.call(this,[]); 
  this.feature = ['beard','strong']; 
} 

man.prototype = new person(); 
var one = new man(); 

console.log(one.feature); //['beard','strong'] 
console.log(one.hair); //black 
console.log(one.eye); //black 
console.log(one.skin); //yellow 
console.log(one.view()); //black,black,yellow 

采用call方式实现继承时,多出一行代码man.prototype = new person();。原因在于,call方法仅实现了方法的借用替换,并未执行对象属性的复制操作。Google Map API的继承机制便采用了这种方式。

上述内容总结了三种继承方式的实现逻辑,但每种方法均存在各自的优缺点。

带参继承的实现与局限

假设父类定义如下:


//父类 
function person(hair,eye,skin){ 
  this.hair = hair; 
  this.eye = eye; 
  this.skin = skin; 
  this.view = function(){ 
    return this.hair + ',' + this.eye + ',' + this.skin; 
  } 
} 

若子类在创建对象时需要向父类person传递参数,则prototype继承方式不再适用,必须采用apply或call方式:


//apply方式 
//子类 
function man(hair,eye,skin){ 
  person.apply(this,[hair,eye,skin]); 
  this.feature = ['beard','strong']; 
} 
//call方式 
//子类 
function man(hair,eye,skin){ 
  person.call(this,hair,eye,skin); 
  this.feature = ['beard','strong']; 
} 

然而,使用apply方法仍存在缺陷。原因在于JavaScript中有一个重要运算符instanceof,用于判断某个对象是否属于某种类型。

对于该示例,one实例除了是man类型外,理论上也应属于person类型。但在apply方式继承后,one并不属于person类型,即(one instanceof person)的值为false。

综合上述分析,最佳的继承方式是call结合prototype的方式。随后你可以验证(one instanceof BaseClass)的值是否为true。

更完善的继承方案

第三种继承方式(call+prototype)也存在缺陷:子类new对象时需传递一遍父类所需的参数,且会重新执行父类中的属性和方法初始化。下面介绍的继承方式才是更完善的方案:


function Person(name){   
  this.name = name; 
} 

Person.prototype.getName = function() { 
  return this.name; 
} 

function Chinese(name, nation) { 
  Person.call(this, name); 
  this.nation = nation; 
} 

//继承方法 
function inherit(subClass, superClass) { 
  function F() {} 
  F.prototype = superClass.prototype; 
  subClass.prototype = new F(); 
  subClass.prototype.constructor = subClass.constructor; 
} 

inherit(Chinese, Person); 

Chinese.prototype.getNation = function() { 
  return this.nation; 
}; 

var p = new Person('shijun'); 
var c = new Chinese("liyatang", "China"); 

console.log(p); // Person {name: "shijun", getName: function} 
console.log(c); // Chinese {name: "liyatang", nation: "China", constructor: function, getNation: function, getName: function} 


console.log(p.constructor); // function Person(name){} 
console.log(c.constructor); // function Chinese(){} 

console.log(c instanceof Chinese); // true 
console.log(c instanceof Person); // true 

以上关于JavaScript三种继承方式及其优缺点的探讨即为全部内容,希望能为大家提供参考,也希望大家多多支持本站。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章