原生JavaScript实现继承方式及其优缺点详解
本文详细解析JavaScript中原生实现的五种继承方式,包括原型继承、构造函数继承、组合式继承及寄生式组合继承。通过代码示例对比各方式的优缺点,重点阐述如何解决属性共享、参数传递及原型链问题,并指出寄生式组合继承为最常用方案。
前言
近期在复习JavaScript的基础知识,为接下来的开发工作做准备,因此整理并记录相关学习心得。
本文主要探讨JavaScript中原生实现的继承机制。
直接通过代码示例进行说明。
首先定义父类代码。
此处创建一个名为Person的类作为父类,其构造函数接收name和age两个参数。
随后在Person的原型上添加一个sayHi方法。
//父类
function Person (name, age) {
this.name = name || 'no name';
this.age = age || 0;
}
Person.prototype.sayHi = function () {
console.log('Hi, I\'m ' + this.name + ' and i\'m ' + this.age + ' years old!');
}
var p = new Person('A',20);
p.sayHi();//Hi, I'm A and i'm 20 years old!
原型继承
//原型继承
function Teacher(){
}
Teacher.prototype=new Person('B',22);
Teacher.prototype.constructor=Teacher;
var t = new Teacher();
t.sayHi();//Hi, I'm B and i'm 22 years old!
console.log(t instanceof Person);//true
console.log(t instanceof Teacher);//true
优点
从上述代码可见,Teacher的实例继承了Person的属性和方法。实例对象既是Person的实例,也是Teacher的实例。此外,这种继承方式的实现逻辑非常简洁。
缺点
观察代码可以发现,Teacher类的name和age属性被固定为B和22,这意味着无法灵活地向父类构造函数传递参数。此外,一个子类只能指定一个原型,因此不支持多继承。参考下方代码:
var t1 = new Teacher();
var t2 = new Teacher();
Teacher.prototype.name = "C";
t1.sayHi();//Hi, I'm C and i'm 22 years old!
t2.sayHi();//Hi, I'm C and i'm 22 years old!
上述代码显示,当原型中的属性或方法被修改时,所有子类实例的属性及方法均会随之改变。这揭示了原型继承的另一个缺陷:所有子类共享同一个原型对象。
此处涉及原型概念,此前曾撰写过相关随笔,但理解尚显模糊,后续将重新撰写关于原型的文章(完成后会附上链接)。
构造函数继承
//构造函数继承
function Teacher (name, age) {
Person.call(this, name, age);
}
var t1 = new Teacher('B', 22);
var t2 = new Teacher('C', 30);
console.log(t1.name);//B
console.log(t2.name);//C
console.log(t1 instanceof Person);//false
console.log(t1 instanceof Teacher);//true
t1.sayHi();//TypeError: t1.sayHi is not a function
t2.sayHi();//TypeError: t1.sayHi is not a function
优点
与原型继承相比,构造函数继承解决了子类实例共享统一原型的问题,支持向父类构造函数传递参数。此外,可以在子类构造函数中调用多个父类构造函数,实现所谓的多继承(注:此处多继承指通过call或apply调用父类构造函数以获取属性和方法,但由于JavaScript函数对象仅存在一个prototype,因此无法通过原型链体现多继承)。
缺点
从上述代码可见,创建的实例仅为子类的实例,而非父类的实例,无法直观体现继承关系。此外,该方式无法继承父类原型上的属性和方法。
组合式继承
//组合式继承
function Teacher (name, age) {
Person.call(this, name, age);
}
Teacher.prototype = new Person();
Teacher.prototype.constructor = Teacher;
var t1 = new Teacher('B', 22);
var t2 = new Teacher('C', 30);
Teacher.prototype.name = "D";
console.log(t1.name);//B
console.log(t2.name);//C
t1.sayHi();//Hi, I'm B and i'm 22 years old!
t2.sayHi();//Hi, I'm C and i'm 30 years old!
console.log(t1 instanceof Person);//true
console.log(t1 instanceof Teacher);//true
组合式继承结合了原型继承和构造函数继承的优点,解决了前两种方式存在的部分缺陷。然而,每次创建子类实例时都会创建一个父类实例,尽管这些父类实例内存地址不同,但属性和方法完全一致。因此,通过下文所述的寄生式组合继承方式对其进行完善,以避免不必要的实例构造。
寄生式组合继承
//寄生式组合继承
function Teacher (name, age) {
Person.call(this, name, age);
}
Teacher.prototype = Object.create(Person.prototype);
Teacher.prototype.constructor = Teacher;
var t1 = new Teacher('B', 22);
var t2 = new Teacher('C', 30);
Teacher.prototype.name = "D";
console.log(t1.name);//B
console.log(t2.name);//C
t1.sayHi();//Hi, I'm B and i'm 22 years old!
t2.sayHi();//Hi, I'm C and i'm 30 years old!
console.log(t1 instanceof Person);//true
console.log(t1 instanceof Teacher);//true
上述方式解决了每次创建子类实例时都创建父类实例的问题,这也是JavaScript中最常用的继承方式。若使用Babel将ES6中的class继承转换为ES5代码,会发现其底层实现即为寄生式组合继承。
总结
至此,关于原生JavaScript实现继承方式及其优缺点的介绍到此结束。如需了解更多相关内容,请搜索本站历史文章或继续浏览相关文章,感谢大家的支持!
JS语法错误会阻止加载吗?区分SyntaxError与资源加载失败
2026-11-05 16:08:46
JavaScript常见SyntaxError语法错误排查与修复方法
2026-11-05 13:58:44
JS中slice、substring和substr截取字符串的区别
2026-11-05 13:43:47
JS 转 TS 手动迁移最佳实践:从配置 tsconfig 到消除 any 类型
2026-11-05 11:53:32
为什么JS数组push后得到的是数字而不是数组?
2026-11-05 09:59:18
