devinxiang/issue-blog

手写六种继承

devinxiang opened this issue · 0 comments

1. 原型链继承

**:利用原型让一个引用类型继承另一个引用类型的属性和方法。

/**
 * 1. 原型链继承
 * child.prototype = new Parent()
 * 把父元素的实例,放大子的 prototype 上面
 */
// 父级元素
function Parent() {
  this.name = "Devin";
  this.species = ["rich", "poor"];
}

Parent.prototype.getName = function() {
  console.log(this.name);
};

// 子元素继承父元素的东西
function Child() {}
Child.prototype = new Parent();
let child1 = new Child();
console.log(child1.name, child1.species);

存在问题

  1. 原始构造函数里面的应用类型属性被所有实例共享,这个属性一变化都在变化;
  2. 在 Child 构造原型链继承的时候不能向 Parent 传参

2. 借用构造函数(经典继承)

**:在子类型构造函数的内部调用超类型构造函数,因为函数只不过是在特定环境中执行代码的对象,因此通过使用 apply() 和 call() 方法也可以在(将来)新创建的对象上执行构造函数。

function Parent2() {
  this.names = ["kevin", "daisy"];
}

function Child() {
  Parent.call(this);
}

var child1 = new Child();

child1.names.push('yayu');

console.log(child1.names); // ["kevin", "daisy", "yayu"]

var child2 = new Child();

console.log(child2.names); // ["kevin", "daisy"]

在新创建的 SubType 实例的环境下调用了 SuperType 构造函数。这样就会在新 SubType 对象上执行 SuperType()函数中定义的所有对象初始化代码。这样,每个实例都会有自己的属性副本了。

优点:

  • 避免了引用类型的属性被所有实例共享;
  • 可以在 Child 中向 Parent 传参;

缺点:

  • 无法复用到 Parent 的 prototype 上面的方法;
  • 需要继承的方法都需要在构造函数中定义,每次创建实例都会创建一遍方法。导致函数无法复用,浪费内存。

3. 组合继承

**:

  • 通过原型链实现对原型属性和方法的继承
  • 通过借用构造函数实现对实例属性的继承
function Parent3(name) {
  this.name = name;
  this.colors = ["red", "blue"];
}

Parent3.prototype.getName = function() {
  console.log(this.name);
};

function Child3(name, age) {
  Parent3.call(this, name);
  this.age = age;
}

Child3.prototype = new Parent3();
Child3.prototype.constructor = Child3;

var child1 = new Child("Devin", "18");

避免了原型链继承的缺点-实例共享,避免了每个实例的方法不能复用。

4. 原型式继承

**:将传入的对象作为创建的对象的原型

function createObj(o) {
	function F(){}
	F.prototype = o;
	return new F();
}

var person = {
    name: 'kevin',
    friends: ['daisy', 'kelly']
}

var person1 = createObj(person);
var person2 = createObj(person);

person1.name = 'person1';
console.log(person2.name); // kevin

person1.firends.push('taylor');
console.log(person2.friends); // ["daisy", "kelly", "taylor"]

缺点:

  1. 引用类型的属性值始终会共享;

5. 寄生式继承

思路:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象

function createAnother(original){
	var clone = Object.create(original);
	clone.sayName = function (){ 
		console.log('hi')
	}

	return clone; 
}

缺点:

  1. 每次创建对象都会创建一次方法

6. 寄生组合式继承

思路:通过组合寄生的方式继承 prototype 的内容;通过构造函数方式继承实例属性;

// 生成一个新的对象,把继承的属性放在 prototype 上面
function object(o) {
	function F() {}
	F.prototype = o;
	return new F();
}
// 继承 Parent 的 prototype 属性
function proto(Child, Parent){
	let prototype = object(Parent.prototype); // 创建对象
	prototype.constructor = Child;            // 增强对象
	Child.prototype = prototype;              // 指定对象
}

function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function() {
  console.log(this.name);
}

function Child(name, age) {
  // 构造函数继承
  Parent.call(this, name);
  this.age = age;
}

proto(Child, Parent);

总结

继承分为

  1. 继承实例属性,构造函数 this 上面的。实例属性需要单独生成一份;
  2. 继承公用方法,在被继承的 prototype 上面的;

参考

  1. JavaScript深入之继承的多种方式和优缺点