欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

javascript模拟面向对象程序设计编程(二)

程序员文章站 2022-06-15 13:46:10
...
  1. 重用与继承

    面向对象代码的意义很大程度上在于通过设计通用父类到更具体的子类来实现功能重用。
    JavaScript模拟实现继承:还是通过版本方式说明

    • ES3 通过函数构造器模拟实现继承

      //子类
      function Teacher(firstName, lastName, subject, yearsExp, gender, birthDate, job) {
      // 增加属性
      this.job = job || 'teacher';
      this.subject = subject || 'English Literature';
      this.yearsExp = yearsExp || 5;
      Person.apply(this, [firstName, lastName, birthDate, gender])
      }
      // - Teacher.prototype = Person.prototype;  因为这样直接继承,添加原型方法会影响父类原型, 有了以下代码 
      // - Teacher.prototype = new Person();  这样就防止修改父对象的原型,完全隔离原型,目的达到了。但是使用new的 
      // 形式会让子类的prototype.constructor指向父类的实体,所以有了修改{1}。如此以为完美了,但是使用new Person()
      // 时会向父类,传递的是空参数,但是Person的构造函数默认参数是有值的,可能会在构造函数中对传入的参数进行各
      // 种处理,传递空参数很有可能导致报错(当然本示例中的Person不会)。于是我们再次修改Teacher的代码如下所示
      function F() {}
      F.prototype = Person.prototype;
      Teacher.prototype = new F();
      //Teacher继承Person的静态属性和方法
      for (var p in Person) {
      if (Person.hasOwnProperty(p)) {
        Teacher[p] = Person[p];
      }
      }
      Teacher.prototype.constructor = Teacher; // {1}
      Teacher.prototype = Person.prototype;
      // 覆盖toString方法
      Teacher.prototype.toString = function() {
      return this.firstName + ' ' + this.lastName + ' ' + ' is a ' + this.getAge() + ' year-old ' + this.gender + ' ' + this.subject + ' ' + this.job + '.';
      }
      
      var bob = new Person('Bob', 'Sabatelli', '1969-06-07');
      var patty = new Teacher('Patricia', 'Hannon', 'chemistry', 20, 'female');
    • ES5 通过对象字面量模拟实现继承

      var Person = {
      firstName: 'John',
      lastName: 'Connolly',
      birthDate: new Date('1964-09-05'),
      gender: 'male',
      getAge: function() {
        var today = new Date();
        var diff = today.getTime() - this.birthDate.getTime();
        var year = 1000 * 60 * 60 * 24 * 365.25;
        return Math.floor(diff / year);
      },
      toString: function() {
        return this.firstName + ' ' + this.lastName + ' ' + ' is a ' + this.getAge() + ' year-old ' + this.gender;
      },
      // 扩展Person包含工厂方法
      extends: function(config) {
        var tmp = Object.create(this);
        for (var key in config) {
          if (config.hasOwnProperty(key)) {
            tmp[key] = config[key];
          }
        }
        return tmp;
      }
      };
      var Teacher = Person.extends({
      // 增加属性
      job: 'teacher',
      subject: 'English Literature',
      yearsExp: 5,
      // 覆盖toString方法
      toString: function() {
        return this.firstName + ' ' + this.lastName + ' ' + ' is a ' + this.getAge() + ' year-old ' + this.gender + ' ' + this.subject + ' ' + this.job + '.';
      }
      });
      
      var bob = Person.extends({
      firstName: 'Bob',
      lastName: 'Sabatelli',
      birthDate: new Date('1969-06-07')
      });
      
      var patty = Teacher.extends({
      firstName: 'Patricia',
      lastName: 'Hannon',
      subject: 'chemistry',
      yearsExp: 20,
      gender: 'female'
      });
      
      console.log('Is bob an instance of Person? ' + Person.isPrototypeOf(bob)); // true
      console.log('Is bob an instance of Teacher? ' + Teacher.isPrototypeOf(bob)); // false
      console.log('Is patty an instance of Teacher? ' + Teacher.isPrototypeOf(patty)); // true
      console.log('Is patty an instance of Person? ' + Person.isPrototypeOf(patty)); // true

    Object.create可以轻松实现建立两个对象之间的联系。只需要对这种方法进行扩展,就可以创造出我们想要的各种继承层次。注意:Object.create不允许多继承,所以只能实现单层继承层级。
    使用Object.create创建子类的时候,他们是将自身的某些行为委托给了原型链种的上层环节。Object.create所构造的继承更倾向于采用自底向上的方式,而非典型的自顶向下式的面向对象。
    继==承很简单:使用Object.create就行了==。使用Object.create在父类和子类之间建立联系。子类可以新增加、删除或是覆盖功能。
    instanceof 可以用用来确定一个对象是否是某种类型的实例。instanceof操作符在这里是没有用的。它依赖显性的prototype属性跟踪对象与类型之间的关系。简单的说,instanceof的右操作数必须是一个函数(绝大多数情况下是函数构造器)。 左操作数必须通过函数构造器创建(尽管右操作数未必是函数构造器)。那么怎样才知道一个对象是否是某种类型的实例呢? 答案是isPrototypeOf()

  // ES6
  class Teacher extends Person {
    constructor(firstName, lastName, subject, yearsExp, gender, birthDate, job) {
        super(firstName, lastName, birthDate, gender); // 等同于parent.constructor(x, y)
       // 增加属性
        this.job = job || 'teacher';
        this.subject = subject || 'English Literature';
        this.yearsExp = yearsExp || 5;
      }
      // 覆盖toString方法
    toString() {
      return this.firstName + ' ' + this.lastName + ' ' + ' is a ' + this.getAge() + ' year-old ' + this.gender + ' ' + this.subject + ' ' + this.job + '.';
    }
  }
  var bob = new Person('Bob', 'Sabatelli', '1969-06-07');
  var patty = new Teacher('Patricia', 'Hannon', 'chemistry', 20, 'female');

如上代码所示,

ES3继承方法代码:

  function extends(Child, Father) {
      //继承父类prototype中定义的实例属性和方法
      function F() {}
      F.prototype = Father.prototype;
      Child.prototype = new F();
      Child.prototype.constructor = Child;

      //继承父类的静态属性和方法
      for (var p in Father) {
          if (Father.hasOwnProperty(p)) {
              Child[p] = Father[p];
          }
      }
  }

ES5 继承,由于Object.create可以简单实现对象的原型链接。而且ES5中新增了Object.keys()(==适用于字面量对象和new的模拟类实例==)方法用以获取对象自身的属性数组,我们可以用该方法简化继承父类静态属性和方法的过程。由于ES5的模拟类使用字面量实现,所以可以简化继承代码如下:

  function extends(Child, Father) {
      //继承父类prototype中定义的实例属性和方法
      Child.prototype = Object.create(Father.prototype);;
      Child.prototype.constructor = Child;

      //继承父类的静态属性和方法
      Object.keys(Father).forEach(function(key) {
          Child[key] = Father[key];
      });
  }

javascript模拟实现继承的总结

  1. javasctipt 模拟实现继承关键在于理解关键字new、原型prototype
  2. javascript 模拟实现继承两种方式:字面量和构造函数。 字面量方式与构造函数全部属性和方法绑定到this相同。
  3. javascript 模拟实现继承字面量方式继承层次越深,会造成各自维持实例上下文过多。由此带来的是内存开销会成倍增加。(这点在==成员可见性==中体现。)