js创建对象与继承总结

最近再次看起了 《JavaScript高级程序设计》,在此做下笔记,总结一下 创建对象与继承的主要方式。

一、创建对象

虽然Object构造函数或对象字面量都可以用来创建对象,但这些方式都有个明显的缺点:使用同个接口创建很多对象,会产生大量的重复代码。为解决这个问题,人们开始使用工厂模式的一种变体。               ——《JavaScript高级程序设计》

1.工厂模式

这种模式抽象了创建具体对象的过程。缺点:无法解决对象类型识别的问题

function createPerson(name, age, job) {
      let obj = new Object();
      obj.name = name;
      obj.age = age;
      obj.job = job;
      obj.sayName = function() {
        console.log(this.name)
      }
      return obj
    }
    let person = createPerson('张三', 26, 'teacher');

2.构造函数模式

优点:解决了对象类型识别的问题   缺点:还是会有 同样的方法在每个实例上都要创建 没有复用。

function Person(name, age) {
      this.name = name;
      this.age = age;
      this.sayName = function () {
        console.log(this.name)
      }
    }
    let person = new Person('张三', 28)
    person.sayName()
    console.log(person instanceof Person) //true

3.原型模式

每个函数都有一个prototype属性,指向该函数的原型对象,该原型对象有一个constructor属性指向函数, 通过构造函数生成的实例也会有一个指针 __proto__ 指向它的原型对象。构造函数、原型对象、实例三者关系如下图所示。对象属性查找时,如果没有找到,会沿着原型链向上查找它的原型对象,如果找到属性这个查找过程会终止,否则会查询到原型链的终点null.

js创建对象与继承总结_第1张图片

优点:解决了方法重复创建的问题    缺点:引用类型数据相互影响的问题;创建时没有办法传递参数

    function Person() { }
    Person.prototype = {
      constructor: Person,
      name: '张三',
      age: 28,
      like: ['food', 'cake'],
      sayName: function () {
        console.log(this.name)
      }
    }
    let person1 = new Person();
    let person2 = new Person();
    person1.like.push('milk')
    console.log(person1.like) //['food', 'cake', 'milke]
    console.log(person2.like) //['food', 'cake', 'milke]

4.组合使用构造函数和原型模式

集合两种模式的长处,并没有它们的缺点。 缺点:构造函数和其原型分离,封装性不够。

    function Person(name, age, like) {
      this.name = name;
      this.age = age;
      this.like = like;
    }
    Person.prototype.sayName = function () {
      console.log(this.name)
    }
    let person1 = new Person('张三', 26, ['food', 'cake']);
    let person2 = new Person('李四', 26, ['food', 'cake']);
    person1.like.push('milk')
    console.log(person1.like) //['food', 'cake', 'milke]
    console.log(person2.like) //['food', 'cake']
    person1.sayName() //张三

5.动态原型模式

解决了组合模式 封装性不够的问题

    function Person(name, age, like) {
      this.name = name;
      this.age = age;
      this.like = like;
      //判断一个应该存在于原型上的属性或方法就知道是否已经添加过
      if (typeof this.sayName !== 'function') {
        //不能用字面量形式 或则会重写原型
        Person.prototype.sayName = function () {
          console.log(this.name)
        }
      }
    }
    let person1 = new Person('张三', 26, ['food', 'cake']);
    let person2 = new Person('李四', 26, ['food', 'cake']);
    person1.like.push('milk')
    console.log(person1.like) //['food', 'cake', 'milke]
    console.log(person2.like) //['food', 'cake']
    person1.sayName() //张三

二、继承

1.原型链继承

缺点 1)、数据共享问题 引用类型数据 实例之间相互影响 2).创建子类型实例时没办法向超类传递参数 3.只能单继承( 只能继承一个父类)

function SuperType() {
      this.name = '张三';
      this.like = ['food', 'cake'];
    }
    SuperType.prototype.sayName = function () {
      console.log(this.name)
    }
    function SubType() {
      this.age = '26';
    }
    SubType.prototype = new SuperType();
    SubType.prototype.sayAge = function () {
      console.log(this.age)
    }
    let person1 = new SubType();
    let person2 = new SubType();
    person1.sayAge() //26
    person1.sayName() // 张三
    person1.like.push('flower')
    console.log(person1.like) // ["food", "cake", "flower"]
    console.log(person2.like) // ["food", "cake", "flower"]

2.构造函数继承

优点:解决原型链继承引用类型数据共享和没办法传递参数问题;可以多继承 。 缺点:方法多次重复定义

 function SuperType(name) {
      this.name = name;
      this.like = ['food', 'cake'];
      this.sayName = function () {
        console.log(this.name)
      }
    }
    function SuperType2(date) {
      this.birthday = date;
      this.sayDate = function () {
        console.log(this.birthday)
      }
    }
    function SubType(name, age, date) {
      //多继承
      SuperType.call(this, name);
      SuperType2.call(this, date)
      this.age = age;
      this.sayAge = function () {
        console.log(this.age)
      }
    }
    let person1 = new SubType('张三', 26, '2013-12');
    let person2 = new SubType('李四', 28, '2013-11');
    person1.sayName() // 张三
    person2.sayName() // 李四
    person1.sayDate() //2013-12
    person1.like.push('flower')
    console.log(person1.like) // ["food", "cake", "flower"]
    console.log(person2.like) // ["food", "cake"]

3.组合继承(原型链 + 构造函数)

优点:避免了原型链和构造函数的缺陷,能实现函数复用,每个实例也都有自己的属性。 缺点:超类构造函数执行了两次

function SuperType(name, like) {
      this.name = name;
      this.like = like
    }
    SuperType.prototype.sayName = function () {
      console.log(this.name)
    }
    function SubType(name, age, like) {
      SuperType.call(this, name, like)
      this.age = age;
    }
    SubType.prototype = new SuperType();
    SubType.prototype.sayAge = function () {
      console.log(this.age)
    }
    let person1 = new SubType('张三', '28', ['food', 'cake']);
    let person2 = new SubType('李四', '28', ['food', 'cake']);
    person1.sayName() //张三
    person2.sayName() //李四
    person1.like.push('flower');
    console.log(person1.like) // ["food", "cake", "flower"]
    console.log(person2.like) // ["food", "cake"]

4.寄生组合式 

优点: 解决超类构造函数调用两次问题。 我们只需要让子类能访问到超类的原型的就可以。

//其他地方和组合继承一致
SubType.prototype = Object.create(SuperType.prototype)

//Object.create 传一个参数时和如下object函数行为一致
function object(obj) {
  function F() { }
  F.prototype = obj;
  return new F()
}

有些人可能会有疑惑,我们为什么不直接让 子类原型等于超类原型呢?

//这样不是更省事吗?
 SubType.prototype = SuperType.prototype

//这样如果在SubType原型上添加属性 SuperType原型上也会影响到
//如
 SubType.prototype.sayAge = function () {
      console.log(this.age)
    }
//SuperType.prototype 也会存在sayAge方法

5.ES6继承

ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。                         —— 《ECMAScript 6入门》阮一峰

class A {
  p() {
    return 2;
  }
}

class B extends A {
  constructor() {
    //作为函数调用 代表父类构造函数
    super();
    //作为对象调用 指向父类原型对象
    console.log(super.p()); // 2
  }
}

 

你可能感兴趣的:(JavaScript)