-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
278 lines (133 loc) · 292 KB
/
search.xml
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>VueModalManager —— 移动端全屏弹窗管理插件</title>
<link href="/2019/11/15/10vue-modal-manager/"/>
<url>/2019/11/15/10vue-modal-manager/</url>
<content type="html"><![CDATA[<p><img src="/2019/11/15/10vue-modal-manager/./vue-plugin.jpg" width="600px"><br>本文记录了一款自己开发的移动端全屏弹窗管理的vuejs插件,目的是针对移动端全屏页面弹窗,对APP外和APP内的环境做差异化处理。该插件没有对外开放,但可以分享一下在设计与实践过程中的一些思路和经验。</p><a id="more"></a><hr><h2 id="Part-1-前言"><a href="#Part-1-前言" class="headerlink" title="Part.1 前言"></a>Part.1 前言</h2><p>不知道大家在移动端h5开发中,有没有遇到过这样一种场景:点击按钮,一个满屏的二级子页面从屏幕从右向左滑出,关闭二级页面时,再从左向右消失?听上去仿佛是一个很常见场景,但在实际开发中,我却发现了这种全屏弹窗很让人崩溃的一面。</p><p>假设我们把全屏弹窗的显隐状态维护在vuex中,通过修改store中visible的值来控制是否显示它,并且配合transition过渡,是可以轻松实现这个功能的。但是,在手机浏览器中,点击浏览器自带的返回按钮会触发history栈的回退,这就导致了页面会直接返回到前一个页面。</p><p>但假设我们把全屏弹窗当做一个子路由,通过修改路由来控制它的显隐,在马蜂窝APP中,又会影响webview对h5页面的统计。</p><p>这样问题就来了,到底应该如何维护全屏弹窗的显隐状态呢?带着这个问题,我尝试着写了一个插件(vue-modal-manage)来解决这个问题。</p><blockquote><p>注:由于该插件是针对马蜂窝APP的业务场景做的,故下文中提到的APP均指马蜂窝APP.</p></blockquote><h2 id="Part-2-设计与实现"><a href="#Part-2-设计与实现" class="headerlink" title="Part.2 设计与实现"></a>Part.2 设计与实现</h2><p>1.目的<br> 通过对功能需求和现有业务场景的梳理,基本确定这个modalManager最终实现的效果期望是:</p><ul><li>在马蜂窝APP内,页面自定义顶部导航栏,全屏弹窗以组件的形式嵌入页面,根据统一的状态管理控制显隐;</li><li>在非马蜂窝APP中,全屏弹窗以子路由的形式渲染,根据路由变化控制显隐;</li></ul><p>2.设计思路</p><ul><li>首先,在实例化vue根组件的时候,需要对router进行拦截并且根据是否是APP环境做差异化处理;</li><li>其次,在VueModalManager插件内部需要定义一套状态管理属性和方法,用于控制当前弹窗的显隐状态;</li><li>设计全屏弹窗包裹组件,用于处理弹窗的渲染、显隐状态,路由钩子等逻辑,以完全地控制全屏弹窗的状态;</li></ul><p>3.具体实现</p><blockquote><p>3.1处理modalConfig</p></blockquote><p>在业务代码中定义一个modalConfig.js文件,它导出一个对象,该对象维护了那些全屏弹窗组件的信息,以及设置一些参数,例如是否开启调试模式等。VueModalManager类在实例化时接受这个配置项,并且在constructor方法中初始化了实例属性modals,用于维护所有全屏弹窗的基础信息以及显隐状态:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> state = {};</span><br><span class="line"><span class="keyword">const</span> { modals } = config;</span><br><span class="line">modals.forEach(<span class="function"><span class="params">modal</span> =></span> {</span><br><span class="line"> state[modal.name] = {</span><br><span class="line"> name: modal.name,</span><br><span class="line"> title: modal.title || <span class="string">''</span>,</span><br><span class="line"> parentRouteName: modal.parentRouteName,</span><br><span class="line"> component: modal.component,</span><br><span class="line"> transitionTime: modal.transitionTime || <span class="number">200</span>,</span><br><span class="line"> isOpen: <span class="literal">false</span>,</span><br><span class="line"> };</span><br><span class="line">});</span><br><span class="line"><span class="keyword">this</span>.modals = Vue.observable(state);</span><br></pre></td></tr></table></figure></p><blockquote><p>3.2路由拦截</p></blockquote><p>VueModalManager类定义了一个wrapRouter方法,接受vue-router实例化后的router对象作为参数,用于在实例化vue根组件时,对router对象进行包裹。内部逻辑主要就是根据环境对router对象进行差异化处理:</p><p>若环境不是APP内,则遍历router对象的route,根据this.modals修改对应页面的路由,设置children组件为由modalWrapper生成的弹窗组件,最后使用router.addRoutes()方法替换路由;<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> child = {</span><br><span class="line"> name,</span><br><span class="line"> path: name,</span><br><span class="line"> components: {</span><br><span class="line"> [name]: Vue.component(<span class="string">`<span class="subst">${name.toLowerCase()}</span>-modal-wrapper`</span>, createModalWrapper(modalConf)),</span><br><span class="line"> },</span><br><span class="line">};</span><br><span class="line">...</span><br><span class="line">routes[i].children.push(child);</span><br><span class="line">...</span><br><span class="line"><span class="keyword">if</span> (!<span class="keyword">this</span>.isApp) {</span><br><span class="line"> router.addRoutes(routes);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>若环境是App内,则不对router做任何处理,直接返回;</p><blockquote><p>3.3vue原型挂载$modalManager</p></blockquote><p>接下来,需要暴露一个在页面组件中可调用的方法,定义为<code>$modalManager</code>。调用<code>$modalManager</code>方法,希望在组件中可以拿到整个modals对象、当前显示的currentModal,以及打开和关闭modal的两个方法,具体实现方法如下:</p><p>首先,在插件的install方法中,通过Vue.mixin全局注入的方式,在beforeCreate()生命周期中,把实例化后的modalManager对象和根vue实例,分别绑定到<code>this._modalManager</code>和<code>this._modalManagerRoot</code>上;<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">Vue.mixin({</span><br><span class="line"> beforeCreate() {</span><br><span class="line"> <span class="keyword">if</span> (isDef(<span class="keyword">this</span>.$options.modalManager)) {</span><br><span class="line"> <span class="keyword">this</span>._modalManagerRoot = <span class="keyword">this</span>;</span><br><span class="line"> <span class="keyword">this</span>._modalManager = <span class="keyword">this</span>.$options.modalManager;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">this</span>._modalManagerRoot = (<span class="keyword">this</span>.$parent && <span class="keyword">this</span>.$parent._modalManagerRoot) || <span class="keyword">this</span>;</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line">});</span><br></pre></td></tr></table></figure></p><p>然后,调用Object.defineProperty()方法,在Vue的原型上定义只读的<code>$modalManager</code>属性。包含modals、currentModal两个属性,以及openModal()和closeModal()两个方法,以供在组件业务代码中灵活的调用:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Object</span>.defineProperty(Vue.prototype, <span class="string">'$modalManager'</span>, {</span><br><span class="line"> get() {</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> modals: <span class="keyword">this</span>._modalManagerRoot._modalManager.modals,</span><br><span class="line"> currentModal: <span class="keyword">this</span>._modalManagerRoot._modalManager.currentModal,</span><br><span class="line"> <span class="comment">// 如果不是APP,只做路由的操作,pushs或者back;modalState的修改在createModalWrapper的路由钩子里做</span></span><br><span class="line"> <span class="comment">// 如果是App,只做modal状态的改变,即modalState的修改;</span></span><br><span class="line"> openModal: <span class="function"><span class="params">name</span> =></span> {</span><br><span class="line"> <span class="keyword">if</span> (!<span class="keyword">this</span>._modalManagerRoot._modalManager.isApp) {</span><br><span class="line"> <span class="keyword">const</span> { query } = <span class="keyword">this</span>.$route;</span><br><span class="line"> <span class="keyword">this</span>.$router.push({</span><br><span class="line"> name,</span><br><span class="line"> query,</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">this</span>._modalManagerRoot._modalManager.openModal(name);</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> closeModal: <span class="function"><span class="params">name</span> =></span> {</span><br><span class="line"> <span class="keyword">if</span> (!<span class="keyword">this</span>._modalManagerRoot._modalManager.isApp) {</span><br><span class="line"> <span class="keyword">this</span>.$router.back();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">this</span>._modalManagerRoot._modalManager.closeModal(name);</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> };</span><br><span class="line"> },</span><br><span class="line">});</span><br></pre></td></tr></table></figure></p><p>至此,VueModalManager这个类的基本内容就结束了,接下来是两个组件createModalWrapper和ModalView的实现。</p><blockquote><p>3.4 createModalWrapper注册全局组件</p></blockquote><p>在<code>VueModalManager类</code>拦截路由的模块里,我们在遍历config文件的同时,通过调用Vue.component()和<code>createModalWrapper</code>方法,把config中的弹窗组件注册到了全局。目的就是把所有的全屏弹窗都经过<code>createModalWrapper</code>包裹,变成<code>xxxx-modal-wrapper</code>全局组件,并且可以统一地在<code>beforeRouteEnter</code>、<code>beforeRouteLeave</code>的路由钩子里控制modal显隐以及一些modals状态维护的操作。</p><p>首先,<code>createModalWrapper</code>方法接受弹窗组件和组件名,内部声明模板内容和component,并且传递了可以在modal组件内部关闭modal的close方法:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> (<span class="params">{ name, component }</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> template: <span class="string">`<<span class="subst">${component.name}</span> :visible="isShowModal" @close="closeModal"/>`</span>,</span><br><span class="line"> components: { [component.name]: component },</span><br><span class="line"> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>然后,监听<code>$modalManager</code>中对应modal组件的<code>isOpen</code>属性,把这个属性值作为visible字段传递给modal组件的内部,在内部接受后,可以传递给由<code><transition></code>组件包裹的modal最外层div。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">computed: {</span><br><span class="line"> isShowModal() {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.$modalManager.modals[name].isOpen;</span><br><span class="line"> },</span><br><span class="line">},</span><br></pre></td></tr></table></figure></p><p>其次,由于在APP外的浏览器环境下,除了点击页面按钮可以打开关闭全屏弹窗以外,还期望用户在操作浏览器自带的前进后退时,也能实现全屏弹窗切换的效果,所以我们需要在<code>beforeRouteEnter</code>和<code>beforeRouteLeave</code>路由钩子阶段分别调用<code>modalManager</code>对象中的<code>openModal</code>和<code>closeModal</code>方法,这样一来,全屏弹窗变回随着路由的变化而变化。</p><p>但是需要注意的一点是,我期望用户在全屏弹窗的页面下刷新浏览器时,能够回退到上一个主页面(目的是弱化全屏弹窗是一个页面的感觉)。所以在<code>beforeRouteEnter</code>中需要特殊处理调用<code>openModal</code>的时机,即:先判断<code>from.name</code>是否存在,若不存在,则说明<code>beforeRouteEnter</code>钩子是页面刷新进来的,这个时候将页面<code>replace</code>到<code>currentRoute</code>中<code>parendRouteName</code>字段对应的路由页面下即可。</p><p>最后,在弹窗组件的<code>closeModal()</code>方法中,需要手动的调用<code>$modalManager.closeModal()</code>方法,这样就可以在弹窗组件中显示的调用<code>$emit('close)</code>来关闭弹窗。<br>并且<code>closeModal(callback)</code>方法接受一个callback回调,这样就可以在关闭弹窗后执行一些额外的逻辑。</p><blockquote><p>3.5 ModalView函数式组件</p></blockquote><p>上一步的<code>createModalWrapper</code>方法渲染出来的<code>xxxx-modal-wrapper</code>modal包裹组件是在真实的modal组件外最近的一层包裹,目的是统一的处理维护一些状态和路由生命周期等。然而最开始我们说过,我们要解决的问题是:要统一化的处理全屏弹窗在APP外和APP内的差异。即希望:<strong>在APP内,弹窗组件以普通组件的形式直接嵌入父组件;而在APP外,弹窗组件以子路由的形式出现在父组件中。</strong></p><p>而我们又知道,在父组件中,子路由组件需要通过<code><router-view /></code>组件来渲染。所以在最后,我们还需要再编写一个组件来控制父组件中,渲染全屏弹窗的位置是需要直接出现<code>xxxx-modal-wrapper</code>modal包裹组件,还是一个<code><router-view /></code>组件,这就是这个<code>ModalView</code>要做的事情。</p><p><code>ModalView</code>这里采用了vue的函数式组件的形式,props接受一个<code>name</code>字段用于声明<code>[name]-modal-wrapper</code>组件,核心的内容如下:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">props: {</span><br><span class="line"> name: {</span><br><span class="line"> type: <span class="built_in">String</span>,</span><br><span class="line"> <span class="keyword">default</span>: <span class="function"><span class="params">()</span> =></span> <span class="string">''</span>,</span><br><span class="line"> },</span><br><span class="line">},</span><br><span class="line">...</span><br><span class="line">render(createElement) {</span><br><span class="line"> <span class="keyword">const</span> isApp = <span class="keyword">this</span>._modalManagerRoot._modalManager.isApp;</span><br><span class="line"> <span class="keyword">if</span> (isApp) {</span><br><span class="line"> <span class="keyword">return</span> createElement(<span class="string">`<span class="subst">${<span class="keyword">this</span>.name.toLowerCase()}</span>-modal-wrapper`</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> createElement(<span class="string">'router-view'</span>, {</span><br><span class="line"> attrs: {</span><br><span class="line"> name: <span class="keyword">this</span>.name,</span><br><span class="line"> },</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>内容其实非常简单,因为要做的事情很明确:根据当前环境来决定是渲染<code>xxxx-modal-wrapper</code>还是<code><router-view /></code>。</p><p>完成<code>ModalView</code>的编写后,我们要知道这个组件才是在业务代码中需要直接写入页面的组件,所以在这之前,我需要在VueModalManager类的install方法里,全局的注册<code>ModalView</code>组件。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Vue.component(ModalView.name, ModalView);</span><br></pre></td></tr></table></figure></p><blockquote><p>3.6 一切就绪,在业务组件中的使用</p></blockquote><p>到这里为止,整个VueModalManager的设计思路以及几个模块的实现方式,都已经介绍完毕了,上面的部分可以整体封装在插件内部,接下来看看如何在业务代码中使用它。</p><p>首先,我们需要在业务中编写一个modalConfig.js文件,定义好所有需要全屏展示的弹窗的基础信息:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> homeSearch <span class="keyword">from</span> <span class="string">'./components/SearchDialog.vue'</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> {</span><br><span class="line"> debug: <span class="literal">false</span>,</span><br><span class="line"> mode: <span class="string">'app'</span>, <span class="comment">// noapp</span></span><br><span class="line"> modals: [</span><br><span class="line"> {</span><br><span class="line"> name: <span class="string">'homeSearch'</span>,</span><br><span class="line"> component: homeSearch,</span><br><span class="line"> parentRouteName: <span class="string">'home'</span>,</span><br><span class="line"> transitionTime: <span class="number">200</span>,</span><br><span class="line"> },</span><br><span class="line"> ...</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p><p>然后,在app.js中做一些初始化的工作,包括:<br>实例化<code>modalManager</code>;调用<code>Vue.use(VueModalManager)</code>;实例化Vue实例时,传入<code>modalManager</code>;拦截router实例。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> modalConfig <span class="keyword">from</span> <span class="string">'./modalConfig'</span>;</span><br><span class="line"><span class="keyword">import</span> VueModalManager <span class="keyword">from</span> <span class="string">'./VueModalManager'</span>;</span><br><span class="line"><span class="keyword">const</span> modalManager = <span class="keyword">new</span> VueModalManager(modalConfig);</span><br><span class="line">...</span><br><span class="line">Vue.use(VueModalManager); <span class="comment">// 执行插件中的install方法</span></span><br><span class="line">...</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> Vue({</span><br><span class="line"> el: <span class="string">'#_j_app'</span>,</span><br><span class="line"> template: <span class="string">'<router-view></router-view>'</span>,</span><br><span class="line"> store,</span><br><span class="line"> router: modalManager.wrapRouter(router),</span><br><span class="line"> modalManager,</span><br><span class="line">});</span><br></pre></td></tr></table></figure></p><p>接下来,需要在页面中声明全屏弹窗。因为已经在插件中去全局定义了ModalView组件,所以只需要直接写<code>modalView</code>组件,并且传递一个映射了modalConfig中配置的modal名称的name字段即可;<br><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">modal-view</span> <span class="attr">name</span>=<span class="string">"homeSearch"</span> /></span></span><br></pre></td></tr></table></figure></p><p>最后,在父组件中调用<code>$modalManager.openModal</code>方法可以开启弹窗;弹窗组件内部直接<code>$emit('close')</code>就可以关闭弹窗了。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 父组件:</span></span><br><span class="line"><span class="keyword">this</span>.$modalManager.openModal(<span class="string">'homeSearch'</span>);</span><br></pre></td></tr></table></figure></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SearchDialog.vue</span></span><br><span class="line">$emit(<span class="string">'close'</span>);</span><br></pre></td></tr></table></figure><h2 id="Part-3-总结"><a href="#Part-3-总结" class="headerlink" title="Part.3 总结"></a>Part.3 总结</h2><p>在前期需求调研中,观察了行业中类似的h5全屏弹窗的处理,确实没有发现一个特别完美且统一的方案。假设拿一个目的地选择的二级页面来说,一个侧滑出来的全屏窗口,痛点就在于到底是是希望强调”弹窗”的概念,还是强调”页面”的概念。如果希望它是一个独立页面,那直接用路由是无可厚非的,但如果不希望这类弹窗是一个单独的路由页(考虑到APP里的统计),那只能根据环境做差异化处理。</p><p>但是,差异化处理的逻辑如果都写到业务代码里,对于项目长期的迭代和维护无疑是一个成本很高的工程。于是就有了开发一个<code>modalManager</code>插件的想法,目的在于提取出全屏弹窗管理的逻辑代码,尽可能地降低业务代码的复杂度,提高可维护性。</p><p>希望文中的思路能在大家日常前端的组件化开发、插件开发中提供一点帮助吧。</p>]]></content>
<categories>
<category> technology project </category>
</categories>
<tags>
<tag> technology project </tag>
</tags>
</entry>
<entry>
<title>OpenResty && Lua学习笔记 👀</title>
<link href="/2019/11/04/9openresty/"/>
<url>/2019/11/04/9openresty/</url>
<content type="html"><![CDATA[<p><img src="/2019/11/04/9openresty/./openresty.svg" width="600px"><br>由于公司C端业务的 Nginx 框架使用的是OpenResty。在上一篇文章学习了 Nginx 基础知识后,决定再学习一下 Lua 和 OpenResty。本文记录了自己学习过程中的一些笔记和总结,尝试了OpenResty在本地搭建简单的API Server框架的demo,包括处理接口路由以及操作 Redis 的的流程。通过学习,未将来可能涉及到OpenResty的开发做一些准备。</p><a id="more"></a><hr><h2 id="Lua"><a href="#Lua" class="headerlink" title="Lua"></a>Lua</h2><p>Lua 是一个小巧的脚本语言。是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组,由 Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo 所组成并于 1993 年开发。其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua 由标准 C 编写而成,几乎在所有操作系统和平台上都可以编译、运行。Lua 并没有提供强大的库,这是由它的定位决定的。所以 Lua 不适合作为开发独立应用程序的语言。Lua 有一个同时进行的 JIT 项目,提供在特定平台上的即时编译功能。</p><p>Lua 脚本可以很容易的被 C/C++ 代码调用,也可以反过来调用 C/C++ 的函数,这使得 Lua 在应用程序中可以被广泛应用。不仅仅作为扩展脚本,也可以作为普通的配置文件,代替 XML、ini 等文件格式,并且更容易理解和维护。标准 Lua 5.1 解释器由标准 C 编写而成,代码简洁优美,几乎在所有操作系统和平台上都可以编译和运行;一个完整的标准 Lua 5.1 解释器不足 200KB。而本书推荐使用的 LuaJIT 2 的代码大小也只有不足 500KB,同时也支持大部分常见的体系结构。在目前所有脚本语言引擎中,LuaJIT 2 实现的速度应该算是最快的之一。这一切都决定了 Lua 是作为嵌入式脚本的最佳选择。</p><p>Lua 语言的各个版本是不相兼容的。因此本书只介绍 Lua 5.1 语言,这是为标准 Lua 5.1 解释器和 LuaJIT 2 所共同支持的。LuaJIT 支持的对 Lua 5.1 向后兼容的 Lua 5.2 和 Lua 5.3 的特性,我们也会在方便的时候予以介绍。</p><p><a href="https://www.jianshu.com/p/70769e81d6cf" target="_blank" rel="noopener">安装LuaJIT遇到的问题</a></p><h3 id="Lua基础数据类型"><a href="#Lua基础数据类型" class="headerlink" title="Lua基础数据类型"></a>Lua基础数据类型</h3><ul><li>nil(空):nil 是一种类型,Lua 将 nil 用于表示“无效值”。一个变量在第一次赋值前的默认值是 nil,将 nil 赋予给一个全局变量就等同于删除它。</li><li>boolean(布尔):可选值 true/false;Lua 中 nil 和 false 为“假”,其它所有值均为“真”。<strong>(0和空字符串是”真”)</strong></li><li>number(数字)</li><li>string(字符串)</li><li>table (表):Table 类型实现了一种抽象的“关联数组”,通常实现为一个哈希表、一个数组、或者两者的混合。</li><li>function (函数):在 Lua 中,函数 也是一种数据类型,函数可以存储在变量中,可以通过参数传递给其他函数,还可以作为其他函数的返回值。</li></ul><h3 id="Lua表达式"><a href="#Lua表达式" class="headerlink" title="Lua表达式"></a>Lua表达式</h3><ul><li>算术运算符: + - * / ^ %</li><li>关系运算符: < > <= >= == ~=(不等于)<blockquote><p>Lua 字符串总是会被“内化”,即相同内容的字符串只会被保存一份,因此 Lua 字符串之间的相等性比较可以简化为其内部存储地址的比较。这意味着 Lua 字符串的相等性比较总是为 O(1). 而在其他编程语言中,字符串的相等性比较则通常为 O(n),即需要逐个字节(或按若干个连续字节)进行比较。</p></blockquote></li><li>逻辑运算符: and(与) or(或) not(非) (对于 not,永远只返回 true 或者 false)</li></ul><h3 id="控制结构"><a href="#控制结构" class="headerlink" title="控制结构"></a>控制结构</h3><ul><li><p>if/else</p><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">score = <span class="number">0</span></span><br><span class="line"><span class="keyword">if</span> score == <span class="number">100</span> <span class="keyword">then</span></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Very good!Your score is 100"</span>)</span><br><span class="line"><span class="keyword">elseif</span> score >= <span class="number">60</span> <span class="keyword">then</span></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Congratulations, you have passed it,your score greater or equal to 60"</span>)</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line"> <span class="keyword">if</span> score > <span class="number">0</span> <span class="keyword">then</span></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Your score is better than 0"</span>)</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"My God, your score turned out to be 0"</span>)</span><br><span class="line"> <span class="keyword">end</span> <span class="comment">--与上一示例代码不同的是,此处要添加一个end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure></li><li><p>while</p><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">local</span> t = {<span class="number">1</span>, <span class="number">3</span>, <span class="number">5</span>, <span class="number">8</span>, <span class="number">11</span>, <span class="number">18</span>, <span class="number">21</span>}</span><br><span class="line"><span class="keyword">local</span> i</span><br><span class="line"><span class="keyword">for</span> i, v <span class="keyword">in</span> <span class="built_in">ipairs</span>(t) <span class="keyword">do</span></span><br><span class="line"> <span class="keyword">if</span> <span class="number">11</span> == v <span class="keyword">then</span></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"index["</span> .. i .. <span class="string">"] have right value[11]"</span>)</span><br><span class="line"> <span class="keyword">break</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure></li></ul><h3 id="模块"><a href="#模块" class="headerlink" title="模块"></a>模块</h3><p>一个 Lua 模块的数据结构是用一个 Lua 值(通常是一个 Lua 表或者 Lua 函数)。一个 Lua 模块代码就是一个会返回这个 Lua 值的代码块。 可以使用内建函数 require() 来加载和缓存模块。简单的说,一个代码模块就是一个程序库,可以通过 require 来加载。模块加载后的结果通过是一个 Lua table,这个表就像是一个命名空间,其内容就是模块中导出的所有东西,比如函数和变量。require 函数会返回 Lua 模块加载后的结果,即用于表示该 Lua 模块的 Lua 值。</p><h2 id="OpenResty"><a href="#OpenResty" class="headerlink" title="OpenResty"></a>OpenResty</h2><p>OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。</p><p>OpenResty® 通过汇聚各种设计精良的 Nginx 模块(主要由 OpenResty 团队自主开发),从而将 Nginx 有效地变成一个强大的通用 Web 应用平台。这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。</p><p>OpenResty® 的目标是让你的Web服务直接跑在 Nginx 服务内部,充分利用 Nginx 的非阻塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应。</p><p><a href="https://moonbingbing.gitbooks.io/openresty-best-practices/lua/class.html" target="_blank" rel="noopener">gitbook</a></p><h3 id="与location配合"><a href="#与location配合" class="headerlink" title="与location配合"></a>与location配合</h3><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line">location = /sum {</span><br><span class="line"> # 只允许内部调用</span><br><span class="line"> internal;</span><br><span class="line"></span><br><span class="line"> # 这里做了一个求和运算只是一个例子,可以在这里完成一些数据库、</span><br><span class="line"> # 缓存服务器的操作,达到基础模块和业务逻辑分离目的</span><br><span class="line"> content_by_lua_block {</span><br><span class="line"> ngx.sleep(<span class="number">0.1</span>)</span><br><span class="line"> <span class="keyword">local</span> args = ngx.req.get_uri_args()</span><br><span class="line"> ngx.say(<span class="built_in">tonumber</span>(args.a) + <span class="built_in">tonumber</span>(args.b))</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">location = /subduction {</span><br><span class="line"> internal;</span><br><span class="line"> content_by_lua_block {</span><br><span class="line"> ngx.sleep(<span class="number">0.1</span>)</span><br><span class="line"> <span class="keyword">local</span> args = ngx.req.get_uri_args()</span><br><span class="line"> ngx.<span class="built_in">print</span>(<span class="built_in">tonumber</span>(args.a) - <span class="built_in">tonumber</span>(args.b))</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">location = /app/test_parallels {</span><br><span class="line"> content_by_lua_block {</span><br><span class="line"> <span class="keyword">local</span> start_time = ngx.now()</span><br><span class="line"> <span class="keyword">local</span> res1, res2 = ngx.location.capture_multi( {</span><br><span class="line"> {<span class="string">"/sum"</span>, {args={a=<span class="number">3</span>, b=<span class="number">8</span>}}},</span><br><span class="line"> {<span class="string">"/subduction"</span>, {args={a=<span class="number">3</span>, b=<span class="number">8</span>}}}</span><br><span class="line"> })</span><br><span class="line"> ngx.say(<span class="string">"status:"</span>, res1.<span class="built_in">status</span>, <span class="string">" response:"</span>, res1.body)</span><br><span class="line"> ngx.say(<span class="string">"status:"</span>, res2.<span class="built_in">status</span>, <span class="string">" response:"</span>, res2.body)</span><br><span class="line"> ngx.say(<span class="string">"time used:"</span>, ngx.now() - start_time)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">location = /app/test_queue {</span><br><span class="line"> content_by_lua_block {</span><br><span class="line"> <span class="keyword">local</span> start_time = ngx.now()</span><br><span class="line"> <span class="keyword">local</span> res1 = ngx.location.capture_multi( {</span><br><span class="line"> {<span class="string">"/sum"</span>, {args={a=<span class="number">3</span>, b=<span class="number">8</span>}}}</span><br><span class="line"> })</span><br><span class="line"> <span class="keyword">local</span> res2 = ngx.location.capture_multi( {</span><br><span class="line"> {<span class="string">"/subduction"</span>, {args={a=<span class="number">3</span>, b=<span class="number">8</span>}}}</span><br><span class="line"> })</span><br><span class="line"> ngx.say(<span class="string">"status:"</span>, res1.<span class="built_in">status</span>, <span class="string">" response:"</span>, res1.body)</span><br><span class="line"> ngx.say(<span class="string">"status:"</span>, res2.<span class="built_in">status</span>, <span class="string">" response:"</span>, res2.body)</span><br><span class="line"> ngx.say(<span class="string">"time used:"</span>, ngx.now() - start_time)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>利用 ngx.location.capture_multi 函数,直接完成了两个子请求并行执行。当两个请求没有相互依赖,这种方法可以极大提高查询效率。</p><h3 id="获取uri参数"><a href="#获取uri参数" class="headerlink" title="获取uri参数"></a>获取uri参数</h3><h4 id="获取请求uri参数"><a href="#获取请求uri参数" class="headerlink" title="获取请求uri参数"></a>获取请求uri参数</h4><ul><li>方法<code>ngx.req.get_uri_args</code>:获取来自 uri 请求的参数</li><li>方法<code>ngx.req.get_post_args</code>:获取来自 post 请求的内容<figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">location /print_param {</span><br><span class="line"> content_by_lua_block {</span><br><span class="line"> <span class="keyword">local</span> <span class="built_in">arg</span> = ngx.req.get_uri_args()</span><br><span class="line"> <span class="keyword">for</span> k,v <span class="keyword">in</span> <span class="built_in">pairs</span>(<span class="built_in">arg</span>) <span class="keyword">do</span></span><br><span class="line"> ngx.say(<span class="string">"[GET ] key:"</span>, k, <span class="string">" v:"</span>, v)</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> ngx.req.read_body() <span class="comment">-- 解析 body 参数之前一定要先读取 body</span></span><br><span class="line"> <span class="keyword">local</span> <span class="built_in">arg</span> = ngx.req.get_post_args()</span><br><span class="line"> <span class="keyword">for</span> k,v <span class="keyword">in</span> <span class="built_in">pairs</span>(<span class="built_in">arg</span>) <span class="keyword">do</span></span><br><span class="line"> ngx.say(<span class="string">"[POST] key:"</span>, k, <span class="string">" v:"</span>, v)</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h4 id="传递请求uri参数"><a href="#传递请求uri参数" class="headerlink" title="传递请求uri参数"></a>传递请求uri参数</h4><ul><li>方法<code>ngx.encode_args</code>:进行规则转义<figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">location /test {</span><br><span class="line"> content_by_lua_block {</span><br><span class="line"> <span class="keyword">local</span> res = ngx.location.capture(</span><br><span class="line"> <span class="string">'/print_param'</span>,</span><br><span class="line"> {</span><br><span class="line"> method = ngx.HTTP_POST,</span><br><span class="line"> args = ngx.encode_args({a = <span class="number">1</span>, b = <span class="string">'2&'</span>}),</span><br><span class="line"> body = ngx.encode_args({c = <span class="number">3</span>, d = <span class="string">'4&'</span>})</span><br><span class="line"> }</span><br><span class="line"> )</span><br><span class="line"> ngx.say(res.body)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h3 id="获取请求body"><a href="#获取请求body" class="headerlink" title="获取请求body"></a>获取请求body</h3><p>在 Nginx 的典型应用场景中,几乎都是只读取 HTTP 头即可,例如负载均衡、正反向代理等场景。但是对于 API Server 或者 Web Application ,对 body 可以说就比较敏感了。由于 OpenResty 基于 Nginx ,所以天然的对请求 body 的读取细节与其他成熟 Web 框架有些不同。 </p><ul><li>方法<code>ngx.req.get_body_data</code>:获取请求的body部分<blockquote><p>注意:获取body部分需要添加指令 <code>lua_need_request_body on;</code>,因为主要是 Nginx 诞生之初主要是为了解决负载均衡情况,而这种情况,是不需要读取 body 就可以决定负载策略的,所以默认是不能获取请求体的;</p><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"># 默认读取 body</span><br><span class="line">lua_need_request_body on;</span><br><span class="line"></span><br><span class="line">location /test {</span><br><span class="line"> content_by_lua_block {</span><br><span class="line"> <span class="keyword">local</span> data = ngx.req.get_body_data()</span><br><span class="line"> ngx.say(<span class="string">"hello "</span>, data)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></blockquote></li></ul><p>如果你只是某个接口需要读取 body(并非全局行为),那么这时候也可以显示调用 <code>ngx.req.read_body()</code> 接口:<br><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">location /test {</span><br><span class="line"> content_by_lua_block {</span><br><span class="line"> ngx.req.read_body()</span><br><span class="line"> <span class="keyword">local</span> data = ngx.req.get_body_data()</span><br><span class="line"> ngx.say(<span class="string">"hello "</span>, data)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h3 id="输出响应体"><a href="#输出响应体" class="headerlink" title="输出响应体"></a>输出响应体</h3><p>在OpenResty 中调用<code>ngx.say</code> 与 <code>ngx.print</code>可以输出响应体,二者区别在于:<code>ngx.say</code> 输出的响应体会多一个换行符 \n。</p><blockquote><p>注意<code>ngx.say</code> 与 <code>ngx.print</code> 都是异步输出</p></blockquote><h4 id="处理响应体过大的输出"><a href="#处理响应体过大的输出" class="headerlink" title="处理响应体过大的输出"></a>处理响应体过大的输出</h4><ul><li><p>输出内容本身体积很大,例如超过 2G 的文件下载<br>利用 HTTP 1.1 特性 CHUNKED 编码,把一个大的响应体拆分成多个小的应答体,分批、有节制的响应给请求方。</p></li><li><p>输出内容本身是由各种碎片拼凑的,碎片数量庞大,例如应答数据是某地区所有人的姓名<br>利用 ngx.print 输入参数可以是单个或多个字符串参数,也可以是 table 对象的这一特性,把碎片数据直接存放在 table 中,用数组的方式把这些碎片数据统一起来,直接调用 ngx.print(table)即可。</p></li></ul><h3 id="简单API-Server框架"><a href="#简单API-Server框架" class="headerlink" title="简单API Server框架"></a>简单API Server框架</h3><ul><li>首先,配置多个API时,为了保持 nginx 配置文件的简洁,要把这些接口的实现放到各自的独立<code>lua</code>文件中;</li><li>其次,对每个 API 都写一个 location会让Nginx配置变得复杂,所以需要把它们都合并到一个location配置中;</li></ul><p>首先,定义lua文件搜索路径,设置location正则匹配,根据匹配到的路由名称指向对应的lua文件;<br><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">http {</span><br><span class="line"> # 设置默认 lua 搜索路径,添加 lua 路径</span><br><span class="line"> # 此处写相对路径时,对启动 nginx 的路径有要求,必须在 nginx 目录下启动,<span class="built_in">require</span> 找不到</span><br><span class="line"> # comm.param 绝对路径当然也没问题,但是不可移植,因此应使用变量 $prefix 或</span><br><span class="line"> # ${prefix},OR 会替换为 nginx 的 prefix <span class="built_in">path</span></span><br><span class="line"> # lua_package_path <span class="string">'lua/?.lua;/blah/?.lua;;'</span>;</span><br><span class="line"> lua_package_path <span class="string">'$prefix/lua/?.lua;/blah/?.lua;;'</span>;</span><br><span class="line"> server {</span><br><span class="line"> listen <span class="number">80</span>;</span><br><span class="line"> location ~ ^/api/([-_a-zA-Z0<span class="number">-9</span>/]+) {</span><br><span class="line"> # 准入阶段完成参数验证 (断言??)</span><br><span class="line"> access_by_lua_file lua/access_check.lua;</span><br><span class="line"> #内容生成阶段</span><br><span class="line"> content_by_lua_file lua/$<span class="number">1.</span>lua;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>第二步,新建实现接口的lua文件,编写接口逻辑,输出响应体;<br><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">--========== {$prefix}/lua/addition.lua</span></span><br><span class="line"><span class="keyword">local</span> args = ngx.req.get_uri_args()</span><br><span class="line">ngx.say(args.a + args.b)</span><br></pre></td></tr></table></figure></p><p>最后,还可以加一个参数的检查验证的逻辑;<br><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">--========== {$prefix}/lua/comm/param.lua</span></span><br><span class="line"><span class="keyword">local</span> _M = {}</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 对输入参数逐个进行校验,只要有一个不是数字类型,则返回 false</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">_M.is_number</span><span class="params">(...)</span></span></span><br><span class="line"> <span class="keyword">local</span> <span class="built_in">arg</span> = {...}</span><br><span class="line"></span><br><span class="line"> <span class="keyword">local</span> num</span><br><span class="line"> <span class="keyword">for</span> _,v <span class="keyword">in</span> <span class="built_in">ipairs</span>(<span class="built_in">arg</span>) <span class="keyword">do</span></span><br><span class="line"> num = <span class="built_in">tonumber</span>(v)</span><br><span class="line"> <span class="keyword">if</span> <span class="literal">nil</span> == num <span class="keyword">then</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> _M</span><br><span class="line"></span><br><span class="line"><span class="comment">--========== {$prefix}/lua/access_check.lua</span></span><br><span class="line"><span class="keyword">local</span> param= <span class="built_in">require</span>(<span class="string">"comm.param"</span>)</span><br><span class="line"><span class="keyword">local</span> args = ngx.req.get_uri_args()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> args.a <span class="keyword">or</span> <span class="keyword">not</span> args.b <span class="keyword">or</span> <span class="keyword">not</span> param.is_number(args.a, args.b) <span class="keyword">then</span></span><br><span class="line"> ngx.<span class="built_in">exit</span>(ngx.HTTP_BAD_REQUEST)</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure></p><h3 id="子查询"><a href="#子查询" class="headerlink" title="子查询"></a>子查询</h3><p>Nginx 子请求是一种非常强有力的方式,它可以发起非阻塞的内部请求访问目标 location。但是子请求只是模拟 HTTP 接口的形式, 没有额外的 HTTP/TCP 流量,也没有 IPC (进程间通信) 调用,所有工作在内部高效地在 C 语言级别完成。API方法<code>ngx.location.capture</code>和<code>ngx.location.capture_multi</code>。</p><h3 id="连接Redis"><a href="#连接Redis" class="headerlink" title="连接Redis"></a>连接Redis</h3><p>首先将连接Redis的方法封装在redis文件夹下的main.lua中,并导出redis连接实例对象:<br><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">local</span> Redis = {}</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Redis.connect</span><span class="params">()</span></span></span><br><span class="line"> <span class="comment">-- 引入redis包</span></span><br><span class="line"> <span class="keyword">local</span> redis = <span class="built_in">require</span> <span class="string">"resty.redis"</span></span><br><span class="line"> <span class="comment">-- 创建新的redis连接</span></span><br><span class="line"> <span class="keyword">local</span> red = redis:new()</span><br><span class="line"> <span class="comment">-- 设置redis连接超时时间</span></span><br><span class="line"> red:set_timeout(<span class="number">1000</span>) <span class="comment">-- 1 sec</span></span><br><span class="line"> <span class="comment">-- 通过ip地址和端口建立连接</span></span><br><span class="line"> <span class="keyword">local</span> ok, err = red:connect(<span class="string">"127.0.0.1"</span>, <span class="number">6379</span>)</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> ok <span class="keyword">then</span></span><br><span class="line"> ngx.say(<span class="string">"failed to connect: "</span>, err)</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">return</span> red</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="keyword">return</span> Redis</span><br></pre></td></tr></table></figure></p><p>然后再新建一个redis操作文件,引用上面导出的redis连接对象,做一些对键值对的 set 和 get 操作(其实,还可以单独封装set、get方法),尝试操作 redis:<br><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">local</span> Redis = <span class="built_in">require</span>(<span class="string">"redis.main"</span>) <span class="comment">-- 引用上面导出的redis连接对象</span></span><br><span class="line"><span class="keyword">local</span> red = Redis.connect() <span class="comment">-- 调用connect连接方法</span></span><br><span class="line"><span class="comment">-- 获取url参数</span></span><br><span class="line"><span class="keyword">local</span> args = ngx.req.get_uri_args()</span><br><span class="line"><span class="keyword">local</span> key = args.key</span><br><span class="line"><span class="keyword">local</span> value = args.value</span><br><span class="line"></span><br><span class="line"><span class="comment">-- set一个键值对</span></span><br><span class="line">ok, err = red:set(key, value)</span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> ok <span class="keyword">then</span></span><br><span class="line"> ngx.say(<span class="string">"failed to set key: "</span>, err)</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line">ngx.say(<span class="string">"set "</span>, key, <span class="string">": "</span>, ok)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 取出刚刚set的键值对</span></span><br><span class="line"><span class="keyword">local</span> res, err = red:get(key)</span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> res <span class="keyword">then</span></span><br><span class="line"> ngx.say(<span class="string">"failed to get key: "</span>, err)</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line">ngx.say(<span class="string">"get "</span>, key, <span class="string">": "</span>, res)</span><br></pre></td></tr></table></figure></p><p>最后,在Nginx.conf里配置location的Lua代码块:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">location ~ ^<span class="regexp">/api/</span>([-_a-zA-Z0<span class="number">-9</span>/]+) {</span><br><span class="line"> access_by_lua_file lua/access_check.lua;</span><br><span class="line"> content_by_lua_file lua/$<span class="number">1.</span>lua;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><!-- reload: nginx -p `pwd`/ -c conf/nginx.conf -s reload查看:ps -ef | grep nginx -->]]></content>
<categories>
<category> study notes </category>
</categories>
<tags>
<tag> study notes </tag>
</tags>
</entry>
<entry>
<title>Nginx 相关知识点总结👀</title>
<link href="/2019/10/10/8nginx/"/>
<url>/2019/10/10/8nginx/</url>
<content type="html"><![CDATA[<p><img src="/2019/10/10/8nginx/./nginx.png" width="600px"><br>由于在近期工作中,前端微服务项目需要自己写一些静态资源访问的相关Nginx配置,而作为前端开发人员,对Nginx这块技术一直缺乏比较系统的了解,所以在这里抽空整理了一些Nginx相关的基本知识。</p><a id="more"></a><hr><h2 id="Nginx基本概念"><a href="#Nginx基本概念" class="headerlink" title="Nginx基本概念"></a>Nginx基本概念</h2><p>Nginx 是一款轻量级、高性能、跨平台的Web服务器。</p><ul><li>更快:在正常情况下,单次请求会得到更快的响应;在高峰期(如有数以万计的并发请求),<code>Nginx</code>可以比其他Web服务器更快地响应请求。</li><li>高扩展性:设计极具扩展性,它完全是由多个不同功能、不同层次、不同类型且耦合度极低的模块组成。</li><li>高可靠性:<code>Nginx</code>的高可靠性来自于其核心框架代码的优秀设计、模块设计的简单性。</li><li>低内存消耗:10000个非活跃的<code>HTTP</code> <code>Keep-Alive</code>连接在<code>Nginx</code>中仅消耗2.5MB的内存。</li><li>高并发:单机支持10万以上的并发连接。</li><li>热部署:<code>master</code>管理进程与<code>worker</code>工作进程的分离设计,使得<code>Nginx</code>能够提供热部署功能,即可以在7×24小时不间断服务的前提下,升级<code>Nginx</code>的可执行文件。</li></ul><p>Nginx常用功能:<strong>HTTP反向代理服务器</strong>、<strong>负载均衡服务器</strong>。</p><h2 id="反向代理"><a href="#反向代理" class="headerlink" title="反向代理"></a>反向代理</h2><h3 id="正向代理-ForwordProxy"><a href="#正向代理-ForwordProxy" class="headerlink" title="正向代理 ( ForwordProxy )"></a>正向代理 ( ForwordProxy )</h3><p>正向代理是一个位于客户端和目标服务器之间的代理服务器,客户端向代理服务器发送一个请求,并且<strong>指定目标服务器</strong>,然后代理服务器向目标服务器转交并将获得的数据返回给客户端,正向代理需要客户端进行一些特殊的设置。</p><p><img src="/2019/10/10/8nginx/./zxdl.jpg" width="350px"></p><blockquote><ul><li>正向代理需要客户端<strong>主动设置代理服务器</strong>ip或者域名进行访问,由设置的服务器ip或者域名去获取内容并返回;</li><li>正向代理是<strong>代理客户端</strong>,为客户端收发请求。使真实客户端对服务器不可见;</li><li>翻墙(VPN)</li></ul></blockquote><h3 id="反向代理-ReverseProxy"><a href="#反向代理-ReverseProxy" class="headerlink" title="反向代理 ( ReverseProxy )"></a>反向代理 ( ReverseProxy )</h3><p>反向代理是代理服务端,对于客户端来说,反向代理的过程是一个黑盒。客户端发送请求到代理服务器,代理服务器来决定具体请求哪个目标服务器,并将请求转交给客户端,客户端并不会感知到反向代理后面的服务,甚至可以将反向代理服务器当做真正的目标服务器,不需要客户端做任何的设置。</p><p><img src="/2019/10/10/8nginx/./fxdl.jpg" width="350px"></p><blockquote><ul><li>反向代理不需要客户端做任何设置,直接访问真实的ip或域名,服务器内部会决定最终访问哪台真实的服务器机器;</li><li>反向代理是<strong>代理服务器端</strong>,为服务器收发请求,使真实服务器对客户端不可见;</li><li>保护和隐藏原始资源服务器 / 负载均衡 / 缓存静态内容 / 外网发布 / (webpack-devServer proxy)</li></ul></blockquote><h2 id="负载均衡"><a href="#负载均衡" class="headerlink" title="负载均衡"></a>负载均衡</h2><p>负载均衡 ( Load Balancing ) 是高可用架构的一个关键组件,主要用来提高性能和可用性,通过负载均衡将流量分发到多个服务器,同时多服务器能够消除这部分的单点故障。</p><p><img src="/2019/10/10/8nginx/./load_balancer.jpg" width="400px"></p><p>但负载均衡器本身就是一个单点故障隐患,其中一个解决方案就是双机热备。</p><p><img src="/2019/10/10/8nginx/./hot_standby.gif" width="400px"></p><h3 id="负载均衡算法"><a href="#负载均衡算法" class="headerlink" title="负载均衡算法"></a>负载均衡算法</h3><p>Nginx 的负载均衡模块目前支持 6 种调度算法,下面进行分别介绍,其中后两项属于第三方调度算法。</p><ul><li>轮询(默认):每个请求按时间顺序逐一分配到不同的后端服务器,如果后端某台服务器宕机,故障系统被自动剔除,使用户访问不受影响。Weight 指定轮询权值,Weight 值越大,分配到的访问机率越高,主要用于后端每个服务器性能不均的情况下。</li><li>ip_hash:每个请求按访问 IP 的 hash 结果分配,这样来自同一个 IP 的访客固定访问一个后端服务器,有效解决了动态网页存在的 session 共享问题。</li><li>fair:这是比上面两个更加智能的负载均衡算法。此种算法可以依据页面大小和加载时间长短智能地进行负载均衡,也就是根据后端服务器的响应时间来分配请求,响应时间短的优先分配。Nginx 本身是不支持 fair 的,如果需要使用这种调度算法,必须下载 Nginx 的 upstream_fair 模块。</li><li>url_hash:此方法按访问 url 的 hash 结果来分配请求,使每个 url 定向到同一个后端服务器,可以进一步提高后端缓存服务器的效率。Nginx 本身是不支持 url_hash 的,如果需要使用这种调度算法,必须安装 Nginx 的 hash 软件包。</li><li>least_conn:最少连接负载均衡算法,简单来说就是每次选择的后端都是当前最少连接的一个 server(这个最少连接不是共享的,是每个 worker 都有自己的一个数组进行记录后端 server 的连接数)。</li><li>hash:这个 hash 模块又支持两种模式 hash, 一种是普通的 hash, 另一种是一致性 hash(consistent)。</li></ul><h2 id="Master-Worker模式"><a href="#Master-Worker模式" class="headerlink" title="Master-Worker模式"></a>Master-Worker模式</h2><p><img src="/2019/10/10/8nginx/./master_worker.jpg" width="400px"></p><p>Nginx启动后,会有一个<code>master</code>进程和多个相互独立的<code>worker</code>进程。</p><ul><li>其中,master进程负责读取并验证配置文件<code>nginx.conf</code>、管理<code>worker</code>进程、接收来自外界的信号,向各<code>worker</code>进程发送信号(每个进程都有可能来处理这个连接)、监控<code>worker</code>进程的运行状态,当<code>worker</code>进程退出后(异常情况下),会自动启动新的<code>worker</code>进程。</li><li>而每一个<code>worker</code>进程负责维护一个线程(避免线程切换)、处理连接和请求。<code>worker</code>进程的个数由配置文件决定,一般会设置成机器<code>cpu</code>核数,有利于进程切换(因为更多的worker 数,只会导致进程相互竞争 cpu,从而带来不必要的上下文切换)。</li></ul><h2 id="常用配置"><a href="#常用配置" class="headerlink" title="常用配置"></a>常用配置</h2><p>Nginx配置文件分为几个部分:</p><ul><li>全局块:配置影响Nginx全局的指令。例如:运行nginx服务器的用户组,nginx进程运行文件存放地址,日志存放路径,配置文件引入,允许生成worker process进程数等。</li><li>events块:配置影响nginx服务器或与用户的网络连接。有每个进程的最大连接数,选取哪种事件驱动模型处理连接请求,是否允许同时接受多个网路连接,开启多个网络连接序列化等。</li><li>http块:可以嵌套多个server,配置代理,缓存,日志定义等绝大多数功能和第三方模块的配置。如文件引入,mime-type定义,日志自定义,是否使用sendfile传输文件,连接超时时间,单连接请求数等。</li><li>server块:配置虚拟主机的相关参数,一个http中可以有多个server。</li><li>location块:配置请求的路由,以及各种页面的处理情况。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"># 全局块</span><br><span class="line">... </span><br><span class="line"># events块</span><br><span class="line">events { </span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"># http块</span><br><span class="line">http </span><br><span class="line">{</span><br><span class="line"> # http全局块</span><br><span class="line"> ... </span><br><span class="line"> # 虚拟主机server块</span><br><span class="line"> server </span><br><span class="line"> { </span><br><span class="line"> # server全局块</span><br><span class="line"> ... </span><br><span class="line"> # location块</span><br><span class="line"> location [PATTERN] </span><br><span class="line"> {</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"> location [PATTERN] </span><br><span class="line"> {</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> server</span><br><span class="line"> {</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"> # http全局块</span><br><span class="line"> ... </span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="虚拟主机与请求的分发-sever"><a href="#虚拟主机与请求的分发-sever" class="headerlink" title="虚拟主机与请求的分发(sever)"></a>虚拟主机与请求的分发(sever)</h3><p>由于IP地址的数量有限,因此经常存在多个主机域名对应着同一个IP地址的情况,这时在<code>nginx.conf</code>中就可以按照<code>server_name</code>(对应用户请求中的主机域名)并通过<code>server</code>块来定义<strong>虚拟主机</strong>,每个<code>server</code>块就是一个<strong>虚拟主机</strong>,它只处理与之相对应的主机域名请求。这样,一台服务器上的Nginx就能以不同的方式处理访问不同主机域名的HTTP请求了。 </p><h4 id="监听端口"><a href="#监听端口" class="headerlink" title="监听端口"></a>监听端口</h4><p>监听端口: 决定Nginx服务如何监听端口。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">server {</span><br><span class="line"> listen <span class="number">80</span>; <span class="comment">// 默认</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h4 id="主机名称"><a href="#主机名称" class="headerlink" title="主机名称"></a>主机名称</h4><p>主机名称,在开始处理一个HTTP请求时,Nginx会取出header头中的Host,与每个server中的server_name进行匹配,以此决定到底由哪一个server块来处理这个请求。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">server {</span><br><span class="line"> server_name www.domain1.com;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h4 id="URI路径匹配"><a href="#URI路径匹配" class="headerlink" title="URI路径匹配"></a>URI路径匹配</h4><p><code>location</code>会尝试根据用户请求中的URI来和配置进行匹配,如果匹配到了对应的<code>location</code>块,就选择用<code>location{}</code>块中的配置来处理用户这次请求。</p><ul><li><p><code>=</code> 表示匹配URI时,是完全匹配:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">location = <span class="regexp">/feature {</span></span><br><span class="line"><span class="regexp"> /</span><span class="regexp">/ 只有当请求是/</span>feature时,才会匹配成功</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p><code>~</code> 表示匹配URI时是字母大小写敏感的;</p></li><li><code>~*</code> 表示匹配URI时忽略字母大小写;</li><li><p><code>^~</code> 表示匹配URI时只需要其前半部分与uri参数匹配即可:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">location ^~ <span class="regexp">/images/</span> {</span><br><span class="line"> <span class="comment">// 所有images开头的都会匹配到,例如/images/activity</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>还可以使用正则匹配:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">location ~* \.(gif|jpg|png)$ {</span><br><span class="line"> <span class="comment">// 匹配以.gif/.jpg/.png结尾的请求</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>匹配所有请求:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">location / {</span><br><span class="line"> <span class="comment">// 可以匹配所有请求</span></span><br><span class="line"> <span class="comment">// 由于匹配是根据配置的顺序从上往下找的,所以`location / {}`一般写在最后</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h3 id="文件路径的定义"><a href="#文件路径的定义" class="headerlink" title="文件路径的定义"></a>文件路径的定义</h3><h4 id="资源根目录"><a href="#资源根目录" class="headerlink" title="资源根目录"></a>资源根目录</h4><ul><li>以root方式设置资源路径(根目录)<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">location /download/ {</span><br><span class="line"> root /home/app/;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><p>在上面的配置中,如果有一个请求的URI是<code>/download/main.html</code>,那么Web服务器将会返回服务器上<code>/home/app/download/main.html</code>文件的内容。</p><ul><li><p>以alias方式设置资源路径(别名)<br>alias也是用来设置文件资源路径的,它与root的不同点主要在于如何解读紧跟location后面的uri参数,下面配置,请求URI是<code>/conf/nginx.conf</code>,但实际访问的文件是<code>usr/local/nginx/conf/nginx.conf</code>的内容</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">location conf {</span><br><span class="line"> alias usr/local/nginx/conf/;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 用root的方法:</span></span><br><span class="line">location conf {</span><br><span class="line"> root usr/local/nginx/;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>访问首页<br>若访问站点的URI是/,这时一般是返回网站的首页,而这与root和alias都不同,这里用ngx_http_index_module模块提供的index配置实现。index后可以跟多个文件参数,Nginx将会按照顺序来尝试访问这些文件。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">location {</span><br><span class="line"> root path;</span><br><span class="line"> index index.html htmlindex.php /index.php;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>根据HTTP返回码重定向页面<br>当对于某个请求返回错误码时,如果匹配上了error_page中设置的code,则重定向到新的URI中:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">error_page <span class="number">404</span> <span class="number">404.</span>html;</span><br><span class="line">error_page <span class="number">502</span> <span class="number">503</span> <span class="number">504</span> <span class="number">50</span>x.html;</span><br><span class="line">error_page <span class="number">403</span> http:<span class="comment">//example.com/forbidden.html</span></span><br><span class="line"></span><br><span class="line">location / {</span><br><span class="line"> error_page <span class="number">404</span> @fallback; <span class="comment">// 返回404的请求会被反向代理到http://backend 上游服务器中</span></span><br><span class="line">}</span><br><span class="line">location @fallback {</span><br><span class="line"> proxy_pass http:<span class="comment">//backend;</span></span><br><span class="line">}</span><br><span class="line"><span class="comment">// @表示仅用于Nginx服务内部请求之间的重定向,带有@的location不直接处理用户请求。</span></span><br></pre></td></tr></table></figure></li><li><p>try_files<br>try_files后要跟若干路径,如path1 path2…,而且最后必须要有uri参数,意义如下:尝试按照顺序访问每一个path,如果可以有效地读取,就直接向用户返回这个path对应的文件结束请求,否则继续向下访问。如果所有的path都找不到有效的文件,就重定向到最后的参数uri上。因此,最后这个参数uri必须存在,而且它应该是可以有效重定向的。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">try_files main.html $uri $uri/index.html $uri.html @other;</span><br><span class="line">location @other {</span><br><span class="line"> proxy_pass http:<span class="comment">//backend;</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h3 id="负载均衡基本配置"><a href="#负载均衡基本配置" class="headerlink" title="负载均衡基本配置"></a>负载均衡基本配置</h3><ul><li><p>upstream块<br>upstream块定义了一个上游服务器的集群,便于反向代理中的proxy_pass使用,例如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">upstream backend {</span><br><span class="line"> server backend1.example.com;</span><br><span class="line"> server backend2.example.com;</span><br><span class="line"> server backend3.example.com;</span><br><span class="line">}</span><br><span class="line">server {</span><br><span class="line"> location / {</span><br><span class="line"> proxy_pass http:<span class="comment">//backend;</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>server块<br>server配置项指定了一台上游服务器的名字,这个名字可以是域名、IP地址端口、UNIX句柄等,在其后还可以跟一些参数。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">upstream backend {</span><br><span class="line"> server backend1.example.com weight=<span class="number">5</span>; <span class="comment">// 向这台上游服务器转发的权重</span></span><br><span class="line"> server <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">8080</span> max_fails=<span class="number">3</span> fail_timeout=<span class="number">30</span>s; <span class="comment">// 在fail_timeout时间段内,如果向当前的上游服务器转发失败次数超过max_fails次,则认为在当前的fail_timeout时间段内这台上游服务器不可用。</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h3 id="反向代理的基本配置"><a href="#反向代理的基本配置" class="headerlink" title="反向代理的基本配置"></a>反向代理的基本配置</h3><ul><li><p>proxy_pass<br>此配置项将当前请求反向代理到URL参数指定的服务器上,URL可以是主机名或IP地址加端口的形式:</p></li><li><p>proxy_method<br>设置转发时的协议方法名</p></li><li><p>proxy_hide_header<br>任意地指定哪些HTTP头部字段不能被转发</p></li><li><p>proxy_pass_header<br>与proxy_hide_header功能相反,proxy_pass_header会将原来禁止转发的header设置为允许转发</p></li><li><p>proxy_pass_request_body<br>作用为确定是否向上游服务器发送HTTP包体部分, on/off, 默认on</p></li><li><p>proxy_pass_request_headers<br>是否转发HTTP头部, on/off, 默认on</p></li><li><p>proxy_set_header<br>是Nginx设置请求头信息给上游服务器;(注意与add_header区分)</p></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">proxy_pass http:<span class="comment">//localhost:8000/uri/;</span></span><br><span class="line">proxy_method POST;</span><br><span class="line">proxy_hide_header Cache-Control;</span><br><span class="line">proxy_pass_header X-Accel-Redirect;</span><br><span class="line">proxy_pass_request_body on;</span><br><span class="line">proxy_pass_request_headers on;</span><br></pre></td></tr></table></figure><h3 id="添加响应头"><a href="#添加响应头" class="headerlink" title="添加响应头"></a>添加响应头</h3><ul><li>add_header 将自定义的头信息的添加到响应头,是设置给浏览器;<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">add_header Access-Control-Allow-Origin *;</span><br><span class="line">add_header Access-Control-Allow-Methods <span class="string">'GET, POST, OPTIONS'</span>;</span><br></pre></td></tr></table></figure></li></ul><h2 id="收获"><a href="#收获" class="headerlink" title="收获"></a>收获</h2><p>经过这段时间查阅相关文章和书籍,对Nginx有了更多的了解,对反向代理 && 负载均衡也有了一个更具象化的认知,对Nginx的一些常用的配置有了更进一步的了解。</p><blockquote><p><strong>参考文献:</strong><br><em><a href="https://zhuanlan.zhihu.com/p/34943332" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/34943332</a></em><br><em><a href="https://www.jianshu.com/p/208c02c9dd1d" target="_blank" rel="noopener">https://www.jianshu.com/p/208c02c9dd1d</a></em><br><em><a href="https://zhuanlan.zhihu.com/p/24524057" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/24524057</a></em><br><em><a href="https://artislong.oss-cn-hangzhou.aliyuncs.com/files/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Nginx%E6%A8%A1%E5%9D%97%E5%BC%80%E5%8F%91%E4%B8%8E%E6%9E%B6%E6%9E%84%E8%A7%A3%E6%9E%90%E7%AC%AC2%E7%89%88.pdf" target="_blank" rel="noopener">《深入理解Nginx模块开发与架构解析第2版》</a></em><br>*<a href="https://moonbingbing.gitbooks.io/openresty-best-practices/ngx/balancer.html" target="_blank" rel="noopener">OpenResty最佳实践</a></p></blockquote>]]></content>
<categories>
<category> study notes </category>
</categories>
<tags>
<tag> study notes </tag>
</tags>
</entry>
<entry>
<title>横向滚动 && 左滑到底触发 "查看更多" 实现方案比较</title>
<link href="/2019/06/15/7horizontal-scroll/"/>
<url>/2019/06/15/7horizontal-scroll/</url>
<content type="html"><![CDATA[<p><img src="/2019/06/15/7horizontal-scroll/./scroll.jpg" width="600px"><br>本文记录了自己在实现横向滚动 && 左滑到底触发 “查看更多” 的功能时,尝试的几种实现方案,主要解决的问题是Android和ios由于弹性盒子特性的差异而采取的差异化处理。</p><a id="more"></a><hr><h2 id="方案一"><a href="#方案一" class="headerlink" title="方案一"></a>方案一</h2><p>Android:待优化<br>ios:<br>思路:弹性滚动可以超出元素scrollWidth / 监听容器scroll事件 / 达到临界值触发”查看更多“</p><p>实现:监听scroll事件</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">scrollListener() {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.topImageList.length === <span class="number">0</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">let</span> isPreventTrigger = <span class="literal">false</span>; <span class="comment">// 标志位:防止重复触发</span></span><br><span class="line"> <span class="keyword">const</span> dom = <span class="keyword">this</span>.$refs.imgContainer; <span class="comment">// 目标头图容器</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">const</span> viewPortWidth = <span class="built_in">parseInt</span>(<span class="built_in">window</span>.innerWidth</span><br><span class="line"> || <span class="built_in">document</span>.documentElement.clientWidth</span><br><span class="line"> || <span class="built_in">document</span>.body.clientWidth); <span class="comment">// 屏幕视图宽度,当前场景中等于容器可视区域</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">const</span> func = <span class="function"><span class="params">()</span> =></span> { <span class="comment">// handler函数,用于事件绑定和解绑</span></span><br><span class="line"> <span class="keyword">const</span> scrollWidth = <span class="built_in">parseInt</span>(dom.scrollWidth); <span class="comment">// 容器内部可滚动的宽度</span></span><br><span class="line"> <span class="keyword">if</span> (dom.scrollLeft > (scrollWidth - viewPortWidth + <span class="number">80</span>)) {</span><br><span class="line"> <span class="comment">// 容器向左👈滚动距离 + 容器可视宽度 > 容器可滚动宽度 + 80像素</span></span><br><span class="line"> <span class="keyword">if</span> (!isPreventTrigger) {</span><br><span class="line"> <span class="comment">// 并且标志位为false => 触发”查看更多“</span></span><br><span class="line"> <span class="keyword">this</span>.onMoreBtnClick();</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="comment">// 2秒(已经触发了”查看更多”)后,标志位置为false</span></span><br><span class="line"> isPreventTrigger = <span class="literal">false</span>;</span><br><span class="line"> }, <span class="number">2000</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 标志位置为true</span></span><br><span class="line"> isPreventTrigger = <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> <span class="comment">// 目标容器绑定handler函数</span></span><br><span class="line"> dom.addEventListener(<span class="string">'throttleScroll'</span>, func);</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="comment">// 返回一个function,用于解除事件绑定</span></span><br><span class="line"> dom.removeEventListener(<span class="string">'throttleScroll'</span>, func);</span><br><span class="line"> };</span><br><span class="line">},</span><br></pre></td></tr></table></figure><p>遇到的问题:</p><ul><li>绑定事件的时机不保准,需要借助对topImageList的watch / $nextTick / 甚至还要setTimeout才行;</li><li>绑定事件时,存一下remove函数<code>this.removeScrollListener = this.addScrollListener();</code>,并且需要手动在<code>destroyed</code>时执行一下事件解绑;</li><li>ios向左滚动到临界点时立即触发“查看更多”(不确定,记不清了)?期望用户抬起手指时触发,并且可以回滑取消触发;</li><li>Android没有弹性滚动,滚动不可以超出元素scrollWidth,需要另辟蹊径;</li></ul><h2 id="方案二"><a href="#方案二" class="headerlink" title="方案二"></a>方案二</h2><p>ios和Android统一使用hotel-common手势库TouchInertial;<br>大体思路:</p><ul><li>监听容器touch事件,模拟惯性滚动、弹性回弹橡皮筋效果,触发临界点根据事件返回的数据自行判断;</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line">handleEnd() {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">this</span>.hastransition = <span class="literal">true</span>;</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.minSportThreshold - <span class="keyword">this</span>.translateX > <span class="keyword">this</span>.threshold * <span class="number">0.66</span>) {</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="keyword">this</span>.onMoreBtnClick();</span><br><span class="line"> }, <span class="number">100</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">this</span>.translateX = <span class="built_in">Math</span>.min(<span class="keyword">this</span>.maxSportThreshold, <span class="built_in">Math</span>.max(<span class="keyword">this</span>.translateX, <span class="keyword">this</span>.minSportThreshold));</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="keyword">this</span>.hastransition = <span class="literal">false</span>;</span><br><span class="line"> }, <span class="number">300</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">this</span>.scroller.setOptions({</span><br><span class="line"> minSportThreshold: <span class="keyword">this</span>.minSportThreshold,</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> }</span><br><span class="line">},</span><br><span class="line"></span><br><span class="line">bindEvents() {</span><br><span class="line"> <span class="keyword">const</span> el = <span class="keyword">this</span>.$refs.imgContainer;</span><br><span class="line"> <span class="keyword">const</span> viewPortWidth = utils.dom.getClientWidth();</span><br><span class="line"> <span class="keyword">const</span> scrollWidth = el.scrollWidth; <span class="comment">// 这一步需要拿到scrollWidth,需要确保scrollWidth是最终值</span></span><br><span class="line"> <span class="keyword">this</span>.minSportThreshold = viewPortWidth - scrollWidth;</span><br><span class="line"> <span class="keyword">this</span>.scroller = <span class="keyword">new</span> InertialMotion(el, {</span><br><span class="line"> maxSportThreshold: <span class="keyword">this</span>.maxSportThreshold,</span><br><span class="line"> minSportThreshold: <span class="keyword">this</span>.minSportThreshold,</span><br><span class="line"> direction: <span class="string">'x'</span>,</span><br><span class="line"> acceleration: <span class="number">0.00003</span>,</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">this</span>.scroller.on(<span class="string">'touchstart'</span>, () => {</span><br><span class="line"> <span class="keyword">this</span>.hastransition = <span class="literal">false</span>;</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">this</span>.scroller.on(<span class="string">'touchmove'</span>, data => {</span><br><span class="line"> <span class="keyword">const</span> willX = <span class="keyword">this</span>.translateX + data.distX;</span><br><span class="line"> <span class="keyword">if</span> (</span><br><span class="line"> (willX > <span class="keyword">this</span>.minSportThreshold - <span class="keyword">this</span>.threshold && willX < <span class="keyword">this</span>.minSportThreshold)</span><br><span class="line"> || (willX > <span class="keyword">this</span>.maxSportThreshold && willX < <span class="keyword">this</span>.maxSportThreshold + <span class="keyword">this</span>.threshold)</span><br><span class="line"> ) {</span><br><span class="line"> <span class="keyword">this</span>.translateX = <span class="keyword">this</span>.translateX + data.distX / <span class="number">3</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (willX >= <span class="keyword">this</span>.minSportThreshold && willX <= <span class="keyword">this</span>.maxSportThreshold) {</span><br><span class="line"> <span class="keyword">this</span>.translateX = willX;</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">this</span>.scroller.on(<span class="string">'beforeAnimation'</span>, () => {</span><br><span class="line"> <span class="keyword">this</span>.scroller.setOptions({</span><br><span class="line"> sportInitValue: <span class="keyword">this</span>.translateX,</span><br><span class="line"> maxSportThreshold: <span class="keyword">this</span>.maxSportThreshold + <span class="keyword">this</span>.threshold * <span class="number">0.66</span>,</span><br><span class="line"> minSportThreshold: <span class="keyword">this</span>.minSportThreshold - <span class="keyword">this</span>.threshold * <span class="number">0.66</span> + <span class="number">10</span>,</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">this</span>.scroller.on(<span class="string">'animationChange'</span>, distance => {</span><br><span class="line"> <span class="keyword">this</span>.translateX = distance;</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">this</span>.scroller.on(<span class="string">'animationEnd'</span>, <span class="keyword">this</span>.handleEnd.bind(<span class="keyword">this</span>));</span><br><span class="line"> <span class="keyword">this</span>.scroller.on(<span class="string">'tap'</span>, <span class="keyword">this</span>.handleEnd.bind(<span class="keyword">this</span>));</span><br><span class="line">},</span><br></pre></td></tr></table></figure><ul><li>watch<code>topImageList</code>的长度变化。但发现<code>nextTick</code>的回调中不能保证正确地取到容器的scrollWidth值,所以加了一个<code>setTimeout</code>,并添加日志,上报<code>setTimeout</code>前后的数据。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">topImageList(nVal, oVal) {</span><br><span class="line"> <span class="keyword">if</span> (nVal.length !== oVal.length) {</span><br><span class="line"> <span class="keyword">this</span>.$nextTick(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="comment">// *****LOG***** //</span></span><br><span class="line"> <span class="keyword">const</span> swBefore = <span class="keyword">this</span>.$refs.imgContainer.scrollWidth;</span><br><span class="line"> <span class="keyword">const</span> maxBefore = <span class="keyword">this</span>.maxSportThreshold;</span><br><span class="line"> <span class="keyword">const</span> minBefore = <span class="keyword">this</span>.minSportThreshold;</span><br><span class="line"> <span class="keyword">const</span> vwBefore = <span class="keyword">this</span>.$refs.imageWrap.offsetWidth;</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="keyword">this</span>.bindEvents();</span><br><span class="line"> <span class="keyword">const</span> swDelay = <span class="keyword">this</span>.$refs.imgContainer.scrollWidth;</span><br><span class="line"> <span class="keyword">const</span> maxDelay = <span class="keyword">this</span>.maxSportThreshold;</span><br><span class="line"> <span class="keyword">const</span> minDelay = <span class="keyword">this</span>.minSportThreshold;</span><br><span class="line"> <span class="keyword">const</span> vwDelay = <span class="keyword">this</span>.$refs.imageWrap.offsetWidth;</span><br><span class="line"> <span class="keyword">if</span> (swDelay - swBefore > <span class="number">1</span> || vwBefore !== vwDelay) {</span><br><span class="line"> utils.statistics.feError({</span><br><span class="line"> pageType: <span class="string">'detail'</span>,</span><br><span class="line"> errorType: <span class="string">'custom'</span>,</span><br><span class="line"> errorSubType: <span class="string">'scrollWidthNew'</span>,</span><br><span class="line"> errorMessage: <span class="string">'scrollWidth获取不一致'</span>,</span><br><span class="line"> extParams: {</span><br><span class="line"> swBefore,</span><br><span class="line"> swDelay,</span><br><span class="line"> maxBefore,</span><br><span class="line"> maxDelay,</span><br><span class="line"> minBefore,</span><br><span class="line"> minDelay,</span><br><span class="line"> vwBefore,</span><br><span class="line"> vwDelay,</span><br><span class="line"> lengthBefore: oVal.length,</span><br><span class="line"> lengthDelay: nVal.length,</span><br><span class="line"> lengthCurrent: <span class="keyword">this</span>.topImageList.length,</span><br><span class="line"> },</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> <span class="comment">// *****LOG***** //</span></span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line">},</span><br></pre></td></tr></table></figure><p>改进点:</p><ul><li>ios Android统一处理</li><li>手动实现惯性滚动参数可调;</li></ul><p>遇到的问题:</p><ul><li>实例化的时机不好把握。需要确保容器渲染完成后,再实例化,但仍然需要依赖<code>setTimeout</code>;</li><li>同样需要手动在<code>destroyed</code>中进行事件的解绑;</li></ul><h2 id="方案三-🌝"><a href="#方案三-🌝" class="headerlink" title="方案三 🌝"></a>方案三 🌝</h2><p>ios 和 Android都使用原生的滚动,监听touch事件<br>ios:监听touchend事件,当touchend触发时,判断一下是否到达零界点即可;<br>Android:当滚动到最右侧时,开始触发translateX;</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line">onTouchStart(e) {</span><br><span class="line"> <span class="keyword">if</span> (isAndroid) {</span><br><span class="line"> <span class="keyword">this</span>.hasTransition = <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">this</span>.clientX = <span class="built_in">parseInt</span>(e.changedTouches[<span class="number">0</span>].clientX);</span><br><span class="line"> }</span><br><span class="line">},</span><br><span class="line"></span><br><span class="line">onTouchMove(e) {</span><br><span class="line"> <span class="keyword">if</span> (isAndroid) {</span><br><span class="line"> <span class="keyword">const</span> el = <span class="keyword">this</span>.$refs.imgContainer;</span><br><span class="line"> <span class="keyword">const</span> { offsetWidth, scrollLeft, scrollWidth } = el;</span><br><span class="line"> <span class="keyword">const</span> threshold = scrollWidth - (offsetWidth + scrollLeft);</span><br><span class="line"> <span class="keyword">const</span> currentX = <span class="built_in">parseInt</span>(e.changedTouches[<span class="number">0</span>].clientX);</span><br><span class="line"> <span class="keyword">const</span> distance = currentX - <span class="keyword">this</span>.clientX;</span><br><span class="line"> <span class="keyword">this</span>.clientX = currentX;</span><br><span class="line"> <span class="keyword">if</span> (threshold <= <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 已经滑到最右侧,即将触发translateX</span></span><br><span class="line"> <span class="keyword">if</span> (!<span class="keyword">this</span>.isMoving) {</span><br><span class="line"> <span class="keyword">this</span>.isMoving = <span class="literal">true</span>;</span><br><span class="line"> <span class="keyword">this</span>.moveStartX = currentX;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.translateX + distance < <span class="number">-60</span>) {</span><br><span class="line"> <span class="keyword">this</span>.translateX = <span class="number">-60</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">this</span>.translateX + distance >= <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">this</span>.translateX = <span class="number">0</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">this</span>.translateX = <span class="keyword">this</span>.translateX + distance;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (currentX < <span class="keyword">this</span>.moveStartX) {</span><br><span class="line"> <span class="keyword">this</span>.$refs.imgContainer.style.setProperty(<span class="string">'overflow-x'</span>, <span class="string">'hidden'</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">},</span><br><span class="line"></span><br><span class="line">onTouchEnd() {</span><br><span class="line"> <span class="keyword">if</span> (isAndroid) {</span><br><span class="line"> <span class="keyword">this</span>.hasTransition = <span class="literal">true</span>;</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.translateX < <span class="number">-55</span>) {</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="keyword">this</span>.onMoreBtnClick();</span><br><span class="line"> }, <span class="number">150</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">this</span>.moveStartX = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">this</span>.isMoving = <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">this</span>.translateX = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">this</span>.$refs.imgContainer.style.setProperty(<span class="string">'overflow-x'</span>, <span class="string">'auto'</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">const</span> el = <span class="keyword">this</span>.$refs.imgContainer;</span><br><span class="line"> <span class="keyword">const</span> { offsetWidth, scrollLeft, scrollWidth } = el;</span><br><span class="line"> <span class="keyword">const</span> threshold = scrollWidth - (offsetWidth + scrollLeft);</span><br><span class="line"> <span class="keyword">if</span> (threshold < <span class="number">-55</span>) {</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="keyword">this</span>.onMoreBtnClick();</span><br><span class="line"> }, <span class="number">200</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">},</span><br></pre></td></tr></table></figure><p>改进点:</p><ul><li>Android,ios都是使用原生的滚动,体验更加流畅;</li><li>不用考虑在何时绑定事件或者等待容器渲染完毕后创建实例等问题,即不用判断头图容器渲染完毕的时机;</li><li>直接使用vue的v-on指令绑定touchstart、touchmove、touchend,不需要手动销毁事件绑定,组件销毁时,事件自动删除;</li></ul><p>缺陷:</p><ul><li>Android中当滑到临界点后,手动回滚,取消触发“查看更多”事件时,需要重新触发touchstart才能重新触发滚动。</li></ul>]]></content>
<categories>
<category> technology share </category>
</categories>
<tags>
<tag> technology share </tag>
</tags>
</entry>
<entry>
<title>JavaScript编程风格</title>
<link href="/2019/01/23/6js-coding-style/"/>
<url>/2019/01/23/6js-coding-style/</url>
<content type="html"><![CDATA[<p><img src="/2019/01/23/6js-coding-style/./jscs.jpg" width="600px"><br>对于团队协作的开发者,编程规范是很重要的。本文持续收集/整理/更新一些js开发的良好代码规范,当做笔记,规范自己的代码风格,让自己的代码更好看👀。</p><a id="more"></a><hr><h3 id="let-const"><a href="#let-const" class="headerlink" title="let const"></a>let const</h3><p>建议优先使用const,尤其是在全局环境。<br>所有函数都应该设置为const。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>, b = <span class="number">2</span>, c = <span class="number">3</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="keyword">const</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">const</span> b = <span class="number">2</span>;</span><br><span class="line"><span class="keyword">const</span> c = <span class="number">3</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// best</span></span><br><span class="line"><span class="keyword">const</span> [a, b, c] = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br></pre></td></tr></table></figure><h3 id="字符串"><a href="#字符串" class="headerlink" title="字符串"></a>字符串</h3><p>静态字符串一律使用单引号或反引号,不使用双引号,动态字符串使用反引号。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="keyword">const</span> a = <span class="string">"foobar"</span>;</span><br><span class="line"><span class="keyword">const</span> b = <span class="string">'foo'</span> + a + <span class="string">'bar'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// acceptable</span></span><br><span class="line"><span class="keyword">const</span> c = <span class="string">`foobar`</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="keyword">const</span> a = <span class="string">'foobar'</span>;</span><br><span class="line"><span class="keyword">const</span> b = <span class="string">`foo<span class="subst">${a}</span>bar`</span>;</span><br></pre></td></tr></table></figure><h3 id="解构赋值"><a href="#解构赋值" class="headerlink" title="解构赋值"></a>解构赋值</h3><p>用数组成员对变量赋值时,优先使用解构赋值。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="keyword">const</span> first = arr[<span class="number">0</span>];</span><br><span class="line"><span class="keyword">const</span> second = arr[<span class="number">1</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="keyword">const</span> [first, second] = arr;</span><br></pre></td></tr></table></figure></p><p>函数的参数如果是对象的成员,优先使用解构赋值。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getFullName</span>(<span class="params">user</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> firstName = user.firstName;</span><br><span class="line"> <span class="keyword">const</span> lastName = user.lastName;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getFullName</span>(<span class="params">obj</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> { firstName, lastName } = obj;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// best</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getFullName</span>(<span class="params">{ firstName, lastName }</span>) </span>{</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>如果函数返回多个值,优先使用对象的解构赋值,而不是数组的解构赋值。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">processInput</span>(<span class="params">input</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> [left, right, top, bottom];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">processInput</span>(<span class="params">input</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> { left, right, top, bottom };</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> { left, right } = processInput(input);</span><br></pre></td></tr></table></figure></p><h3 id="对象"><a href="#对象" class="headerlink" title="对象"></a>对象</h3><p>单行定义的对象,最后一个成员不以逗号结尾;<br>多行定义的对象,最后一个成员以逗号结尾。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> a = { <span class="attr">k1</span>: v1, <span class="attr">k2</span>: v2 };</span><br><span class="line"><span class="keyword">const</span> b = {</span><br><span class="line"> k1: v1,</span><br><span class="line"> k2: v2,</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p><p>对象尽量静态化,一旦定义不得随意添加新的属性,实在要添加,使用Object.assgin合并<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="keyword">const</span> a = {};</span><br><span class="line">a.x = <span class="number">3</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// if reshape unavoidable</span></span><br><span class="line"><span class="keyword">const</span> a = {};</span><br><span class="line"><span class="built_in">Object</span>.assign(a, { <span class="attr">x</span>: <span class="number">3</span> });</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="keyword">const</span> a = { <span class="attr">x</span>: <span class="literal">null</span> };</span><br><span class="line">a.x = <span class="number">3</span>;</span><br></pre></td></tr></table></figure></p><p>如果对象的属性名是动态的,可以在创造对象的时候就用属性表达式定义,将其与其他属性定义在一起。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="keyword">const</span> obj = {</span><br><span class="line"> id: <span class="number">5</span>,</span><br><span class="line"> name: <span class="string">'San Francisco'</span>,</span><br><span class="line">};</span><br><span class="line">obj[getKey(<span class="string">'enabled'</span>)] = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="keyword">const</span> obj = {</span><br><span class="line"> id: <span class="number">5</span>,</span><br><span class="line"> name: <span class="string">'San Francisco'</span>,</span><br><span class="line"> [getKey(<span class="string">'enabled'</span>)]: <span class="literal">true</span>,</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p><p>对象的属性和方法,尽量采用简洁表达法。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> ref = <span class="string">'some value'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="keyword">const</span> atom = {</span><br><span class="line"> ref: ref,</span><br><span class="line"></span><br><span class="line"> value: <span class="number">1</span>,</span><br><span class="line"></span><br><span class="line"> addValue: <span class="function"><span class="keyword">function</span> (<span class="params">value</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> atom.value + value;</span><br><span class="line"> },</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="keyword">const</span> atom = {</span><br><span class="line"> ref,</span><br><span class="line"></span><br><span class="line"> value: <span class="number">1</span>,</span><br><span class="line"></span><br><span class="line"> addValue(value) {</span><br><span class="line"> <span class="keyword">return</span> atom.value + value;</span><br><span class="line"> },</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p><h3 id="数组"><a href="#数组" class="headerlink" title="数组"></a>数组</h3><p>使用扩展运算符拷贝数组<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="keyword">const</span> len = items.length;</span><br><span class="line"><span class="keyword">const</span> itemsCopy = [];</span><br><span class="line"><span class="keyword">let</span> i;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (i = <span class="number">0</span>; i < len; i++) {</span><br><span class="line"> itemsCopy[i] = items[i];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="keyword">const</span> itemsCopy = [...items];</span><br></pre></td></tr></table></figure></p><p>使用Array.from方法,将类数组的对象转为数组<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> foo = <span class="built_in">document</span>.querySelectorAll(<span class="string">'.foo'</span>);</span><br><span class="line"><span class="keyword">const</span> nodes = <span class="built_in">Array</span>.from(foo);</span><br></pre></td></tr></table></figure></p><h3 id="Map结构"><a href="#Map结构" class="headerlink" title="Map结构"></a>Map结构</h3><p>注意区分Object和Map,只有模拟现实世界的实体对象时,才使用Object。如果只是需要<code>key: value</code>的数据结构,使用Map结构。因为Map有内建的遍历机制。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> map = <span class="keyword">new</span> <span class="built_in">Map</span>(arr);</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> key <span class="keyword">of</span> map.keys()) {</span><br><span class="line"> <span class="built_in">console</span>.log(key);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> value <span class="keyword">of</span> map.values()) {</span><br><span class="line"> <span class="built_in">console</span>.log(value);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> item <span class="keyword">of</span> map.entries()) {</span><br><span class="line"> <span class="built_in">console</span>.log(item[<span class="number">0</span>], item[<span class="number">1</span>]);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h3 id="Class"><a href="#Class" class="headerlink" title="Class"></a>Class</h3><p>总是用Class来写需要prototype的操作,用extends实现继承。</p><h3 id="模块"><a href="#模块" class="headerlink" title="模块"></a>模块</h3><p>使用import替代require。</p><p>使用export替代module.exports。</p><p>export default与普通的export尽量不要混用。</p><h3 id="变量"><a href="#变量" class="headerlink" title="变量"></a>变量</h3><p>使用有意义并且可读性强的变量名<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="keyword">const</span> yyyymmdstr = moment().format(<span class="string">'YYYY/MM/DD'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="keyword">const</span> currentDate = moment().format(<span class="string">'YYYY/MM/DD'</span>);</span><br></pre></td></tr></table></figure></p><p>使用同样的的单词来命名同样类型的变量</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line">getUserInfo();</span><br><span class="line">getClientData();</span><br><span class="line">getCustomerRecord();</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line">getUser();</span><br></pre></td></tr></table></figure><p>使用可搜索的名称: <em>让代码具有可读性和可搜索性是很重要的</em><br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="comment">// What the heck is 86400000 for?</span></span><br><span class="line">setTimeout(blastOff, <span class="number">86400000</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="comment">// 声明它们为大写的常量</span></span><br><span class="line"><span class="keyword">const</span> MILLISECONDS_IN_A_DAY = <span class="number">86400000</span>;</span><br><span class="line">setTimeout(blastOff, MILLISECONDS_IN_A_DAY);</span><br></pre></td></tr></table></figure></p><p>使用有解释性的变量<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="keyword">const</span> address = <span class="string">'One Infinite Loop, Cupertino 95014'</span>;</span><br><span class="line"><span class="keyword">const</span> cityZipCodeRegex = <span class="regexp">/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/</span>;</span><br><span class="line">saveCityZipCode(address.match(cityZipCodeRegex)[<span class="number">1</span>], address.match(cityZipCodeRegex)[<span class="number">2</span>]);</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="keyword">const</span> address = <span class="string">'One Infinite Loop, Cupertino 95014'</span>;</span><br><span class="line"><span class="keyword">const</span> cityZipCodeRegex = <span class="regexp">/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/</span>;</span><br><span class="line"><span class="keyword">const</span> [, city, zipCode] = address.match(cityZipCodeRegex) || [];</span><br><span class="line">saveCityZipCode(city, zipCode);</span><br></pre></td></tr></table></figure></p><p>避免不清晰的mapping <em>明确的比隐式的要好</em><br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="keyword">const</span> locations = [<span class="string">'Austin'</span>, <span class="string">'New York'</span>, <span class="string">'San Francisco'</span>];</span><br><span class="line">locations.forEach(<span class="function">(<span class="params">l</span>) =></span> {</span><br><span class="line"> doStuff();</span><br><span class="line"> doSomeOtherStuff();</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="comment">// Wait, what is `l` for again?</span></span><br><span class="line"> dispatch(l);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="keyword">const</span> locations = [<span class="string">'Austin'</span>, <span class="string">'New York'</span>, <span class="string">'San Francisco'</span>];</span><br><span class="line">locations.forEach(<span class="function">(<span class="params">location</span>) =></span> {</span><br><span class="line"> doStuff();</span><br><span class="line"> doSomeOtherStuff();</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> dispatch(location);</span><br><span class="line">});</span><br></pre></td></tr></table></figure></p><p>不要添加没有必要的多余的文本:<em>如果你的对象名或者类名已经清晰的指明了它的作用,没有必要再在属性名中重复的写多余的内容</em></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="keyword">const</span> Car = {</span><br><span class="line"> carMake: <span class="string">'Honda'</span>,</span><br><span class="line"> carModel: <span class="string">'Accord'</span>,</span><br><span class="line"> carColor: <span class="string">'Blue'</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">paintCar</span>(<span class="params">car</span>) </span>{</span><br><span class="line"> car.carColor = <span class="string">'Red'</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="keyword">const</span> Car = {</span><br><span class="line"> make: <span class="string">'Honda'</span>,</span><br><span class="line"> model: <span class="string">'Accord'</span>,</span><br><span class="line"> color: <span class="string">'Blue'</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">paintCar</span>(<span class="params">car</span>) </span>{</span><br><span class="line"> car.color = <span class="string">'Red'</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>定义默认的参数,而不是在函数中用条件语句定义参数:<em>定义默认参数往往比短竖线条件语句更加清晰,而且最大的区别在于,你的函数仅仅会在参数是<code>undefined</code>的时候才会提供默认参数。而其他的“falsy”值,比如<code>'',false,null,0,NaN</code>都不会被默认值替代。然而,短竖线的条件语句就不是这样。</em><br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createMicrobrewery</span>(<span class="params">name</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> breweryName = name || <span class="string">'Hipster Brew Co.'</span>;</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createMicrobrewery</span>(<span class="params">name = <span class="string">'Hipster Brew Co.'</span></span>) </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h3 id="函数1"><a href="#函数1" class="headerlink" title="函数1"></a>函数1</h3><p>立即执行的函数可以写成箭头函数形式。</p><p>需要使用函数表达式的场合,尽量用箭头函数代替。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// best</span></span><br><span class="line"><span class="keyword">let</span> a = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].map(<span class="function"><span class="params">x</span> =></span> x * x);</span><br></pre></td></tr></table></figure></p><p>用箭头函数取代Function.prototype.bind,简单的、单行的、不会复用的函数,建议使用箭头函数。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="keyword">const</span> self = <span class="keyword">this</span>;</span><br><span class="line"><span class="keyword">const</span> boundMethod = <span class="function"><span class="keyword">function</span>(<span class="params">...params</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> method.apply(self, params);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// acceptable</span></span><br><span class="line"><span class="keyword">const</span> boundMethod = method.bind(<span class="keyword">this</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// best</span></span><br><span class="line"><span class="keyword">const</span> boundMethod = <span class="function">(<span class="params">...params</span>) =></span> method.apply(<span class="keyword">this</span>, params);</span><br></pre></td></tr></table></figure></p><p>函数所有配置项option都应该集中在一个对象中,并且放在函数的最后一参数中,布尔值不要直接当做参数。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">divide</span>(<span class="params">a, b, option = false </span>) </span>{</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">divide</span>(<span class="params">a, b, { option = false } = {}</span>) </span>{</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>不要在函数体内使用arguments变量,使用rest运算符(扩展运算符的逆运算)代替。因为arguments是一个类数组对象,而rest运算符提供的是一个真正的数组。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">concatenateAll</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> args = <span class="built_in">Array</span>.prototype.slice.call(<span class="built_in">arguments</span>);</span><br><span class="line"> <span class="keyword">return</span> args.join(<span class="string">''</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">concatenateAll</span>(<span class="params">...args</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> args.join(<span class="string">''</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>使用默认值语法设置函数参数的默认值,而不是在函数内部设置。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">handleThings</span>(<span class="params">opts</span>) </span>{</span><br><span class="line"> opts = opts || {};</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">handleThings</span>(<span class="params">opts = {}</span>) </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h3 id="函数2"><a href="#函数2" class="headerlink" title="函数2"></a>函数2</h3><p>函数的参数,在理想情况下,定义成少于等于2个。<br><em>限制函数参数的数量是非常重要的,因为这样可以使得测试你的函数更加简单。函数定义超过三个变量时,当你针对每个独立的参数使用大量的不同的情况来测试函数时,会诱发组合性的爆炸。</em></p><p><em>一个或者两个参数是最理想的情况,三个参数的情况要尽量避免。任何更多的参数的情况,参数应该被合并。通常,如果一个函数有超过两个参数,那么使用一个高级的对象当做函数的参数就可以了。</em></p><p><em>为了更清晰的体现函数期望的参数,我们可以使用ES6的解构赋值语法,它的优点在于:</em></p><blockquote><ol><li>函数的参数清晰明了,可以清晰直观地看到函数要用到哪些属性。</li><li>解构赋值拷贝了传递给函数的参数对象的指定的原始值。(ps: 从参数对象中解构出来的对象和数组是没有被拷贝的)</li><li>Linters can warn you about unused properties, which would be impossible without destructuring。(但我尝试了一下,无论有没有解构传参,eslint都会提示unused参数,🤔 奇怪 )</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createMenu</span>(<span class="params">title, body, buttonText, cancellable</span>) </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createMenu</span>(<span class="params">{ title, body, buttonText, cancellable }</span>) </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">createMenu({</span><br><span class="line"> title: <span class="string">'Foo'</span>,</span><br><span class="line"> body: <span class="string">'Bar'</span>,</span><br><span class="line"> buttonText: <span class="string">'Baz'</span>,</span><br><span class="line"> cancellable: <span class="literal">true</span></span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>一个函数应该只做一件事:<em>函数更容易重构,代码更加可读,更加干净。</em><br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">emailClients</span>(<span class="params">clients</span>) </span>{</span><br><span class="line"> clients.forEach(<span class="function">(<span class="params">client</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> clientRecord = database.lookup(client);</span><br><span class="line"> <span class="keyword">if</span> (clientRecord.isActive()) {</span><br><span class="line"> email(client);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">emailActiveClients</span>(<span class="params">clients</span>) </span>{</span><br><span class="line"> clients</span><br><span class="line"> .filter(isActiveClient)</span><br><span class="line"> .forEach(email);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">isActiveClient</span>(<span class="params">client</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> clientRecord = database.lookup(client);</span><br><span class="line"> <span class="keyword">return</span> clientRecord.isActive();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>函数的命名应该表达清楚这个函数做了什么。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">addToDate</span>(<span class="params">date, month</span>) </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> date = <span class="keyword">new</span> <span class="built_in">Date</span>();</span><br><span class="line"><span class="comment">// It's hard to tell from the function name what is added</span></span><br><span class="line">addToDate(date, <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">addMonthToDate</span>(<span class="params">month, date</span>) </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> date = <span class="keyword">new</span> <span class="built_in">Date</span>();</span><br><span class="line">addMonthToDate(<span class="number">1</span>, date);</span><br></pre></td></tr></table></figure></p><p>函数应该只有一层的抽象概念</p><p>移除重复的代码: <em>重复的代码的缺点在于,如果需要修改相同的逻辑,你需要修改不同的好几个地方</em></p><p>使用Object.assign来设置默认对象参数<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad:</span></span><br><span class="line"><span class="keyword">const</span> menuConfig = {</span><br><span class="line"> title: <span class="literal">null</span>,</span><br><span class="line"> body: <span class="string">'Bar'</span>,</span><br><span class="line"> buttonText: <span class="literal">null</span>,</span><br><span class="line"> cancellable: <span class="literal">true</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createMenu</span>(<span class="params">config</span>) </span>{</span><br><span class="line"> config.title = config.title || <span class="string">'Foo'</span>;</span><br><span class="line"> config.body = config.body || <span class="string">'Bar'</span>;</span><br><span class="line"> config.buttonText = config.buttonText || <span class="string">'Baz'</span>;</span><br><span class="line"> config.cancellable = config.cancellable !== <span class="literal">undefined</span> ? config.cancellable : <span class="literal">true</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">createMenu(menuConfig);</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="keyword">const</span> menuConfig = {</span><br><span class="line"> title: <span class="string">'Order'</span>,</span><br><span class="line"> <span class="comment">// User did not include 'body' key</span></span><br><span class="line"> buttonText: <span class="string">'Send'</span>,</span><br><span class="line"> cancellable: <span class="literal">true</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createMenu</span>(<span class="params">config</span>) </span>{</span><br><span class="line"> config = <span class="built_in">Object</span>.assign({</span><br><span class="line"> title: <span class="string">'Foo'</span>,</span><br><span class="line"> body: <span class="string">'Bar'</span>,</span><br><span class="line"> buttonText: <span class="string">'Baz'</span>,</span><br><span class="line"> cancellable: <span class="literal">true</span></span><br><span class="line"> }, config);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">createMenu(menuConfig);</span><br></pre></td></tr></table></figure></p><p>不要把标志位当做函数的参数来传递:<em>函数应该只做一件事情,但标志位的使用,说明你用一个函数来做了不止一件事情。如果你通过一个Boolean值来决定了不同的代码路径,那么请拆分成不同的函数。</em><br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// bad:</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createFile</span>(<span class="params">name, temp</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (temp) {</span><br><span class="line"> fs.create(<span class="string">`./temp/<span class="subst">${name}</span>`</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> fs.create(name);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createFile</span>(<span class="params">name</span>) </span>{</span><br><span class="line"> fs.create(name);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createTempFile</span>(<span class="params">name</span>) </span>{</span><br><span class="line"> createFile(<span class="string">`./temp/<span class="subst">${name}</span>`</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>避免函数的副作用(两个例子)<br>1) 避免修改函数体外的全局变量<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad:</span></span><br><span class="line"><span class="comment">// Global variable referenced by following function.</span></span><br><span class="line"><span class="comment">// If we had another function that used this name, now it'd be an array and it could break it.</span></span><br><span class="line"><span class="keyword">let</span> name = <span class="string">'Ryan McDermott'</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">splitIntoFirstAndLastName</span>(<span class="params"></span>) </span>{</span><br><span class="line"> name = name.split(<span class="string">' '</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">splitIntoFirstAndLastName();</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(name); <span class="comment">// ['Ryan', 'McDermott'];</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">splitIntoFirstAndLastName</span>(<span class="params">name</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> name.split(<span class="string">' '</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> name = <span class="string">'Ryan McDermott'</span>;</span><br><span class="line"><span class="keyword">const</span> newName = splitIntoFirstAndLastName(name);</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(name); <span class="comment">// 'Ryan McDermott';</span></span><br><span class="line"><span class="built_in">console</span>.log(newName); <span class="comment">// ['Ryan', 'McDermott'];</span></span><br></pre></td></tr></table></figure></p><p>2)如果需要处理传递进来的参数,总是拷贝它再编辑它,避免直接修改传递的参数。<br><em>购物车案例:购物车cart是一个包含了所有要购买的物品item的数组,它是purchase请求的一个参数。当网络比较差的时候,purchase接口可能会一直处于尝试发送请求的状态,而在这个时候,如果用户不小心点击了一个不想购买的物品,添加到了购物车,然后这时候开始了网络请求,此时的副作用就是,请求可能会传递修改后的cart对象。</em><br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="keyword">const</span> addItemToCart = <span class="function">(<span class="params">cart, item</span>) =></span> {</span><br><span class="line"> cart.push({ item, <span class="attr">date</span>: <span class="built_in">Date</span>.now() });</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// good:</span></span><br><span class="line"><span class="keyword">const</span> addItemToCart = <span class="function">(<span class="params">cart, item</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> [...cart, { item, <span class="attr">date</span>: <span class="built_in">Date</span>.now() }];</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p><p>不要写全局的函数</p><h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><blockquote><p><a href="http://es6.ruanyifeng.com/#docs/style" target="_blank" rel="noopener">http://es6.ruanyifeng.com/#docs/style</a><br><a href="https://github.com/ryanmcdermott/clean-code-javascript" target="_blank" rel="noopener">https://github.com/ryanmcdermott/clean-code-javascript</a> (译)</p></blockquote>]]></content>
<categories>
<category> study notes </category>
</categories>
<tags>
<tag> study notes </tag>
</tags>
</entry>
<entry>
<title>你不知道的JavaScript(上卷)读书笔记</title>
<link href="/2018/11/20/5you-dont-know-js-1/"/>
<url>/2018/11/20/5you-dont-know-js-1/</url>
<content type="html"><![CDATA[<p><img src="/2018/11/20/5you-dont-know-js-1/./ydkjs.jpg" width="250px"><br>本文记录了我在<a href="https://read.douban.com/ebook/12051836/" title="你不知道的JavaScript(上卷)" target="_blank" rel="noopener">豆瓣读书</a>阅读《你不知道的JavaScript(上卷)》的过程中,对一些重要的内容进行的划线和批注,经常来看看,巩固巩固基础。</p><a id="more"></a><hr><h2 id="章节:1-2-理解作用域"><a href="#章节:1-2-理解作用域" class="headerlink" title="章节:1.2 理解作用域"></a>章节:1.2 理解作用域</h2><blockquote><p>LHS</p></blockquote><p>Left-Hand-Side</p><blockquote><p>RHS</p></blockquote><p>Right-Hand-Side</p><h2 id="章节:4-1-先有鸡还是先有蛋"><a href="#章节:4-1-先有鸡还是先有蛋" class="headerlink" title="章节:4.1 先有鸡还是先有蛋"></a>章节:4.1 先有鸡还是先有蛋</h2><blockquote><p>考虑另外一段代码:</p></blockquote><p>打印漏了,原书是: var a; console.log(a); a=2;</p><h2 id="章节:4-2-编译器再度来袭"><a href="#章节:4-2-编译器再度来袭" class="headerlink" title="章节:4.2 编译器再度来袭"></a>章节:4.2 编译器再度来袭</h2><blockquote><p>正确的思考思路是,包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。</p></blockquote><blockquote><p>当你看到var a = 2;时,可能会认为这是一个声明。但JavaScript实际上会将其看成两个声明:var a;和a = 2;。第一个定义声明是在编译阶段进行的。第二个赋值声明会被留在原地等待执行阶段。</p></blockquote><blockquote><p>这个过程就好像变量和函数声明从它们在代码中出现的位置被“移动”到了最上面。这个过程就叫作提升。<br>换句话说,先有蛋(声明)后有鸡(赋值)。</p></blockquote><blockquote><p>只有声明本身会被提升,而赋值或其他运行逻辑会留在原地。</p></blockquote><blockquote><p>函数声明会被提升,但是函数表达式却不会被提升。</p></blockquote><h2 id="章节:4-3-函数优先"><a href="#章节:4-3-函数优先" class="headerlink" title="章节:4.3 函数优先"></a>章节:4.3 函数优先</h2><blockquote><p>函数声明和变量声明都会被提升。但是一个值得注意的细节(这个细节可以出现在有多个“重复”声明的代码中)是函数会首先被提升,然后才是变量。</p></blockquote><blockquote><p>尽管重复的var声明会被忽略掉,但出现在后面的函数声明还是可以覆盖前面的。</p></blockquote><blockquote><p>尽管重复的var声明会被忽略掉,但出现在后面的函数声明还是可以覆盖前面的。</p></blockquote><p>又漏印了,最前面少一行代码: foo(); // 3</p><h2 id="章节:4-4-小结"><a href="#章节:4-4-小结" class="headerlink" title="章节:4.4 小结"></a>章节:4.4 小结</h2><blockquote><p>我们习惯将var a = 2;看作一个声明,而实际上JavaScript引擎并不这么认为。它将var a和a = 2当作两个单独的声明,第一个是编译阶段的任务,而第二个则是执行阶段的任务。</p></blockquote><blockquote><p>意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理。可以将这个过程形象地想象成所有的声明(变量和函数)都会被“移动”到各自作用域的最顶端,这个过程被称为提升。</p></blockquote><blockquote><p>声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。</p></blockquote><h2 id="章节:5-2-实质问题"><a href="#章节:5-2-实质问题" class="headerlink" title="章节:5.2 实质问题"></a>章节:5.2 实质问题</h2><blockquote><p>当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。</p></blockquote><blockquote><p>闭包使得函数可以继续访问定义时的词法作用域。</p></blockquote><h2 id="章节:5-3-现在我懂了"><a href="#章节:5-3-现在我懂了" class="headerlink" title="章节:5.3 现在我懂了"></a>章节:5.3 现在我懂了</h2><blockquote><p>本质上无论何时何地,如果将函数(访问它们各自的词法作用域)当作第一级的值类型并到处传递,你就会看到闭包在这些函数中的应用。在定时器、事件监听器、Ajax请求、跨窗口通信、Web Workers或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包!</p></blockquote><h2 id="章节:5-6-小结"><a href="#章节:5-6-小结" class="headerlink" title="章节:5.6 小结"></a>章节:5.6 小结</h2><blockquote><p>当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。</p></blockquote><blockquote><p>闭包也是一个非常强大的工具,可以用多种形式来实现模块等模式。</p></blockquote><blockquote><p>模块有两个主要特征:(1)为创建内部作用域而调用了一个包装函数;(2)包装函数的返回值必须至少包括一个对内部函数的引用,这样就会创建涵盖整个包装函数内部作用域的闭包。</p></blockquote><h2 id="章节:附录C-this-词法"><a href="#章节:附录C-this-词法" class="headerlink" title="章节:附录C this 词法"></a>章节:附录C this 词法</h2><blockquote><p>箭头函数在涉及this绑定时的行为和普通函数的行为完全不一致。它放弃了所有普通this绑定的规则,取而代之的是用当前的词法作用域覆盖了this本来的值。</p></blockquote><h2 id="章节:1-2-误解"><a href="#章节:1-2-误解" class="headerlink" title="章节:1.2 误解"></a>章节:1.2 误解</h2><blockquote><p>this在任何情况下都不指向函数的词法作用域</p></blockquote><h2 id="章节:1-3-this-到底是什么"><a href="#章节:1-3-this-到底是什么" class="headerlink" title="章节:1.3 this 到底是什么"></a>章节:1.3 this 到底是什么</h2><blockquote><p>this是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。</p></blockquote><h2 id="章节:1-4-小结"><a href="#章节:1-4-小结" class="headerlink" title="章节:1.4 小结"></a>章节:1.4 小结</h2><blockquote><p>学习this的第一步是明白this既不指向函数自身也不指向函数的词法作用域,你也许被这样的解释误导过,但其实它们都是错误的</p></blockquote><blockquote><p>this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。</p></blockquote><h2 id="章节:2-1-调用位置"><a href="#章节:2-1-调用位置" class="headerlink" title="章节:2.1 调用位置"></a>章节:2.1 调用位置</h2><blockquote><p>调用位置:调用位置就是函数在代码中被调用的位置(而不是声明的位置)</p></blockquote><blockquote><p>寻找调用位置就是寻找“函数被调用的位置”</p></blockquote><blockquote><p>最重要的是要分析调用栈(就是为了到达当前执行位置所调用的所有函数)。我们关心的调用位置就在当前正在执行的函数的前一个调用中</p></blockquote><blockquote><p>绝大多数现代桌面浏览器都内置了开发者工具,其中包含JavaScript调试器。就本例来说,你可以在工具中给foo()函数的第一行代码设置一个断点,或者直接在第一行代码之前插入一条debugger;语句。运行代码时,调试器会在那个位置暂停,同时会展示当前位置的函数调用列表,这就是你的调用栈。因此,如果你想要分析this的绑定,使用开发者工具得到调用栈,然后找到栈中第二个元素,这就是真正的调用位置。</p></blockquote><h2 id="章节:2-2-绑定规则"><a href="#章节:2-2-绑定规则" class="headerlink" title="章节:2.2 绑定规则"></a>章节:2.2 绑定规则</h2><blockquote><p>我们来看看在函数的执行过程中调用位置如何决定this的绑定对象。<br>你必须找到调用位置,然后判断需要应用下面四条规则中的哪一条</p></blockquote><p>默认绑定 隐式绑定 显示绑定 new绑定</p><blockquote><p>2.2.1 默认绑定<br>首先要介绍的是最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用其他规则时的默认规则。</p></blockquote><blockquote><p>foo()是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。</p></blockquote><blockquote><p>2.2.2 隐式绑定<br>另一条需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含,</p></blockquote><blockquote><p>当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。</p></blockquote><blockquote><p>对象属性引用链中只有最顶层或者说最后一层会影响调用位置。</p></blockquote><blockquote><p>隐式丢失<br>一个最常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定</p></blockquote><blockquote><p>虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。</p></blockquote><blockquote><p>参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值</p></blockquote><blockquote><p>2.2.3 显式绑定</p></blockquote><blockquote><p>这两个方法是如何工作的呢?它们的第一个参数是一个对象,它们会把这个对象绑定到this,接着在调用函数时指定这个this。因为你可以直接指定this的绑定对象,因此我们称之为显式绑定。</p></blockquote><p>call() / apply()</p><blockquote><p>如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作this的绑定对象,这个原始值会被转换成它的对象形式(也就是new String(..)、new Boolean(..)或者new Number(..))。这通常被称为“装箱”。</p></blockquote><blockquote><ol><li>硬绑定</li></ol></blockquote><blockquote><p>硬绑定的典型应用场景就是创建一个包裹函数,传入所有的参数并返回接收到的所有值:</p></blockquote><blockquote><p>由于硬绑定是一种非常常用的模式,所以在ES5中提供了内置的方法Function.prototype.bind</p></blockquote><blockquote><p>bind(..)会返回一个硬编码的新函数,它会把参数设置为this的上下文并调用原始函数。</p></blockquote><blockquote><ol start="2"><li>API调用的“上下文”</li></ol></blockquote><blockquote><p>第三方库的许多函数,以及JavaScript语言和宿主环境中许多新的内置函数,都提供了一个可选的参数,通常被称为“上下文”(context),其作用和bind(..)一样,确保你的回调函数使用指定的this。</p></blockquote><blockquote><p>2.2.4 new绑定</p></blockquote><blockquote><p>在传统的面向类的语言中,“构造函数”是类中的一些特殊方法,使用new初始化类时会调用类中的构造函数。</p></blockquote><p>注意:说的是在传统面向类的语言中!</p><blockquote><p>绝大多数开发者都认为JavaScript中new的机制也和那些语言一样。然而,JavaScript中new的机制实际上和面向类的语言完全不同。</p></blockquote><blockquote><p>首先我们重新定义一下JavaScript中的“构造函数”。JavaScript,构造函数只是一些使用new操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上,它们甚至都不能说是一种特殊的函数类型,它们只是被new操作符调用的普通函数而已。</p></blockquote><blockquote><p>包括内置对象函数(比如Number(..),详情请查看第3章)在内的所有函数都可以用new来调用,这种函数调用被称为构造函数调用。这里有一个重要但是非常细微的区别:实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。</p></blockquote><blockquote><p>使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。</p><ol><li>创建(或者说构造)一个全新的对象。</li><li>这个新对象会被执行[[原型]]连接。</li><li>这个新对象会绑定到函数调用的this。</li><li>如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。</li></ol><ul><li>js 高程写的:</li></ul><ol><li>创建一个新对象</li><li>将构造函数的作用域赋给新对象(因此this就指向了这个新对象)</li><li>执行构造函数中的代码(为这个新对象添加属性)</li><li>返回这个新对象</li></ol></blockquote><h2 id="章节:2-3-优先级"><a href="#章节:2-3-优先级" class="headerlink" title="章节:2.3 优先级"></a>章节:2.3 优先级</h2><blockquote><p>默认绑定的优先级是四条规则中最低的</p></blockquote><blockquote><p>显式绑定优先级更高,也就是说在判断时应当先考虑是否可以应用显式绑定。</p></blockquote><blockquote><p>new绑定比隐式绑定优先级高</p></blockquote><blockquote><p>MDN提供的一种bind(..)实现</p></blockquote><p>bind实现</p><blockquote><p>可以按照下面的顺序来进行判断:</p><ol><li>函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。<br>var bar = new foo()</li><li>函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。<br>var bar = foo.call(obj2)</li><li>函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。<br>var bar = obj1.foo()</li><li>如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。<br>var bar = foo()<br>就是这样。</li></ol></blockquote><h2 id="章节:3-1-语法"><a href="#章节:3-1-语法" class="headerlink" title="章节:3.1 语法"></a>章节:3.1 语法</h2><blockquote><p>对象可以通过两种形式定义:声明(文字)形式和构造形式。</p></blockquote><h2 id="章节:3-2-类型"><a href="#章节:3-2-类型" class="headerlink" title="章节:3.2 类型"></a>章节:3.2 类型</h2><blockquote><p>对象是JavaScript的基础。在JavaScript中一共有六种主要类型(术语是“语言类型”):<br>• string<br>• number<br>• boolean<br>• null<br>• undefined<br>• object</p></blockquote><blockquote><p>注意,简单基本类型(string、boolean、number、null和undefined)本身并不是对象</p></blockquote><blockquote><p>有一种常见的错误说法是“JavaScript中万物皆是对象”,这显然是错误的。</p></blockquote><blockquote><p>构造函数(由new产生的函数调用</p></blockquote><blockquote><p>我们都可以直接在字符串字面量上访问属性或者方法,之所以可以这样做,是因为引擎自动把字面量转换成String对象,所以可以访问属性和方法。</p></blockquote><blockquote><p>null和undefined没有对应的构造形式,它们只有文字形式。相反,Date只有构造,没有文字形式。</p></blockquote><blockquote><p>对于Object、Array、Function和RegExp(正则表达式)来说,无论使用文字形式还是构造形式,它们都是对象,不是字面量。</p></blockquote><h2 id="章节:3-3-内容"><a href="#章节:3-3-内容" class="headerlink" title="章节:3.3 内容"></a>章节:3.3 内容</h2><blockquote><p>ES6增加了可计算属性名,可以在文字形式中使用[]包裹一个表达式来当作属性名</p></blockquote><blockquote><p>ES6定义了Object.assign(..)方法来实现浅复制。Object.assign(..)方法的第一个参数是目标对象,之后还可以跟一个或多个源对象。它会遍历一个或多个源对象的所有可枚举(enumerable,参见下面的代码)的自有键(owned key,很快会介绍)并把它们复制(使用=操作符赋值)到目标对象,最后返回目标对象</p></blockquote><blockquote><p>它还包含另外三个特性:writable(可写)、enumerable(可枚举)和configurable(可配置)。</p></blockquote><blockquote><p>在创建普通属性时属性描述符会使用默认值,我们也可以使用Object.defineProperty(..)来添加一个新属性或者修改一个已有属性(如果它是configurable)并对特性进行设置。</p></blockquote><blockquote><ol><li>Writable<br>writable决定是否可以修改属性的值。</li></ol></blockquote><blockquote><ol start="2"><li>Configurable<br>只要属性是可配置的,就可以使用defineProperty(..)方法来修改属性</li></ol></blockquote><blockquote><ol start="3"><li>Enumerable</li></ol></blockquote><blockquote><p>myObject.a在myObject上实际上是实现了[[Get]]操作(有点像函数调用:<a href="">[Get]</a>)</p></blockquote><h2 id="章节:4-1-类理论"><a href="#章节:4-1-类理论" class="headerlink" title="章节:4.1 类理论"></a>章节:4.1 类理论</h2><blockquote><p>其他语言中的类和JavaScript中的“类”并不一样</p></blockquote><h2 id="章节:4-2-类的机制"><a href="#章节:4-2-类的机制" class="headerlink" title="章节:4.2 类的机制"></a>章节:4.2 类的机制</h2><blockquote><p>类实例是由一个特殊的类方法构造的,这个方法名通常和类名相同,被称为构造函数。</p></blockquote><h2 id="章节:4-3-类的继承"><a href="#章节:4-3-类的继承" class="headerlink" title="章节:4.3 类的继承"></a>章节:4.3 类的继承</h2><blockquote><p>这个技术被称为多态或者虚拟多态。在本例中,更恰当的说法是相对多态。</p></blockquote><h2 id="章节:5-1-Prototype"><a href="#章节:5-1-Prototype" class="headerlink" title="章节:5.1 [[Prototype]]"></a>章节:5.1 [[Prototype]]</h2><blockquote><p>JavaScript中的对象有一个特殊的[[Prototype]]内置属性,其实就是对于其他对象的引用。</p></blockquote><blockquote><p>Object.create(..)的原理,现在只需要知道它会创建一个对象并把这个对象的[[Prototype]]关联到指定的对象。</p></blockquote><blockquote><p>使用for..in遍历对象时原理和查找[[Prototype]]链类似,任何可以通过原型链访问到(并且是enumerable,参见第3章)的属性都会被枚举。使用in操作符来检查属性在对象中是否存在时,同样会查找对象的整条原型链(无论属性是否可枚举)</p></blockquote><blockquote><p>所有普通的[[Prototype]]链最终都会指向内置的Object.prototype。</p></blockquote><blockquote><p>你希望在第二种和第三种情况下也屏蔽foo,那就不能使用=操作符来赋值,而是使用Object.defineProperty(..)(参见第3章)来向myObject添加foo。</p></blockquote><h2 id="章节:5-2-“类”"><a href="#章节:5-2-“类”" class="headerlink" title="章节:5.2 “类”"></a>章节:5.2 “类”</h2><blockquote><p>因为根本就不存在类</p></blockquote><blockquote><p>再说一遍,JavaScript中只有对象</p></blockquote><blockquote><p>所有的函数默认都会拥有一个名为prototype的公有并且不可枚举(参见第3章)的属性,它会指向另一个对象:<br>function Foo() {<br> // …<br>}</p><p>Foo.prototype; // { }<br>这个对象通常被称为Foo的原型,因为我们通过名为Foo.prototype的属性引用来访问它。</p></blockquote><blockquote><p>这个对象是在调用new Foo()(参见第2章)时创建的,最后会被(有点武断地)关联到这个“Foo点prototype”对象上</p></blockquote><blockquote><p>调用new Foo()时会创建a(具体的4个步骤参见第2章),其中的一步就是给a一个内部的[[Prototype]]链接,关联到Foo.prototype指向的那个对象。</p></blockquote><blockquote><p>你不能创建一个类的多个实例,只能创建多个对象,它们[[Prototype]]关联的是同一个对象。</p></blockquote><blockquote><p>new Foo()这个函数调用实际上并没有直接创建关联,这个关联只是一个意外的副作用。new Foo()只是间接完成了我们的目标:一个关联到其他对象的新对象。</p></blockquote><blockquote><p>Foo.prototype默认(在代码中第一行声明时!)有一个公有并且不可枚举(参见第3章)的属性.constructor,这个属性引用的是对象关联的函数(本例中是Foo)</p></blockquote><blockquote><p>这很令人吃惊,我们竟然会如此努力地维护JavaScript中(假)“面向类”的权力,尽管对于JavaScript引擎来说首字母大写没有任何意义。</p></blockquote><blockquote><p>实际上,Foo和你程序中的其他函数没有任何区别。函数本身并不是构造函数,然而,当你在普通的函数调用前面加上new关键字之后,就会把这个函数调用变成一个“构造函数调用”。实际上,new会劫持所有普通函数并用构造对象的形式来调用它。</p></blockquote><blockquote><p>,在JavaScript中对于“构造函数”最准确的解释是,所有带new的函数调用。</p></blockquote><blockquote><p>函数不是构造函数,但是当且仅当使用new时,函数调用会变成“构造函数调用”</p></blockquote><blockquote><p>之前讨论.constructor属性时我们说过,看起来a.constructor === Foo为真意味着a确实有一个指向Foo的.constructor属性,但是事实不是这样。<br>这是一个很不幸的误解。实际上,.constructor引用同样被委托给了Foo.prototype,而Foo.prototype.constructor默认指向Foo。<br>把.constructor属性指向Foo看作是a对象由Foo“构造”非常容易理解,但这只不过是一种虚假的安全感。a.constructor只是通过默认的[[Prototype]]委托指向Foo,这和“构造”毫无关系。相反,对于.constructor的错误理解很容易对你自己产生误导。<br>举例来说,Foo.prototype的.constructor属性只是Foo函数在声明时的默认属性。如果你创建了一个新对象并替换了函数默认的.prototype对象 […]</p></blockquote><blockquote><p>.constructor并不是一个不可变属性。它是不可枚举(参见上面的代码)的,但是它的值是可写的(可以被修改)。</p></blockquote><blockquote><p>a1.constructor是一个非常不可靠并且不安全的引用。通常来说要尽量避免使用这些引用。</p></blockquote><h2 id="章节:5-3-(原型)继承"><a href="#章节:5-3-(原型)继承" class="headerlink" title="章节:5.3 (原型)继承"></a>章节:5.3 (原型)继承</h2><blockquote><p>如果忽略掉Object.create(..)方法带来的轻微性能损失(抛弃的对象需要进行垃圾回收),它实际上比ES6及其之后的方法更短而且可读性更高。不过无论如何,这是两种完全不同的语法。</p></blockquote><blockquote><p>假设有对象a,如何寻找对象a委托的对象(如果存在的话)呢?在传统的面向类环境中,检查一个实例(JavaScript中的对象)的继承祖先(JavaScript中的委托关联)通常被称为内省(或者反射)</p></blockquote><blockquote><p>instanceof操作符的左操作数是一个普通的对象,右操作数是一个函数。instanceof回答的问题是:在a的整条[[Prototype]]链中是否有指向Foo.prototype的对象?</p></blockquote><blockquote><p>instanceof操作符的左操作数是一个普通的对象,右操作数是一个函数。instanceof回答的问题是:在a的整条[[Prototype]]链中是否有指向Foo.prototype的对象?</p></blockquote><p>instanceof</p><blockquote><p>可惜,这个方法只能处理对象(a)和函数(带.prototype引用的Foo)之间的关系。如果你想判断两个对象(比如a和b)之间是否通过[[Prototype]]链关联,只用instanceof无法实现。</p></blockquote><blockquote><p>如果使用内置的.bind(..)函数来生成一个硬绑定函数(参见第2章)的话,该函数是没有.prototype属性的。在这样的函数上使用instanceof的话,目标函数的.prototype会代替硬绑定函数的.prototype。</p></blockquote><blockquote><p>通常我们不会在“构造函数调用”中使用硬绑定函数,不过如果你这么做的话,实际上相当于直接调用目标函数。同理,在硬绑定函数上使用instanceof也相当于直接在目标函数上使用instanceof。</p></blockquote><blockquote><p>下面是第二种判断[[Prototype]]反射的方法,它更加简洁:<br>Foo.prototype.isPrototypeOf( a ); // true<br>注意,在本例中,我们实际上并不关心(甚至不需要)Foo,我们只需要一个可以用来判断的对象(本例中是Foo.prototype)就行。isPrototypeOf(..)回答的问题是:在a的整条[[Prototype]]链中是否出现过Foo.prototype?</p></blockquote><blockquote><p>我们只需要两个对象就可以判断它们之间的关系。举例来说:<br>// 非常简单:b是否出现在c的[[Prototype]]链中?<br>b.isPrototypeOf( c );<br>注意,这个方法并不需要使用函数(“类”),它直接使用b和c之间的对象引用来判断它们的关系。换句话说,语言内置的isPrototypeOf(..)函数就是我们的isRelatedTo(..)函数。</p></blockquote><blockquote><p>这个奇怪的.<strong>proto</strong>(在ES6之前并不是标准!)属性“神奇地”引用了内部的[[Prototype]]对象,如果你想直接查找(甚至可以通过.<strong>proto</strong>.<strong>ptoto</strong>…来遍历)原型链的话,这个方法非常有用。<br>和我们之前说过的.constructor一样,.<strong>proto</strong>实际上并不存在于你正在使用的对象中(本例中是a)。实际上,它和其他的常用函数(.toString()、.isPrototypeOf(..),等等)一样,存在于内置的Object.prototype中。</p></blockquote><blockquote><p>JavaScript社区中对于双下划线有一个非官方的称呼,他们会把类似<strong>proto</strong>的属性称为“笨蛋(dunder)”。所以,JavaScript潮人会把<strong>proto</strong>叫作“笨蛋proto”。</p></blockquote><h2 id="章节:5-4-对象关联"><a href="#章节:5-4-对象关联" class="headerlink" title="章节:5.4 对象关联"></a>章节:5.4 对象关联</h2><blockquote><p>现在我们知道了,[[Prototype]]机制就是存在于对象中的一个内部链接,它会引用其他对象。<br>通常来说,这个链接的作用是:如果在对象上没有找到需要的属性或者方法引用,引擎就会继续在[[Prototype]]关联的对象上进行查找。同理,如果在后者中也没有找到需要的引用就会继续查找它的[[Prototype]],以此类推。这一系列对象的链接被称为“原型链”。</p></blockquote><blockquote><p>我们并不需要类来创建两个对象之间的关系,只需要通过委托来关联对象就足够了。而Object.create(..)不包含任何“类的诡计”,所以它可以完美地创建我们想要的关联关系。</p></blockquote><blockquote><p>我并不赞同这个严格的观点,相反,我很赞同在ES5中使用上面那段polyfill代码。如何选择取决于你。</p></blockquote><blockquote><p>在ES6中有一个被称为“代理”(Proxy)的高端功能,它实现的就是“方法无法找到”时的行为。</p></blockquote><blockquote><p>千万不要忽略这个微妙但是非常重要的区别。</p></blockquote><blockquote><p>但是你可以让你的API设计不那么“神奇”,同时仍然能发挥[[Prototype]]关联的威力:<br>var anotherObject = {<br> cool: function() {<br> console.log( “cool!” );<br> }<br>};</p><p>var myObject = Object.create( anotherObject );</p><p>myObject.doCool = function() {<br> this.cool(); // 内部委托!<br>};</p><p>myObject.doCool(); // “cool!”<br>这里我们调用的myObject.doCool()是实际存在于myObject中的,这可以让我们的API设计更加清晰(不那么“神奇”)。从内部来说,我们的实现遵循的是委托设计模式(参见第6章),通过[[Prototype]]委托到anotherObj […]</p></blockquote><h2 id="章节:5-5-小结"><a href="#章节:5-5-小结" class="headerlink" title="章节:5.5 小结"></a>章节:5.5 小结</h2><blockquote><p>所有普通对象都有内置的Object.prototype,指向原型链的顶端(比如说全局作用域),如果在原型链中找不到指定的属性就会停止。</p></blockquote><blockquote><p>关联两个对象最常用的方法是使用new关键词进行函数调用,在调用的4个步骤(第2章)中会创建一个关联其他对象的新对象。<br>使用new调用函数时会把新对象的.prototype属性关联到“其他对象”。带new的函数调用通常被称为“构造函数调用”,尽管它们实际上和传统面向类语言中的类构造函数不一样。</p></blockquote><blockquote><p>虽然这些JavaScript机制和传统面向类语言中的“类初始化”和“类继承”很相似,但是JavaScript中的机制有一个核心区别,那就是不会进行复制,对象之间是通过内部的[[Prototype]]链关联的。<br>出于各种原因,以“继承”结尾的术语(包括“原型继承”)和其他面向对象的术语都无法帮助你理解JavaScript的真实机制(不仅仅是限制我们的思维模式)。<br>相比之下,“委托”是一个更合适的术语,因为对象之间的关系不是复制而是委托。</p></blockquote><h2 id="章节:第-6-章-行为委托"><a href="#章节:第-6-章-行为委托" class="headerlink" title="章节:第 6 章 行为委托"></a>章节:第 6 章 行为委托</h2><blockquote><p>不出意外,绝大多数JavaScript开发者从来没有如此深入地了解过JavaScript,他们只是把这些交给一个“类”库来处理。</p></blockquote><blockquote><p>[[Prototype]]机制就是指对象中的一个内部链接引用另一个对象。</p></blockquote><blockquote><p>如果在第一个对象上没有找到需要的属性或者方法引用,引擎就会继续在[[Prototype]]关联的对象上进行查找。同理,如果在后者中也没有找到需要的引用就会继续查找它的[[Prototype]],以此类推。这一系列对象的链接被称为“原型链”。</p></blockquote><blockquote><p>如果在第一个对象上没有找到需要的属性或者方法引用,引擎就会继续在[[Prototype]]关联的对象上进行查找。同理,如果在后者中也没有找到需要的引用就会继续查找它的[[Prototype]],以此类推。这一系列对象的链接被称为“原型链”。</p></blockquote><p>划重点</p><blockquote><p>JavaScript中这个机制的本质就是对象之间的关联关系。</p></blockquote><h2 id="章节:6-1-面向委托的设计"><a href="#章节:6-1-面向委托的设计" class="headerlink" title="章节:6.1 面向委托的设计"></a>章节:6.1 面向委托的设计</h2><blockquote><p>我们需要试着把思路从类和继承的设计模式转换到委托行为的设计模式。</p></blockquote><blockquote><p>非常重要的是,类设计模式鼓励你在继承时使用方法重写(和多态),比如说在XYZ任务中重写Task中定义的一些通用方法,甚至在添加新行为时通过super调用这个方法的原始版本。你会发现许多行为可以先“抽象”到父类然后再用子类进行特殊化(重写)。</p></blockquote><blockquote><p>首先你会定义一个名为Task的对象(和许多JavaScript开发者告诉你的不同,它既不是类也不是函数),它会包含所有任务都可以使用(写作使用,读作委托)的具体行为。接着,对于每个任务(“XYZ”、“ABC”)你都会定义一个对象来存储对应的数据和行为。你会把特定的任务对象都关联到Task功能对象上,让它们在需要的时候可以进行委托。</p></blockquote><blockquote><p>相比于面向类(或者说面向对象),我会把这种编码风格称为“对象关联”(OLOO,objects linked to other objects)。我们真正关心的只是XYZ对象(和ABC对象)委托了Task对象。</p></blockquote><blockquote><p>相比于面向类(或者说面向对象),我会把这种编码风格称为“对象关联”(OLOO,objects linked to other objects)。我们真正关心的只是XYZ对象(和ABC对象)委托了Task对象。</p></blockquote><p>OLOO objects linked to other objects</p><blockquote><p>无论你多么努力地说服自己,JavaScript中就是没有类似“类”的抽象机制</p></blockquote><blockquote><ol><li>在上面的代码中,id和label数据成员都是直接存储在XYZ上(而不是Task)。通常来说,在[[Prototype]]委托中最好把状态保存在委托者(XYZ、ABC)而不是委托目标(Task)上。</li><li>在类设计模式中,我们故意让父类(Task)和子类(XYZ)中都有outputTask方法,这样就可以利用重写(多态)的优势。在委托行为中则恰好相反:我们会尽量避免在[[Prototype]]链的不同级别中使用相同的命名,否则就需要使用笨拙并且脆弱的语法来消除引用歧义(参见第4章)。</li></ol></blockquote><blockquote><p>这个设计模式要求尽量少使用容易被重写的通用方法名,提倡使用更有描述性的方法名,尤其是要写清相应对象行为的类型。</p></blockquote><blockquote><p>委托行为意味着某些对象(XYZ)在找不到属性或者方法引用时会把这个请求委托给另一个对象(Task)</p></blockquote><blockquote><p>这是一种极其强大的设计模式,和父类、子类、继承、多态等概念完全不同。在你的脑海中对象并不是按照父类到子类的关系垂直组织的,而是通过任意方向的委托关联并排组织的。</p></blockquote><blockquote><p>这是一种极其强大的设计模式,和父类、子类、继承、多态等概念完全不同。在你的脑海中对象并不是按照父类到子类的关系垂直组织的,而是通过任意方向的委托关联并排组织的。</p></blockquote><p>记住这个描述</p><blockquote><ol><li>互相委托(禁止)<br>你无法在两个或两个以上互相(双向)委托的对象之间创建循环委托。如果你把B关联到A然后试着把A关联到B,就会出错。</li></ol></blockquote><blockquote><ol start="2"><li>调试</li></ol></blockquote><blockquote><p>首先,类风格代码的思维模型强调实体以及实体间的关系:</p></blockquote><blockquote><p>JavaScript中的函数之所以可以访问call(..)、apply(..)和bind(..)(参见第2章),就是因为函数本身是对象。而函数对象同样有[[Prototype]]属性并且关联到Function.prototype对象,因此所有函数对象都可以通过委托调用这些默认方法。JavaScript能做到这一点,你也可以!</p></blockquote><blockquote><p>通过比较可以看出,对象关联风格的代码显然更加简洁,因为这种代码只关注一件事:对象之间的关联关系。</p></blockquote><h2 id="章节:6-2-类与对象"><a href="#章节:6-2-类与对象" class="headerlink" title="章节:6.2 类与对象"></a>章节:6.2 类与对象</h2><blockquote><p>使用类构造函数的话,你需要(并不是硬性要求,但是强烈建议)在同一个步骤中实现构造和初始化。然而,在许多情况下把这两步分开(就像对象关联代码一样)更灵活。</p></blockquote><blockquote><p>对象关联可以更好地支持关注分离(separation of concerns)原则,创建和初始化并不需要合并为一个步骤。</p></blockquote><h2 id="章节:6-3-更简洁的设计"><a href="#章节:6-3-更简洁的设计" class="headerlink" title="章节:6.3 更简洁的设计"></a>章节:6.3 更简洁的设计</h2><blockquote><p>所有控制器共享的基础行为是success(..)、failure(..)和showDialog(..)。子类LoginController和AuthController通过重写failure(..)和success(..)来扩展默认基础类行为。此外,注意AuthController需要一个LoginController的实例来和登录表单进行交互,因此这个实例变成了一个数据属性。<br>另一个需要注意的是我们在继承的基础上进行了一些合成。AuthController需要使用LoginController,因此我们实例化后者(new LoginController())并用一个类成员属性this.login来引用它,这样AuthController就可以调用LoginController的行为。</p></blockquote><p>上面👆代码最后实例化的部分写错了,特意翻了纸质版原书,果然写错了,正确的是这样: var auth = new AuthController( new LoginController() ); auth.checkAuth();</p><blockquote><p>由于AuthController只是一个对象(LoginController也一样),因此我们不需要实例化(比如new AuthController()),只需要一行代码就行:</p></blockquote><blockquote><p>这种模式的重点在于只需要两个实体(LoginController和AuthController),而之前的模式需要三个。</p></blockquote><blockquote><p>我们不需要Controller基类来“共享”两个实体之间的行为,因为委托足以满足我们需要的功能。同样,前面提到过,我们也不需要实例化类,因为它们根本就不是类,它们只是对象。此外,我们也不需要合成,因为两个对象可以通过委托进行合作。</p></blockquote><blockquote><p>总结:我们用一种(极其)简单的设计实现了同样的功能,这就是对象关联风格代码和行为委托设计模式的力量。</p></blockquote><h2 id="章节:6-4-更好的语法"><a href="#章节:6-4-更好的语法" class="headerlink" title="章节:6.4 更好的语法"></a>章节:6.4 更好的语法</h2><blockquote><p>在ES6中我们可以在任意对象的字面形式中使用简洁方法声明(concise method declaration),所以对象关联风格的对象可以这样声明(和class的语法糖一样):<br>var LoginController = {<br> errors: [],<br> getUser() { // 妈妈再也不用担心代码里有function了!<br> // …<br> },<br> getPassword() {<br> // …<br> }<br> // …<br>};<br>唯一的区别是对象的字面形式仍然需要使用“,”来分隔元素,而class语法不需要。这个区别对于整体的设计来说无关紧要。</p></blockquote><blockquote><p>使用简洁方法时一定要小心这一点。如果你需要自我引用的话,那最好使用传统的具名函数表达式来定义对应的函数(·baz: function baz(){..}·),不要使用简洁方法。</p></blockquote><h2 id="章节:6-5-内省"><a href="#章节:6-5-内省" class="headerlink" title="章节:6.5 内省"></a>章节:6.5 内省</h2><blockquote><p>自省就是检查实例的类型。类实例的自省主要目的是通过创建方式来判断对象的结构和功能。</p></blockquote><blockquote><p>因为Foo.prototype(不是Foo!)在a1的[[Prototype]]链上(参见第5章)</p></blockquote><blockquote><p>再说一次,我们认为JavaScript中对象关联比类风格的代码更加简洁(而且功能相同)。</p></blockquote><h2 id="章节:6-6-小结"><a href="#章节:6-6-小结" class="headerlink" title="章节:6.6 小结"></a>章节:6.6 小结</h2><blockquote><p>在软件架构中你可以选择是否使用类和继承设计模式。大多数开发者理所当然地认为类是唯一(合适)的代码组织方式,但是本章中我们看到了另一种更少见但是更强大的设计模式:行为委托。</p></blockquote><blockquote><p>行为委托认为对象之间是兄弟关系,互相委托,而不是父类和子类的关系。JavaScript的[[Prototype]]机制本质上就是行为委托机制。也就是说,我们可以选择在JavaScript中努力实现类机制(参见第4和第5章),也可以拥抱更自然的[[Prototype]]委托机制。</p></blockquote><blockquote><p>当你只用对象来设计代码时,不仅可以让语法更加简洁,而且可以让代码结构更加清晰。</p></blockquote><blockquote><p>对象关联(对象之前互相关联)是一种编码风格,它倡导的是直接创建和关联对象,不把它们抽象成类。对象关联可以用基于[[Prototype]]的行为委托非常自然地实现。</p></blockquote><h2 id="章节:附录A-ES6中的Class"><a href="#章节:附录A-ES6中的Class" class="headerlink" title="章节:附录A ES6中的Class"></a>章节:附录A ES6中的Class</h2><blockquote><p>类是一种可选(而不是必须)的设计模式,而且在JavaScript这样的[[Prototype]]语言中实现类是很别扭的。</p></blockquote><blockquote><p>类并不适用于JavaScript。</p></blockquote><h2 id="章节:A-1-class"><a href="#章节:A-1-class" class="headerlink" title="章节:A.1 class"></a>章节:A.1 class</h2><blockquote><p>除了语法更好看之外,ES6还解决了什么问题呢?<br>1.(基本上,下面会详细介绍)不再引用杂乱的.prototype了。 </p><ol start="2"><li>Button声明时直接“继承”了Widget,不再需要通过Object.create(..)来替换.prototype对象,也不需要设置.<strong>proto</strong>或者Object.setPrototypeOf(..)。 </li><li>可以通过super(..)来实现相对多态,这样任何方法都可以引用原型链上层的同名方法。这可以解决第4章提到过的那个问题:构造函数不属于类,所以无法互相引用——super()可以完美解决构造函数的问题。 </li><li>class字面语法不能声明属性(只能声明方法)。看起来这是一种限制,但是它会排除掉许多不好的情况,如果没有这种限制的话,原型链末端的“实例”可能会意外地获取其他地方的属性(这些属性隐式被所有“实例”所“共享”)。所以,class语法实际 […]</li></ol></blockquote><h2 id="章节:A-2-class-陷阱"><a href="#章节:A-2-class-陷阱" class="headerlink" title="章节:A.2 class 陷阱"></a>章节:A.2 class 陷阱</h2><blockquote><p>首先,你可能会认为ES6的class语法是向JavaScript中引入了一种新的“类”机制,其实不是这样。class基本上只是现有[[Prototype]](委托!)机制的一种语法糖。</p></blockquote><blockquote><p>class并不会像传统面向类的语言一样在声明时静态复制所有行为。如果你(有意或无意)修改或者替换了父“类”中的一个方法,那子“类”和所有实例都会受到影响,因为它们在定义时并没有进行复制,只是使用基于[[Prototype]]的实时委托:</p></blockquote><blockquote><p>ES6中的class语法不是会让传统类和委托对象之间的区别更加难以发现和理解吗?</p></blockquote><p>思考</p><blockquote><p>无论目前的方法在原型链中处于什么位置,super总会绑定到链中的上一层。</p></blockquote><blockquote><p>然而,出于性能考虑(this绑定已经是很大的开销了),super并不是动态绑定的,它会在声明时“静态”绑定</p></blockquote><blockquote><p>出于性能考虑,super并不像this一样是晚绑定(late bound, 或者说动态绑定)的,它在[[HomeObject]].[[Prototype]]上,[[HomeObject]]会在创建时静态绑定。</p></blockquote><h2 id="章节:A-3-静态大于动态吗"><a href="#章节:A-3-静态大于动态吗" class="headerlink" title="章节:A.3 静态大于动态吗"></a>章节:A.3 静态大于动态吗</h2><blockquote><p>在传统面向类的语言中,类定义之后就不会进行修改,所以类的设计模式就不支持修改。但是JavaScript最强大的特性之一就是它的动态性,任何对象的定义都可以修改(除非你把它设置成不可变)。</p></blockquote><blockquote><p>换句话说,class似乎想告诉你:“动态太难实现了,所以这可能不是个好主意。这里有一种看起来像静态的语法,所以编写静态代码吧。”</p></blockquote><blockquote><p>动态太难实现了,我们假装成静态吧。(但是实际上并不是!)</p></blockquote><blockquote><p>总地来说,ES6的class想伪装成一种很好的语法问题的解决方案,但是实际上却让问题更难解决而且让JavaScript更加难以理解。</p></blockquote><h2 id="章节:A-4-小结"><a href="#章节:A-4-小结" class="headerlink" title="章节:A.4 小结"></a>章节:A.4 小结</h2><blockquote><p>class很好地伪装成JavaScript中类和继承设计模式的解决方案,但是它实际上起到了反作用:它隐藏了许多问题并且带来了更多更细小但是危险的问题。</p></blockquote><blockquote><p>class加深了过去20年中对于JavaScript中“类”的误解,在某些方面,它产生的问题比解决的多,而且让本来优雅简洁的[[Prototype]]机制变得非常别扭。</p></blockquote><blockquote><p>如果ES6的class让[[Prototype]]变得更加难用而且隐藏了JavaScript对象最重要的机制——对象之间的实时委托关联,我们难道不应该认为class产生的问题比解决的多吗?难道不应该抵制这种设计模式吗?</p></blockquote>]]></content>
<categories>
<category> study notes </category>
</categories>
<tags>
<tag> study notes </tag>
</tags>
</entry>
<entry>
<title>UI组件🔧Ax-View</title>
<link href="/2018/10/18/4ax-view/"/>
<url>/2018/10/18/4ax-view/</url>
<content type="html"><![CDATA[<p><img src="/2018/10/18/4ax-view/./wss.jpg" width="600px"><br>基于vue实现一些常用UI组件,封装成库,取名ax-view</p><a id="more"></a><hr><p>更多文档移步GitHub:<a href="https://github.com/Alexixyc/AlexiComponents" target="_blank" rel="noopener">Ax-View</a></p><blockquote><p><a href="http://alexixyc.cn/dist/#/pagination" title="分页组件pagination">分页组件</a></p></blockquote><blockquote><p><a href="http://alexixyc.cn/dist/#/input" title="输入框组件input">输入框组件</a></p></blockquote><blockquote><p><a href="http://alexixyc.cn/dist/#/select" title="选择器组件select">选择器组件</a></p></blockquote><blockquote><p><a href="http://alexixyc.cn/dist/#/date-picker" title="日期选择组件date-picker">日期选择组件</a></p></blockquote><p>….未完待续</p>]]></content>
<categories>
<category> technology project </category>
</categories>
<tags>
<tag> ui-components </tag>
<tag> personal program </tag>
</tags>
</entry>
<entry>
<title>初探微信小程序——mpvue踩坑总结</title>
<link href="/2018/08/04/3mpvue/"/>
<url>/2018/08/04/3mpvue/</url>
<content type="html"><![CDATA[<p><img src="/2018/08/04/3mpvue/./wechat.png" width="500px"><br>  前段时间,在第一次接触小程序开发的项目中,使用了美团的小程序框架mpvue。本文旨在给大家分享一些,自己在第一次接触小程序开发时遇到的一些常见需求的解决办法,以及在使用mpvue框架时踩过的坑,希望能帮助到大家。</p><a id="more"></a><hr><h1 id="小程序常见需求"><a href="#小程序常见需求" class="headerlink" title="小程序常见需求"></a>小程序常见需求</h1><h2 id="登录"><a href="#登录" class="headerlink" title="登录"></a>登录</h2><blockquote><p>常用参数<br>  code:调用wx.login() 获取的用户登录凭证(有效期5min)<br>  session_key:会话密钥<br>  openid:用户唯一标识<br>  unionid:用户在开放平台的唯一标识符</p></blockquote><blockquote><p>登录流程图<br><img src="/2018/08/04/3mpvue/./api-login.jpg" width="500px"></p></blockquote><h3 id="普通登录"><a href="#普通登录" class="headerlink" title="普通登录"></a>普通登录</h3><ol><li>小程序客户端调用wx.login()获取code。</li><li>发送code至开发者服务端,开发者后端调用<a href="https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code" title="获取openid,session_key(后端调用)" target="_blank" rel="noopener">微信服务端接口</a>获取openid、session_key(在满足一定<a href="https://developers.weixin.qq.com/miniprogram/dev/api/unionID.html" title="UnionID机制说明" target="_blank" rel="noopener">条件</a>下还会返回UnionID)。</li><li>开发者服务端自定义登录状态,返回小程序客户端一个token,当做用户的登录状态。</li><li>小程序客户端存储token,在后续的业务请求接口中携带token,作用类似于web中的cookie。</li></ol><h3 id="绑定手机号登录"><a href="#绑定手机号登录" class="headerlink" title="绑定手机号登录"></a>绑定手机号登录</h3><p>  绑定手机号登录其实就是将用户手机号和用户的openid绑定起来存入数据库,其实登录流程也是和普通的登录流程一样,唯一的区别就是,第一次绑定的时候,需要首先让用户授权获取手机号。</p><h2 id="微信授权用户手机号"><a href="#微信授权用户手机号" class="headerlink" title="微信授权用户手机号"></a>微信授权用户手机号</h2><p>  相对于用户头像昵称来说,用户手机号是一个更隐私的用户信息,所以手机号的授权操作会繁琐一些,并且需要后端的支持。<code>getPhoneNumber(OBJECT)</code>需要用户主动触发才能发起获取手机号接口,所以该功能不由API来调用,需用<code><button></code>组件的点击来触发。</p><ol><li>小程序客户端调用wx.login()获取code。</li><li>设置<code><button></code>组件<code>open-type</code>的值设置为<code>getPhoneNumber</code>,用户点击并同意授权后,通过<code>bindgetphonenumber</code>事件回调获取到微信服务器返回的加密数据。</li></ol><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">button</span> <span class="attr">open-type</span>=<span class="string">"getPhoneNumber"</span> <span class="attr">bindgetphonenumber</span>=<span class="string">"getPhoneNumber"</span>></span></span><br><span class="line"> 微信授权登录</span><br><span class="line"><span class="tag"></<span class="name">button</span>></span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">getPhoneNumber(e) {</span><br><span class="line"> <span class="keyword">if</span> (e.mp.detail.encryptedData) {</span><br><span class="line"> <span class="comment">// 授权成功 将encryptedData和iv发送给开发者服务端</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 拒绝收取,做相应的逻辑处理</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="3"><li>小程序客户端将加密数据和code一同发送给开发者服务器端。</li><li>开发者服务端将,根据code获取openid,和通过加密数据、以及微信提供的<a href="https://developers.weixin.qq.com/miniprogram/dev/api/signature.html#wxchecksessionobject" title="加密数据解密算法" target="_blank" rel="noopener">加密数据解密算法</a>得到的用户手机号绑定在一起插入数据库。</li></ol><p>注意:目前该接口针对非个人开发者,且完成了认证的小程序开放。需谨慎使用,若用户举报较多或被发现在不必要场景下使用,微信有权永久回收该小程序的该接口权限。</p><h2 id="微信授权用户信息"><a href="#微信授权用户信息" class="headerlink" title="微信授权用户信息"></a>微信授权用户信息</h2><p>  微信小程序可以通过调用<code>wx.getUserInfo(OBJECT)</code>来获取用户头像,昵称等基本的微信账号信息。这个功能很常见,一般是用于展示用户的微信头像和昵称。<br>  但有一点要注意的就是,最新的小程序api中,不支持直接引导用户授权用户信息了。也就是说,如果用户没有授权过用户信息的话,直接调用<code>wx.getUserInfo()</code>将不会再弹出授权弹窗了。微信推荐的方法是,设置<code><button></code>组件的<code>open-type</code>属性为<code>getUserinfo</code>,引导用户主动的点击按钮来进行授权操作。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">button</span> <span class="attr">open-type</span>=<span class="string">"getUserInfo"</span> <span class="attr">bindgetuserinfo</span>=<span class="string">"onGotUserInfo"</span>></span></span><br><span class="line"> 分享喜悦给好友</span><br><span class="line"><span class="tag"></<span class="name">button</span>></span></span><br></pre></td></tr></table></figure><p>  这个变化目前对已发布的小程序暂时没有影响,但是【开发版】和【体验版】小程序,已经有了限制。<strong><code>所以,如果小程序需要授权用户基本信息,在设计时,需要设计一个用户按钮点击的交互。</code></strong></p><h2 id="下拉刷新、上拉加载"><a href="#下拉刷新、上拉加载" class="headerlink" title="下拉刷新、上拉加载"></a>下拉刷新、上拉加载</h2><blockquote><p>小程序自带下拉刷新:<br>  在页面json配置中开启<code>enablePullDownRefresh</code>选项<br>然后在函数<code>onPullDownRefresh()</code>中监听用户下拉刷新事件,处理完刷新数据后再用<code>wx.stopPullDownRefresh()</code>停止当前页面的下拉刷新。</p></blockquote><blockquote><p>scroll-view下拉刷新<br>  但是如果当前页面使用了<code>scroll-view</code>组件,这种自带的下拉刷新是不能用的。这种情况下就只能通过监听<code>scroll-view</code>组件的<code>bindscroll、以及bindtouchend、bindtouchstart</code>等事件来手动实现下拉刷新。</p></blockquote><h2 id="微信分享(群-私聊)"><a href="#微信分享(群-私聊)" class="headerlink" title="微信分享(群/私聊)"></a>微信分享(群/私聊)</h2><p>  分享功能应该可以说是每款小程序比不可少功能了,小程序支持分享到聊天回话,不支持直接分享到朋友圈。小程序的分享有几个特点:页面可配置、文字/图片可配置、可携带参数。</p><p>  首先,设置<code><button></code>组件的<code>open-type</code>属性为<code>share</code></p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">button</span> <span class="attr">open-type</span>=<span class="string">"share"</span>></span>分享转发<span class="tag"></<span class="name">button</span>></span></span><br></pre></td></tr></table></figure><p>  然后通过<code>onShareAppMessage(Object)</code>事件来监听用户点击转发按钮的事件,并自定义转发的内容。(如果当前页面没有定义此事件,则点击后无效果)<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">Page({</span><br><span class="line"> onShareAppMessage: <span class="function"><span class="keyword">function</span> (<span class="params">res</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (res.from === <span class="string">'button'</span>) {</span><br><span class="line"> <span class="comment">// 来自页面内转发按钮 button || menu</span></span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> title: <span class="string">'自定义转发标题'</span>,</span><br><span class="line"> path: <span class="string">'/page/sharepage?id=123'</span>,</span><br><span class="line"> imageUrl: <span class="string">'xxx.png'</span></span><br><span class="line"> }</span><br><span class="line"> }<span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> title: <span class="string">'自定义转发标题'</span>,</span><br><span class="line"> path: <span class="string">'/page/sharepage?id=123'</span>,</span><br><span class="line"> imageUrl: <span class="string">'xxx.png'</span>,</span><br><span class="line"> success: <span class="function"><span class="params">res</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'转发成功~'</span>)</span><br><span class="line"> },</span><br><span class="line"> fail: <span class="function"><span class="params">error</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'转发失败'</span>, error)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure></p><p>  我们看到分享时,可以传入参数自定义分享卡片的标题,图片以及从卡片进入时跳转的页面路径。在官方文档上没有看到有写关于分享成功或者失败的回调,但我试了一下,是有的,可能是文档上漏了。这两个回调可以帮助我们在分享操作结束后,根据分享结果做一些不同的操作和处理。</p><p>  其次,通过设置 <code>withShareTicket</code> 可以获取更多转发信息。在群聊中打开分享卡片时,可以取到<code>shareTicket</code>,通过调用 <code>wx.getShareInfo()</code> 接口,传入 <code>shareTicket</code> 可以获取到转发信息。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ps: 如果设置了withShareTicket: true,在聊天对话中,长按分享发片是不可以直接二次转发的。</span></span><br><span class="line">wx.showShareMenu({</span><br><span class="line"> withShareTicket: <span class="literal">true</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure></p><p>  但是如果遇到一个需求,要让你区别是分享到群还是分享到私聊回话,怎么办呢?其实,在分享的这一步,是没办法判断用户到底分享给谁的,所以只能在进入小程序的时候,根据场景值来判断了。</p><h2 id="场景值"><a href="#场景值" class="headerlink" title="场景值"></a>场景值</h2><p>  <a href="https://developers.weixin.qq.com/miniprogram/en/dev/framework/app-service/scene.html?search-key=%E5%9C%BA%E6%99%AF%E5%80%BC" title="小程序场景值" target="_blank" rel="noopener">场景值</a>可以用来判断小程序是从哪个场景下被打开的。场景值可以在 App 的 <code>onlaunch</code> 和 <code>onshow</code> 中获取到。根据不同的场景值,可以知道用户是从群聊还是私聊对话中进入小程序,从而可以做一些不同的逻辑处理。</p><h2 id="小程序二维码生成"><a href="#小程序二维码生成" class="headerlink" title="小程序二维码生成"></a>小程序二维码生成</h2><p>  小程序二维码的生成官方提供了三种<a href="https://developers.weixin.qq.com/miniprogram/dev/api/qrcode.html?search-key=%E5%B0%8F%E7%A8%8B%E5%BA%8F%E7%A0%81" target="_blank" rel="noopener">接口</a>,分别有不同的特点,可以根据需求来选择接口。官方的描述是:</p><blockquote><p>A接口:生成小程序码,可接受path参数较长,生成个数受限。<br>B接口:生成小程序码,可接受页面参数较短,生成个数不受限。<br>C接口:生成二维码,可接受path参数较长,生成个数受限。</p></blockquote><p>  生成二维码的操作一般是开发者服务端来完成的,微信官方推荐生成并使用小程序码,而其中B接口又是最常用的,因为B接口生成的小程序码永久有效且不限制个数。<br>  需要注意的几点:</p><ul><li>B接口的路径不携带参数,参数都需要放在 <code>scene</code> 字段里,在 <code>onLoad()</code> 中可以获取到二维码中的 <code>scene</code> 字段的值</li><li><code>page</code> 参数指定的路径必须是已经发布的小程序存在的页面,否则报错</li><li>调试阶段可以使用开发工具的条件编译自定义参数 <code>scene=xxxx</code> 进行模拟</li></ul><p><img src="/2018/08/04/3mpvue/./compile-mode.jpg" width="600px"></p><h2 id="其他小问题"><a href="#其他小问题" class="headerlink" title="其他小问题"></a>其他小问题</h2><h3 id="tabBar"><a href="#tabBar" class="headerlink" title="tabBar"></a>tabBar</h3><p>如果使用小程序提供的自带的tab栏,根据官方文档进行配置即可,tab栏的页面配置最少2个、最多5个。</p><blockquote><p>唯一需要注意的一点是:小程序的首页必须配置在这几个tab栏中,否则就不能使用官方的tabBar,而只能手写tabBar,这点在设计的时候需要注意一下。</p></blockquote><p>关于手动实现tabBar,遇到了一个问题,就是在切换tab标签时,会用 <code>wx.redirectTo()</code> 进行整个page页面之间的跳转,有的时候底部的tab栏就会出现闪烁的问题,暂时没想到更好的方案。</p><h3 id="图片-字体的引用"><a href="#图片-字体的引用" class="headerlink" title="图片/字体的引用"></a>图片/字体的引用</h3><p>  由于小程序代码包大小上限是2M,所以建议不要引用本地图片字体,全部改成引用网络资源,也不要静态资源打包到小程序的代码包里。</p><h3 id="WXSS"><a href="#WXSS" class="headerlink" title="WXSS"></a>WXSS</h3><ul><li><p>WXSS中background-image只支持网络图片,或者base64格式的图片。</p></li><li><p>新增了尺寸单位rpx,开发者不需要考虑设配问题,全都交给小程序底层来换算。</p></li><li><p>WXSS不再支持媒体查询,通过 <code>wx.getSystemInfo()</code> 来获取设备信息。</p></li></ul><h1 id="mpvue"><a href="#mpvue" class="headerlink" title="mpvue"></a>mpvue</h1><p><br><a href="http://mpvue.com/mpvue/" target="_blank" rel="noopener">mpvue</a> 继承自 <code>Vue.js</code>,其技术规范和语法特点与 <code>Vue.js</code> 保持一致。</p><h2 id="开始项目"><a href="#开始项目" class="headerlink" title="开始项目"></a>开始项目</h2><ol><li><p>新建项目模板</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个基于 mpvue-quickstart 模板的新项目</span></span><br><span class="line">vue init mpvue/mpvue-quickstart my-project</span><br><span class="line">npm install</span><br><span class="line">npm run dev</span><br></pre></td></tr></table></figure></li><li><p>微信开发者工具中选择项目dist目录</p></li></ol><h2 id="踩坑总结"><a href="#踩坑总结" class="headerlink" title="踩坑总结"></a>踩坑总结</h2><ul><li><p>新建pages后,需要重新 npm run dev 一下。</p></li><li><p>mpvue 中使用 vuex: </p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Vue.prototype.$store = store</span><br></pre></td></tr></table></figure></li><li><p>不支持vue-router</p></li><li><p>在引用子组件时,不支持在组件上定义v-show,只能使用v-if替代。</p></li><li><p>不支持组件上定义class,style等样式属性,建议写在内部顶级元素上。</p></li><li><p>小程序在 <code>page</code> <code>onLoad</code> 时传递的 <code>options</code> , 可以在所有页面的组件内通过 <code>this.$root.$mp.query</code> 获取到。</p></li><li><p>小程序在 <code>app</code> <code>onLaunch/onShow</code> 时传递的 <code>options</code> , 可以在所有页面的组件内通过 <code>this.$root.$mp.appOptions</code> 获取到。</p></li><li><p>不支持transition过渡</p></li><li><p>设置横向滑动:</p><blockquote><p>设置横向滑动 scroll-x=”true”;<br>scroll-view需要设置宽度,并且设置white-space属性为nowrap;<br>item设置display: inline-block属性;</p></blockquote></li><li><p>引用网络字体文件时,开发工具上可以支持http、但真机上只能引用https文件。</p></li><li><p>定义vue组件名时需要注意,<a href="http://mpvue.com/qa/#_3" target="_blank" rel="noopener">一些保留关键字</a>,暂不支持作为组件名称。(自定义tabbar时遇到的大坑!!!)</p></li></ul>]]></content>
<categories>
<category> technology share </category>
</categories>
<tags>
<tag> technology share </tag>
</tags>
</entry>
<entry>
<title>前端数据Mock---Express + Mockjs</title>
<link href="/2018/04/24/2fe-mock/"/>
<url>/2018/04/24/2fe-mock/</url>
<content type="html"><![CDATA[<p><img src="/2018/04/24/2fe-mock/./mock.jpg" width="600px"><br>前端开发中,数据mock是一个可大可小、可繁可简的开发流程,完整的前端数据mock最大的目的是旨在<strong>降低前后端接口联调的成本</strong>。<br>本文给大家分享自己在开发中的mock数据的方法,内容简单易懂,欢迎各位大佬批评指正。</p><a id="more"></a><hr><h1 id="前端数据模拟"><a href="#前端数据模拟" class="headerlink" title="前端数据模拟"></a>前端数据模拟</h1><p>在前后端分离的开发模式中,前后端通常是先约定好接口的输入输出以及格式,然后各自按照接口格式来进行同步的开发,等后端接口实现完成,前端数据逻辑处理也完成后,再进行前后端联调。</p><p>所以当项目接口比较多,而且后端接口还未全部实现的时候,前端需要对接口数据进行模拟,以便编写前端的交互逻辑等功能性的代码。这个时候我们就需要尽可能全面地模拟接口数据的各种情况,这样的话,当后端接口全部实现后,可以降低前后端的联调成本。</p><h1 id="MockJs"><a href="#MockJs" class="headerlink" title="MockJs"></a>MockJs</h1><p>mockJs是一个可以模拟接口数据的插件,它可以很方便地模拟各种类型的接口数据,而且模拟的数据时可随机的。虽然这个库有不少的坑点(后面会讲)而且貌似已经很久没有维护了,但是仅仅用来做数据模拟还是挺方便的,首先,需要安装一下:</p><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install mockjs --save-dev</span><br></pre></td></tr></table></figure><h2 id="简单用法"><a href="#简单用法" class="headerlink" title="简单用法"></a>简单用法</h2><p>首先,新建一个<code>mock.js</code>文件,模拟一个简单的接口:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> Mock = <span class="built_in">require</span>(<span class="string">'mockjs'</span>)</span><br><span class="line">Mock.mock(<span class="string">'//mock/base/getUserInfoFromPC'</span>, {</span><br><span class="line"> <span class="string">'code'</span>: <span class="number">200</span>,</span><br><span class="line"> <span class="string">'data'</span>: {</span><br><span class="line"> <span class="string">'nickName'</span>: <span class="string">'alexixiang'</span>,</span><br><span class="line"> <span class="string">'id'</span>: <span class="number">1707000766</span></span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>上面的代码就模拟了一个接口名为<code>/base/getUserInfoFromPC</code>的接口。我一般会在后端定义的真实接口前面加一个标志位,这里是加一个<code>mock</code>,目的是方便区分什么时候取自己模拟的数据,时候取后端真实接口的数据。</p><p>然后,在项目的请求封装的文件中添加下列代码:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> mock = <span class="literal">true</span> <span class="comment">// true</span></span><br><span class="line"><span class="keyword">let</span> baseURL = mock ? <span class="string">'//mock/'</span> : <span class="string">`//activity.f***s.cn/`</span></span><br></pre></td></tr></table></figure><p>在项目前后端独立开发阶段,设置<code>mock</code>为true,所有的请求都会以<code>//mock</code>开头,刚好和我们<code>mock.js</code>中设置的一一对应,mockjs会拦截到所有<code>//mock</code>开头的请求,并且返回它自己模拟的数据。等到前后端联调的时候,再设置为false,然后所有的请求就会按照真实的后端接口地址来发送请求。</p><h2 id="随机数据"><a href="#随机数据" class="headerlink" title="随机数据"></a>随机数据</h2><p>mockjs可以随机地模拟各类的数据,常用的包括日期时间、城市省份、姓名、颜色、关键字、email、句子段落、甚至可以按尺寸模拟图片、带文字的图片等等。这里就举一个简单例子,更多种类的数据可以查看官方文档。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> data = Mock.mock({</span><br><span class="line"> <span class="string">'code'</span>: <span class="number">200</span>,</span><br><span class="line"> <span class="string">'message'</span>: <span class="string">'success'</span>,</span><br><span class="line"> <span class="string">'data'</span>: {</span><br><span class="line"> <span class="string">'list|5-10'</span>: [{ <span class="comment">// 随机返回一个包含5到10个对象的数组</span></span><br><span class="line"> <span class="string">'num|0-100'</span>: <span class="number">0</span>, <span class="comment">// 随机返回一个0到100的整数</span></span><br><span class="line"> <span class="string">'name'</span>: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{ <span class="comment">// 随机返回一个姓名</span></span><br><span class="line"> <span class="keyword">return</span> Mock.mock(<span class="string">'@cname'</span>)</span><br><span class="line"> },</span><br><span class="line"> <span class="string">'date'</span>: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{ <span class="comment">// 随机返回一个指定格式的日期</span></span><br><span class="line"> <span class="keyword">return</span> Mock.Random.date(<span class="string">'yyyy-MM-dd'</span>)</span><br><span class="line"> }</span><br><span class="line"> }],</span><br><span class="line"> <span class="string">'img'</span>: Mock.Random.image(<span class="string">'450x400'</span>, <span class="string">'#50B347'</span>, <span class="string">'#FFF'</span>, <span class="string">'Hello!Mock.js!'</span>)</span><br><span class="line"> <span class="comment">// 返回指定尺寸、颜色和文字的一张图</span></span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>mockjs在模拟数据这一块非常的方便,甚至有时候mockjs模拟的数据比联调的时候,后端在开发环境数据库里模拟的数据更全面、情况更多。前端在拿到接口格式后,可以独立于后端进行开发,并且前后端的开发进度基本上不会相互影响,联调成本也会大大降低。但是在实际的应用中,我还是发现mockjs存在一些坑点,毕竟这个库的GitHub上issues还是挺多的。</p><h2 id="坑点1:get请求不能传参"><a href="#坑点1:get请求不能传参" class="headerlink" title="坑点1:get请求不能传参"></a>坑点1:get请求不能传参</h2><p>mockjs只会拦截你的请求,但不会对你请求发送的参数进行任何验证处理等。所以在模拟post请求数据时,我可以按照真实接口的请求方式,把请求的参数完整的写在代码里,因为无论发送什么参数,数据都会按照mock的格式正确返回。</p><p>但是,我在模拟get请求的时候,如果携带上接口需要的参数,get请求会将参数加载请求的url后面。例如,我在<code>mock.js</code>中设置的接口是<code>//mock/base/getSomeData</code>,然后发送get请求并携带参数后,真实地址变成了<code>//mock/base/getSomeData?userId=12345</code>。这个时候我发现mockjs就没办法正确的返回mock数据了,我查阅了mockjs文档,有看到关于get请求的模拟,但是尝试之后发现任然没用,所以我就只能在封装get请求的时候,判断一下当前状态是mock状态还是非mock状态,如果是mock状态就不携带参数,虽然方法有点笨,但也算是解决了这个问题,并且在联调的时候,不需要修改业务逻辑层的代码。如下:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> mock = <span class="literal">true</span> <span class="comment">// false</span></span><br><span class="line"><span class="keyword">let</span> baseURL = mock ? <span class="string">'//mock/'</span> : <span class="string">`//activity.f***s.cn/`</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> {</span><br><span class="line"> get(url, params) {</span><br><span class="line"> <span class="keyword">return</span> Axios({</span><br><span class="line"> url: url,</span><br><span class="line"> method: <span class="string">'get'</span>,</span><br><span class="line"> baseURL: baseURL,</span><br><span class="line"> params: mock ? {} : params,</span><br><span class="line"> withCredentials: <span class="literal">true</span></span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"> ......</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="坑点2:无法携带cookie"><a href="#坑点2:无法携带cookie" class="headerlink" title="坑点2:无法携带cookie"></a>坑点2:无法携带cookie</h2><p>这个坑就厉害了,曾经整整折磨了我一个下午。。。。。。</p><p>前不久,在开发一个后台管理项目的时候,后端写好了一个用户信息验证接口准备和我一起调试一下,这个接口需要携带cookie。我就从mock模式切换到非mock模式,仅仅修改了封装请求的js文件中的<code>let mock = false</code>,然后我就天真地以为,可以顺利的调试正式接口了。但我发现我前端请求头设置了<code>withCredentials: true</code>,后端响应头也设置了<code>'Access-Control-Allow-Credentials', 'true'</code>,但是发送的请求就是死活携带不上cookie 🙂我和隔壁组两个前端,三个人花了大半个下午的时间,最后发现是引用了mockjs模块的原因。后来去GitHub上搜了mockjs的源码,搜索出来这么一行。</p><p><img src="/2018/04/24/2fe-mock/fe-mock/withCredentials.jpg" alt="mockjs"></p><p>然后我当时就心里MMP了🙂。。。<br>它拦截了我的请求,并且修改了<code>withCredentials</code>,所以cookie携带不上。我的所以下次“切换非mock模式”的时候,还需要注释掉<code>require('mockjs)</code>,也算是吃一堑长一智吧。</p><h1 id="Express-Mockjs"><a href="#Express-Mockjs" class="headerlink" title="Express+Mockjs"></a>Express+Mockjs</h1><p>express是一个基于Node.js平台的web开发框架。<br>mockjs模拟数据固然很方便,但它仅仅使用来模拟接口数据的,如果想把前端数据mock的过程更真实,更接近正式的请求,那可以结合express来起一个后端服务器,模拟整个请求的发送接收过程。</p><h2 id="安装-1"><a href="#安装-1" class="headerlink" title="安装"></a>安装</h2><p>新建一个mock服务端的小demo,安装mockjs和express:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install express mockjs --save-dev</span><br></pre></td></tr></table></figure><h2 id="搭建服务"><a href="#搭建服务" class="headerlink" title="搭建服务"></a>搭建服务</h2><p>新建一个<code>server.js</code>文件:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>)</span><br><span class="line"><span class="keyword">let</span> app = express()</span><br><span class="line"></span><br><span class="line">app.all(<span class="string">'/api/demo'</span>, (req, res) => {</span><br><span class="line"> res.send(<span class="string">'Mock!! This is MockData!!'</span>)</span><br><span class="line">})</span><br><span class="line"><span class="keyword">let</span> server = app.listen(<span class="number">9999</span>, () => {</span><br><span class="line"> <span class="keyword">let</span> port = server.address().port</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'server running successful...'</span>)</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`server listening at http://localhost:<span class="subst">${port}</span>`</span>)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>运行<code>node server.js</code>把服务跑起来后,浏览器打开<code>http://localhost:9999/api/demo</code>,可以看到<code>Mock!! This is MockData!!</code>。</p><h2 id="引入mockjs"><a href="#引入mockjs" class="headerlink" title="引入mockjs"></a>引入mockjs</h2><p>修改<code>server.js</code>文件,把前面介绍的mockjs模拟的数据,并放到response中:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>)</span><br><span class="line"><span class="keyword">let</span> Mock = <span class="built_in">require</span>(<span class="string">'mockjs'</span>)</span><br><span class="line"><span class="keyword">let</span> app = express()</span><br><span class="line"></span><br><span class="line">app.all(<span class="string">'/api/demo'</span>, (req, res) => {</span><br><span class="line"> <span class="keyword">let</span> data = Mock.mock({</span><br><span class="line"> <span class="string">'code'</span>: <span class="number">200</span>,</span><br><span class="line"> <span class="string">'message'</span>: <span class="string">'success'</span>,</span><br><span class="line"> <span class="string">'data'</span>: {</span><br><span class="line"> <span class="string">'list|5-10'</span>: [{ <span class="comment">// 随机返回一个包含5到10个对象的数组</span></span><br><span class="line"> <span class="string">'num|0-100'</span>: <span class="number">0</span>, <span class="comment">// 随机返回一个0到100的整数</span></span><br><span class="line"> <span class="string">'name'</span>: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{ <span class="comment">// 随机返回一个姓名</span></span><br><span class="line"> <span class="keyword">return</span> Mock.mock(<span class="string">'@cname'</span>)</span><br><span class="line"> },</span><br><span class="line"> <span class="string">'date'</span>: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{ <span class="comment">// 随机返回一个指定格式的日期</span></span><br><span class="line"> <span class="keyword">return</span> Mock.Random.date(<span class="string">'yyyy-MM-dd'</span>)</span><br><span class="line"> }</span><br><span class="line"> }],</span><br><span class="line"> <span class="string">'img'</span>: Mock.Random.image(<span class="string">'450x400'</span>, <span class="string">'#50B347'</span>, <span class="string">'#FFF'</span>, <span class="string">'Hello!Mock.js!'</span>)</span><br><span class="line"> <span class="comment">// 返回指定尺寸、颜色和文字的一张图</span></span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> res.json(data)</span><br><span class="line">})</span><br><span class="line"><span class="keyword">let</span> server = app.listen(<span class="number">9999</span>, () => {</span><br><span class="line"> <span class="keyword">let</span> port = server.address().port</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'server running successfully...'</span>)</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`server listening at http://localhost:<span class="subst">${port}</span>`</span>)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>浏览器访问<code>http://localhost:9999/api/demo</code>后可以看到相应的模拟数据。</p><h2 id="修改前端请求封装"><a href="#修改前端请求封装" class="headerlink" title="修改前端请求封装"></a>修改前端请求封装</h2><p>有了前面的思路,我们修改前端项目封装请求的文件:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> mock = <span class="literal">true</span> <span class="comment">// true</span></span><br><span class="line"><span class="keyword">let</span> baseURL = mock ? <span class="string">'//localhost:9999/'</span> : <span class="string">`//activity.f***s.cn/`</span></span><br></pre></td></tr></table></figure><p>即在“mock模式下”,前端的请求全都去请求本地的<code>9999</code>端口的服务。</p><h2 id="完善mock服务端"><a href="#完善mock服务端" class="headerlink" title="完善mock服务端"></a>完善mock服务端</h2><p>然后,我们再把mock服务端项目完善一下。为了方便和后端的正式接口一一对应,我在mock服务端项目新建一个mockApi文件夹,并在里面按照正式接口的名称层级,创建每个接口对应的mock文件。例如,有个名称为<code>/base/getUserInfoFromPC</code>的接口,就在<code>./mockApi/base</code>目录下新建getUserInfoFromPC.js文件,添加如下代码:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> Mock = <span class="built_in">require</span>(<span class="string">'mockjs'</span>)</span><br><span class="line"><span class="keyword">let</span> data = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> Mock.mock({</span><br><span class="line"> <span class="string">'code'</span>: <span class="number">200</span>,</span><br><span class="line"> <span class="string">'data'</span>: {</span><br><span class="line"> <span class="string">'nickName'</span>: <span class="string">'alexixiang'</span>,</span><br><span class="line"> <span class="string">'id'</span>: <span class="number">1707000766</span></span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line">}</span><br><span class="line"><span class="built_in">module</span>.exports = data</span><br></pre></td></tr></table></figure><p>然后修改server.js文件:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>)</span><br><span class="line"><span class="keyword">let</span> app = express()</span><br><span class="line"><span class="keyword">let</span> baseRoot = <span class="string">'./mockApi'</span> <span class="comment">// mock数据文件夹路径</span></span><br><span class="line">app.post(<span class="string">'/base/getUserInfoFromPC'</span>, (req, res) => {</span><br><span class="line"> res.json(<span class="built_in">require</span>(<span class="string">`<span class="subst">${baseRoot}</span>/base/getUserInfoFromPC.js`</span>)())</span><br><span class="line">})</span><br><span class="line"><span class="keyword">let</span> server = app.listen(<span class="number">9999</span>, () => {</span><br><span class="line"> <span class="keyword">let</span> port = server.address().port</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'server running successfully...'</span>)</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`server listening at http://localhost:<span class="subst">${port}</span>`</span>)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>然后在开发中,本地需要跑两个服务,一个是mock服务端,另一个是本地的前端页面。通常两个服务的端口不一样,存在跨域问题,所以还需要在<code>server.js</code>中添加配置跨域,如果需要携带cookie还需要设置<code>'Access-Control-Allow-Credentials', 'true'</code>:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 中间件</span></span><br><span class="line">app.use(<span class="function">(<span class="params">req, res, next</span>) =></span> {</span><br><span class="line"> res.header(<span class="string">'Access-Control-Allow-Origin'</span>, req.headers.origin)</span><br><span class="line"> res.header(<span class="string">'Access-Control-Allow-Headers'</span>, <span class="string">'Content-Type'</span>)</span><br><span class="line"> res.header(<span class="string">'Access-Control-Allow-Methods'</span>, <span class="string">'PUT, GET, POST, DELETE, OPTIONS'</span>)</span><br><span class="line"> res.header(<span class="string">'Access-Control-Allow-Credentials'</span>, <span class="string">'true'</span>)</span><br><span class="line"> next()</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>至此,利用express和mockjs搭建的mock服务端就基本完成了。我觉得这种方法相比于第一种方法好处有以下几点:</p><ul><li>前面讲到的两个坑点,不会再出现了。</li><li>mock代码都在mock服务端写,前端项目不写mock相关代码。把前端项目的业务逻辑、接口请求和mock数据的代码独立开来,不会相互影响。</li><li>而且在mock服务端还可以对请求的传参进行一些简单的判断和处理等,从而让整个mock数据的过程更接近正式联调接口的状态。</li></ul><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>前端数据mock本身是一个可大可小、可繁可简的开发流程。但是在实际开发中,一个新的项目通常都是前后端同时进行开发,为了降低前后端接口联调的成本,提高联调效率,完善前端mock数据和mock接口请求的过程是很有必要的。<br>前端mock的方法就简单介绍到这里,欢迎各位大佬批评指正。</p><h1 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h1><ul><li>mock:<a href="http://mockjs.com/" target="_blank" rel="noopener">http://mockjs.com/</a></li><li>express:<a href="http://www.expressjs.com.cn/" target="_blank" rel="noopener">http://www.expressjs.com.cn/</a></li></ul>]]></content>
<categories>
<category> technology share </category>
</categories>
<tags>
<tag> technology share </tag>
</tags>
</entry>
<entry>
<title>VUEX学习笔记</title>
<link href="/2018/03/07/1vuex/"/>
<url>/2018/03/07/1vuex/</url>
<content type="html"><![CDATA[<p><img src="/2018/03/07/1vuex/./vuex.png" width="600px"><br>vuex学习笔记</p><a id="more"></a><hr><h1 id="Vuex-简介"><a href="#Vuex-简介" class="headerlink" title="Vuex 简介"></a>Vuex 简介</h1><p>Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。我们可以利用vuex来管理多个组件的共享状态。</p><p>每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:</p><ul><li><p>Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。</p></li><li><p>你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。</p></li></ul><h1 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h1><p>使用npm安装vuex<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install vuex --save-dev</span><br></pre></td></tr></table></figure></p><h1 id="简单用法"><a href="#简单用法" class="headerlink" title="简单用法"></a>简单用法</h1><p>首先,我们需要在src目录下新建一个store目录,用来对项目的各个状态进行管理。再store文件夹下新建一个index.js,作为整个项目状态管理的入口文件(就好比于route文件下的index.js)。然后在index.js中添加如下代码:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"><span class="keyword">import</span> vuex <span class="keyword">from</span> <span class="string">'vuex'</span></span><br><span class="line">Vue.use(vuex)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> store = <span class="keyword">new</span> vuex.Store({</span><br><span class="line"> state: {</span><br><span class="line"> hasLogin: <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> store</span><br></pre></td></tr></table></figure></p><p>这样我们就定义好了一个简单的store,接下来需要在项目入口文件的Vue实例中载入这个store。修改main.js如下:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"><span class="keyword">import</span> App <span class="keyword">from</span> <span class="string">'./App'</span></span><br><span class="line"><span class="keyword">import</span> store <span class="keyword">from</span> <span class="string">'./store'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> Vue({</span><br><span class="line"> el: <span class="string">'#app'</span>,</span><br><span class="line"> store,</span><br><span class="line"> components: { App },</span><br><span class="line"> template: <span class="string">'<App/>'</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure></p><p>然后我们在页面上使用 <code>this.$store.state.hasLogin</code> 就可以获取到hasLogin这个状态。<br>但是在实际开发中,有时候这样简单的用法不能满足复杂的状态管理需求,所以我们需要进一步的学习Vuex的几个核心概念。</p><h1 id="State"><a href="#State" class="headerlink" title="State"></a>State</h1><p>state可以理解成状态,它记录了整个项目的各个状态属性。Vuex使用单一状态树,简单的说,就是每个应用仅仅包含一个store实例。只要在根实例中注册了 store 选项,该 store 实例会注入到根组件下的所有子组件中,然后子组件就都可以通过 <code>this.$store</code> 访问到了。例如上面的代码中,在整个项目的vue组件中都可以使用<code>this.$store.state.hasLogin</code> 来获取hasLogin这个状态。在Vue组件中,通常使用计算属性返回某个状态,来获得Vuex状态。</p><h1 id="Getter"><a href="#Getter" class="headerlink" title="Getter"></a>Getter</h1><p>有时候我们需要从store中的state中派生出一些状态,例如某个state的值是随着另个state的值的变化而变化的。这时候我们就需要用到getter,可以理解为 store 的计算属性。getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。<br>修改store文件夹下的index.js:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"><span class="keyword">import</span> vuex <span class="keyword">from</span> <span class="string">'vuex'</span></span><br><span class="line">Vue.use(vuex)</span><br><span class="line"><span class="keyword">const</span> store = <span class="keyword">new</span> vuex.Store({</span><br><span class="line"> state: {</span><br><span class="line"> userId: <span class="number">123456</span>,</span><br><span class="line"> hasLogin: <span class="literal">true</span></span><br><span class="line"> },</span><br><span class="line"> <span class="comment">// 根据某个state计算一些跟它有关系的状态等</span></span><br><span class="line"> getters: {</span><br><span class="line"> defaultName(state) {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">`user<span class="subst">${state.userId}</span>`</span></span><br><span class="line"> },</span><br><span class="line"> isVisitor(state) {</span><br><span class="line"> <span class="keyword">return</span> !state.hasLogin</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> store</span><br></pre></td></tr></table></figure></p><p>像上面这样定义了两个getter之后,可以在任何组件中访问这些值:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">this</span>.$store.getters.defaultName <span class="comment">// user123456</span></span><br><span class="line"><span class="keyword">this</span>.$store.getters.isVisitor <span class="comment">// false</span></span><br></pre></td></tr></table></figure></p><p>一旦getter对象中的属性所依赖的state发生了变化,那对应的getter属性也会跟着变化</p><h1 id="Mutation"><a href="#Mutation" class="headerlink" title="Mutation"></a>Mutation</h1><p>mutation是用来修改Vuex的store中的状态state的,并且这是唯一的方法。在index.js中添加mutation:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"><span class="keyword">import</span> vuex <span class="keyword">from</span> <span class="string">'vuex'</span></span><br><span class="line">Vue.use(vuex)</span><br><span class="line"><span class="keyword">const</span> store = <span class="keyword">new</span> vuex.Store({</span><br><span class="line"> state: {</span><br><span class="line"> userId: <span class="number">123456</span>,</span><br><span class="line"> hasLogin: <span class="literal">true</span></span><br><span class="line"> },</span><br><span class="line"> <span class="comment">// 根据某个state计算一些跟它有关系的状态等</span></span><br><span class="line"> getters: {</span><br><span class="line"> defaultName(state) {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">`用户<span class="subst">${state.userId}</span>`</span></span><br><span class="line"> },</span><br><span class="line"> isVisitor(state) {</span><br><span class="line"> <span class="keyword">return</span> !state.hasLogin</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> <span class="comment">// 修改state中的属性</span></span><br><span class="line"> mutations: {</span><br><span class="line"> <span class="comment">// 第一个参数传入state,第二个参数(可选)传入额外的参数(载荷payload)</span></span><br><span class="line"> switchUser(state, newId) {</span><br><span class="line"> <span class="comment">// 切换用户,修改存储的用户信息</span></span><br><span class="line"> state.userId = newId</span><br><span class="line"> },</span><br><span class="line"> logout(state) {</span><br><span class="line"> state.hasLogin = !state.hasLogin</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> store</span><br></pre></td></tr></table></figure></p><p>定义好mutation后,在任何组件中调用 store.commit 方法就可以对state进行修改:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">this</span>.$store.commit(<span class="string">'switchUser'</span>, <span class="number">654321</span>)</span><br><span class="line"><span class="keyword">this</span>.$store.commit(<span class="string">'logout'</span>)</span><br></pre></td></tr></table></figure><h1 id="Action"><a href="#Action" class="headerlink" title="Action"></a>Action</h1><p>Action 和 mutation比较类似,但不同在于:</p><ul><li>Action提交的是mutation,而不是直接变更状态。</li><li>Action可以包含任意异步操作。</li></ul><p>在index.js中添加action:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"><span class="keyword">import</span> vuex <span class="keyword">from</span> <span class="string">'vuex'</span></span><br><span class="line">Vue.use(vuex)</span><br><span class="line"><span class="keyword">const</span> store = <span class="keyword">new</span> vuex.Store({</span><br><span class="line"> state: {</span><br><span class="line"> userId: <span class="number">123456</span>,</span><br><span class="line"> hasLogin: <span class="literal">true</span></span><br><span class="line"> },</span><br><span class="line"> <span class="comment">// 根据某个state计算一些跟它有关系的状态等</span></span><br><span class="line"> getters: {</span><br><span class="line"> defaultName(state) {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">`用户<span class="subst">${state.userId}</span>`</span></span><br><span class="line"> },</span><br><span class="line"> isVisitor(state) {</span><br><span class="line"> <span class="keyword">return</span> !state.hasLogin</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> <span class="comment">// 修改state中的属性</span></span><br><span class="line"> mutations: {</span><br><span class="line"> <span class="comment">// 第一个参数传入state,第二个参数(可选)传入额外的参数(载荷payload)</span></span><br><span class="line"> switchUser(state, newId) {</span><br><span class="line"> <span class="comment">// 切换用户,修改存储的用户信息</span></span><br><span class="line"> state.userId = newId</span><br><span class="line"> },</span><br><span class="line"> logout(state) {</span><br><span class="line"> state.hasLogin = !state.hasLogin</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> <span class="comment">// 操作mutation</span></span><br><span class="line"> actions: {</span><br><span class="line"> logout(context) {</span><br><span class="line"> context.commit(<span class="string">'logout'</span>)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> store</span><br></pre></td></tr></table></figure></p><p>Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此在action中,可以调用 <code>context.commit</code> 提交一个 mutation,或者通过 <code>context.state</code> 和 <code>context.getters</code> 来获取 state 和 getters。</p><p>定义好action后,vue组件中通过<code>store.dispatch</code>触发:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">this</span>.$store.dispatch(<span class="string">'login'</span>)</span><br></pre></td></tr></table></figure></p><p>action内部可以执行异步操作,也可以像mutation一样用载荷的方式分发。</p><h1 id="Module"><a href="#Module" class="headerlink" title="Module"></a>Module</h1><p>如果项目有很多的状态需要管理,但却把store全都写在index.js中,会显得很臃肿不便于维护,所以我们需要将store拆分成不同的模块(module),每个模块可以拥有自己的state,mutation,action。<br>在store文件夹下新建一个名为modules的文件夹,下面可以新建一个authority.js,定义一个模块:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 用户权限状态</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> {</span><br><span class="line"> state: {</span><br><span class="line"> roles: []</span><br><span class="line"> },</span><br><span class="line"> getters: {</span><br><span class="line"> },</span><br><span class="line"> mutations: {</span><br><span class="line"> },</span><br><span class="line"> actions: {</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>然后修改index.js:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"><span class="keyword">import</span> vuex <span class="keyword">from</span> <span class="string">'vuex'</span></span><br><span class="line"><span class="keyword">import</span> authority <span class="keyword">from</span> <span class="string">'./modules/authority'</span></span><br><span class="line">Vue.use(vuex)</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">new</span> vuex.Store({</span><br><span class="line"> modules: {</span><br><span class="line"> authority: authority</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure></p><p>然后在组件中通过<code>this.$store.state.authority.roles</code>来获取state</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>vuex的基本用法就写到这里,算是记录并梳理一下自己学习vuex的过程,欢迎各位大佬批评指正。<br>更多关于vuex的高级用法可以参考官方文档。<br></p><h1 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h1><ul><li><a href="https://vuex.vuejs.org/zh-cn/" target="_blank" rel="noopener">https://vuex.vuejs.org/zh-cn/</a></li></ul>]]></content>
<categories>
<category> study notes </category>
</categories>
<tags>
<tag> study notes </tag>
</tags>
</entry>
<entry>
<title>First Blog</title>
<link href="/2018/02/26/0firstblog/"/>
<url>/2018/02/26/0firstblog/</url>
<content type="html"><![CDATA[<p><img src="/2018/02/26/0firstblog/./alexi.jpg" width="400px"><br>Welcome to my personal website, this is my first blog.</p><a id="more"></a><hr><p>Metal never die.</p>]]></content>
<tags>
<tag> welcome </tag>
</tags>
</entry>
</search>