这个系列是我读冴羽老师博客的感悟,
加入了个人的解读和练习题的解答
本文讲解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。
开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。