本文详细解析JavaScript ES5环境下的五种继承实现方式,包括对象冒充、原型链、组合、原型式及寄生式继承。通过对比ES6的extends语法,深入剖析各方式在构造函数执行次数、原型方法共享及参数传递上的差异,重点讲解寄生式继承如何实现近乎完美的ES5继承效果,并探讨其在面试与实际开发中的应用场景。
ES6与ES5继承概述
随着ES6标准的普及,ES5的继承机制在实际开发中已较少使用,但在技术面试中仍常被考察。
首先回顾ES6的继承实现。
class Father{
constructor(a){
console.log(a);
}
play(){
console.log("aaa");
}
static run(){
console.log("static");
}
}
class Son extends Father{
constructor(){
super();
}
}
var s=new Son();
s.play();
Father.run()
Son.run();
在ES6规范中,仅需使用extends关键字和super函数即可轻松继承父类的实例属性、方法以及静态成员。
相比之下,ES5标准中并不存在这些便捷关键字。
接下来探讨ES5环境下的继承实现。
ES5中主要存在五种继承模式:
- 第一种是对象冒充继承。
- 第二种是原型链继承。
- 第三种是组合继承。
- 第四种是原型式继承。
- 第五种是寄生式继承,该方式被视为较完美的方案。
对象冒充继承
对象冒充继承的核心在于利用call或apply方法改变this指向,从而执行父类的构造函数。
function Father(_r){
this.r=_r;
console.log("aa");
console.log(this.r);
}
Father.a=3;
Father.run=function(){
console.log(Box.a);
}
function Son(){
Father.call(this,3);//改变this的指向,执行父类构造函数并传参到父类
}
var b=new Son();//"aa",3
b.run();//TypeError
这种方式的局限性在于,它仅能继承父类的构造函数逻辑,无法获取原型链上定义的方法。
其缺点明确:子类无法访问父类原型链上的方法,仅能继承构造函数部分。
原型链继承
第二种模式为原型链继承。
function Father(){
console.log("aa");
}
Father.prototype.b=10;
Father.prototype.play=function(){
console.log(this.b);
}
Son.prototype=new Father();
function Son(){
}
var b=new Son();
b.play();//10
其实现原理是将父类的实例对象赋值给子类的原型属性,以此建立继承关系。
该方式的主要缺陷在于会覆盖子类原有的属性和方法,导致只能执行父类的属性和方法,且无法在继承过程中执行父类的构造函数。
组合继承
第三种模式称为组合继承。
鉴于前两种继承方式(对象冒充与原型链)各有优劣,将二者结合使用的方案被称为组合继承。
function Father(_r){
this.r=_r;
console.log("aa");
}
function Son(_r){
Father.call(this,_r);//冒充,改变父类的this指向子类
}
Son.prototype=new Father(3);//原型链继承
var c=new Son(10);
具体实现上,它利用原型链来继承父类的属性和方法,同时通过对象冒充来继承父类的构造函数。
尽管看似完善,但这并非理想的继承方案。
其核心缺点在于会覆盖子类原有属性和方法,因为原型链继承需提前实例化父类,导致父类构造函数被执行一次;随后子类实例化时再次执行父类构造函数,总计执行两次。
适用场景主要限于子类原本无属性和方法,且父类构造函数内部无具体逻辑的情况。
原型式继承
第四种模式为原型式继承。
为解决构造函数被执行两次的问题,原型式继承引入中介对象,使得在继承过程中不直接执行父类构造函数。
function Father(_a){
this.a=_a
}
Father.prototype.play=function(){
console.log("aaa");
}
function Agent(){
}
Agent.prototype=Father.prototype;
function Son(){
}
Son.prototype=new Agent();
var o=new Son();
o.play();//aaa
该方式利用一个中介类(如Agent),将父类的原型复制给中介类,随后进行实例化继承,从而避免执行父类构造函数。
尽管解决了构造函数执行两次的问题,但该方法的副作用是构造函数一次也不会被执行。
寄生式继承
第五种模式为寄生式继承,常被视为ES5下的完美继承方案。
该方案封装了一个extend函数,接收父类和子类作为两个参数。
function extend(subClass, supClass) {
function Agent() {}
Agent.prototype = supClass.prototype;
var o = subClass.prototype;
subClass.prototype = new Agent();
if (Object.assign) {
Object.assign(subClass.prototype, o);
} else {
if (Object.getOwnPropertyNames) {
var names = Object.getOwnPropertyNames(o);
for (var i = 0; i < names.length; i++) {
var desc = Object.getOwnPropertyDescriptor(names[i]);
Object.defineProperty(subClass.prototype, names[i], desc);
}
} else {
for (var prop in o) {
subClass.prototype[prop] = o[prop];
}
}
}
subClass.prototype.constructor = subClass; //防止子类的构造函数被覆盖
if (supClass.prototype.constructor === Object) {
supClass.prototype.constructor = supClass; //防止父类类的构造函数被覆盖
}
// 存储父类,方便继承构造函数调用
subClass.prototype.superClass = supClass;
}
//调用
function Father(_r) {
this.r = _r;
console.log("Father");
}
Father.prototype.play = function () {
console.log("play game");
};
function Ball(_r) {
this.superClass.call(this, _r);
}
var s = new Son(10);//Father
s.play();//play game
在extend函数内部,由于Object.assign、Object.getOwnPropertyNames、Object.getOwnPropertyDescriptor及Object.defineProperty等方法存在浏览器兼容性问题,因此代码中加入了相应的兼容性判断逻辑。
该方法集合了前四种继承方式的优点,实现了ES5环境下近乎完美的继承效果。
总结来看。
与ES6的继承相比,ES5的继承实现过程繁琐复杂,且在实际工程项目中已极少使用。
然而,在面试场景中,面试官可能会考察相关知识,因此多掌握一些理论总无坏处。
以上即为详解JavaScript ES5继承的详细内容,更多关于JavaScript ES5继承的资料请关注本站其它相关文章!