Skip to content

Commit 318aeab

Browse files
committed
完善JavaScript深入之继承
2 parents 1c627e1 + 86311e3 commit 318aeab

File tree

1 file changed

+334
-0
lines changed

1 file changed

+334
-0
lines changed
Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
# JavaScript深入之继承
2+
3+
## 写在前面
4+
5+
本文讲解JavaScript各种继承方式和优缺点。
6+
7+
注意:
8+
9+
跟《JavaScript深入之创建对象》一样,更像是笔记,哎,再让我感叹一句:《JavaScript高级程序设计》写得真是太好了!
10+
11+
## 1.原型链继承
12+
13+
```js
14+
function Parent () {
15+
this.name = 'kevin';
16+
}
17+
18+
Parent.prototype.getName = function () {
19+
console.log(this.name);
20+
}
21+
22+
function Child () {
23+
24+
}
25+
26+
Child.prototype = new Parent();
27+
28+
var child1 = new Child();
29+
30+
console.log(child1.getName()) // kevin
31+
```
32+
33+
问题:
34+
35+
1.引用类型的属性被所有实例共享,举个例子:
36+
37+
```js
38+
function Parent () {
39+
this.names = ['kevin', 'daisy'];
40+
}
41+
42+
function Child () {
43+
44+
}
45+
46+
Child.prototype = new Parent();
47+
48+
var child1 = new Child();
49+
50+
child1.names.push('yayu');
51+
52+
console.log(child1.names); // ["kevin", "daisy", "yayu"]
53+
54+
var child2 = new Child();
55+
56+
console.log(child2.names); // ["kevin", "daisy", "yayu"]
57+
58+
```
59+
60+
2.在创建 Child 的实例时,不能向Parent传参
61+
62+
## 2.借用构造函数(经典继承)
63+
64+
```js
65+
function Parent () {
66+
this.names = ['kevin', 'daisy'];
67+
}
68+
69+
function Child () {
70+
Parent.call(this);
71+
}
72+
73+
var child1 = new Child();
74+
75+
child1.names.push('yayu');
76+
77+
console.log(child1.names); // ["kevin", "daisy", "yayu"]
78+
79+
var child2 = new Child();
80+
81+
console.log(child2.names); // ["kevin", "daisy"]
82+
```
83+
84+
优点:
85+
86+
1.避免了引用类型的属性被所有实例共享
87+
88+
2.可以在 Child 中向 Parent 传参
89+
90+
举个例子:
91+
92+
```js
93+
function Parent (name) {
94+
this.name = name;
95+
}
96+
97+
function Child (name) {
98+
Parent.call(this, name);
99+
}
100+
101+
var child1 = new Child('kevin');
102+
103+
console.log(child1.name); // kevin
104+
105+
var child2 = new Child('daisy');
106+
107+
console.log(child2.name); // daisy
108+
109+
```
110+
111+
缺点:
112+
113+
方法都在构造函数中定义,每次创建实例都会创建一遍方法。
114+
115+
## 3.组合继承
116+
117+
原型链继承和经典继承双剑合璧。
118+
119+
```js
120+
function Parent (name) {
121+
this.name = name;
122+
this.colors = ['red', 'blue', 'green'];
123+
}
124+
125+
Parent.prototype.getName = function () {
126+
console.log(this.name)
127+
}
128+
129+
function Child (name, age) {
130+
131+
Parent.call(this, name);
132+
133+
this.age = age;
134+
135+
}
136+
137+
Child.prototype = new Parent();
138+
139+
var child1 = new Child('kevin', '18');
140+
141+
child1.colors.push('black');
142+
143+
console.log(child1.name); // kevin
144+
console.log(child1.age); // 18
145+
console.log(child1.colors); // ["red", "blue", "green", "black"]
146+
147+
var child2 = new Child('daisy', '20');
148+
149+
console.log(child2.name); // daisy
150+
console.log(child2.age); // 20
151+
console.log(child2.colors); // ["red", "blue", "green"]
152+
153+
```
154+
155+
优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。
156+
157+
## 4.原型式继承
158+
159+
```js
160+
function createObj(o) {
161+
function F(){}
162+
F.prototype = o;
163+
return new F();
164+
}
165+
```
166+
167+
就是 ES5 Object.create 的模拟实现,将传入的对象作为创建的对象的原型。
168+
169+
缺点:
170+
171+
包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。
172+
173+
```js
174+
var person = {
175+
name: 'kevin',
176+
friends: ['daisy', 'kelly']
177+
}
178+
179+
var person1 = createObj(person);
180+
var person2 = createObj(person);
181+
182+
person1.name = 'person1';
183+
console.log(person2.name); // kevin
184+
185+
person1.firends.push('taylor');
186+
console.log(person2.friends); // ["daisy", "kelly", "taylor"]
187+
```
188+
189+
注意:修改`person1.name`的值,`person2.name`的值并未发生改变,并不是因为`person1``person2`有独立的 name 值,而是因为`person1.name = 'person1'`,给`person1`添加了 name 值,并非修改了原型上的 name 值。
190+
191+
## 5. 寄生式继承
192+
193+
创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。
194+
195+
```js
196+
function createObj (o) {
197+
var clone = object.create(o);
198+
clone.sayName = function () {
199+
console.log('hi');
200+
}
201+
return clone;
202+
}
203+
```
204+
205+
缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。
206+
207+
## 6. 寄生组合式继承
208+
209+
为了方便大家阅读,在这里重复一下组合继承的代码:
210+
211+
```js
212+
function Parent (name) {
213+
this.name = name;
214+
this.colors = ['red', 'blue', 'green'];
215+
}
216+
217+
Parent.prototype.getName = function () {
218+
console.log(this.name)
219+
}
220+
221+
function Child (name, age) {
222+
Parent.call(this, name);
223+
this.age = age;
224+
}
225+
226+
Child.prototype = new Parent();
227+
228+
var child1 = new Child('kevin', '18');
229+
230+
console.log(child1)
231+
```
232+
233+
组合继承最大的缺点是会调用两次父构造函数。
234+
235+
一次是设置子类型实例的原型的时候:
236+
237+
```js
238+
Child.prototype = new Parent();
239+
```
240+
241+
一次在创建子类型实例的时候:
242+
243+
```js
244+
var child1 = new Child('kevin', '18');
245+
```
246+
247+
回想下 new 的模拟实现,其实在这句中,我们会执行:
248+
249+
```js
250+
Parent.call(this, name);
251+
```
252+
253+
在这里,我们又会调用了一次 Parent 构造函数。
254+
255+
所以,在这个例子中,如果我们打印 child1 对象,我们会发现 Child.prototype 和 child1 都有一个属性为`colors`,属性值为`['red', 'blue', 'green']`
256+
257+
那么我们该如何精益求精,避免这一次重复调用呢?
258+
259+
如果我们不使用 Child.prototype = new Parent() ,而是间接的让 Child.prototype 访问到 Parent.prototype 呢?
260+
261+
看看如何实现:
262+
263+
```js
264+
function Parent (name) {
265+
this.name = name;
266+
this.colors = ['red', 'blue', 'green'];
267+
}
268+
269+
Parent.prototype.getName = function () {
270+
console.log(this.name)
271+
}
272+
273+
function Child (name, age) {
274+
Parent.call(this, name);
275+
this.age = age;
276+
}
277+
278+
// 关键的三步
279+
var F = function () {};
280+
281+
F.prototype = Parent.prototype;
282+
283+
Child.prototype = new F();
284+
285+
286+
var child1 = new Child('kevin', '18');
287+
288+
console.log(child1);
289+
```
290+
291+
最后我们封装一下这个继承方法:
292+
293+
```js
294+
function object(o) {
295+
function F() {}
296+
F.prototype = o;
297+
return new F();
298+
}
299+
300+
function prototype(child, parent) {
301+
var prototype = object(parent.prototype);
302+
prototype.constructor = child;
303+
child.prototype = prototype;
304+
}
305+
306+
// 当我们使用的时候:
307+
prototype(Child, Parent);
308+
```
309+
310+
引用《JavaScript高级程序设计》中对寄生组合式继承的夸赞就是:
311+
312+
这种方式的高效率体现它只调用了一次Parent构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
313+
314+
## 相关链接
315+
316+
[《JavaScript深入之从原型到原型链》](https://github.com/mqyqingfeng/Blog/issues/2)
317+
318+
[《JavaScript深入之call和apply的模拟实现》](https://github.com/mqyqingfeng/Blog/issues/11)
319+
320+
[《JavaScript深入之new的模拟实现》](https://github.com/mqyqingfeng/Blog/issues/13)
321+
322+
[《JavaScript深入之创建对象》](https://github.com/mqyqingfeng/Blog/issues/15)
323+
324+
## 深入系列
325+
326+
JavaScript深入系列目录地址:[https://github.com/mqyqingfeng/Blog](https://github.com/mqyqingfeng/Blog)
327+
328+
JavaScript深入系列预计写十五篇左右,旨在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。
329+
330+
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。
331+
332+
333+
334+

0 commit comments

Comments
 (0)