Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

js高手进阶 —— 实现继承的八种方式 #2

Open
Heqingsong opened this issue Apr 28, 2019 · 0 comments
Open

js高手进阶 —— 实现继承的八种方式 #2

Heqingsong opened this issue Apr 28, 2019 · 0 comments

Comments

@Heqingsong
Copy link
Owner

extends

继承是OO语言中的一个最为人津津乐道的概念。许多OO语言都支持两种继承方式: 接口继承 和 实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。由于js中方法没有签名,在ECMAScript中无法实现接口继承。ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链来实现的. —— JavaScript高级程序设计

1. 原型链继承

核心: 将父类实例作为子类原型
优点: 方法复用
缺点:

  • 创建子类实例的时候,不能传参数。
  • 多个实例对引用类型的操作会被篡改。
let Person = function(){
    this.color = ['red', 'blue', 'yellow'];
};

Person.prototype.getColor = function() {
    return this.color;
}

let Child = function() {}
// 这里是关键,创建Person的实例,并将该实例赋值给Child.prototype
Child.prototype = new Person();

let result = new Child();
result.color.push('black');
console.log(result.getColor()); // ["red", "blue", "yellow", "black"]

let result2 = new Child();
result2.color.push('olive');
console.log(result2.getColor()); //  ["red", "blue", "yellow", "black", "olive"]

2. 借用构造函数继承

核心: 借用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类。
优点:

  • 实例之间独立。
  • 创建子类实例,可以向父类构造函数传参数。
  • 子类实例不共享父类构造函数的引用属性。

缺点:

  • 只能继承父类的实例属性和方法,不能继承原型属性/方法
  • 无法实现复用,每个子类都有父类实例函数的副本,影响性能
let Cart = function(){
    this.color = ['red'];
};

Cart.prototype.getColor = function() {
    return this.color;
}

let Child = function() {
    // 核心
    Cart.call(this);
}

let result = new Child();
result.color.push('black');
//console.log(result.getColor()); // TypeError: result.getColor is not a function
console.log(result.color); // ["red", "black"]

let result2 = new Child();
result2.color.push('yellow');
console.log(result2.color); // ["red", "yellow"]

3. 组合继承法

核心:
通过调用父类构造函数,继承父类的属性并保留传参的优点;然后通过将父类实例作为子类原型,实现函数复用。
优点:

  • 创建子类实例,可以向父类构造函数传参数。
  • 父类的实例方法定义在父类的原型对象上,可以实现方法复用。
  • 不共享父类的引用属性。

缺点:

  • 由于调用了2次父类的构造方法,会存在一份多余的父类实例属性。

注意:
组合继承这种方式,要记得修复Child.prototype.constructor指向

let SuperClass = function(name) {
  this.name = name;
  this.list = [1, 2, 3];
};

SuperClass.prototype.getName = function() {
  console.log(`name = ${this.name}; list = ${this.list}`);
}

let SubClass = function(name, age) {
  SuperClass.call(this, name); // 第二次执行构造函数
  this.age = age;
}

SubClass.prototype = new SuperClass(); // 第一次执行构造函数
// 重写SubClass.prototype的constructor属性,指向自己的构造函数SubClass
SubClass.prototype.constructor = SubClass;
SubClass.prototype.getAge = function() {
  console.log(this.age);
}

let result = new SubClass('小米', 2);
result.list.push(4);
// name = 小米; list = 1,2,3,4
result.getName();
// 2
result.getAge();

let result2 = new SubClass('滴滴', 3);
// name = 滴滴; list = 1,2,3,5
result2.getName();
// 3
result2.getAge();

4. 原型式继承

缺点:

  • 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
  • 无法传递参数
/*
var obj  = {};
obj.__proto__ = F.prototype;
F.call(obj);
以 new 操作符调用构造函数的时候,函数内部实际上发生以下变化:
1、创建一个空对象,并且this变量引用该对象,同时还继承了该函数的原型。
2、属性和方法被加入到 this 引用的对象中。
3、新创建的对象由 this 所引用,并且最后隐式的返回 this.
*/
Object.create = Object.create || function( object ) {
    let F = function(){};
    F.prototype = object;
    return new F();
}

