js 面向对象编程之继承

作者: tww844475003 分类: 前端开发 发布时间: 2021-05-23 12:10

私有属性与私有方法,特权方法,对象公有属性和对象共有方法,构造器,类静态公有属性,类静态公有方法

   var Book = function(id, name, price) {
      // 私有属性
      var num = 1;
      // 私有方法
      function checkId() {
        console.log('checkId');
      }
      // 特权方法
      this.getName = function() {};
      this.getPrice = function() {};
      this.setName = function() {};
      this.setPrice = function() {};
      // 对象公有属性
      this.id = id;
      // 对象公有方法
      this.copy = function() {};
      // 构造器
      this.setName(name);
      this.setPrice(price);
    }

    // 类静态公有属性(对象不能访问)
    Book.isChinese = true;
    // 类静态公有方法(对象不能访问)
    Book.resetTime = function() {
      console.log('resetTime');
    }
    Book.prototype = {
      // 公有属性
      isJsBook: false,
      // 公有方法
      display: function() {
        console.log('display')
      }
    }

    var b = new Book(11, 'Javascript设计模式', 50);
    console.log(b.num); // undefined
    console.log(b.isJsBook); // false
    console.log(b.id); // 11
    console.log(b.isChinese); // undefined
    console.log(b.display()); // display
    
    // 类的静态公有属性isChinese可以通过类的自身访问
    console.log(Book.isChinese); // true
    Book.resetTime(); // resetTime

闭包实现

   var Book = (function() {
      // 静态私有变量
      var num = 0;
      // 静态私有方法
      function checkBook(name) {
        console.log('checkBook');
      }
      // 创建类
      function _book(newId, newName, newPrice) {
        // 私有变量
        var name, price;
        // 私有方法
        function checkId(id) {
          console.log('checkId');
        }
        // 特权方法
        this.getName = function() {};
        this.getPrice = function() {};
        this.setName = function() {};
        this.setPrice = function() {};
        // 公有属性
        this.id = newId;
        // 公有方法
        this.copy = function() {};
        num++;
        if (num > 100)
          throw new Error('我们仅出版100本书.');
        // 构造器
        this.setName(name);
        this.setPrice(price);
      }

      _book.isChinese = true;
      _book.resetTime = function() {
        console.log('resetTime')
      }

      _book.prototype = {
        // 静态公有属性
        isJsBook: false,
        // 静态公有方法
        display: function() {
          console.log('display');
        }
      }

      // 返回类
      return _book;
    })();

    var b = new Book(11, 'Javascript设计模式', 50);
    console.log(b.num); // undefined
    console.log(b.isJsBook); // false
    console.log(b.id); // 11
    console.log(b.isChinese); // undefined
    b.display(); // display
    
    // 类的静态公有属性isChinese可以通过类的自身访问
    console.log(Book.isChinese); // true
    Book.resetTime(); // resetTime

子类的原型对象——类式继承

    function superclass() {
      this.superValue = true;
    }
    // 为父类添加共有方法
    superclass.prototype.getsuperValue = function() {
      return this.superValue;
    }
    // 声明子类
    function subclass() {
      this.subValue = false;
    }
    // 继承父类
    subclass.prototype = new superclass();
    // 为子类添加共有方法
    subclass.prototype.getsubValue = function() {
      return this.subValue;
    }

    var instance = new subclass();
    console.log(instance.getsuperValue()); // ture
    console.log(instance.getsubValue()); // false
    console.log(instance instanceof superclass); // true
    console.log(instance instanceof subclass); // true
    console.log(subclass instanceof superclass); // false
    console.log(subclass.prototype instanceof superclass); // true
    console.log(instance instanceof Object); // true

这种类式继承有两个缺点:其一,由于子类通过其原型prototype对父类实例化,继承了父类。所以说父类中的共有属性只要是引用类型,就会在子类中被所有实例共用,因此一个子类的实例更改子类原型从父类构造函数中继承来的共有属性就会直接影响到其他子类

   function superclass() {
      this.books = ['Jvascript', 'html', 'css'];
    }
    function subclass() {};
    subclass.prototype = new superclass();
    
    var instance1 = new subclass();
    var instance2 = new subclass();
    console.log(instance2.books); // ['Jvascript', 'html', 'css']
    instance1.books.push('设计模式');
    console.log(instance2.books); // ['Jvascript', 'html', 'css', '设计模式']

