JS基于原型链的继承复用
由上文【JS原型链】我们了解原型链自身的特性,正因这些特性,让我们得以实现原型链上的继承。
一、原型继承
// 废话少说,上代码,搞一个构造函数,封装公共方法和属性(这是老子)
function Person() {
this.eyes = '两只眼睛';
this.eat = function () {
return '会吃';
}
this.play = function () {
return '会玩';
}
}
// 再搞一个构造函数,封装个性化方法和属性(这是儿子)
function Man() {
this.skill = '挣钱养家';
this.characterFn = function(){
return '逗比一个';
}
}
// 重写儿子 prototype 属性,继承老子的各种功能
Man.prototype = new Person();
// 使用 new 关键字实例化
var boy1 = new Man();
// 调用父类方法和属性如下:
boy1.eat() // 返回 "会吃"
boy1.play() // 返回 "会玩"
boy1.eyes // 返回 "两只眼睛"
// 综上可得,实例的子类继承了父类的所有方法和属性
// 调用子类自己独有的方法和属性如下:
boy1.skill // 返回 "挣钱养家"
boy1.characterFn() // 返回 "逗比一个"
// 再次实例化一个子类,同样也继承了上述的所有方法和属性
var boy2 = new Man();
// 那么,若修改了boy2 的属性?
boy2.eyes = '三眼娃';
// 取值如下
boy2.eyes // 返回 "三眼娃"
boy1.eyes // 返回 "两只眼睛"
综上所示:通过修改子类的 prototype 属性,继承了父类所有的属性和方法,实例化对象属性的修改,不会影响其他实例话对象,(实际上:实例化返回的就是一个对象,上面所展示的赋值并没有修改原型上的数据,只是在当前实例对象上新增了一个eyes属性而已,在原型链查找属性的时候的时候,是就近的原则,先查到链当前实例对象上有该属性,那就直接返回该属性的值,若没有该属性,就会顺着原型链向上查找),基本类型的复制是不会影响其他实例对象的,但是问题就处在“引用类型上” 引用类型的值,一旦修改,所有的子类都跟着修改 。
// 废话少说,上代码,搞一个构造函数,封装公共方法和属性(这是老子)
function Person() {
this.eyes = '两只眼睛';
this.status = ['开心', '快乐']
this.eat = function () {
return '会吃';
}
this.play = function () {
return '会玩';
}
}
// 再搞一个构造函数,封装个性化方法和属性(这是儿子)
function Man() {
this.skill = '挣钱养家';
this.characterFn = function(){
return '逗比一个';
}
}
// 重写儿子 prototype 属性,继承老子的各种功能
Man.prototype = new Person();
// 使用 new 关键字实例化(实例化两个对象)
var boy1 = new Man();
var boy2 = new Man();
// 修改boy1的实例对象的值
boy1.status.push('闹心');
// 取值如下
boy1.status // 返回 ['开心', '快乐','闹心']
boy2.status // 返回 ['开心', '快乐','闹心']
// 尴尬了一批啊
如上所示:问题来了,我明明只修改了boy1的status值,status2的值也跟着变了,直接导致了所有继承了这个父类的实例,值都跟着变了,我们发现了原型继承的缺点:引用类型的值,一旦修改就会影响全局,着实有点坑。秉承有坑填坑的原则就有了如下的构造函数继承。
二、构造函数继承
其原理是:在子类的构造函数中,通过 apply ( ) 或 call ( )的形式,调用父类构造函数,以实现继承。
// 废话少说,上代码,搞一个构造函数,封装公共方法和属性(这是老子)
function Person() {
this.eyes = '两只眼睛';
this.status = ['开心', '快乐']
this.eat = function () {
return '会吃';
}
this.play = function () {
return '会玩';
}
}
// 再搞一个构造函数,封装个性化方法和属性(这是儿子)
function Man() {
this.skill = '挣钱养家';
Person.call(this); // 关键是这一步,改边了父类的this指向,让它指向子类的this
}
// 使用 new 关键字实例化
var boy1 = new Man();
var boy2 = new Man();
// 修改boy1的实例对象的值
boy1.status.push('闹心');
// 取值如下
boy1.status // 返回 ['开心', '快乐','闹心']
boy2.status // 返回 ['开心', '快乐']
这种构造函数的继承确实解决了,原型继承中“引用类型值容易误修改” 的问题,但是构造函数继承也有其自身的不足。
缺点一:每个实例都拷贝一份父类的构造函,每一份都要占用内存的,方法过多的时候,就会出现性能问题
法都作为了实例自己的方法,当需求改变,方法有变动时候,之前的实例他们的该方法都不能及时作出更新。新的实例才能访问到新方法。
三、组合继承
既然两种方法各有优劣,我们又都是追求完美的少年,很自然就会想到,提取各自的优势,组合到一起使用,这样就能实现完美继承类,真TM机智。
// 废话少说,上代码,父类改造如下,封装公共方法和属性(这是还是老子)
function Person() {
this.eyes = '两只眼睛';
this.status = ['开心', '快乐']
}
Person. prototype.eat = function () {
return '会吃';
}
Person. prototype.play = function () {
return '会玩';
}
// 搞一个子类构造函数(这还是儿子)
function Man() {
this.skill = '挣钱养家';
this.characterFn = function(){
return '逗比一个';
}
Person.call(this); // 让父类构造函数里的属性拷贝的一份给子类
}
// 重写儿子 prototype 属性,继承父类的挂在 prototype 上的各种的方法(这是共用,并非拷贝)
Man.prototype = new Person(); // 这一步实际上也改变了子类的构造函数,这使得子类自己独有的方法和属性没有了,所以要执行下面的操作
Man.prototype.constructor = Man // 将子类 Man 原型对象上的构造函数 constructor 重新指向 Man 自己(把自己的方法和属性找回来)
// 使用 new 关键字实例化(实例化两个对象)
var boy1 = new Man();
var boy2 = new Man();
// 修改boy1的实例对象的值
boy1.status.push('闹心');
// 取值如下
boy1.status // 返回 ['开心', '快乐','闹心']
boy2.status // 返回 ['开心', '快乐']
// boy1 调用父类方法和属性如下:
boy1.eat() // 返回 "会吃"
boy1.play() // 返回 "会玩"
boy1.eyes // 返回 "两只眼睛"
// boy2 调用父类方法和属性如下:
boy2.eat() // 返回 "会吃"
boy2.play() // 返回 "会玩"
boy2.eyes // 返回 "两只眼睛"
三、优劣总结
| 名称 | 优点 | 缺点 |
|---|---|---|
| 原型继承 | 完全继承复用父类的属性和方法 | 引用类型的值变动,污染其他实例 |
| 构造函数继承 | 每个实例单独拷贝父类方法 | 内村占用,方法更新不及时 |
| 组合继承 | 完美继承父类的方法和属性 | 操作有点骚 |

浙公网安备 33010602011771号