Please enable Javascript to view the contents

从头学习js-15-继承

 ·  ☕ 4 分钟

这个系列是我读冴羽老师博客的感悟,
加入了个人的解读和练习题的解答

本文讲解javascript的各种继承和优缺点

1.原型链继承

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function Parent() {
  this.name = "Neo"
}

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

function Child() {}

Child.prototype = new Parent() 
var child1 = new Child()

child1.getName()

问题:
1.引用类型的属性被所有实例共享,看例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function Parent() {
  this.names = ["Anderson", "Neo"];
}

function Child(){}

Child.prototype = new Parent()
var child1 = new Child()
var child2 = new Child()

child1.names.push("the one")
console.log(child1.names) //['Anderson', 'Neo', 'the one']
console.log(child2.names) //['Anderson', 'Neo', 'the one'] child2与child1共享了原型Parent上的属性names

2.在创建Child实例时,不能向Parent传参


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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function Parent() {
  this.names = ["Anderson", "Neo"]
}

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

var child1 = new Child()
child1.names.push("the one");
console.log(child1.names) //["Anderson", "Neo", "the one"]

var child2 = new Child()
console.log(child2.names) //["Anderson", "Neo"]

优点:
1.避免了引用类型的属性被所有实例共享
2.可在在Child中向Parents传参

例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function Parent(name) {
  this.name = name
}

function Child(name) {
  Parent.call(this, name)
}

var child1 = new Child("child_A")
var child2 = new Child("child_B")

console.log(child1.name) //child_A
console.log(child2.name) //child_B

缺点:
方法都在构造函数中定义,每次创建实例都会调用一遍方法


3.组合继承

1的原型和2的构造函数继承的结合

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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
}

Child.prototype = new Parent();
Child.prototype.constructor = Child;

var child1 = new Child("Neo", 20)
child1.colors.push('black')
console.log(child1.name) // Neo
console.log(child1.age) // 20
console.log(child1.colors) // ["red", "blue", "green", "black"]


var chlid2 = new Child("Smith" 28)
console.log(child2.name); // Smith
console.log(child2.age); // 28
console.log(child2.colors); // ["red", "blue", "green"]

优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式
缺点:缺少封装性


4.原型式封装

1
2
3
4
5
function createObj(o) {
  function F(){};
  F.prototype = o;
  return new F();
}

新创建一个构造函数,这个构造函数的原型设为父对象,返回这个构造函数创建的实例对象

这就是ES5 Object.create的模拟实现,将传入的对象作为创建的对象的原型

缺点:包含引用类型的属性值会在实例之间共享,这点和原型继承一样

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
var person = {
  name: "Neo",
  friends: ["Morpheus", "Trinity"]
}

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

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

person1.friends.push("Smith")
console.log(person2.friends) //["Morpheus", "Trinity", "Smith"]

这里修改person1.name的值,person2的值没有改变,并不是因为person1和person2有独立的name值,而是因为person1.name = “Agent”,给person1添加了name值


5.寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象

1
2
3
4
5
6
7
function createObj(o) {
  var clone = Object.create(o)
  clone.sayHi = function() {
    console.log("Hi")
  }
  return clone
}

缺点和借用构造函数模式一样,每次生成一个新的对象都会创建一遍方法


6.寄生组合式继承

再看一次组合继承式的代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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
}

Child.prototype = new Parent()

var child1 = new Child("Neo", 20)

组合继承最大的缺点是会调用两次父构造函数

一次是在设置Child.prototype = new Parent()的时候

一次是在实例化Child新对象的时候 new Child(“Neo”, 20),
在new的时候会执行Parent.call(this, name)

所以此时
Child.prototype

child1
都有一个color属性,值为[‘red’, ‘blue’, ‘green’]

我们可以继续改进方法来避免这一次的重复调用
不使用Child.prototype = new Parent(),而是间接的让Child.prototype可以访问到Parent.prototype

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function Parent(name) {
  this.name = name
  this.colors = ['red', 'blue', 'green']
}

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

function Child(name, age) {
  this.name = name
  this.age = age
}

//关健的3步
function F(){}
F.prototype = Parent.prototype
Child.prototype = new F() 

var child3 = new Child("Neo", 25)

最后我们来封装一下这个继承方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function object(o) {
  function F(){}
  F.prototype = o
  return new F()
}

function prototype(Child, Parent) {
  var prototype = object(Parent.prototype)
  prototype.constructor = Child
  Child.prototype = prototype
}

//使用时
prototype(Child, Parent)

这种方式的高效率体现在它只调用了一次Parent构造函数,因此避免了在Parent.prototype上创建了不必要的、多余的属性。与此同时原型链还能保持不变,可以正常使用instanceof和isPrototypeOf。
开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

分享

Llane00
作者
Llane00
Web Developer