instance1的一个无意的修改就会无情地伤害了instance2的book属性,这在编程中很容易埋藏陷阱,其二,由于子类实现的继承是靠其原型prototype对父类的实例化实现的,因此在创建父类的时候,是无法向父类传递参数的,因而在实例化父类的时候也无法对父类构造函数内的属性进行初始化。

创建即继承——构造函数继承

// 声明父类
    function superclass(id) {
      // 引用类型共有属性
      this.books = ['Jvascript', 'html', 'css'];
      // 值类型共有属性
      this.id = id;
    }
    // 父类声明原型方法
    superclass.prototype.showBooks = function() {
      console.log(this.books);
    }
    // 声明子类
    function subclass(id) {
      // 继承父类
      superclass.call(this, id);
    }

    // 创建第一个子类的实例
    var instance1 = new subclass(10);
    // 创建第二个子类的实例
    var instance2 = new subclass(11);
    instance1.books.push('设计模式');
    console.log(instance1.books); // ['Jvascript', 'html', 'css', '设计模式']
    console.log(instance1.id); // 10
    console.log(instance2.books); // ['Jvascript', 'html', 'css']
    console.log(instance2.id); // 10
    instance1.showBooks(); // TypeError

superclass(this, id); 这条语名是构造函数式继承的精华, 由于call这个方法可以更改函数的作用环境,因此在子类中,对superclass调用这个方法就是将子类中的变量在父类中执行一遍,由于父类中是给this绑定属性的,因此子类自然也就继承了父类的共有属性,由于这种类型的继承没有涉及原型 prototype,所以父类的原型方法自然不会被子类继承,而如果要想被子类继承就必须要放在构造函数中,这样创建出来的每个实例都会单独拥有一份而不能共用,这样就违背了代码利用的原则。为了综合这两种模式的优点,后来有了组合式继承。

将优点为我所用——组合继承

“组合继承是不是说将这两种继承模式综合到一起呀?那么它又是如何做到的呢?”

“别着急,我们先总结一下之前两种模式的特点,类式继承是通过子类的原型prototype对父类实例化来实现的,构造函数式继承是通过在子类的构造函数作用环境中执行一次父类的构造函数来实现的,所以只要在继承中同时做到这两点即可,看下面的代码。”

// 声明父类
    function superclass(name) {
      // 值类型共有属性
      this.name = name;
      // 引用类型共有属性
      this.books = ['html', 'css', 'Javascript'];
    }
    // 父类原型共有方法
    superclass.prototype.getName = function() {
      console.log(this.name);
    }
    // 声明子类
    function subclass(name, time) {
      // 构造函数式继承父类name属性
      superclass.call(this, name);
      // 子类中新增共有属性
      this.time = time;
    }
    // 类式继承 子类原型继承父类
    subclass.prototype = new superclass();
    // 子类原型方法
    subclass.prototype.getTime = function() {
      console.log(this.time);
    }

    var instance1 = new subclass('js book', 2014);
    instance1.books.push('设计模式');
    console.log(instance1.books); // ['html', 'css', 'Javascript', '设计模式']
    instance1.getName(); // js book
    instance1.getTime(); // 2014
    
    var instance2 = new subclass('css book', 2013);
    console.log(instance2.books); // ['html', 'css', 'Javascript']
    instance2.getName(); // css book
    instance2.getTime(); // 2013

“子类的实例中更改父类继承下来的引用类型属性如books,根本不会影响到其他实例,并且子类实例化过程中又能将参数传递到父类的构造函数中,如name。这种模式真的很强大,所以这应该是继承中最完美的版本吧?”

“还不是,因为我们在使用构造函数继承时执行了一遍父类的构造函数,而在实现子类原型的类式继承时又调用了一遍父类构造函数。因此父类构造函数调用了两遍,所以这还不是最完美的方式。”

洁净的继承者——原型式继承