let SuperType = {
    name: '小米',
    friend: ['小李', '小刚'],
    getName: function() {
        console.log(this.name);
    }
};

let result1 = Object.create(SuperType);
result1.name = '小莉';
result1.friend.push('小红');
result1.getName();
console.log(result1.friend); // ["小李", "小刚", "小红"]

let result2 = Object.create(SuperType);
result2.name = '小赵';
result2.friend.push('小钱');
result2.getName();
console.log(result2.friend); //  ["小李", "小刚", "小红", "小钱"]

5. 寄生式继承

缺点:

  • 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
  • 无法传递参数。
var createObject = function(object) {
  let fn = function(){};
  fn.prototype = object;
  return new fn();
}

var parasiticObject = function(object){
  let host = createObject(object);

  // 寄生在宿主对象上
  host.newMethod = function() {
    console.log(object);
  }
  return host;
}

var datas = {
  name: '小米',
  friend: ['小李', '小刚']
};

var instanceObject = parasiticObject(datas);
instanceObject.newMethod();

6. 寄生组合式继承

结合借用构造函数传递参数和寄生模式实现继承

function inheritPrototype(subType, superType){
  // 创建对象,创建父类原型的一个副本
  let prototype = Object.create(superType.prototype);

  // 增强对象,弥补因重写原型而失去的默认的constructor 属性
  prototype.constructor = subType;

  // 指定对象,将新创建的对象赋值给子类的原型
  subType.prototype = prototype;
}

function SuperType(name){
  this.name = name;
  this.speak = ['中文'];
}

SuperType.prototype.superMethod = function() {
  console.log(this.name);
}
// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function SubType(name, age){
  SuperType.call(this, name);
  this.age = age;
}

// 将父类原型指向子类
inheritPrototype(SubType, SuperType);

SubType.prototype.subMethod = function() {
  console.log(this.age);
}

var obj1 = new SubType('小李', 28);
var obj2 = new SubType('小张', 22);

obj1.speak.push('粤语');
obj2.speak.push('韩语');

console.log(obj1.speak); // ["中文", "粤语"]
console.log(obj2.speak); //  ["中文", "韩语"]

7. 混入方式继承多个对象

function MyClass() {
  SuperType.call(this);
  otherSuperType.call(this);
}

MyClass.prototype = Object.create(SuperType.prototype);
Object.assign(MyClass.prototype, otherSuperType.prototype);
MyClass.prototype.constructor = MyClass;
MyClass.prototype.myMethed = function() {}

8. ES6 Class extends

class Father {
  constructor(name, age){
    this.name = name;
    this.age = age;
  }

  get getAbout() {
    return this.speak();
  }

  speak() {
    return `我叫:${this.name},今年: ${this.age}岁`;
  }
}

class Child extends Father {
  constructor(...args) {
    super(...args);
  }
}

ES6实现继承的原理

Object.setPrototypeOf()是ECMAScript 6最新草案中的方法,相对于 Object.prototype.proto ,它被认为是修改对象原型更合适的方法

class A {}
class B {}

Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
  obj.__proto__ = proto;
  return obj;
}

// B的实例继承A的实例
Object.setPrototypeOf(B.prototype, A.prototype);

// B 继承 A 的静态属性
Object.setPrototypeOf(B, A);

const result = new B();

babel 对 ES6 extends 的转义结果

function _inherits(subClass, superClass) {
  if (typeof superClass !== "function" && superClass !== null) {
      throw new TypeError("Super expression must either be null or a function");
  }
  subClass.prototype = Object.create(superClass && superClass.prototype, {
      constructor: {
          value: subClass,
          writable: true,
          configurable: true
      }
  });
  if (superClass) _setPrototypeOf(subClass, superClass);
}

function _setPrototypeOf(o, p) {
  _setPrototypeOf = Object.setPrototypeOf ||  function _setPrototypeOf(o, p) {
      o.__proto__ = p;
      return o;
  };
  return _setPrototypeOf(o, p);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant