Skip to content

Commit f824270

Browse files
author
丁晓杰
committed
Merge branch 'dev'
# Conflicts: # _config.yml
2 parents ad8cc77 + dc7910c commit f824270

File tree

13 files changed

+1564
-16
lines changed

13 files changed

+1564
-16
lines changed

.deploy_git

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit c425f0f8b13d3a92764fc963b8810ded0c0ee0a8

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules
22
public
3-
db.json
3+
db.json
4+
.deploy_git

_config.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
## Source: https://github.com/hexojs/hexo/
44

55
# Site
6-
title: VioletJack
7-
subtitle: 专注于前端知识的学习和分享
8-
description: 如果我衣食无忧,我最想做的事就是玩技术,写博客
6+
title: VioletJack 技术日志
7+
subtitle: 专注!坚持!求真!
8+
description: Violetjack 的个人博客,记录了一些技术笔记和思考
99
author: VioletJack
1010
language: zh-Hans
1111
timezone: Asia/Shanghai

source/_posts/2018-sum.md

Lines changed: 257 additions & 0 deletions
Large diffs are not rendered by default.

source/_posts/behavior.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
---
2+
title: 聊聊对象行为委托
3+
date: 2019-02-13
4+
tag: "JavaScript 基础"
5+
---
6+
7+
> 昨天我们聊到了对象的原型,知道了有 new 和 Object.create() 两种操作原型的方式。今天我们来对比下使用这两种方式进行面向对象编程的特点。
8+
9+
# 使用 new 关键字写面向类
10+
11+
先来一段面向类的代码实现
12+
13+
```js
14+
function Foo(who) {
15+
this.me = who;
16+
}
17+
Foo.prototype.identify = function () {
18+
return "I am " + this.me;
19+
};
20+
21+
function Bar(who) {
22+
Foo.call(this, who);
23+
}
24+
Bar.prototype = Object.create(Foo.prototype);
25+
Bar.prototype.speak = function () {
26+
console.log("Hello, " + this.identify() + ".");
27+
};
28+
29+
var b1 = new Bar("b1");
30+
var b2 = new Bar("b2");
31+
32+
b1.speak();
33+
b2.speak();
34+
```
35+
36+
当然也可以用 ES6 的 class 语法糖。class 的出现避免了在函数的 `prototype` 上添加属性的奇怪的行为。
37+
38+
# 混乱不堪的原型关系
39+
40+
无论是 function 还是 class,其背后还是逃不开对于 prototype 的操作。而原型中各种关系令人头疼。下面是这段代码的关系图:
41+
42+
![原型关系](https://upload-images.jianshu.io/upload_images/1987062-b78fe4bbd7d6532a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
43+
44+
抛开函数与 Function 和 Object 的关系,我们来捋一捋代码中的逻辑。
45+
46+
1. Foo 函数的 prototype 上创建了函数 identify
47+
2. Foo 函数的 prototype 引用了 Object 的原型(原型链理论)
48+
3. Bar 函数 prototype 引用了以 Foo 的 prototype 为原型创建的对象
49+
4. Bar 函数的 prototype 上创建了函数 speak
50+
5. 以 Bar 为构造器分别 new 了两个对象 b1 和 b2,b1 和 b2 的原型引用了 Bar 函数的 prototype
51+
6. 由于 Bar 进行了修改原型的操作,所以没有 constructor 函数。
52+
7. 所以根据原型链理论,Bar 的 prototype 和 b1、b2 的 constructor 都指向了 Foo 函数的 constructor。
53+
54+
就这么个逻辑(这还没有包括函数与对象之前的关系),我表示我讨厌在 prototype 上去添加属性,这显得非常乱。
55+
56+
最后输出的 b1 对象的原型结构如下:
57+
58+
![原型结构](https://upload-images.jianshu.io/upload_images/1987062-1cd13125458907a5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
59+
60+
61+
# 行为委托闪亮登场
62+
63+
可以看到上面的写法导致指向非常混乱,实际上我们并不需要了解这么多逻辑。下面介绍一种方式完全不管 prototype 真正面向对象的写法 —— 行为委托。
64+
65+
就拿上面的例子来说:
66+
67+
```js
68+
Foo = {
69+
init: function (who) {
70+
this.me = who;
71+
},
72+
identify: function () {
73+
return "I am " + this.me;
74+
}
75+
};
76+
77+
Bar = Object.create(Foo);
78+
Bar.speak = function () {
79+
alert("Hello, " + this.identify() + ".");
80+
};
81+
82+
var b1 = Object.create(Bar);
83+
b1.init("b1");
84+
var b2 = Object.create(Bar);
85+
b2.init("b2");
86+
87+
b1.speak();
88+
b2.speak();
89+
```
90+
91+
其中的关键就是完全使用对象来写面向对象编程,而避免使用 new 一个构造器的写法。这里我们也来理一理逻辑:
92+
93+
1. 创建 Foo 对象,它包含 init 和 identify 两个函数属性。
94+
2. 创建 Bar 对象原型继承 Foo 对象。
95+
3. 在 Bar 对象上添加 speak 方法。
96+
4. 创建 b1 和 b2 对象原型继承 Bar 对象。
97+
5. 使用原型链上的 init 函数为 b1 和 b2 对象传入数据。
98+
6. 通过原型链调用 speak 函数。
99+
100+
这种写法的原型关系图就简单了很多:
101+
102+
![原型关系](https://upload-images.jianshu.io/upload_images/1987062-bdc4c082cbe29864.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
103+
104+
可以看到与第一中方法比少了函数构造器,少了函数构造器就没了 constructor 和 prototype。完全通过对象与对象之间的原型继承引用关系来实现面向对象的编程思想。
105+
106+
最后看下输出的 b1 对象的原型结构:
107+
108+
![原型结构](https://upload-images.jianshu.io/upload_images/1987062-20caa15116450797.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
109+
110+
# 行为委托的好处
111+
112+
* 行为委托避免使用 new 构造器形式来实现面向对象,减少了大量构造器所带出的复杂关系。
113+
* 行为委托只使用对象之间的原型继承关系,让整个代码逻辑变得非常清晰。
114+
115+
# 最后
116+
117+
这里介绍了行为委托这一种面向对象的设计思想,它让面向对象编程变得更加简洁、更加自然。
118+
119+
当然,这只是一种设计方式。如果执意要用 new 写法来写面向对象编程当然没有问题,推荐使用 class 语法糖,它可以将操作 prototype 的行为给隐藏起来,这使得代码更像 Java(引用对象的特性并未改变,所以只是看着像),从而让代码更好理解。
120+
121+
赶快去试试行为委托吧,我认为它是种很适合 JavaScript 的设计模式。
122+
123+
明天我们聊聊 JavaScript 的类型~

source/_posts/prototype.md

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
---
2+
title: 聊聊 JavaScript 的原型
3+
date: 2019-02-12
4+
tag: "JavaScript 基础"
5+
---
6+
7+
> JavaScript 中的原型也是一个非常让人头疼的东西,很多前端同学对此也是一知半解,比如我。今天我们就好好捋一捋这个原型。
8+
9+
# 创建对象的方式
10+
11+
下面就是创建对象的几种方式:
12+
13+
```js
14+
var o1 = {
15+
a: 123,
16+
b: 'hello world'
17+
}
18+
console.log(o1.b)
19+
20+
21+
function fun2() {
22+
this.a = 33
23+
this.b = 'hello o2'
24+
}
25+
26+
var o2 = new fun2()
27+
console.log(o2.b)
28+
29+
class Fun3 {
30+
constructor() {
31+
this.a = 365
32+
this.b = 'hello class'
33+
}
34+
}
35+
var o3 = new Fun3()
36+
console.log(o3.b)
37+
```
38+
39+
有人说这是三种创建方式,但是我认为其实是两种创建方式(因为 class 语法糖的本质还是 function):**直接定义对象****使用 new 关键词构造对象**
40+
41+
# 原型和原型链
42+
43+
当我们创建了一个对象之后,就产生了原型(`Object.create(null)` 是特例)。
44+
45+
## prototype 和 `__proto__` 的区别
46+
47+
`__proto__` 是一个非正式的属性,很多环境中不支持该属性。它指向当前对象的原型。如下图:
48+
49+
![__proto__](https://upload-images.jianshu.io/upload_images/1987062-1002cf3d69276266.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
50+
51+
上面的代码是一段原型继承,可以看到对象 obj1 继承了对象 obj,所以 obj1 的 `__proto__` 就指向了 obj,而 obj 的 `__proto__` 则指向了 Object。所有对象的原型链最终都将指向 Object。
52+
53+
而关于 prototype 我摘录了一段话:
54+
55+
> 当你创建函数时,JS 会为这个函数自动添加 `prototype` 属性,值是一个有 constructor 属性的对象。而一旦你把这个函数当作构造函数(`constructor`)调用(即通过`new`关键字调用),那么 JS 就会帮你创建该构造函数的实例,实例继承构造函数 `prototype` 的所有属性和方法。
56+
57+
![prototype](https://upload-images.jianshu.io/upload_images/1987062-c600260dc91c2314.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
58+
59+
可以看到,对象 bar 的 `__proto__` 属性指向了函数 func 的 `prototype`
60+
61+
总结下,`__proto__` 指向原型,而 `prototype` 是函数独有且构造的对象原型指向 `prototype`
62+
63+
## 理解原型链
64+
65+
每个对象都是原型,而对象之间是可以继承的。所以就产生了原型链。看图说话:
66+
67+
![原型链](https://upload-images.jianshu.io/upload_images/1987062-aa7441f2c85bb6f5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
68+
69+
很好理解了,我们创建了四个对象逐层进行原型继承。最后打印 obj3 对象可以看到 `obj3 -> obj2 -> obj1 -> obj -> Object` 这就是原型链。
70+
71+
如果我要在 obj3 对象上访问 a 属性,那么 JavaScript 就会顺着原型链逐层往下找,最终在 obj 对象上找到了a 属性,这就是原型链查找数据的方式。如果找到 Object 也没有找到属性就返回 `undefined`
72+
73+
![原型链查找](https://upload-images.jianshu.io/upload_images/1987062-07ac0815c99f5d7d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
74+
75+
# 为对象指定原型的两种方式
76+
77+
那么如何为对象添加原型呢?
78+
79+
## 1. new 关键字
80+
81+
第一种就是通过构造器的方式来创建。
82+
83+
```js
84+
function Foo () {
85+
this.a = 11
86+
this.b = 22
87+
}
88+
Foo.prototype.c = 33
89+
Foo.prototype.func = () => {
90+
console.log('hello')
91+
}
92+
93+
var f = new Foo()
94+
95+
console.log(f)
96+
console.log(Object.getPrototypeOf(f))
97+
```
98+
99+
当然,不得不说的是 ES6 的 class 语法糖写法:
100+
101+
```js
102+
class Foo {
103+
constructor() {
104+
this.a = 11
105+
this.b = 22
106+
}
107+
108+
func() {
109+
console.log('hello')
110+
}
111+
}
112+
113+
var f = new Foo()
114+
```
115+
116+
两者其实是一样的效果,但是 class 写法更接近常规的类写法。(终于可以让 function 回归它原本的作用上了。)
117+
118+
## 2. Object.create(obj) 面向对象
119+
120+
Object.create() 可以很好的实现原型继承行为,也能通过 Object API 来修改原型:
121+
122+
```js
123+
var obj = { a: 123, b: 456 }
124+
Object.setPrototypeOf(obj, { c: 789 })
125+
var obj2 = Object.create(obj)
126+
obj2.e = 555
127+
```
128+
129+
代码输出结果如下图,的确实现了为对象指定原型的行为。
130+
131+
![原型继承和修改](https://upload-images.jianshu.io/upload_images/1987062-b592d69f559248af.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
132+
133+
# 引用流还是复制流?
134+
135+
使用 JavaScript 原型是特别要主义的一个点是:**JavaScript 对于原型的继承是一种引用行为**,即所引用的对象改变,继承对象的原型也会改变。
136+
137+
与之相反的,有些语言会使用复制的方式。即在原型继承时复制一份原型到当前对象,从此被复制的对象和复制对象再无瓜葛。
138+
139+
# 总结
140+
141+
随着 Object.create() 等一系列新 API 和 ES6 的 class 写法的出现,使用 function 作为构造器并使用 prototype 来修改原型的方式将逐渐被抛弃。但是由于历史原因这部分知识还是要理解其中原理的。
142+
143+
`__proto__` 属性是非正式属性,不适合在通用场景下使用。
144+
145+
而对于原型的写法,我认为有两种不错的处理方式:
146+
147+
> 1. 完全使用 class 构造器写法来替代使用 function 构造器的写法来进行**面向类**的开发方式。
148+
> 2. 放弃原型写法,使用 Object 系列 API 进行**面向对象**的开发(行为委托就是这样的方式)。
149+
150+
# 最后
151+
152+
关于原型,先聊这么多。明天我们聊聊基于 Object API 来实现的面向对象模式 —— 行为委托,敬请期待。

0 commit comments

Comments
 (0)