zoukankan      html  css  js  c++  java
  • JS对象进阶实现继承的3种形式

    上一篇介绍了创建对象的5种模式,本篇介绍对象实现继承的3种形式。继承简单说就是在原有对象基础上稍作改动,得到一个新的对象,这个新对象可以拥有原对象的属性和方法。JS实现继承的3种方式:类式继承、class继承和拷贝继承。

    JS这门语言和其他面向对象的语言不同,它并不支持类和类继承特性,只能通过其他方法定义并关联多个相似对象。虽然ES6增加了class关键字,但只是构造函数的语法糖而已,并不是意味着JS中是有类的。

    类式继承

    类式继承本质是通过构造函数实例化对象,然后用原型链把实例对象关联起来。

    原型链继承

    原型链继承本质是通过超类型的实例重写子类型的原型对象。

    // 超类
    function Super(){
      this.name = 'li'
    }
    Super.prototype.sayName = function(){
      return this.name
    }
    
    // 子类
    function Sub(){}
    // Sub继承Super
    Sub.prototype = new Super()
    Sub.prototype.constructor = Sub
    
    var s1 = new Sub()
    console.log(s1.sayName()) // 'li'
    

    原型链继承有两个问题:第一是如果包含引用类型值会被所有实例共享;第二是在创建子类型实例时不能向超类型传递参数,因为会影响所有对象实例。

    function Super() {
      this.hobbies=['编程']
    }
    
    Super.prototype.sayHobby=function() {
      return this.hobbies
    }
    
    function Sub() {}
    
    Sub.prototype=new Super() 
    Sub.prototype.constructor=Sub;
    
    var s1=new Sub();
    var s2=new Sub();
    s1.hobbies.push('音乐')
    
    console.log(s1.sayHobby()) // ['编程','音乐']
    console.log(s2.sayHobby()) // ['编程','音乐']
    

    借用构造函数继承

    借用构造函数继承(constructor stealing)也叫伪类继承或经典继承。它的思想是在子类型构造函数内部调用超类型构造函数,并通过apply()或call()方法使子类型实例继承超类型的属性和方法。

    function Super() {
      this.hobbies=['编程']
    }
    
    function Sub() {
      Super.call(this)
    }
    
    var s1=new Sub();
    var s2=new Sub();
    s1.hobbies.push('音乐')
    
    console.log(s1.hobbies) // ['编程','音乐']
    console.log(s2.hobbies) // ['编程']
    

    使用这种方式不仅可以解决引用类型值共享的问题,而且可以在子类型构造函数中向超类型构造函数传递参数。

    function Super(hobbies) {
      this.hobbies=hobbies
    }
    
    function Sub() {
      Super.call(this,['编程'])
      this.school='Tsinghua'
    }
    
    var s1=new Sub();
    console.log(s1.hobbies) // ['编程']
    console.log(s1.school) // 'Tsinghua'
    

    但是使用这种方式,方法和属性都要在构造函数中定义,导致函数无法复用。

    组合继承

    组合继承(combination inheritance)也叫伪经典继承。它是原型链继承和借用构造函数继承的组合,取两者的优势。组合继承的思路是使用原型链实现对原型属性和方法的继承,使用构造函数实现对超类型实例属性的继承。这样不仅实现了函数的复用,而且保证了每个实例都有自己的属性。

    function Super(name) {
      this.name=name;
      this.school='Tsinghua';
      this.hobbies=['编程','音乐'];
    }
    
    Super.prototype.sayName=function() {
      return this.name;
    }
    
    function Sub(name, age) {
      // 继承超类属性
      Super.call(this, name); 
      // 定义子类属性
      this.age=age;
    }
    
    // 继承超类方法
    Sub.prototype=new Super();
    Sub.prototype.constructor=Sub;
    // 定义子类方法
    Sub.prototype.sayAge=function() {
      return this.age;
    }
    
    var p1=new Sub('li', 10);
    var p2=new Sub('wang', 20);
    
    console.log(p1.school, p1.sayName(), p1.sayAge()) // 'Tsinghua' 'li' 10
    console.log(p2.school, p2.sayName(), p2.sayAge()) // 'Tsinghua' 'wang' 20
    
    p1.hobbies.push('跑步');
    console.log(p1.hobbies, p2.hobbies); // ["编程", "音乐", "跑步"]  ["编程", "音乐"]
    

    组合模式似乎很完美,但是它也有一个问题,那就是它会调用两次超类型的构造函数。一次是在子类构造函数内部,一次是在创建子类型原型的时候。每次调用子类型都会得到超类型的属性,但得不到超类型的方法,只有两次调用配合才能继承超类型的方法。

    function Super(name) {
      this.name=name;
      this.school='Tsinghua';
      this.hobbies=['编程','音乐'];
    }
    
    Super.prototype.sayName=function() {
      return this.name;
    }
    
    function Sub(name, age) {
      // 第二次调用 Sub.prototype又得到了name、school和hobbies属性,并覆盖上次得到的值
      Super.call(this, name); 
      // 定义子类属性
      this.age=age;
    }
    
    // 第一次调用 Sub.prototype得到了name、school和hobbies属性
    Sub.prototype=new Super();
    Sub.prototype.constructor=Sub;
    Sub.prototype.sayAge=function() {
      return this.age;
    }
    

    寄生组合继承

    寄生组合继承解决了组合继承调用两次的问题,寄生组合继承和组合继承类似,它是寄生式继承和构造函数继承的组合。它的思路是通过构造函数实现对实例属性的继承,通过Object.create()方法实现对原型属性和方法的继承。

    function Super(name) {
      this.name=name;
      this.school='Tsinghua';
      this.hobbies=['编程', '音乐'];
    }
    
    Super.prototype.sayName=function() {
      return this.name;
    }
    
    function Sub(name, age) {
      Super.call(this, name);
      this.age=age;
    }
    
    Sub.prototype=Object.create(Super.prototype);
    Sub.prototype.constructor=Sub;
    Sub.prototype.sayAge=function() {
      return this.age;
    }
    

    寄生组合继承是认可度最高的继承方式。

    ES6 class

    第二种继承方式是使用ES6中的class,它思路上和类式继承一样,只不过隐藏了很多类式继承的细节。如果使用ES6的class改写上面的示例,代码会精简很多。

    class Super {
      constructor(name) {
        this.name = name;
        this.school = 'Tsinghua';
        this.hobbies = ['编程','音乐'];
      }
      sayName() {
        return this.name;
      }
    }
    
    class Sub extends Super {
      constructor(name, age) {
        super(name);
        this.age = age;
      }
      sayAge() {
        return this.age
      }
    }
    
    var p1=new Sub('li', 10);
    var p2=new Sub('wang', 20);
    
    console.log(p1.school, p1.sayName(), p1.sayAge()) // 'Tsinghua' 'li' 10
    console.log(p2.school, p2.sayName(), p2.sayAge()) // 'Tsinghua' 'wang' 20
    
    p1.hobbies.push('跑步');
    console.log(p1.hobbies, p2.hobbies); // ["编程", "音乐", "跑步"]  ["编程", "音乐"]
    

    拷贝继承

    拷贝继承不需要改变原型链,它的思路是通过对象深拷贝将超类型的属性和方法复制给子类型。拷贝继承解决了引用类型值被实例共享的问题,所以可以不适用构造函数实现对象的继承。JQuery使用的就是拷贝继承。

    关于深拷贝和浅拷贝移步此文

    // 深拷贝函数
    function extend(obj, cloneObj) {
      var isObj = obj instanceof Object;
      if(!isObj) {return false;}
      var cloneObj = cloneObj || {};
    
      for (var i in obj) {
        if (typeof obj[i] === 'object') {
          cloneObj[i] = (obj[i] instanceof Array) ? [] : {};
          arguments.callee(obj[i], cloneObj[i]);
        } else {
          cloneObj[i] = obj[i];
        }
      }
      return cloneObj;
    }
    
    var Super = {
      init: function(value){
        this.value = value
      },
      sayName: function(){
        return this.name
      },
      hobbies: ['编程'],
        school: 'Tsinghua'
    }
    
    var s1 = extend(Super);
    var s2 = extend(Super);
    s1.hobbies.push('音乐');
    
    console.log(s1.hobbies,s2.hobbies) // ["编程", "音乐"] ["编程"]
    

    构造函数的拷贝组合继承

    如果构造函数和拷贝继承组合使用,则可以拷贝继承超类型的引用类型值和方法,利用构造函数定义子类型的属性值。

    // 深拷贝函数
    function extend(obj, cloneObj) {
      var isObj = obj instanceof Object;
      if(!isObj) {return false;}
      var cloneObj = cloneObj || {};
    
      for (var i in obj) {
        if (typeof obj[i] === 'object') {
          cloneObj[i] = (obj[i] instanceof Array) ? [] : {};
          arguments.callee(obj[i], cloneObj[i]);
        } else {
          cloneObj[i] = obj[i];
        }
      }
      return cloneObj;
    }
    
    function Super(name){
      this.name = name;
      this.school = 'Tsinghua';
      this.hobbies = ['编程','音乐'];
    }
    
    Super.prototype.sayName = function() {
      return this.name
    }
    
    function Sub(name,age){
      Super.call(this,name);
      this.age = age;
    }
    
    Sub.prototype = extend(Super.prototype);
    
    var p1=new Sub('li', 10);
    var p2=new Sub('wang', 20);
    
    console.log(p1.school, p1.sayName()) // 'Tsinghua' 'li'
    console.log(p2.school, p2.sayName()) // 'Tsinghua' 'wang'
    
    p1.hobbies.push('跑步');
    console.log(p1.hobbies, p2.hobbies); // ["编程", "音乐", "跑步"]  ["编程", "音乐"]
    优秀文章首发于聚享小站,欢迎关注!
  • 相关阅读:
    数据库是什么以及用来干嘛
    10.3
    10.2
    12.7
    12.5
    12.4
    12.3
    12.2
    12.1JOptionPane
    11.30eclipse常用快捷键
  • 原文地址:https://www.cnblogs.com/yesyes/p/15352087.html
Copyright © 2011-2022 走看看