JavaScript几种继承方式的总结
1、原型链继承
直接将子类型的原型指向父类型的实例,即“子类型.prototype = new 父类型();”,实现方法如下:
//父类构造函数
function father(name) {
this.name = name;
this.colors = ['red'];
}
//父类添加属性
father.prototype.sayName = function() {
console.log(this.name);
}
//子类构造函数(声明)
function son(name,age) {
this.age = age;
}
// 实现继承的关键步骤,子类型的原型指向父类型的实例
son.prototype = new father();
// 在此处给子类型添加方法,一定要在实现继承之后,否则会在将指针指向父类型的实例,那么方法就为空(被父类型给覆盖了)
son.prototype.sayAge = function() {
console.log(this.age);
}
var tmp1 = new son('try',18);
console.log(tmp1.colors); //'red'
tmp1.sayAge();
var tmp2 = new son();
tmp2.colors.unshift('yellow');
console.log(tmp1.colors); //'yellow','red'
以上代码不难看出,有两个问题
- 原型链方式可以实现所有属性方法共享,但无法做到属性、方法独享
- 在创建子类型的实例时,不能向父类型的构造函数中传递参数。实际上是没有办法在不影响所有对象实例的情况下,给父类型的构造函数传递参数
基于此,实践中很少单独使用原型链。
2、借用构造函数
为了解决上面两个问题,出现了这种方法。在子类型的构造函数内调用父类型的构造函数,函数只不过是在特定环境中执行代码的对象,因此可以通过apply()或call()方法执行构造函数
除了能独享属性、方法外还能在子类构造函数中传递参数,但代码无法复用。
function father(name) {
this.name = name;
this.colors = ['red'];
this.test = function() {
console.log('test success');
}
}
father.prototype.sayName = function() {
console.log(this.name);
}
function son(name,age) {
//就只是加了下面这句
father.apply(this);
this.age = age;
}
var tmp1 = new son('try',18);
console.log(tmp1.colors); //'red'
tmp1.sayName(); //error!
tmp1.test(); //OK
var tmp2 = new son();
tmp2.colors.unshift('yellow');
console.log(tmp1.colors); // 'red'
不难发现,这种方法也有一个超大的问题。子类只能调用父类构造函数里的函数(如上面的test),对于其后面定义在其属性里的函数(如上面的sayName)就无法调用了。因此函数复用就无从谈起了。
3、组合继承
顾名思义,组合,就是将上两种方式组合在一起,从而发挥二者之长的一种继承模式。使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现堆实例属性的继承
分别是 son.prototype = new father();
apply(this);
但混合模式也并非没有缺点,在继承方法的时候实际已经继承了父类型的属性,只不过此时对于引用类型属于共享的;
因此在子类型的构造函数内再次调用父类型的构造函数从而继承了父类型的属性而去覆盖了原型中所继承的属性。
即调用了两次父类构造函数
function father(name) {
this.name = name;
this.colors = ['red'];
this.test = function() {
console.log('test success');
}
}
father.prototype.sayName = function() {
console.log(this.name);
}
function son(name,age) {
father.apply(this); //一次调用
this.age = age;
}
son.prototype = new father(); //两次调用
var tmp1 = new son('try',18);
console.log(tmp1.colors); //'red'
tmp1.sayName(); //error!
tmp1.test(); //OK
var tmp2 = new son();
tmp2.colors.unshift('yellow');
console.log(tmp1.colors); // 'red'
4、原型式继承(寄生和这个差不多 不单独介绍了,但寄生有增强对象的过程 即构造函数指向自己)
和原型链唯一的不一样在于,原型式继承 就是不用实例化父类了,直接实例化一个临时副本实现了相同的原型链继承。(即子类的原型指向父类副本的实例从而实现原型共享)。
原型式继承并没有使用严格意义上的构造函数,而是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
当然,这种方式,包含引用类型值的属性时钟会共享相应的值。
//原型式继承
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var father = {
name: 'try',
colors: ['red']
}
//ECMAScript规范了原型式继承
var son1 = Object.create(father);
son1.name = 'rr';
son1.colors.push('yellow');
console.log(son1.colors);
var son2 = Object.create(father);
console.log(son2.colors);
5、寄生组合式继承
完美的继承方式,重点掌握。寄生+借用构造,解决了之前需要调用两个父类构造函数的问题。
所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
不必为例指定子类型的原型而调用父类型的构造函数,我们所需要的无非是父类型原型的一个副本而已。
本质上,就是使用寄生式继承来的父类型的原型,然后再将结果指定给子类型的原型。
function object(fa) {
//实际上是实行了一次浅复制
function F() {}; //为了得到fa的原型创造出的临时对象
F.prototype = fa; //获得原型
return new F();
}
function inheritPrototype(son, father) {
var prototype = object(father.prototype); //创建对象
prototype.constructor = son; //构造函数指向要创建的 即增强对象
son.prototype = prototype; //原型继承
}
function father(name) {
this.name = name;
this.colors = ['red'];
}
father.prototype.sayName = function() {
console.log(this.name);
}
function son(name,age) {
father.call(this, name);
this.age = age;
}
inheritPrototype(son,father);
var tmp1 = new son('try',19);
tmp1.colors.unshift('yellow');
var tmp2 = new son('tt',10);
console.log(tmp2.colors); //'red'
tmp2.sayName(); //tt
属性继承代码是SuperType.call(this,name);
原型继承代码是inheritPrototype(son,father);
参考《JavaScript高级程序设计第3版》。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现