“2006 年道格拉斯·克罗克福德发表一篇《JavaScript 中原型式继承》的文章,他的观点是,借助原型 prototype 可以根据己有的对象创建一个新的对象,同时不必创建新的自定义对象类型。大师的话理解起来可能很困难,不过我们还是先看一下他实现的代码吧。”

    // 原型是继承
    function inheritObject(o) {
      // 声明一个过渡函数对象
      function F() {};
      // 过渡对象的原型继承父对象
      F.prototype = o;
      // 返回过渡对象的一人实例,该实例的原型继承了父对象
      return new F();
    }

    var book = {
      name: 'js book',
      alikeBook: ['css book', 'html book']
    };

    var newBook = inheritObject(book);
    newBook.name = 'ajax book';
    newBook.alikeBook.push('xml book');

    var otherBook = inheritObject(book);
    otherBook.name = 'flash book';
    otherBook.alikeBook.push('as book');

    console.log(newBook.name); // ajax book
    console.log(newBook.alikeBook); // ['css book', 'html book', 'xml book', 'as book']

    console.log(otherBook.name); // flash book
    console.log(otherBook.alikeBook); // ['css book', 'html book', 'xml book', 'as book']

    console.log(book.name); 

“跟类式继承一样,父类对象book中的值类型的属性被复制,引用类型的属性被共用。
“然而道格拉斯·克罗克福德推广的继承并不只这一种,他在此基础上做了一些增强而推出一种寄生式继承。”

如虎添翼——寄生式继承

    // 原型是继承
    function inheritObject(o) {
      // 声明一个过渡函数对象
      function F() {};
      // 过渡对象的原型继承父对象
      F.prototype = o;
      // 返回过渡对象的一人实例,该实例的原型继承了父对象
      return new F();
    }

    function createBook(obj) {
      // 通过原型继承方式创建对象
      var o = new inheritObject(obj);
      // 拓展新对象
      o.getName = function() {
        console.log(name);
      }

      return o;
    }

    var book = {
      name: 'js book',
      alikeBook: ['css book', 'html book']
    };

    var newBook = createBook(book);
    newBook.name = 'ajax book';
    newBook.alikeBook.push('xml book');

    var otherBook = createBook(book);
    otherBook.name = 'flash book';
    otherBook.alikeBook.push('as book');

    console.log(newBook.name); // ajax book
    console.log(newBook.alikeBook); // ['css book', 'html book', 'xml book', 'as book']

    console.log(otherBook.name); // flash book
    console.log(otherBook.alikeBook); // ['css book', 'html book', 'xml book', 'as book']

    console.log(book.name); //js book
   console.log(book.alikeBook); // ['css book', 'html book', 'xml book', 'as book']

“其实寄生式继承就是对原型继承的第二次封装,并且在这第二次封装过程中对继承的对象进行了拓展,这样新创建的对象不仅仅有父类中的属性和方法而且还添加新的属性和方法。”
“哦,这种类型的继承果如其名,寄生大概指的就是像寄生虫一样寄托于某个对象内部生长。当然寄生式继承这种增强新创建对象的继承思想也是寄托于原型继承模式吧。”
“嗯,是这个道理,而这种思想的作用也是为了寄生组合式继承模式的实现。”

终极继承者——寄生组合式继承

  // 原型式继承
  function inheritObject(o) {
    // 声明一个过渡函数对象
    function F() {};
    // 过渡对象的原型继承父对象
    F.prototype = o;
    // 返回过渡对象的一人实例,该实例的原型继承了父对象
    return new F();
  }

  function inheritPrototype(subclass, superclass) {
    // 复制一份父类的原型副本保存在变量中
    var p = inheritObject(superclass.prototype);
    // 修正因为重写子类原型导致子类的constructor属性被修改
    p.constructor = subclass;
    // 设置子类的原型
    subclass.prototype = p;
  }

  // 定义父类
  function superclass(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
  }
  // 定义父类原型方法
  superclass.prototype.getName = function() {
    console.log(this.name);
  }
  // 定义子类
  function subclass(name, time) {
    // 构造函数式继承
    superclass.call(this, name);
    // 子类新增属性
    this.time = time;
  }
  // 寄生式继承原型
  inheritPrototype(subclass, superclass);
  // 子类新增原型方法
  subclass.prototype.getTime = function() {
    console.log(this.time);
  }
  
  // 创建两个测试方法
  var instance1 = new subclass('js book', 2014);
  var instance2 = new subclass('css boook', 2013);
  instance1.colors.push('block');
  console.log(instance1.colors); // ['red', 'blue', 'green', 'black']
  console.log(instance2.colors); // ['red', 'blue', 'green']
  instance2.getName(); // css book
  instance2.getTime(); // 2013

前端开发那点事
微信公众号搜索“前端开发那点事”

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注