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 // 返回 "两只眼睛"

三、优劣总结

名称 优点 缺点
原型继承 完全继承复用父类的属性和方法 引用类型的值变动,污染其他实例
构造函数继承 每个实例单独拷贝父类方法 内村占用,方法更新不及时
组合继承 完美继承父类的方法和属性 操作有点骚
posted @ 2020-11-04 10:37  习者,必求甚解  阅读(110)  评论(0)    收藏  举报