-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
509 lines (276 loc) · 235 KB
/
atom.xml
File metadata and controls
509 lines (276 loc) · 235 KB
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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>exp team</title>
<subtitle>together, stronger</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="https://exp-team.github.io/"/>
<updated>2017-11-05T02:48:25.000Z</updated>
<id>https://exp-team.github.io/</id>
<author>
<name>exp developer</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>从 React 绑定 this,看 JS 语言发展和框架设计</title>
<link href="https://exp-team.github.io/blog/2017/11/04/js/react-this/"/>
<id>https://exp-team.github.io/blog/2017/11/04/js/react-this/</id>
<published>2017-11-03T16:00:00.000Z</published>
<updated>2017-11-05T02:48:25.000Z</updated>
<content type="html"><![CDATA[<p><img src="http://upload-images.jianshu.io/upload_images/4363003-37229a920f78982c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="react"></p><p>在 javascript 语言中,关于 this 这个关键字的行为一直以来困扰着一代又一代初级开发者。同时 this,也充分反应了 javascript 的诡异与灵活。</p><p>但是请别误会,这篇文章并不会对 this 的特征进行全方位讲解,因为这些内容都可以在各种前端书籍中找到答案。这里,我试图结合 React 事件处理函数关于 this 绑定的演化史,<strong>谈一谈这个框架设计以及 javascript 语言在这一细节上的进步和完善</strong>。同时对比 this 绑定的不同方案,让大家对 React 、ES next 有一个更清晰的认识。</p><p>React 处理 this 上下文环境已经有至少五年历史了。五年期间,方案辈出,我们先来总结一下。<br><br></p><h2 id="方法一:React-createClass-自动绑定"><a href="#方法一:React-createClass-自动绑定" class="headerlink" title="方法一:React.createClass 自动绑定"></a>方法一:React.createClass 自动绑定</h2><p>React 中创建组件的方式已经很多,比较古老的诸如 React.createClass 应该很多人并不陌生。当然,从 React 0.13 开始,可以使用 ES6 Class 代替 React.createClass 了,这应该是今后推荐的方法。<br>但是需要知道,React.createClass 创建的组件,可以自动绑定 this。也就是说,this 这个关键字会自动绑定在组件实例上面。</p><pre><code>// This magically works with React.createClass// because `this` is bound for you.onChange = {this.handleChange}</code></pre><p>当然很遗憾,对于组件的创建,官方已经推荐使用 class 声明组件或使用 functional 无状态组件:</p><blockquote><p>Later, classes were added to the language as part of ES2015, so we added the ability to create React components using JavaScript classes. Along with functional components, JavaScript classes are now the preferred way to create components in React.<br>For your existing createClass components, we recommend that you migrate them to JavaScript classes. </p></blockquote><p>我认为,这其实是 React 框架本身的自我完善和对未来的迎合,是框架和语言发展的大势所趋。</p><p><br></p><h2 id="方法二:渲染时绑定"><a href="#方法二:渲染时绑定" class="headerlink" title="方法二:渲染时绑定"></a>方法二:渲染时绑定</h2><p>通过前文,我们知道最传统的组件创建方式不会有 this 绑定的困扰。接下来,我们假定所有的组件都采取 ES6 classes 方式声明。这种情况下,this 无法自动绑定。一个常见的解决方案便是:</p><pre><code>onChange = {this.handleChange.bind(this)}</code></pre><p>这种方法简明扼要,但是有一个潜在的性能问题:<strong>当组件每次重新渲染时,都会有一个新的函数创建</strong>。OMG! 这听上去貌似是一个很大的问题,但是其实在真正的开发场景中,由此引发的性能问题往往不值一提(除非是大型组件消费类应用或游戏)。</p><p><br></p><h2 id="方法三:箭头函数绑定"><a href="#方法三:箭头函数绑定" class="headerlink" title="方法三:箭头函数绑定"></a>方法三:箭头函数绑定</h2><p>这种方法其实和第二种类似,拜 ES6 箭头函数所赐,我们可以隐式绑定 this:</p><pre><code>onChange = {e => this.handleChange(e)}</code></pre><p>当然,也与第二种方法一样,<strong>它同样存在潜在的性能问题</strong>。<br>下面将要介绍的两种方法,可以有效规避不必要的性能消耗,请继续阅读。</p><p><br></p><h2 id="方法四:Constructor-内绑定"><a href="#方法四:Constructor-内绑定" class="headerlink" title="方法四:Constructor 内绑定"></a>方法四:Constructor 内绑定</h2><p>constructor 方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。</p><p>所以我们可以:</p><pre><code>constructor(props) { super(props); this.handleChange = this.handleChange.bind(this);}</code></pre><p>这种方式往往被推荐为“<strong>最佳实践</strong>”,也是笔者最为常用的方法。</p><p>但是就个人习惯而言,我认为与前两种方法相比,constructor 内绑定在可读性和可维护性上也许有些欠缺。<br>同时,我们知道在 constructor 声明的方法不会存在实例的原型上,而属于实例本身的方法。每个实例都有同样一个 handleChange,这本身也是一种重复和浪费。</p><p>如果你对 ES next 一直抱有开放的思想,且能够使用 stage-2 的特性,不妨尝试一下最后一种方案。</p><p><br></p><h2 id="方法五:Class-属性中使用-和箭头函数"><a href="#方法五:Class-属性中使用-和箭头函数" class="headerlink" title="方法五:Class 属性中使用 = 和箭头函数"></a>方法五:Class 属性中使用 = 和箭头函数</h2><p>这个方法依赖于 ES next 的新特性,请参考<a href="https://tc39.github.io/proposal-class-public-fields/" target="_blank" rel="external"> tc 39: Public Class Fields</a></p><pre><code>handleChange = () => { // call this function from render // and this.whatever in here works fine.};</code></pre><p>我们来总结一下这种方式的优点:</p><ul><li>使用箭头函数,有效绑定了 this;</li><li>没有第二种方法和第三种方法的潜在性能问题;</li><li>避免了方法四的组件实例重复问题;</li><li>我们可以直接从 ES5 createClass 重构得来。</li></ul><p><br></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文在对比 React 绑定 this 的五种方法的同时,也由远及近了解了 javascript 语言的发展:从 ES5 的 bind, 到 ES6 的箭头函数,再到 ES next 对 class 的改进。</p><p>React 作为蓬勃发展的框架也同样在与时具进,不断完善,结合语言特性的发展不断调整着自身。</p><p>最后,我们通过这张图片来完整回顾:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-25a0fd5ac57e5b6c?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="各种方式逻辑"></p><p>本文参考了 Cory House 的文章:<a href="https://medium.freecodecamp.com/react-binding-patterns-5-approaches-for-handling-this-92c651b5af56" target="_blank" rel="external">5 Approaches for Handling <code>this</code></a>,并在此基础上进行延伸。<br><br><br><br><strong>我的其他一些关于 React 文章:</strong></p><ul><li><a href="http://www.jianshu.com/p/78cfc7e0067f" target="_blank" rel="external">React 组件设计和分解思考</a></li><li><a href="http://www.jianshu.com/p/49029b49f2b4" target="_blank" rel="external">做出Uber移动网页版还不够 极致性能打造才见真章</a></li><li><a href="http://www.jianshu.com/p/7a56ac1de2a8" target="_blank" rel="external">解析Twitter前端架构 学习复杂场景数据设计</a></li><li><a href="http://www.jianshu.com/p/83c86dd0802d" target="_blank" rel="external">React Conf 2017 干货总结1: React + ES next = ♥</a></li><li><a href="http://www.jianshu.com/p/cde3cf7e2760" target="_blank" rel="external">React+Redux打造“NEWS EARLY”单页应用 一个项目理解最前沿技术栈真谛</a></li><li><a href="http://www.jianshu.com/p/8e28be0e7ab1" target="_blank" rel="external">一个react+redux工程实例</a></li><li>……</li></ul><p>Happy Coding!</p><p>PS: 作者<a href="https://github.com/HOUCe" target="_blank" rel="external">Github仓库</a>,欢迎通过代码各种形式交流。</p>]]></content>
<summary type="html">
<p><img src="http://upload-images.jianshu.io/upload_images/4363003-37229a920f78982c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"
</summary>
<category term="js" scheme="https://exp-team.github.io/categories/js/"/>
<category term="js" scheme="https://exp-team.github.io/tags/js/"/>
</entry>
<entry>
<title>React 服务端渲染如此轻松 从零开始构建前后端应用</title>
<link href="https://exp-team.github.io/blog/2017/11/04/js/next/"/>
<id>https://exp-team.github.io/blog/2017/11/04/js/next/</id>
<published>2017-11-03T16:00:00.000Z</published>
<updated>2017-11-05T02:44:12.000Z</updated>
<content type="html"><![CDATA[<p>参加或留意了最近举行的<a href="https://www.zhihu.com/question/62154473/answer/200045569" target="_blank" rel="external">JSConf CN 2017</a>的同学,想必对 Next.js 不再陌生, Next.js 的作者之一 Guillermo Rauch 到场进行了精彩的演讲。其实在更早些时候,由 Facebook 举办的 React Conf 2017,他就到场并有近40分钟的分享。但两次分享带来的 demo 都是 hacker news。我观察 Next.js 时间较长,看着它从1.x 版本一直到了今天的 3.x,终于决定写一篇入门级的新手指导文章。而这篇文章试图通过一个全新的例子,来让大家了解 Next.js 到底是如何与 React 配合,达到服务端渲染的。</p><p>“React universal” 是社区上形容基于 React 构建 web 应用,并采用“服务端渲染”方式的一个词语。也许很多人对 “isomorphic” 这个单词更加熟悉,其实这两个词语想要表达的概念类似。今天这篇文章显然不是讨论这两个词语的,<strong>我们要尝试使用最新版 <a href="https://github.com/zeit/next.js" target="_blank" rel="external">Next.js</a>,构件一个简单的服务端渲染 React 应用。</strong>最终项目地址可以<a href="https://github.com/HOUCe/server-side-react-football" target="_blank" rel="external">点击这里查看。</a></p><h2 id="为何要开发-Universal-应用?"><a href="#为何要开发-Universal-应用?" class="headerlink" title="为何要开发 Universal 应用?"></a>为何要开发 Universal 应用?</h2><p>React app 实现了虚拟 DOM,来实现对真实 DOM 的抽象。这样的设计迅速引领了前端开发浪潮。但是 “Every great thing comes with a price”,虚拟 DOM 同样带来了一些弊端,比如在前后端分离的开发模式下,SEO就成了问题;同样首屏加载时间变长,各种 loading 消磨人的耐心。就像下面截图所展现的那样:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-0caa6abef06e4c3a.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="页面"></p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-fec93cb7b90b4938.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="查看网页源码"></p><h2 id="使用-Next-js-实现-Universal"><a href="#使用-Next-js-实现-Universal" class="headerlink" title="使用 Next.js 实现 Universal"></a>使用 Next.js 实现 Universal</h2><p>Universal 应用架构可以简单粗暴先而片面的理解成应用将在客户端和服务端共同完成渲染。这样取代了完全由客户端渲染(前后端分离方式)模式。在 React 场景下,我们可以使用 React 自身的 renderToString 完成服务端初次渲染。但是如果我们每次手动来完成这些过程,手动实现服务端繁琐配置,难免令人头大心烦。</p><p>Next.js 的出现,就是为你解决这种恼人的问题。我们先来认识一下它的几个原则和思想:</p><ul><li>不需要除 Next 之外,多余的配置和安装(比如 webpack,babel);</li><li>使用 <a href="https://github.com/threepointone/glamor" target="_blank" rel="external">Glamor</a> 处理样式;</li><li>自动编译和打包;</li><li>热更新;</li><li>方便的静态资源管理;</li><li>成熟灵活的路由配置,包括路由级别 prefetching;</li></ul><h2 id="Demo:英超联赛积分榜"><a href="#Demo:英超联赛积分榜" class="headerlink" title="Demo:英超联赛积分榜"></a>Demo:英超联赛积分榜</h2><p>其实关于更多的 Next.js 设计理念我不想再赘述了,读者都可以在其官网找到丰富的内容。下面,我将使用 Football Data API 来简单开发一个基于 Next.js 的应用,这个应用将展现英超联赛的实时积分榜。同时包含了简单的路由开发和页面跳转。</p><h3 id="小试牛刀"><a href="#小试牛刀" class="headerlink" title="小试牛刀"></a>小试牛刀</h3><p>相信所有的开发者都厌恶超长时间的安装和各种依赖、插件配置。不要担心,Next.js 作为一个独立的 npm package 最大限度的替你完成了很多耗时且无趣的工作。我们首先需要进行安装:</p><pre><code># Start a new projectnpm init# Install Next.jsnpm install next@beta react react-dom --save</code></pre><p>安装结束后,我们就可以开启脚本:</p><pre><code>"scripts": { "start": "next" },</code></pre><p>接下来所需要做的很简单,就是在根目录下创建一个 pages 文件夹,并在其下新建一个 index.js 文件:</p><pre><code>// ./pages/index.js// Import Reactimport React from 'react'// Export an anonymous arrow function// which returns the templateexport default () => ( <h1>This is just so easy!</h1>)</code></pre><p>好了,现在就可以直接看到结果:</p><pre><code># Start your appnpm start</code></pre><p><img src="http://upload-images.jianshu.io/upload_images/4363003-b1afa6031e077f67.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="页面"></p><p>验证一下它来自服务端渲染:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-e1310faf126ca61e.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="查看网页源码"></p><p>就是这么简单清新。总结一下,<strong>安装这个框架会搭建一个基于 React、webpack 和 Babel 的构建过程。</strong>如果我们自己手段实现这一切的话,除了 NodeJS 的种种繁琐不说,webpack 配置,node_modules 依赖,babel插件等等就够折腾半天的了。</p><h2 id="添加-Page-Head"><a href="#添加-Page-Head" class="headerlink" title="添加 Page Head"></a>添加 Page Head</h2><p>在 ./pages/index.js 文件内,我们可以添加页面 head 标签、meta 信息、样式资源等等:</p><pre><code>// ./pages/index.jsimport React from 'react'// Import the Head Componentimport Head from 'next/head'export default () => ( <div> <Head> <title>League Table</title> <meta name="viewport" content="initial-scale=1.0, width=device-width" /> <link rel="stylesheet" href="https://unpkg.com/purecss@0.6.1/build/pure-min.css" /> </Head> <h1>This is just so easy!</h1> </div>)</code></pre><p>这个 head 当然不是指真实的 DOM,千万别忘了 React 虚拟 DOM 的概念。其实这是 Next 提供的 Head 组件,不过最终一定还是被渲染成为真实的 head 标签。</p><h2 id="发送-Ajax-请求"><a href="#发送-Ajax-请求" class="headerlink" title="发送 Ajax 请求"></a>发送 Ajax 请求</h2><p>Next 还提供了 getInitialProps() 组件生命周期钩子方法,使得框架能够在服务器上进行初始渲染,如果需要的话,还可以在客户端继续进行渲染。这个高级特性很小却功能强大。</p><p>这个方法支持异步选项,并且是服务端/客户端同构的。我们可以使用 async/await 方式,处理异步请求。请看下面的示例:</p><pre><code>import React from 'react'import Head from 'next/head'import axios from 'axios';export default class extends React.Component { // Async operation with getInitialProps static async getInitialProps () { // res is assigned the response once the axios // async get is completed const res = await axios.get('http://api.football-data.org/v1/competitions/426/leagueTable'); // Return properties return {data: res.data} } }</code></pre><p>我们使用了 axios 类库来发送 HTTP 请求。网络请求是异步的,因此我们需要在未来某个合适的时候(请求结果返回时)接收数据。这里使用先进的 async/await,以同步的方式处理,从而避免了回调嵌套和 promises 链。</p><p>我们将异步获得的数据返回,它将自动挂载在 props 上(注意 getInitialProps 方法名,顾名思义),render 方法里便可以通过 this.props.data 获取:</p><pre><code>import React from 'react'import Head from 'next/head'import axios from 'axios';export default class extends React.Component { static async getInitialProps () { const res = await axios.get('http://api.football-data.org/v1/competitions/426/leagueTable'); return {data: res.data} } render () { return ( <div> <Head> ...... </Head> <div className="pure-g"> <div className="pure-u-1-3"></div> <div className="pure-u-1-3"> <h1>Barclays Premier League</h1> <table className="pure-table"> <thead> <tr> ...... </tr> </thead> <tbody> {this.props.data.standing.map((standing, i) => { const oddOrNot = i % 2 == 1 ? "pure-table-odd" : ""; return ( <tr key={i} className={oddOrNot}> <td>{standing.position}</td> <td><img className="pure-img logo" src={standing.crestURI}/></td> <td>{standing.points}</td> <td>{standing.goals}</td> <td>{standing.wins}</td> <td>{standing.draws}</td> <td>{standing.losses}</td> </tr> ); })} </tbody> </table> </div> <div className="pure-u-1-3"></div> </div> </div> ); }}</code></pre><p>这样,再访问我们的页面,就有了:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-da9e671c438902f2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="页面"></p><h2 id="路由和页面跳转"><a href="#路由和页面跳转" class="headerlink" title="路由和页面跳转"></a>路由和页面跳转</h2><p>也许你已经有所感知:我们已经有了最基本的一个路由。Next 不需要任何额外的路由配置信息,你只需要在 pages 文件夹下新建文件,每一个文件都将是一个独立的页面。事实上,Next.js 使用 <a href="https://zeit.co/blog/next#zero-setup.-use-the-filesystem-as-an-api" target="_blank" rel="external">filesystem作为API</a>,所以每个放到 pages 文件夹中的组件将会自动映射为一个基于服务器的路由。比如,磁盘上的 pages/detail.js 组件将会自动服务于 /about 这个 URL。</p><p>让我们来新建一个 team 页面吧!新建 ./pages/details.js 文件:</p><pre><code>// ./pages/details.jsimport React from 'react'export default () => ( <p>Coming soon. . .!</p>)</code></pre><p>我们使用 Next 已经准备好的组件 <link> 来进行页面跳转:</p><pre><code>// ./pages/details.jsimport React from 'react'// Import Link from nextimport Link from 'next/link'export default () => ( <div> <p>Coming soon. . .!</p> <Link href="/"><a>Go Home</a></Link> </div>)</code></pre><p>这个页面不能总是 “Coming soon. . .!” 的信息,我们来进行完善以展示更多内容,通过页面 URL 的 query id 变量,我们来请求并展现当前相应队伍的信息:</p><pre><code>import React from 'react'import Head from 'next/head'import Link from 'next/link'import axios from 'axios';export default class extends React.Component { static async getInitialProps ({query}) { // Get id from query const id = query.id; if(!process.browser) { // Still on the server so make a request const res = await axios.get('http://api.football-data.org/v1/competitions/426/leagueTable') return { data: res.data, // Filter and return data based on query standing: res.data.standing.filter(s => s.position == id) } } else { // Not on the server just navigating so use // the cache const bplData = JSON.parse(sessionStorage.getItem('bpl')); // Filter and return data based on query return {standing: bplData.standing.filter(s => s.position == id)} } } componentDidMount () { // Cache data in localStorage if // not already cached if(!sessionStorage.getItem('bpl')) sessionStorage.setItem('bpl', JSON.stringify(this.props.data)) } // . . . render method truncated }</code></pre><p>这个页面根据 query 变量,动态展现出球队信息。具体来看,getInitialProps 方法获取 URL query id,根据 id 筛选出(filter 方法)展示信息。有意思的是,因为一直球队的信息比较稳定,所以在客户端使用了 sessionStorage 进行存储。</p><p>完整的 render 方法:</p><pre><code>// . . . truncatedexport default class extends React.Component { // . . . truncated render() { const detailStyle = { ul: { marginTop: '100px' } } return ( <div> <Head> <title>League Table</title> <meta name="viewport" content="initial-scale=1.0, width=device-width" /> <link rel="stylesheet" href="https://unpkg.com/purecss@0.6.1/build/pure-min.css" /> </Head> <div className="pure-g"> <div className="pure-u-8-24"></div> <div className="pure-u-4-24"> <h2>{this.props.standing[0].teamName}</h2> <img src={this.props.standing[0].crestURI} className="pure-img"/> <h3>Points: {this.props.standing[0].points}</h3> </div> <div className="pure-u-12-24"> <ul style={detailStyle.ul}> <li><strong>Goals</strong>: {this.props.standing[0].goals}</li> <li><strong>Wins</strong>: {this.props.standing[0].wins}</li> <li><strong>Losses</strong>: {this.props.standing[0].losses}</li> <li><strong>Draws</strong>: {this.props.standing[0].draws}</li> <li><strong>Goals Against</strong>: {this.props.standing[0].goalsAgainst}</li> <li><strong>Goal Difference</strong>: {this.props.standing[0].goalDifference}</li> <li><strong>Played</strong>: {this.props.standing[0].playedGames}</li> </ul> <Link href="/">Home</Link> </div> </div> </div> ) }}</code></pre><p>注意下面截图中,同一页面不同 query 值,分别展示了冠军🏆切尔西和曼联的信息。</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-463c88cdb1e175ed.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="切尔西"></p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-14352128450bb0fb.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="曼联"></p><p>别忘了我们的主页(排行榜页面)index.js 中,也要使用相应的 sessionStorage 逻辑。同时,在 render 方法里加入一条链接到详情页的 <link>:</p><pre><code><td><Link href={`/details?id=${standing.position}`}>More...</Link></td></code></pre><h2 id="错误页面"><a href="#错误页面" class="headerlink" title="错误页面"></a>错误页面</h2><p>在 Next 中,我们同样可以通过 error.js 文件定义错误页面。在 ./pages 下新建 error.js:</p><pre><code>// ./pages/_error.jsimport React from 'react'export default class Error extends React.Component { static getInitialProps ({ res, xhr }) { const statusCode = res ? res.statusCode : (xhr ? xhr.status : null) return { statusCode } } render () { return ( <p>{ this.props.statusCode ? `An error ${this.props.statusCode} occurred on server` : 'An error occurred on client' }</p> ) }}</code></pre><p>当传统情况下页面404时,得到:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-811c8bca1607cca0.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="404页面"></p><p>在我们设置 _ error.js 之后,便有:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-af29722f2a62216e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="自定义错误页面"></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这篇文章实现了一个简易 demo,只是介绍了<strong>最基本</strong>的 Next.JS 搭建 React 同构应用的基本步骤。</p><p>想想你是否厌烦了 webpack 恼人的配置?是否对于 Babel 各种插件云里雾里?<br><strong>使用 Next.js,简单、清新而又设计良好。这也是它在推出短短时间以来,便迅速走红的原因之一。</strong></p><p>除此之外,Next 还有非常多的功能,非常多的先进理念可以应用。</p><ul><li>比如 <link> 搭配 prefetch,预先请求资源;</li><li>再如动态加载组件(Next.js 支持 TC39 dynamic import proposal),从而减少首次 bundle size;</li><li>虽然它替我们封装好了 Webpack、Babel 等工具,但是我们又能 customizing,根据需要自定义。</li></ul><p>最后,对于这些本文章没有演示到的功能是否有些手痒?感兴趣的读者可以关注本文 demo 的<a href="https://github.com/HOUCe/server-side-react-football" target="_blank" rel="external">Github项目地址</a>,自己手动尝试起来吧~</p><p>本文意译了<a href="https://scotch.io/tutorials/react-universal-with-next-js-server-side-react" target="_blank" rel="external">Chris Nwamba的:React Universal with Next.js: Server-side React 一文,</a>并对原文进行了升级,兼容了最新的 Next 设计。</p><p>我的其他关于 React 文章:</p><ul><li><a href="http://www.jianshu.com/p/49029b49f2b4" target="_blank" rel="external">做出Uber移动网页版还不够 极致性能打造才见真章</a></li><li><a href="http://www.jianshu.com/p/7a56ac1de2a8" target="_blank" rel="external">解析Twitter前端架构 学习复杂场景数据设计</a></li><li><a href="http://www.jianshu.com/p/83c86dd0802d" target="_blank" rel="external">React Conf 2017 干货总结1: React + ES next = ♥</a></li><li><a href="http://www.jianshu.com/p/cde3cf7e2760" target="_blank" rel="external">React+Redux打造“NEWS EARLY”单页应用 一个项目理解最前沿技术栈真谛</a></li><li><a href="http://www.jianshu.com/p/8e28be0e7ab1" target="_blank" rel="external">一个react+redux工程实例</a></li></ul><p>Happy Coding!</p><p>PS:<br>作者<a href="https://github.com/HOUCe" target="_blank" rel="external">Github仓库</a> 和 <a href="https://www.zhihu.com/people/lucas-hc/answers" target="_blank" rel="external">知乎问答链接</a><br>欢迎各种形式交流。</p>]]></content>
<summary type="html">
<p>参加或留意了最近举行的<a href="https://www.zhihu.com/question/62154473/answer/200045569" target="_blank" rel="external">JSConf CN 2017</a>的同学,想必对 Ne
</summary>
<category term="js" scheme="https://exp-team.github.io/categories/js/"/>
<category term="js" scheme="https://exp-team.github.io/tags/js/"/>
</entry>
<entry>
<title>从贺老微博引出的“遍历器(Iterators)加速那些奥秘”</title>
<link href="https://exp-team.github.io/blog/2017/11/04/js/iterator/"/>
<id>https://exp-team.github.io/blog/2017/11/04/js/iterator/</id>
<published>2017-11-03T16:00:00.000Z</published>
<updated>2017-11-05T02:43:30.000Z</updated>
<content type="html"><![CDATA[<p>我关注的贺老—贺师俊前辈@johnhax 最近发表个这样一条微博:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-2178ac3c9b666439.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="原微博"></p><p>虽然这条微博没有引起大范围的关注和讨论,但是作为新人,我陷入了思考。<strong>究竟 V8 引擎做了哪些魔法,达到了极大限度的优化呢?</strong></p><p>这篇文章,将会深入浅出分析这些优化背后的奥秘。希望大神给予斧正和引导,同时对读者有所启发和帮助。</p><h2 id="我们到底在讨论什么?"><a href="#我们到底在讨论什么?" class="headerlink" title="我们到底在讨论什么?"></a>我们到底在讨论什么?</h2><p><a href="https://www.ecma-international.org/ecma-262/6.0/" target="_blank" rel="external">ECMAScript 2015 语言标准规格</a>介绍了几种新的数据结构:比如 Maps 和 Sets(当然还有类似 WeakMaps 和 WeakSets等),而这几个新引入的数据结构有一个共性,那就是都可以根据同样新引入的<strong>遍历协议(iteration protocol)进行遍历</strong>。这就意味着你可以使用 for…of 循环或者扩展运算符进行操作。举一个 Sets 简单的例子:</p><pre><code>const s = new Set([1, 2, 3, 4]);console.log(...s);// 1 2 3 4for (const x of s) console.log(x);// 1// 2// 3// 4</code></pre><p>同样对于 Maps:</p><pre><code>const m = new Map([[1, "1"], [2, "2"], [3, "3"], [4, "4"]]);console.log(...m);// (2) [1, "1"] (2) [2, "2"] (2) [3, "3"] (2) [4, "4"]console.log(...m.keys());// 1 2 3 4console.log(...m.values());// 1 2 3 4for (const [x, y] of m) console.log(x, "is", y);// 1 "is" "1"// 2 "is" "2"// 3 "is" "3"// 4 "is" "4"</code></pre><p>通过这两个简单的例子,展示了最基本的用法。感兴趣的读者可以参考 ECMA 规范:<a href="https://www.ecma-international.org/ecma-262/6.0/#sec-map-iterator-objects" target="_blank" rel="external"> Map Iterator Objects</a> 和 <a href="https://www.ecma-international.org/ecma-262/6.0/#sec-set-iterator-objects" target="_blank" rel="external">Set Iterator Objects</a>。</p><p>然而不幸的是,这些可遍历的数据结构在 V8 引擎中并没有很好的进行优化。或者说,对于这些实现细节优化程度很差。包括 ECMAScript 成员 Brian Terlson 也曾在 Twitter 上抱怨,指出他使用 Sets 来实现一个正则引擎时遇到了恼人的性能问题。</p><p>所以,现在是时间来对他们进行优化了!但在着手优化前,我们需要先彻底认清一个问题:<strong>这些数据结构处理慢的真实原因究竟是什么?性能瓶颈到底在哪里?</strong><br>为了弄清这个问题,我们就需要理解<strong>底层实现上,迭代器究竟是如何工作的?</strong></p><h2 id="深入主题探索究竟"><a href="#深入主题探索究竟" class="headerlink" title="深入主题探索究竟"></a>深入主题探索究竟</h2><p>为此,我们先从一个简单的 for…of 循环说起。</p><p>ES6 借鉴 C++、Java、C# 和 Python 语言,引入了 for…of 循环,作为遍历所有数据结构的统一方法。</p><p>一个最简单的使用:</p><pre><code>function sum(iterable) { let x = 0; for (const y of iterable) x += y; return x;}</code></pre><p>将这段代码使用 Babel 进行编译,我们得到:</p><pre><code>function sum(iterable) { var x = 0; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = iterable[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var y = _step.value; x += y; } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } return x;}</code></pre><p>需要告诉大家的一个事实是:目前现代 JavaScript 引擎在本质上和 Babel 对 for…of 的去语法糖化处理是相同的,仅仅是在具体一些细节上有差别。</p><p>这个事实出处:</p><blockquote><p>All modern JavaScript engines essentially perform the same desugaring that Babel does here to implement for-of (details vary)</p></blockquote><p>上文引自 V8 核心成员,谷歌工程师 Benedikt Meurer。</p><p>可是上面经过 Babel 编译后的代码不太好阅读。别急,我删去了烦人的异常处理,只保留了最核心的逻辑供大家参考,以便研究:</p><pre><code>function sum(iterable) { var x = 0; var iterator = iterable[Symbol.iterator](); for (;;) { var iterResult = iterator.next(); if (iterResult.done) break; x += iterResult.value; } return x;}</code></pre><p>理解这段代码需要的预备知识需要清楚 for…of 和 Symbol.iterator 方法关系:</p><blockquote><p>一个数据结构只要部署了 Symbol.iterator 属性,就被视为具有 iterator 接口,就可以用 for…of 循环遍历它的成员。也就是说,for…of 循环内部调用的是数据结构的 Symbol.iterator 方法。</p></blockquote><p>for…of 循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如 arguments 对象、DOM NodeList 对象)、Generator 对象,以及字符串。</p><p>我们仔细观察上段段代码,便可以发现迭代器性能优化的关键是:<strong>保证在循环中多次重复调用的 iterator.next() 能得到最大限度的优化</strong>,同时,最理想的情况是完全避免对 iterResult 的内存分配。能够达到这种目的的几个手段便是使用类似 store-load propagation, escape analysis 和 scalar replacement of aggregates 先进的编译处理技术。</p><p>同时,优化后的编译还需要完全消除迭代器本身 —— iterable[Symbol.iterator] 的调用分配,而直接在迭代器 backing-store 上直接操作。</p><p>事实上,在数组和字符串迭代器的优化过程中,就是使用了这样的技术和理念。具体的实施文档可以<a href="https://docs.google.com/document/d/13z1fvRVpe_oEroplXEEX0a3WK94fhXorHjcOMsDmR-8" target="_blank" rel="external">参考这里。</a></p><p><strong>那么具体到 Set 迭代器的性能问题,其实关键原因在与:其实现上混合了 JavaScript 和 C++ 环境。</strong>比如,我们看 %SetIteratorPrototype%.next() 的实现:</p><pre><code>function SetIteratorNextJS() { if (!IS_SET_ITERATOR(this)) { throw %make_type_error(kIncompatibleMethodReceiver, 'Set Iterator.prototype.next', this); } var value_array = [UNDEFINED, UNDEFINED]; var result = %_CreateIterResultObject(value_array, false); switch (%SetIteratorNext(this, value_array)) { case 0: result.value = UNDEFINED; result.done = true; break; case ITERATOR_KIND_VALUES: result.value = value_array[0]; break; case ITERATOR_KIND_ENTRIES: value_array[1] = value_array[0]; break; } return result;}</code></pre><p> 这段代码实际上按部就班做了这么几件事情:</p><ul><li>定义分配了 value_array 数组,初始化时为两项 undefined;</li><li>定义分配了 result 对象,其格式为 {value: value_array, done: false};</li><li>调用 C++ %SetIteratorNext runtime 函数,将迭代器的 this 和 value_array 作为参数传递。</li><li>根据 C++ %SetIteratorNext runtime 函数返回结果,将 result 正确赋值</li></ul><p>这就造成什么样的后果呢?遍历的每一步我们都在不断地分配两个对象:value_array 和 result。不管是 V8 的 TurboFan 还是 Crankshaft (V8的优化编译器) 我们都无法消除这样的操作。更糟糕的是,每一步遍历我们都要在 JavaScript 和 C++ 之间进行切换。下面的图,简要表示了一个简单的 SUM 函数在底层的运行流程:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-cda48b6ba23d106b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="set-iterator-next-js-20170714.png"></p><p>在 V8 执行时,总处在两个状态(事实上更多):执行 C++ 代码和执行 JavaScript 当中。这两个状态之间的转换开销巨大。从 JavaScript 到 C++,是依赖所谓的 CEntryStub 完成,CEntryStub 会触发 C++ 当中指定的 Runtime_Something 函数(本例中为 Runtime_SetIteratorNext)。所以,<strong>是否可以避免这种转换,以及避免 value_array 和 result 对象的分配又决定了性能的关键。</strong></p><p>最新的 %SetIteratorPrototype%.next() 实现正是切中要害,做了这个“关键”的处理。我们想要执行的代码在调用之前就会变得 hot (热处理),TurboFan 进而最终得以热处理优化。借助所谓的 <a href="https://github.com/v8/v8/wiki/CodeStubAssembler-Builtins" target="_blank" rel="external">CodeStubAssembler</a>,<a href="https://chromium-review.googlesource.com/563626" target="_blank" rel="external">baseline implementation,</a>现在已经完全在 JavaScript 层面实现接入。这样我们仅仅只需要调用 C++ 来做垃圾回收(在可用内存耗尽时)以及异常处理的工作。</p><h2 id="基于回调函数的遍历"><a href="#基于回调函数的遍历" class="headerlink" title="基于回调函数的遍历"></a>基于回调函数的遍历</h2><p>在遍历协议中,JavaScript 同样提供 Set.prototype.forEach 和 Map.prototype.forEach 方法,来接收一个回调函数。这些同样依赖于 C++ 的处理逻辑,这样不仅仅需要我们将 JavaScript 转换为 C++,还要处理回调函数转换为 Javascript,这样的工作模式如下图:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-a7c35b576fc03f3c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="set-iterator-foreach-20170714.png"></p><p>所以,上面使用 <a href="https://github.com/v8/v8/wiki/CodeStubAssembler-Builtins" target="_blank" rel="external">CodeStubAssembler</a> 的方式只能处理简单的非回调函数场景。真正完全意义上的优化,包括 forEach 方法的 TurboFan 化还需要一些待开发的魔法。</p><h2 id="优化提升-Benchmark"><a href="#优化提升-Benchmark" class="headerlink" title="优化提升 Benchmark"></a>优化提升 Benchmark</h2><p>我们使用下面的 benchmark 代码进行优化程度的评测:</p><pre><code>const s = new Set;const m = new Map;for (let i = 0; i < 1e7; ++i) { m.set(i, i); s.add(i);}function SetForOf() { for (const x of s) {}}function SetForOfEntries() { for (const x of s.entries()) {}}function SetForEach() { s.forEach(function(key, key, set) {});}function MapForOf() { for (const x of m) {}}function MapForOfKeys() { for (const x of m.keys()) {}}function MapForOfValues() { for (const x of m.values()) {}}function MapForEach() { m.forEach(function(val, key, map) {});}const TESTS = [ SetForOf, SetForOfEntries, SetForEach, MapForOf, MapForOfKeys, MapForOfValues, MapForEach];// Warmup.for (const test of TESTS) { for (let i = 0; i < 10; ++i) test();}// Actual tests.for (const test of TESTS) { console.time(test.name); test(); console.timeEnd(test.name);}</code></pre><p>在 Chrome60 和 Chrome61 版本的对比中,得到下面图标结论:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-f59e194150f56b9f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="improvements-20170714.png"></p><p>可见,虽然大幅提升,但是我们还是得到了一些不太理想的结果。尤其体现在 SetForOfEntries 和 MapForOf 上。但是这将会在更长远的计划上进行处理。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这篇文章只是在大面上介绍了遍历器性能所面临的瓶颈和现有的解决方案。通过贺老的微博,对一个问题进行探究,最终找到 V8 核心成员 Benedikt Meurer 的 <a href="http://benediktmeurer.de/2017/07/14/faster-collection-iterators/" target="_blank" rel="external">Faster Collection Iterators</a>一文,进行参考并翻译。想要完全透彻理解原文章中内容,还需要扎实的计算机基础、v8<br> 引擎工作方式以及编译原理方面的知识储备。</p><p>因我才疏学浅,入行也不到两年,更不算计算机科班出身,拾人牙慧的同时难免理解有偏差和疏漏。更多 V8 引擎方面的知识,建议大家更多关注@justjavac,编译方面的知识关注 Vue 作者@尤雨溪 以及他在 JS Conf 上的演讲内容: <a href="http://2017.jsconf.cn/files/04-compile-time-optimizations-evan-you.pdf" target="_blank" rel="external">前端工程中的编译时优化。</a>毕竟,我们关注前端大 V、网红的根本并不是为了看热闹、看撕 b,而是希望在前辈身上学习到更多知识、得到更多启发。</p><p>最后,随着 ECMAScript 的飞速发展,让每一名前端开发者可能都会感觉到处在不断地学习当中,甚至有些疲于奔命。但是在学习新的特性和语法之外,理解更深层的内容才是学习进阶的关键。</p><blockquote><p>我同样不知道什么是海,</p><p>赤脚站在沙滩上,</p><p>急切地等待着黎明的到来。</p></blockquote>]]></content>
<summary type="html">
<p>我关注的贺老—贺师俊前辈@johnhax 最近发表个这样一条微博:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/4363003-2178ac3c9b666439.png?imageMogr2/
</summary>
<category term="js" scheme="https://exp-team.github.io/categories/js/"/>
<category term="js" scheme="https://exp-team.github.io/tags/js/"/>
</entry>
<entry>
<title>教你编写 Node.js 中间件,实现服务端缓存(附demo源码)</title>
<link href="https://exp-team.github.io/blog/2017/11/04/js/node-cache/"/>
<id>https://exp-team.github.io/blog/2017/11/04/js/node-cache/</id>
<published>2017-11-03T16:00:00.000Z</published>
<updated>2017-11-05T02:45:00.000Z</updated>
<content type="html"><![CDATA[<p><img src="http://upload-images.jianshu.io/upload_images/4363003-bd4c498721be23c5.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt=""></p><p>Express 作为 Node.js 的框架,如今发展可谓如日中天。我很喜欢其灵活、易扩展的设计理念。尤其是该框架的中间件架构设计:使得在应用中加入新特性更加标准化、成本最小化。<strong>这篇文章,我会尝试编写一个非常简单、小巧的中间件,完成服务端缓存功能,进而优化性能。</strong></p><h2 id="关于中间件"><a href="#关于中间件" class="headerlink" title="关于中间件"></a>关于中间件</h2><p>说到中间件,Express 官网对它的阐述是这样的:</p><blockquote><p>“Express 是一个自身功能极简,完全是路由和中间件构成一个web开发框架:从本质上来说,一个 Express 应用就是在调用各种中间件。”</p></blockquote><p>也许你使用过各种各样的中间件进行开发,但是可能并不理解中间件原理,也没有深入过 Express 源码,探究其实现。这里并不打算长篇大论帮您分析,但是使用层面上大致可以参考下图:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-f8d70520775b89c4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="中间件原理"></p><p>建议有兴趣、想深入的读者自己分析,有任何问题欢迎与我讨论。即便您不打算深入,也不会影响对下文中间件编写的理解。</p><h2 id="关于服务端缓存"><a href="#关于服务端缓存" class="headerlink" title="关于服务端缓存"></a>关于服务端缓存</h2><p>缓存已经被广泛应用,来提高页面性能。一说到缓存,可能读者脑海里马上冒出来:“客户端缓存,CDN 缓存,服务器端缓存……”。另一维度上,也会想到:“200(from cache),expire,eTag……”等概念。</p><p>当然作为前端开发者,我们一定要明白这些缓存概念,这些缓存理念是相对于某个具体用户访问来说的,性能优化体现在单个用户上。比如说,我第一次打开页面 A,耗时超长,下一次打开页面由于缓存的作用,时间缩短了。</p><p>但是在服务器端,还存在另外一个维度,思考一下这样的场景:</p><p>我们有一个<strong>静态</strong>页面 B,这个页面服务端需要从数据库获取部分数据 b1,根据 b1 又要计算得到部分数据 b2,还得做各种高复杂度操作,最终才能“东拼西凑”出需要返回的完整页面 B,整个过程耗时2s。</p><p>那么面临的灾难就是,user1 打开页面耗时2s,user2同样打开页面耗时2s……而这些页面都是静态页面 B,内容是完全一样的。为了解决这个灾难,这时候我们也需要缓存,这种缓存就叫先做服务端缓存(server-side cache)。</p><p><strong>总结一下,服务端缓存的目的其实就是对于同一个页面请求,而返回(缓存的)同样的页面内容。这个过程完全独立于不同的用户。</strong></p><p>上面的话有些拗口,可以参考英文表达更清晰:</p><blockquote><p>The goal of server side cache is responding to the same content for the same request independently of the client’s request. </p></blockquote><p>因此,下面展示的 demo 在第一次请求到达时,服务端耗费5秒来返回 HTML;而接下来再次请求该页面,将会命中缓存,不过是哪个用户访问,只需要几毫秒便可得到完整页面。</p><h2 id="Show-me-the-code-amp-Demo"><a href="#Show-me-the-code-amp-Demo" class="headerlink" title="Show me the code & Demo"></a>Show me the code & Demo</h2><p>其实上文提到的缓存概念非常简单,稍微有些后端经验的同学都能很好理解。但是这篇文章除去科普基本概念外,更重要的就是介绍 Express 中间件思想,并自己来实现一个服务端缓存中间件。</p><p>让我们开工吧!<br>最终 Demo 代码,欢迎访问<a href="https://github.com/HOUCe/server-side-cache-with-express" target="_blank" rel="external">它的Github地址</a>。</p><p>我将会使用 npm 上 memory-cache 这个包,以方便进行缓存的读写。最终的中间件代码很简单:</p><pre><code>'use strict'var mcache = require('memory-cache');var cache = (duration) => { return (req, res, next) => { let key = '__express__' + req.originalUrl || req.url let cachedBody = mcache.get(key) if (cachedBody) { res.send(cachedBody) return } else { res.sendResponse = res.send res.send = (body) => { mcache.put(key, body, duration * 1000); res.sendResponse(body) } next() } }}</code></pre><p>为了简单,我使用了请求 URL 作为 cache 的 key:</p><ul><li>当它(cache key)及其对应的 value 值存在时,便直接返回其 value 值;</li><li>当它(cache key)及其对应的 value 值不存在时,我们将对 Express send 方法做一层拦截:在最终返回前,存入这对 key-value。</li></ul><p>缓存的有效时间是10秒。</p><p>最终在判断之外,<strong>我们的中间件把控制权交给下一个中间件。</strong></p><p>最终使用和测试如下代码:</p><pre><code>app.get('/', cache(10), (req, res) => { setTimeout(() => { res.render('index', { title: 'Hey', message: 'Hello there', date: new Date()}) }, 5000) //setTimeout was used to simulate a slow processing request})</code></pre><p>我使用了 setTimeout 来模拟一个超长(5s)的操作。</p><p>打开浏览器控制面板,发现在10秒缓存到期以内:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-c46f7fbd8238dc23.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="加载信息"></p><p>至于为什么 cache 中间件要那样子写、next() 为什么是中间件把控制权传递,我并不打算展开去讲。有兴趣的读者可以看一下 Express 源码。</p><h2 id="还有几个小问题"><a href="#还有几个小问题" class="headerlink" title="还有几个小问题"></a>还有几个小问题</h2><p>仔细看我们的页面,再去体会一下实现代码。也许细心的读者能发现一个问题:刚才的实现我们<strong>缓存了整个页面</strong>,并将 date: new Date() 传入了 jade 模版 index.jade 里。那么,在命中缓存的条件下,10秒内,页面无法动态刷新来同步,直到10秒缓存到期。</p><p>同时,我们什么时候可以使用上述中间件,进行服务端缓存呢?当然是静态内容才可以使用。同时,PUT, DELETE 和 POST 操作都不应该进行类似的缓存处理。</p><p>同样,我们使用了 npm 模块:memory-cache,它存在优缺点如下:</p><ul><li>读写迅速而简单,不需要其他依赖;</li><li>当服务器或者这个进程挂掉的时候,缓存中的内容将会全部丢失。</li><li>memcache 是将缓存内容存放在了自己进程的内存中,所以这部分内容是无法在多个 Node.js 进程之间共享的。</li></ul><p>如果这些弊端 really matter,在实际开发中我们可以选择分布式的 cache 服务,比如 Redis。同样你可以在 npm 上找到:express-redis-cache 模块使用。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>在真实的开发场景中,服务端缓存已经成为 common sense,但是在 Node.js 的世界里,体会其中间件思想,自己手动编写服务,同样乐趣无穷。</p><p>与实践相结合,我认为真正缓存整个页面(如同 demo 那样)并不是一个推荐的做法(当时实际场景实际分析),同样使用请求 url 作为缓存的 key 也有待考虑。比如,页面中的一些静态内容可能会在其他页面中重复使用到,复用就成了问题。</p><p>真实场景下,<strong>一切设计和逻辑都要为自己业务情况所负责。脱离需求谈实现,都是耍流氓。</strong>这个 demo 简易轻巧,有需要的读者可以访问<a href="https://github.com/HOUCe/server-side-cache-with-express" target="_blank" rel="external">它的Github地址</a>,欢迎玩出各种花样。</p><p>Happy Coding!</p><p>PS:<br>作者<a href="https://github.com/HOUCe" target="_blank" rel="external">Github仓库</a> 和 <a href="https://www.zhihu.com/people/lucas-hc/answers" target="_blank" rel="external">知乎问答链接</a><br>欢迎各种形式交流。</p>]]></content>
<summary type="html">
<p><img src="http://upload-images.jianshu.io/upload_images/4363003-bd4c498721be23c5.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"
</summary>
<category term="js" scheme="https://exp-team.github.io/categories/js/"/>
<category term="js" scheme="https://exp-team.github.io/tags/js/"/>
</entry>
<entry>
<title>从 setState promise 化的探讨 体会 React 团队设计思想</title>
<link href="https://exp-team.github.io/blog/2017/11/04/js/setState-promise/"/>
<id>https://exp-team.github.io/blog/2017/11/04/js/setState-promise/</id>
<published>2017-11-03T16:00:00.000Z</published>
<updated>2017-11-05T02:42:46.000Z</updated>
<content type="html"><![CDATA[<p><img src="http://upload-images.jianshu.io/upload_images/4363003-cf1dab2cd6997834.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="Tomorrow Land 2017 - Martin Garrix"></p><h2 id="从-setState-那个众所周知的小秘密说起…"><a href="#从-setState-那个众所周知的小秘密说起…" class="headerlink" title="从 setState 那个众所周知的小秘密说起…"></a>从 setState 那个众所周知的小秘密说起…</h2><p>在 React 组件中,调用 this.setState() 是最基本的场景。这个方法描述了 state 的变化、触发了组件 re-rendering。但是,也许看似平常的 this.setState() 里面却也许<strong>蕴含了很多鲜为人知的设计和讨论。</strong></p><p>相信很多开发者已经意识到,setState 方法“或许”是<strong>异步的。</strong>也许你觉得,看上去更新 state 是如此轻而易举的操作,这并没有什么可异步处理的。但是要意识到,因为 state 的更新会触发 re-rendering,而 re-rendering 代价昂贵,短时间内反复进行渲染在性能上肯定是不可取的。所以,React 采用 batching 思想,它会 batches 一系列连续的 state 更新,而只触发一次 re-render。</p><p>关于这些内容,如果你还不清楚,推荐参考@程墨的系列文章:<a href="https://zhuanlan.zhihu.com/p/25954470" target="_blank" rel="external">setState:这个API设计到底怎么样</a>;英语好的话,可以直接关注长发飘飘的 <a href="https://medium.com/javascript-scene/setstate-gate-abc10a9b2d82" target="_blank" rel="external">Eric Elliott 著名的引起系列口水战的吐槽文:setState() Gate</a>。</p><p>或者,直接看下面的一个小例子。<br>比如,最简单的一个场景是:</p><pre><code>function incrementMultiple() { this.setState({count: this.state.count + 1}); this.setState({count: this.state.count + 1}); this.setState({count: this.state.count + 1});}</code></pre><p>直观上来看,当上面的 incrementMultiple 函数被调用时,组件状态的<br> count 值被增加了3次,每次增加1,那最后 count 被增加了3。但是,实际上的结果只给 state 增加了1。不信你自己试试~</p><h2 id="让-setState-连续更新的几个-hack"><a href="#让-setState-连续更新的几个-hack" class="headerlink" title="让 setState 连续更新的几个 hack"></a>让 setState 连续更新的几个 hack</h2><p>如果想让 count 一次性加3,<strong>应该如何优雅地处理潜在的异步操作,规避上述问题呢?</strong></p><p>以下提供几种解决方案:</p><ul><li><p><strong>方法一:</strong>常见的一种做法便是将一个回调函数传入 setState 方法中。即 setState 著名的函数式用法。这样能保证即便在更新被 batched 时,也能访问到预期的 state 或 props。(后面会解释这么做的原理)</p></li><li><p><strong>方法二:</strong>另外一个常见的做法是需要在 setState 更新之后进行的逻辑(比如上述的连续第二次 count + 1),封装到一个函数中,并作为第二个参数传给 setState。这段函数逻辑将会在更新后由 React 代理执行。即:</p><blockquote><p>setState(updater, [callback])</p></blockquote></li><li><p><strong>方法三:</strong>把需要在 setState 更新之后进行的逻辑放在一个合适的生命周期 hook 函数中,比如 componentDidMount 或者 componentDidUpdate 也当然可以解决问题。也就是说 count 第一次 +1 之后,出发 componentDidUpdate 生命周期 hook,第二次 count +1 操作直接放在 componentDidUpdate 函数里面就好啦。</p></li></ul><h2 id="一个引起广泛讨论的-Issue"><a href="#一个引起广泛讨论的-Issue" class="headerlink" title="一个引起广泛讨论的 Issue"></a>一个引起广泛讨论的 Issue</h2><p>这些内容貌似已经不再新鲜,很多 React 资深开发者其实都是了解的,或能很快理解。</p><p><strong>可是,你想过这个问题吗:</strong><br><strong>现代 javascript 处理异步流程,很流行的一个做法是使用 promises,那么我们能否应用这个思路解决呢?</strong></p><p>说具体一些,就是调用 setState 方法之后,返回一个 promise,状态更新完毕后我们在调用 promise.then 进行下一步处理。</p><p><strong>答案是肯定的,但是却被官方否决了。</strong></p><p>我是如何得出“答案是肯定的,但是是不被官方建议的。”这个结论,喜欢刨根问底的读者请继续往下阅读,相信你一定会有所启发,也能更充分理解 React 团队的设计思想。</p><h2 id="第-2642-Issue-解读和深入分析"><a href="#第-2642-Issue-解读和深入分析" class="headerlink" title="第 2642 Issue 解读和深入分析"></a>第 2642 Issue 解读和深入分析</h2><p>我是一步一步在 Facebook 开源 React 的官方 <a href="https://github.com/facebook/react" target="_blank" rel="external">Github仓库</a>上,找到了线索。</p><p>整个过程跟下来,相信在各路大神的 comments 之间,你会对 React 的设计理念以及 javascript 解决问题的思路有一个更清晰的认识。</p><p>一切的探究始于 React 第 <a href="https://github.com/facebook/react/issues/2642" target="_blank" rel="external">#2642 号 issue: Make setState return a promise</a>,上面关于 count 连续 +3 大家已经有所了解。接下来我举一个真正在生产开发中的例子,方便大家理解讨论。</p><p>我们现在开发一个可编辑的 table,需求是:当用户敲下“回车”,光标将会进入下一行(调用 setState 进行光标移动);如果用户当前已经在最后一行,那么敲下回车时,第一步将先创建一个新行(调用 setState 创建新的最后一行),在新行创建之后,再去新的最后一行进行光标聚焦(调用 setState 进行光标移动)。</p><p>常见且错误的处理在于:</p><pre><code>this.setState({ selected: input // 创建新行}.bind(this));this.props.didSelect(this.state.selected);</code></pre><p>因为第一个 this.setState 是异步进行的话,下一处 didSelect 方法执行 this.setState 时,所处理的参数 this.state.selected 可能还不是预期的下一行。很明显,这就是 this.setState 的异步性带来的问题。</p><p>为了解决这个完成这样的逻辑,想到了 setState 第二个参数解决方案,用代码简单表述就是:</p><pre><code>this.setState({ selected: input // 创建新行}, function() { this.props.didSelect(this.state.selected);}).bind(this));</code></pre><p>这种解决方案是使用嵌套的 setState 方法。但这无疑潜在地会带来嵌套地狱的问题。</p><h2 id="Promise-化方案登场"><a href="#Promise-化方案登场" class="headerlink" title="Promise 化方案登场"></a>Promise 化方案登场</h2><p>这一切是不是像极了传统 Javascript 处理异步老套路?解决回调地狱,你是不是应激性地想到了 promise?</p><p>如果 setState 方法返回的是一个 promises,自然会更加优雅:</p><blockquote><p>setState() currently accepts an optional second argument for callback and returns undefined.<br>This results in a callback hell for a very stateful component. Having it return a promise would make it much more managable.</p></blockquote><p>如果用 promise 风格解决问题的话,无非就是:</p><pre><code>this.setState({ selected: input}).then(function() { this.props.didSelect(this.state.selected);}.bind(this));</code></pre><p>看上去没什么问题,一个很时髦的设计。但是,我们进一步想:<strong>如果想让 React 支持这样的特性,采用提出 pull request 的方式,我们该如何去改源代码呢?</strong></p><h2 id="探索-React-源码,完成-setState-promise-化的改造"><a href="#探索-React-源码,完成-setState-promise-化的改造" class="headerlink" title="探索 React 源码,完成 setState promise 化的改造"></a>探索 React 源码,完成 setState promise 化的改造</h2><p>首先找到源码中关于 setState 定义的地方,它在 react/src/isomorphic/modern/class/ReactBaseClasses.js 这个目录下:</p><pre><code>ReactComponent.prototype.setState = function(partialState, callback) { invariant( typeof partialState === 'object' || typeof partialState === 'function' || partialState == null, 'setState(...): takes an object of state variables to update or a ' + 'function which returns an object of state variables.', ); this.updater.enqueueSetState(this, partialState, callback, 'setState');};</code></pre><p>我们首先看到一句注释:</p><blockquote><p>You can provide an optional callback that will be executed when the call to setState is actually completed.</p></blockquote><p>这是采用 setState 第二个参数传入处理回调的基础。</p><p>另外,从注释中我们还找到:</p><blockquote><p>When a function is provided to setState, it will be called at some point in the future (not synchronously). It will be called with the up to date component arguments (state, props, context). </p></blockquote><p>这是给 setState 方法直接传入一个函数的基础。</p><p> <strong>言归正传,如何改动源码,使得 setState promise 化呢?</strong><br> 其实很简单,我直接上代码:</p><pre><code>ReactComponent.prototype.setState = function(partialState, callback) { invariant( typeof partialState === 'object' || typeof partialState === 'function' || partialState == null, 'setState(...): takes an object of state variables to update or a ' + 'function which returns an object of state variables.', );+ let callbackPromise;+ if (!callback) {+ class Deferred {+ constructor() {+ this.promise = new Promise((resolve, reject) => {+ this.reject = reject;+ this.resolve = resolve;+ });+ }+ }+ callbackPromise = new Deferred();+ callback = () => {+ callbackPromise.resolve();+ };+ } this.updater.enqueueSetState(this, partialState, callback, 'setState');++ if (callbackPromise) {+ return callbackPromise.promise;+ } }; </code></pre><p>我用 “+” 标注了对源码所做的更改。如果开发者调用 setState 方法时,传入的是一个 javascript 对象的话,那么会返回一个 promise,这个 promise 将会在 state 更新完毕后 resolve。<br>如果您看不懂的话,建议补充一下相关的基础知识,或者留言与我讨论。</p><h2 id="解决方案有了,可是-React-官方会接受这个-PR-吗?"><a href="#解决方案有了,可是-React-官方会接受这个-PR-吗?" class="headerlink" title="解决方案有了,可是 React 官方会接受这个 PR 吗?"></a>解决方案有了,可是 React 官方会接受这个 PR 吗?</h2><p>很遗憾,答案是否定的。我们来从 React 设计思想上,和 React 官方团队的回应上,了解一下否决理由。</p><p>sebmarkbage(Facebook 工程师,React 核心开发者)认为:解决异步带来的困扰方案其实很多。比如,我们可以在合适的生命周期 hook 函数中完成相关逻辑。在这个场景里,就是在行组件的 componentDidMount 里调用 focus,自然就完成了自动聚焦。</p><p>此外,还有一个方法:新的 refs 接口设计支持接收一个回调函数,当其子组件挂载时,这个回调函数就会相应触发。</p><p><strong>所有上述模式都可以完全取代之前的问题方案,即使不能也不意味着要接受 promises 化这个PR。</strong></p><p>为此,sebmarkbage 说了一段很扎心的话:</p><blockquote><p>Honestly, the current batching strategy comes with a set of problems right now. I’m hesitant to expand on it’s API before we’re sure that we’re going to keep the current model. I think of it as a temporary escape until we figure out something better.</p></blockquote><p>问题的根源在于现有的 batching 策略,实话实说,这个策略带来了一系列问题。也许这个在后期后有调整,在 batching 策略是否调整之前,<strong>盲目的扩充 setState 接口只会是一个短视的行为。</strong></p><p>对此,Redux 原作者 Dan Abramov 也发表了自己的看法。他认为,以他的经验来看,任何需要使用 setState 第二个参数 callback 的场景,都可以使用生命周期函数 componentDidUpdate (and/or componentDidMount) 来复写。</p><blockquote><p>In my experience, whenever I’m tempted to use setState callback, I can achieve the same by overriding componentDidUpdate (and/or componentDidMount).</p></blockquote><p>另外,在一些极端场景下,如果开发者确实需要同步的处理方式,比如如果我想在某 DOM 元素挂载到屏幕之前做一些操作,promises 这种方案便不可行。因为 Promises 总是异步的。反过来,如果 setState 支持这两种不同的方式,那么似乎也是完全没有必要而多余的。</p><p>在社区,确实很多第三方库渐渐地接受使用 promises 风格,但是这些库解决的问题往往都是强异步性的,比如文件读取、网络操作等等。 React 似乎没有必要增加这么一个 confusing 的特性。</p><p>另外,如果每个 setState 都返回一个 promises,也会带来性能影响:对于 React 来说,setState 将必然产生一个 callback,这些 callbacks 需要合理储存,以便在合适时间来触发。</p><p>总结一下,解决 setState 异步带来的问题,有很多方式能够完美优雅地解决。在这种情况下,直接让 setState 返回 promise 是画蛇添足的。另外,这样也会引起性能问题等等。</p><p>我个人认为,这样的思路很好,但是难免有些 Overengineering。</p><h2 id="这一次为自己疯狂,我和我的倔强"><a href="#这一次为自己疯狂,我和我的倔强" class="headerlink" title="这一次为自己疯狂,我和我的倔强"></a>这一次为自己疯狂,我和我的倔强</h2><p>怎么样,是否说服你了呢?如果没有,在不能更改 React 源码情况下,你就是想用 promise 化的 setState,怎么办呢?</p><p>这里提供一个“反模式”的方案:我们不改变源码,自己也可以进行改造,原理上就是直接对 this.setState 进行拦截,进而进行 promise 化,再封装一个新的接口出来。</p><pre><code>import Promise from "bluebird";export default { componentWillMount() { this.setStateAsync = Promise.promisify(this.setState); },};</code></pre><p>之后,便可以异步地:</p><pre><code>this.setStateAsync({ loading: true,}).then(this.loadSomething).then((result) => { return this.setStateAsync({result, loading: false});});</code></pre><p>当然,也可以使用原声的 promises:</p><pre><code>function setStatePromise(that, newState) { return new Promise((resolve) => { that.setState(newState, () => { resolve(); }); });}</code></pre><p>甚至…我们还可以脑洞大开使用 async/await。</p><p>最后,所有这种做法非常的 dirty,我是不建议这么使用的。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>其实研究一下 React Issue,深入源码学习,收获确实很多。总结也没有更多想说的了,无耻滴做个广告吧:</p><p> <strong>我的其他关于 React 文章:</strong></p><ul><li><p><a href="https://zhuanlan.zhihu.com/p/28525821" target="_blank" rel="external">React Redux 中间件思想遇见 Web Worker 的灵感(附demo)</a></p></li><li><p><a href="https://zhuanlan.zhihu.com/p/27825741" target="_blank" rel="external">通过实例,学习编写 React 组件的“最佳实践”</a></p></li><li><p><a href="https://zhuanlan.zhihu.com/p/27727292" target="_blank" rel="external">React 组件设计和分解思考</a></p></li><li><p><a href="">从 React 绑定 this,看 JS 语言发展和框架设计</a></p></li><li><p><a href="https://zhuanlan.zhihu.com/p/28004982" target="_blank" rel="external">React 服务端渲染如此轻松 从零开始构建前后端应用</a></p></li><li><p><a href="http://www.jianshu.com/p/49029b49f2b4" target="_blank" rel="external">做出Uber移动网页版还不够 极致性能打造才见真章</a></p></li><li><p><a href="http://www.jianshu.com/p/7a56ac1de2a8" target="_blank" rel="external">解析Twitter前端架构 学习复杂场景数据设计</a></p></li><li><p><a href="http://www.jianshu.com/p/83c86dd0802d" target="_blank" rel="external">React Conf 2017 干货总结1: React + ES next = ♥</a></p></li><li><p><a href="http://www.jianshu.com/p/cde3cf7e2760" target="_blank" rel="external">React+Redux打造“NEWS EARLY”单页应用 一个项目理解最前沿技术栈真谛</a></p></li><li><p><a href="http://www.jianshu.com/p/8e28be0e7ab1" target="_blank" rel="external">一个react+redux工程实例</a></p></li></ul><p>Happy Coding!</p><p>PS:<br>作者<a href="https://github.com/HOUCe" target="_blank" rel="external">Github仓库</a> 和 <a href="https://www.zhihu.com/people/lucas-hc/answers" target="_blank" rel="external">知乎问答链接</a><br>欢迎各种形式交流。</p>]]></content>
<summary type="html">
<p><img src="http://upload-images.jianshu.io/upload_images/4363003-cf1dab2cd6997834.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"
</summary>
<category term="js" scheme="https://exp-team.github.io/categories/js/"/>
<category term="js" scheme="https://exp-team.github.io/tags/js/"/>
</entry>
<entry>
<title>人工智能离前端并不远 一步步教你开发一个机器学习APP(附源码)</title>
<link href="https://exp-team.github.io/blog/2017/11/04/js/ai/"/>
<id>https://exp-team.github.io/blog/2017/11/04/js/ai/</id>
<published>2017-11-03T16:00:00.000Z</published>
<updated>2017-11-05T02:46:30.000Z</updated>
<content type="html"><![CDATA[<p>最近HBO电视网推出的美剧《硅谷Silicon Valley》席卷全球,里面有一个桥段介绍了超级有趣的iOS app- <a href="https://www.theverge.com/tldr/2017/5/14/15639784/hbo-silicon-valley-not-hotdog-app-download" target="_blank" rel="external">Not Hotdog。</a>你甚至可以在APP Store上<a href="https://itunes.apple.com/app/not-hotdog/id1212457521" target="_blank" rel="external">下载</a>到它。</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-b6c49a02a1b0d3ba.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="是不是热狗?"></p><p>受启发于此,我打算开发一个实现同样功能的“机器人”:用户只需要上传任何一张图片,马上就可以得到反馈,告诉你这张图片的内容是不是一个热狗。最重要的是,我的代码<strong>全部以JS实现</strong>,是时候让前端工程师们在人工智能/机器学习领域大展身手了。</p><h2 id="实现细节"><a href="#实现细节" class="headerlink" title="实现细节"></a>实现细节</h2><p>这个APP以Twitter为宿主,基于Twitter Bot机器人:任何Twitter用户都可以发布一张图片,并且在上传描述文字中加入“@IsItAHotdog”,就能立即得到回复。就像大陆常用的微博加入”#”描述符一样简单。</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-12400866446d9728.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="热狗探测APP"></p><p>千万不要被表象所困扰,更不要被“人工智能/机器学习”的标签所迷惑。其实实现方式和原理非常简单。</p><p>首先,我forked <a href="http://twitter.com/BryanEBraun" target="_blank" rel="external">@BryanEBraun’s </a>的开源作品<a href="https://github.com/bryanbraun/twitter-listbot" target="_blank" rel="external">Twitter bot</a>,它基于NodeJS,Twitter Bot译为机器人:会定时发推,或随机回复。</p><p>官方介绍内容也非常简洁明了:</p><blockquote><p>This is a simple twitter bot, designed to retweet the contents of a twitter list.</p></blockquote><p>借助这个工具,接下来我的工作就是对提到”IsItAHotdog”的推文(即含有IsItAHotdog标签),作出回应。</p><p>在安装 <a href="https://www.npmjs.com/package/tuiter" target="_blank" rel="external">tuiter</a> NPM包之后,代码中引入依赖,并加入:</p><pre><code> var tu = require('tuiter')(config.keys); function listen() { tu.filter({ track: 'isitahotdog' }, function(stream) { console.log("listening to stream"); stream.on('tweet', onTweet); })}</code></pre><p>当然,我们只对含有图片的推文进行处理:</p><pre><code>if(tweet.entities.hasOwnProperty('media') && tweet.entities.media.length > 0)</code></pre><p>最后,我们把结果写进推文回复中:</p><pre><code>tu.update({ status: "@" + tweet.user.screen_name + message, in_reply_to_status_id: tweet.id_str }, onReTweet);</code></pre><h2 id="训练模型"><a href="#训练模型" class="headerlink" title="训练模型"></a>训练模型</h2><p>以上只是介绍了劫持推文,发布回复的内容。那么回复的结果应该怎么获得呢?我们怎么知道图片是不是热狗呢?这就到了最重要的一步。</p><p>熟悉深度学习的朋友可能会了解,接下来我们可能需要收集图片,并用Keras搭建CNN常用神经网络。其中Keras是一个兼容Theano和Tensorflow的神经网络高级包, 高度模块化,用他来组建一个神经网络非常快速便捷。</p><p>这些内容可能中文资料并不多,仅有的一些如果大家感兴趣的话,我推荐:</p><ul><li><a href="http://www.jianshu.com/p/9efae7a20493" target="_blank" rel="external">对比学习用 Keras 搭建 CNN RNN 等常用神经网络</a></li><li><a href="https://github.com/wepe/MachineLearning" target="_blank" rel="external">Basic Machine Learning and Deep Learning</a></li></ul><p><strong>但是这些深度学习的内容,可能很多前端工程师并不是太了解,那么我们就得重新修炼才能玩转这一切吗?</strong></p><p>别急,现在就可以开始!这里我给大家安利一下<a href="https://aws.amazon.com/rekognition/" target="_blank" rel="external">AWS Rekognition,</a>我们的APP也是基于AWS Rekognition来完成。</p><blockquote><p>Amazon Rekognition 是一种让您能够轻松为应用程序添加图像分析功能的服务。利用 Rekognition,您可以检测对象、场景和面孔,可以搜索和比较面孔,还可以识别图像中的不当内容。借助 Rekognition 的 API,您可以快速为应用程序添加基于深度学习的复杂视觉搜索和图像分类功能。</p></blockquote><p>换句话说,“不了解机器学习,简单的调用几个API都应该会吧。”</p><p>Amazon Rekognition基于同样由Amazon计算机视觉科学家开发的成熟且高度可扩展的深度学习技术,每天能够分析数十亿张 Prime Photos 图像。</p><p>说到这里可能有些绕,其实来看下代码,非常的简单:</p><pre><code> var params = { Image: { Bytes: body }, MaxLabels: 20, MinConfidence: 70 }; rekognition.detectLabels(params, function(err, data) { if (err) console.log(err, err.stack); // an error occurred else { console.log(data); // successful response var isItAHotdog = false; for (var label_index in data.Labels) { var label = data.Labels[label_index]; if(label['Name'] == "Hot Dog") { if(label['Confidence'] > 85) { isItAHotdog = true; tweetBasedOnCategorization(tweet, true); } } } if(isItAHotdog == false) { tweetBasedOnCategorization(tweet, false); } }});</code></pre><p>我把推文附带的图片下载到自己的服务器机器上,然后通过AWS Node SDK传递给Rekognition,并设置相应的参数,包括置信区间等。<br>最后,在回调中获得最终结果。</p><h2 id="最终结果"><a href="#最终结果" class="headerlink" title="最终结果"></a>最终结果</h2><p>让我们来看一组测试结果吧:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-f236923571a6f99f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="测试"></p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-c388ddadb880d566.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="测试"></p><p>这一切的开发过程都是非常的简单,如果你想看到源码,我fork了一份,并加入了中文注解。请点击<a href="https://github.com/HOUCe/hot-dog-bot" target="_blank" rel="external">这里查看源码。</a></p><p>本文翻译自<a href="https://hackernoon.com/building-silicon-valleys-hot-dog-app-in-one-night-aab8969cef0b" target="_blank" rel="external">Building Silicon Valley’s Hot Dog App in One Night</a>,对于原文进行了部分扩展。</p><p>Happy Coding!</p><p><strong>最后,可耻地做一波广告:</strong></p><p><strong>受到gitChat的邀请,我要开分享了。</strong>形式类似知乎Live,但是这个平台我感觉少了浮躁而更加专业。<br>主题内容为:<strong>面对前端六年历史代码,如何接入并应用ES6解放开发效率</strong>。</p><p><strong>我邀请了资深前端专家,社区网红<a href="https://zhuanlan.zhihu.com/p/26929765?group_id=847800456846639104" target="_blank" rel="external">@颜海镜</a>同我一起,详情介绍点击<a href="https://zhuanlan.zhihu.com/p/26929765?group_id=847800456846639104" target="_blank" rel="external">这里。</a></strong></p><p>微信扫描下方二维码,即可参加:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-5058b054ba03b9bc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="扫描此二维码"></p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-ecf3f61112d467b5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="内容介绍"></p><p>PS: 作者<a href="https://github.com/HOUCe" target="_blank" rel="external">Github仓库</a>,欢迎通过代码各种形式交流。</p>]]></content>
<summary type="html">
<p>最近HBO电视网推出的美剧《硅谷Silicon Valley》席卷全球,里面有一个桥段介绍了超级有趣的iOS app- <a href="https://www.theverge.com/tldr/2017/5/14/15639784/hbo-silicon-valley-
</summary>
<category term="js" scheme="https://exp-team.github.io/categories/js/"/>
<category term="js" scheme="https://exp-team.github.io/tags/js/"/>
</entry>
<entry>
<title>React Render Array 性能大乱斗</title>
<link href="https://exp-team.github.io/blog/2017/11/04/js/render-array/"/>
<id>https://exp-team.github.io/blog/2017/11/04/js/render-array/</id>
<published>2017-11-03T16:00:00.000Z</published>
<updated>2017-11-05T02:40:01.000Z</updated>
<content type="html"><![CDATA[<p>现在关于 React 最新 v16 版本新特性的宣传、讲解已经“铺天盖地”了。你最喜欢哪一个 new feature?<br>截至目前,组件构建方式已经琳琅满目。那么,你考虑过他们的性能对比吗?这篇文章,聚焦其中一个小细节,进行对比,望读者参考的同时,期待大神斧正。</p><h2 id="从-React-PureComponent-说起"><a href="#从-React-PureComponent-说起" class="headerlink" title="从 React.PureComponent 说起"></a>从 React.PureComponent 说起</h2><p>先上结论:在我们的测试当中,使用 React.PureComponent 能够提升 30% JavaScript 执行效率。测试场景是反复操作数组,这个“反复操作”有所讲究,我们计划持续不断地改变数组的某一项(而不是整个数组的大范围变动)。</p><p>线上参考地址: <a href="https://react-array-perf.now.sh/" target="_blank" rel="external">请点击这里</a></p><p>那么这样的场景,作为开发者有必要研究吗?如果你的应用并不涉及到高频率的更新数组某几项,那么大可不必在意这些性能的微妙差别。但是如果存在一些“实时更新”的场景,比如:</p><ul><li>用户输入改变数组(点赞者显示);</li><li>轮询(股票实时);</li><li>推更新(比赛比分实时播报);</li></ul><p>那么就需要进行考虑。我们定义:changedItems.length / array.length 比例越小,本文所涉及的性能优化越应该实施,即越有必要使用 React.PureComponent。</p><h2 id="代码和性能测试"><a href="#代码和性能测试" class="headerlink" title="代码和性能测试"></a>代码和性能测试</h2><p>在使用 React 开发时,相信很多开发者在搭配函数式的状态管理框架 Redux 使用。Redux reducers 作为纯函数的同时,也要保证 state 的不可变性,在我们的场景中,也就是说在相关 action 被触发时,需要返回一个新的数组。</p><pre><code>const users = (state, action) => { if (action.type === 'CHANGE_USER_1') { return [action.payload, ...state.slice(1)] } return state}</code></pre><p>如上代码,当 CHANGE_USER_1 时,我们对数组的第一项进行更新,使用 slice 方法,不改变原数组的同时返回新的数组。</p><p>我们设想所有的 users 数组被 Users 函数式组件渲染:</p><pre><code>import User from './User'const Users = ({users}) => <div> { users.map(user => <User {...user} /> } </div></code></pre><p>问题的关键在于:users 数组作为 props 出现,当数组中的<strong>第 K 项</strong>改变时,所有的 <user> 组件都会进行 reconciliation 的过程,即使<strong>非 K 项</strong>并没有发生变化。</user></p><p>这时候,我们可以引入 React.PureComponent,它通过浅对比规避了不必要的更新过程。即使浅对比自身也有计算成本,但是一般情况下这都不值一提。</p><p>以上内容其实已经“老生常谈”了,下面直接进入代码和性能测试环节。</p><p>我们渲染了一个有 200 项的数组:</p><pre><code>const arraySize = 200;const getUsers = () => Array(arraySize) .fill(1) .map((_, index) => ({ name: 'John Doe', hobby: 'Painting', age: index === 0 ? Math.random() * 100 : 50 }));</code></pre><p>注意在 getUsers 方法中,关于 age 属性我们做了判断,保证每次调用时,getUsers 返回的数组只有第一项的 age 属性不同。<br>这个数组将会触发 400 次 re-renders 过程,并且每一次只改变数组第一项的一个属性(age):</p><pre><code>const repeats = 400;componentDidUpdate() { ++this.renderCount; this.dt += performance.now() - this.startTime; if (this.renderCount % repeats === 0) { if (this.componentUnderTestIndex > -1) { this.dts[componentsToTest[this.componentUnderTestIndex]] = this.dt; console.log( 'dt', componentsToTest[this.componentUnderTestIndex], this.dt ); } ++this.componentUnderTestIndex; this.dt = 0; this.componentUnderTest = componentsToTest[this.componentUnderTestIndex]; } if (this.componentUnderTest) { setTimeout(() => { this.startTime = performance.now(); this.setState({ users: getUsers() }); }, 0); } else { alert(` Render Performance ArraySize: ${arraySize} Repeats: ${repeats} Functional: ${Math.round(this.dts.Functional)} ms PureComponent: ${Math.round(this.dts.PureComponent)} ms Component: ${Math.round(this.dts.Component)} ms `); }}</code></pre><p>为此,我们采用三种方式设计 <user> 组件。</user></p><h3 id="函数式方式"><a href="#函数式方式" class="headerlink" title="函数式方式"></a>函数式方式</h3><pre><code>export const Functional = ({ name, age, hobby }) => ( <div> <span>{name}</span> <span>{age}</span> <span>{hobby}</span> </div>);</code></pre><h3 id="PureComponent-方式"><a href="#PureComponent-方式" class="headerlink" title="PureComponent 方式"></a>PureComponent 方式</h3><pre><code>export class PureComponent extends React.PureComponent { render() { const { name, age, hobby } = this.props; return ( <div> <span>{name}</span> <span>{age}</span> <span>{hobby}</span> </div> ); }}</code></pre><h3 id="经典-class-方式"><a href="#经典-class-方式" class="headerlink" title="经典 class 方式"></a>经典 class 方式</h3><pre><code>export class Component extends React.Component { render() { const { name, age, hobby } = this.props; return ( <div> <span>{name}</span> <span>{age}</span> <span>{hobby}</span> </div> ); }}</code></pre><p>同时,在不同的浏览器环境下,我得出:</p><ul><li>Firefox 下,PureComponent 收益 30%;</li><li>Safari 下,PureComponent 收益 6%;</li><li>Chrome 下,PureComponent 收益 15%;</li></ul><p>测试硬件环境:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-b4ad265d8e72d92c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="机器"></p><p>最终结果:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-e5bf30012d678fa7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt=""></p><p>最后,送给大家鲁迅先生的一句话:</p><blockquote><p>“Early optimization is the root of all evil” - 鲁迅</p></blockquote>]]></content>
<summary type="html">
<p>现在关于 React 最新 v16 版本新特性的宣传、讲解已经“铺天盖地”了。你最喜欢哪一个 new feature?<br>截至目前,组件构建方式已经琳琅满目。那么,你考虑过他们的性能对比吗?这篇文章,聚焦其中一个小细节,进行对比,望读者参考的同时,期待大神斧正。</p>
</summary>
<category term="js" scheme="https://exp-team.github.io/categories/js/"/>
<category term="js" scheme="https://exp-team.github.io/tags/js/"/>
</entry>
<entry>
<title>React 组件设计和分解思考</title>
<link href="https://exp-team.github.io/blog/2017/11/04/js/react-component/"/>
<id>https://exp-team.github.io/blog/2017/11/04/js/react-component/</id>
<published>2017-11-03T16:00:00.000Z</published>
<updated>2017-11-05T02:49:24.000Z</updated>
<content type="html"><![CDATA[<p><img src="http://upload-images.jianshu.io/upload_images/4363003-7fd2c6c5f0f372a7.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="React"></p><p>之前分享过几篇关于React技术栈的文章:</p><ul><li><a href="http://www.jianshu.com/p/49029b49f2b4" target="_blank" rel="external">做出Uber移动网页版还不够 极致性能打造才见真章</a></li><li><a href="http://www.jianshu.com/p/7a56ac1de2a8" target="_blank" rel="external">解析Twitter前端架构 学习复杂场景数据设计</a></li><li><a href="http://www.jianshu.com/p/83c86dd0802d" target="_blank" rel="external">React Conf 2017 干货总结1: React + ES next = ♥</a></li><li><a href="http://www.jianshu.com/p/cde3cf7e2760" target="_blank" rel="external">React+Redux打造“NEWS EARLY”单页应用 一个项目理解最前沿技术栈真谛</a></li><li><a href="http://www.jianshu.com/p/8e28be0e7ab1" target="_blank" rel="external">一个react+redux工程实例</a></li><li>……</li></ul><p>今天再来同大家讨论 React 组件设计的一个有趣话题:<strong>分解 React 组件的几种进阶方法。</strong></p><p>React 组件魔力无穷,同时灵活性超强。我们可以在组件的设计上,玩转出很多花样。但是保证组件的<a href="https://en.wikipedia.org/wiki/Single_responsibility_principle" target="_blank" rel="external">Single responsibility principle: 单一原则</a>非常重要,它可以使得我们的组件更简单、更方便维护,更重要的是使得组件更加具有复用性。</p><p>但是,如何对一个功能复杂且臃肿的 React 组件进行分解,也许并不是一件简单的事情。本文由浅入深,介绍三个分解 React 组件的方法。</p><h2 id="切割-render-方法"><a href="#切割-render-方法" class="headerlink" title="切割 render() 方法"></a>切割 render() 方法</h2><p>这是一个最容易想到的方法:当一个组件渲染了很多元素时,就需要尝试分离这些元素的渲染逻辑。最迅速的方式就是切割 render() 方法为多个 sub-render 方法。</p><p>看下面的例子会更加直观:</p><pre><code>class Panel extends React.Component { renderHeading() { // ... } renderBody() { // ... } render() { return ( <div> {this.renderHeading()} {this.renderBody()} </div> ); }</code></pre><p>细心的读者很快就能发现,<strong>其实这并没有分解组件本身,</strong>该 Panel 组件仍然保持有原先的 state, props, 以及 class methods。</p><p>如何真正地做到减少复杂度呢?我们需要创建一些子组件。此时,采用最新版 React 支持并推荐的函数式组件/无状态组件一定会是一个很好的尝试:</p><pre><code>const PanelHeader = (props) => ( // ...);const PanelBody = (props) => ( // ...);class Panel extends React.Component { render() { return ( <div> // Nice and explicit about which props are used <PanelHeader title={this.props.title}/> <PanelBody content={this.props.content}/> </div> ); } }</code></pre><p>同之前的方式相比,这个微妙的改进是革命性的。我们新建了两个单元组件:PanelHeader 和 PanelBody。这样带来了测试的便利,我们可以直接分离测试不同的组件。同时,借助于 React 新的算法引擎 <a href="https://github.com/acdlite/react-fiber-architecture" target="_blank" rel="external">React Fiber,</a>两个单元组件在渲染的效率上,乐观地预计会有较大幅度的提升。</p><h2 id="模版化组件"><a href="#模版化组件" class="headerlink" title="模版化组件"></a>模版化组件</h2><p>回到问题的起点,为什么一个组件会变的臃肿而复杂呢?其一是渲染元素较多且嵌套,另外就是组件内部变化较多,或者存在多种 configurations 的情况。</p><p>此时,我们便可以将组件改造为模版:父组件类似一个模版,只专注于各种 configurations。</p><p>还是要举例来说,这样理解起来更加清晰。</p><p>比如我们有一个 Comment 组件,这个组件存在多种行为或事件。同时组件所展现的信息根据用户的身份不同而有所变化:用户是否是此 comment 的作者,此 comment 是否被正确保存,各种权限不同等等都会引起这个组件的不同展示行为。这时候,与其把所有的逻辑混淆在一起,也许更好的做法是利用 React 可以传递 React element 的特性,我们将 React element 进行组件间传递,这样就更加像一个模版:</p><pre><code>class CommentTemplate extends React.Component { static propTypes = { // Declare slots as type node metadata: PropTypes.node, actions: PropTypes.node, }; render() { return ( <div> <CommentHeading> <Avatar user={...}/> // Slot for metadata <span>{this.props.metadata}</span> </CommentHeading> <CommentBody/> <CommentFooter> <Timestamp time={...}/> // Slot for actions <span>{this.props.actions}</span> </CommentFooter> </div> ...</code></pre><p>此时,我们真正的 Comment 组件组织为:</p><pre><code>class Comment extends React.Component { render() { const metadata = this.props.publishTime ? <PublishTime time={this.props.publishTime} /> : <span>Saving...</span>; const actions = []; if (this.props.isSignedIn) { actions.push(<LikeAction />); actions.push(<ReplyAction />); } if (this.props.isAuthor) { actions.push(<DeleteAction />); } return <CommentTemplate metadata={metadata} actions={actions} />; }</code></pre><p>metadata 和 actions 其实就是在特定情况下需要渲染的 React element。</p><p>比如,如果 this.props.publishTime 存在,metadata 就是 <publishtime time="{this.props.publishTime}">;反正则为 <span>Saving…</span>。</publishtime></p><p>如果用户已经登陆,则需要渲染(即actions值为) <likeaction> 和 <replyaction>,如果是作者本身,需要渲染的内容就要加入 <deleteaction>。</deleteaction></replyaction></likeaction></p><h2 id="高阶组件"><a href="#高阶组件" class="headerlink" title="高阶组件"></a>高阶组件</h2><p>在实际开发当中,组件经常会被其他需求所污染。</p><p>比如,我们想统计页面中所有链接的点击信息。在链接点击时,发送统计请求,同时包含此页面 document 的 id 值。常见的做法是在 Document 组件的生命周期函数 componentDidMount 和 componentWillUnmount 增加代码逻辑:</p><pre><code>class Document extends React.Component { componentDidMount() { ReactDOM.findDOMNode(this).addEventListener('click', this.onClick); } componentWillUnmount() { ReactDOM.findDOMNode(this).removeEventListener('click', this.onClick); } onClick = (e) => { if (e.target.tagName === 'A') { // Naive check for <a> elements sendAnalytics('link clicked', { documentId: this.props.documentId // Specific information to be sent }); } }; render() { // ...</code></pre><p>这么做的几个问题在于:</p><ul><li>相关组件 Document 除了自身的主要逻辑:显示主页面之外,多了其他统计逻辑;</li><li>如果 Document 组件的生命周期函数中,还存在其他逻辑,那么这个组件就会变的更加含糊不合理;</li><li>统计逻辑代码无法复用;</li><li>组件重构、维护都会变的更加困难。</li></ul><p>为了解决这个问题,我们提出了高阶组件这个概念:<a href="https://facebook.github.io/react/docs/higher-order-components.html" target="_blank" rel="external"> higher-order components (HOCs)</a>。不去晦涩地解释这个名词,我们来直接看看使用高阶组件如何来重构上面的代码:</p><pre><code>function withLinkAnalytics(mapPropsToData, WrappedComponent) { class LinkAnalyticsWrapper extends React.Component { componentDidMount() { ReactDOM.findDOMNode(this).addEventListener('click', this.onClick); } componentWillUnmount() { ReactDOM.findDOMNode(this).removeEventListener('click', this.onClick); } onClick = (e) => { if (e.target.tagName === 'A') { // Naive check for <a> elements const data = mapPropsToData ? mapPropsToData(this.props) : {}; sendAnalytics('link clicked', data); } }; render() { // Simply render the WrappedComponent with all props return <WrappedComponent {...this.props} />; } }</code></pre><p>需要注意的是,withLinkAnalytics 函数并不会去改变 WrappedComponent 组件本身,更不会去改变 WrappedComponent 组件的行为。而是返回了一个被包裹的新组件。实际用法为:</p><pre><code>class Document extends React.Component { render() { // ... }}export default withLinkAnalytics((props) => ({ documentId: props.documentId}), Document);</code></pre><p>这样一来,Document 组件仍然只需关心自己该关心的部分,而 withLinkAnalytics 赋予了复用统计逻辑的能力。</p><p>高阶组件的存在,完美展示了 React 天生的复合(compositional)能力,在 React 社区当中,react-redux,styled-components,react-intl 等都普遍采用了这个方式。值得一提的是,<a href="https://github.com/acdlite/recompose/" target="_blank" rel="external">recompose</a> 类库又利用高阶组件,并发扬光大,做到了“脑洞大开”的事情。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>React 及其周边社区的崛起,让函数式编程风靡一时,受到追捧。其中关于 decomposing 和 composing 的思想,我认为非常值得学习。同时,对开发设计的一个建议是,不要犹豫将你的组件拆分的更小、更单一,因为这样能换来强健和复用。</p><p>本文意译了<a href="https://medium.com/dailyjs/techniques-for-decomposing-react-components-e8a1081ef5da" target="_blank" rel="external">Chris Nwamba的:React Universal with Next.js: Server-side React 一文,</a>并对原文进行了升级,兼容了最新的 Next 设计。</p><p>Happy Coding!</p><p>PS: 作者<a href="https://github.com/HOUCe" target="_blank" rel="external">Github仓库</a>,欢迎通过代码各种形式交流。</p>]]></content>
<summary type="html">
<p><img src="http://upload-images.jianshu.io/upload_images/4363003-7fd2c6c5f0f372a7.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"
</summary>
<category term="js" scheme="https://exp-team.github.io/categories/js/"/>
<category term="js" scheme="https://exp-team.github.io/tags/js/"/>
</entry>
<entry>
<title>React 模态框秘密和“轮子”渐进设计</title>
<link href="https://exp-team.github.io/blog/2017/11/04/js/react-modal/"/>
<id>https://exp-team.github.io/blog/2017/11/04/js/react-modal/</id>
<published>2017-11-03T16:00:00.000Z</published>
<updated>2017-11-05T02:42:04.000Z</updated>
<content type="html"><![CDATA[<p>今天上午组内小朋友们谈到 React 实践,提到 React 模态框(弹窗)的使用。我发现很多一些 React 开发者对于 React 模态框的具体设计思路和实现存在一些疑惑。因而特写此文,分享我对<strong>模态框这个“重要且典型”的前端交互,在 React 框架里实现的一些想法。</strong>准备时间短促且匆忙,难免有遗漏之处,希望大神给予斧正。</p><p>这篇文章“进阶式”渐进地,由浅入深分析三种实现。从最初的简单粗暴到接近 <a href="https://github.com/reactjs/react-modal" target="_blank" rel="external">react-modal</a> 库设计思想,一步步打磨分析,适合初学者阅读思考。</p><h2 id="原始级实现-——-暴力美学"><a href="#原始级实现-——-暴力美学" class="headerlink" title="原始级实现 —— 暴力美学"></a>原始级实现 —— 暴力美学</h2><p>世界上大部分网站都离不开模态框交互。事实上,模态框就是我们俗称的“弹窗”,只不过这个弹窗相比简单的:</p><pre><code>alert('我是一个简单、原生的 alert~');</code></pre><p>多了更多的信息承载和交互行为。同时为了更佳美化和吸引眼球,模态框往往伴随着深色透明的遮罩。比如下图:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-bdbf145a382c6272.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="模态框举例"></p><p>想想常见的用户登录框、错误信息提示等等,都是非常典型的模态框实现。</p><p>在传统的 jQuery 操作 DOM 类库的技术栈下,我们可以“肆无忌惮”地选择 DOM 节点,完成 append, remove 等操作,实现模态框并不复杂。可是在 React 和 Redux 世界里,我们该如何实现?</p><p>我们先来看一下场景和初版设计思路:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-48669842b6993eef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="场景和初版设计思路"></p><p>如图,箭头标记的组件需要触发模态框的出现。<br>图中组件树对应基本页面代码如下:</p><pre><code>export default class App extends Component { render() { <div className="app"> <div className="left"> <h1>Hello left</h1> // ... </div> <div className="right"> <h1>Hello right</h1> // ... <div> <BadModal> // 模态框内容 <h1> Modal title </h1> <p> Modal content</p> </BadModal> </div> </div> </div> }}</code></pre><p>细心的读者会发现,作为 amazing 的程序员,尽管这是最初版本的实现,但是还是思考一些最基本的“复用”问题。</p><p>我们设计完成的模态框组件 <badmodal>,因为每个模态框里内容和交互不尽相同,所以在 <badmodal> 组件内,我们渲染 child component,这个 child component 即业务对应的模态框内容,它将会由业务逻辑开发完成,实现模态框内容、交互的复用。如下代码:</badmodal></badmodal></p><pre><code>class BadModal extends Comment { render() { return ( <div className="modal"> { this.props.children } </div> ) }}</code></pre><p>至此,我们已经实现了最基本的模态框。可是为什么说这是最原始、简陋的方法呢?细想一下,似乎不完美的地方还很多。翻开我们的样式表:</p><pre><code>body .modal { position: fixed; // ...}.left { z-index: 3}.right { z-index: 1}</code></pre><p>你会发现恼人的 z-index 问题,我们模态框是 .right 节点的子孙节点,而 .right 的 z-index 小于 .left 的 z-index,这样造成的直接问题就是模态框最终不能脱离页面整体而“突出显示”!</p><p><strong>细想一下,这个问题的根本就出现在我们的组件设计图中。</strong><br>仔细观察上图,因为很深层次的子孙组件触发模态框,而使得该组件内的模态框组件层级较深。如果你对 z-index 比较规则有所了解的话,这样的情况很难完成模态框凌驾于页面整体而出现的,遮罩也无法覆盖整个页面。</p><p>想想我们平时使用的 jQuery 是怎么做的吧:</p><pre><code>$('body').append('<div class="overlay"></div>');</code></pre><p>一般情况,模态框和遮罩总是作为在 body 下的第一层子节点出现。由此,引出了我们的第二种进阶思路。请读者继续阅读。</p><h2 id="实现方案二-——-乾坤大挪移"><a href="#实现方案二-——-乾坤大挪移" class="headerlink" title="实现方案二 —— 乾坤大挪移"></a>实现方案二 —— 乾坤大挪移</h2><p>解决方法很简单,我们可以很自然地想到:只需要<strong>对 <modal> 组件出现的位置进行移动。可是这就需要 <modal> 组件和触发模态框出现的深层次组件进行某种意义上的通信。</modal></modal></strong></p><p>传统的 React 组件间通信无外乎 props 和基于 props 的回调实现(不考虑 context 的黑魔法)。可是这样的做法太过复杂,也难以实现复用,更不利于维护。</p><p>至于我这里采用的做法,还要从调整后的页面组件树设计出发:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-97c2399ecd828fb4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt=""></p><p>如图,我们在 document.body 下加入了 <modal> 组件,并列于 Root Component。同时,至关重要的一步设计是,<strong>我们在触发模态框的组件下,加入了一个 Fake Modal 组件。</strong></modal></p><p>这个神秘的 Fake Modal 组件做了什么呢?<br><strong>事实上,他并不渲染任何结果,而是借助其生命周期函数,完成在 document.body 下新建并插入 <modal> 组件的使命。</modal></strong></p><p>借助代码进行理解:</p><pre><code>class Modal extends Comment { componentDidMount() { this.modalTarget = document.createElement('div'); this.modalTarget.className = 'modal'; document.body.appendChild(this.modalTarget); this.renderModal(); } componentWillUpdate() { this.renderModal(); } componentWillUnmount() { ReactDom.unmountComponentAtNode(this.modalTarget); document.body.removeChild(this.modalTarget)l } renderModal() { ReactDom.render( <div>{ this.props.children }</div>, this.modalTarget ); } render() { return <noscript /> }}</code></pre><p>具体进行分析在真正的 render 方法中,我们不渲染任何实质的内容,而是:</p><pre><code>return <noscript />;</code></pre><p>同时,借助生命周期函数 componentDidMount,我们使用原生 JavaScript 实现在 body 下的模态框创建:</p><pre><code>this.modalTarget = document.createElement('div');this.modalTarget.className = 'modal';document.body.appendChild(this.modalTarget);this.renderModal();</code></pre><p>并最终调用 renderModal 方法完成插入:</p><pre><code>ReactDom.render( <div>{ this.props.children }</div>, this.modalTarget);</code></pre><h2 id="实现方案三-——-搭配-Redux"><a href="#实现方案三-——-搭配-Redux" class="headerlink" title="实现方案三 —— 搭配 Redux"></a>实现方案三 —— 搭配 Redux</h2><p>相信很多 React 开发者都会使用 Redux 来做数据管理。仔细看上图的结构中,我们难以实现对 Redux 的友好兼容。</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-8039ace2b5b26aaa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="image.png"></p><p>图片</p><p>比如说,如果在 <modal> 组件的子组件 child component 中,需要使用 Redux store 里的数据,那么因为 <provider> 实质上是一个“高阶组件”且不在 <modal> 组件的组件链中,因为 child component 无法感知 Redux store 的存在。</modal></provider></modal></p><p>为了解决这个问题,我们继续改进组件树结构为:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-4643613c42286b86.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="image.png"></p><p>图片</p><p>为此,我们引入应用的 store,以及 react-redux 包提供的 <provider> 组件:</provider></p><pre><code>import { store } from '../index';import { Provider } from 'react-redux';</code></pre><p>同时改动先前的 renderModal 方法,加入对 <provider> 的支持:</provider></p><pre><code>renderModal() { ReactDom.render( <Provider store={ store }> <div>{ this.props.children }</div> <Provider>, this.modalTarget );}</code></pre><h2 id="著名的-react-modal-探秘和-React16-版本惊喜"><a href="#著名的-react-modal-探秘和-React16-版本惊喜" class="headerlink" title="著名的 react-modal 探秘和 React16 版本惊喜"></a>著名的 react-modal 探秘和 React16 版本惊喜</h2><p>在 React 开发中,我想很多工程师对 <a href="https://github.com/reactjs/react-modal" target="_blank" rel="external">react-modal</a> 非常熟悉。我们往往依赖它,完成模态框的使用。</p><p>这个库设计良好,请封装完善。如果你好奇它是如何实现的,源码又是如何组织?那么我可以告诉你,你已经了解了他的设计哲学。事实上,文章介绍的思路就是它的奥秘。</p><p>了解了这些,你也可以动手实现“一个轮子”,或者扩充本文源码,实现更多的功能。比如样式的自定义、弹出前后的回调等等。相信一定会有很多收获。</p><p>同时,React 最新版本 0.16 已经横空出世,它带来的很多新特性之一就与本文密切相关。那就是 —— Portal,Portal 我们把它翻译为“传送门、任意门”。Portals 允许将组件渲染到父节点之外的 DOM 节点中。它的基本使用如下代码示例:</p><pre><code>render() { return ReactDOM.createPortal( this.props.children, anyDomNode, );}</code></pre><p>这里 React 并不会在当前结构中渲染组件,而是向 anyDomNode 中渲染 this.props.children,这里的 anyDomNode 是任何有效的DOM节点,无论它处于哪个层级位置。</p><p>了解了这些,我们当然能够使用此特性,简化上文逻辑。翻开 react-modal 最新提交的源码,便能够发现对这一新特性的支持,react-modal/src/components/Modal.js 文件中:</p><pre><code>const isReact16 = ReactDOM.createPortal !== undefined;const createPortal = isReact16 ? ReactDOM.createPortal : ReactDOM.unstable_renderSubtreeIntoContainer;</code></pre><p>这里对 React 版本进行判断,并设置 isReact16 标识位表示是否支持 createPortal 的方法(个人认为这个标识位的命名非常不合适…)</p><p>最终在 render 方法内:</p><pre><code>render() { if (!canUseDOM || !isReact16) { return null; } if (!this.node && isReact16) { this.node = document.createElement("div"); } return createPortal( <ModalPortal ref={this.portalRef} defaultStyles={Modal.defaultStyles} {...this.props} />, this.node );}</code></pre><p>非常明显地看到,对于不支持 createPortal 的情况采用与我们类似的 return null; 否则愉快地使用 createPortal 方法。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文介绍内容虽然基础,但是很好地贯穿了 React 思想以及实现一个“模态框轮子”的演进思路。同时介绍了 React 新版本的一项特性。</p><p>我的其他几篇关于React技术栈的文章:</p><ul><li><a href="https://zhuanlan.zhihu.com/p/28525821" target="_blank" rel="external">React Redux 中间件思想遇见 Web Worker 的灵感(附demo)</a></li><li><a href="https://zhuanlan.zhihu.com/p/29732224" target="_blank" rel="external">了解 Twitter 前端架构 学习复杂场景数据设计</a></li><li><a href="https://zhuanlan.zhihu.com/p/29711902" target="_blank" rel="external">React 探秘 - React Component 和 Element(文末附彩蛋demo和源码)</a></li><li><a href="https://zhuanlan.zhihu.com/p/28905707" target="_blank" rel="external">从setState promise化的探讨 体会React团队设计思想</a></li><li><a href="https://zhuanlan.zhihu.com/p/27825741" target="_blank" rel="external">通过实例,学习编写 React 组件的“最佳实践”</a></li><li><a href="https://zhuanlan.zhihu.com/p/27727292" target="_blank" rel="external">React 组件设计和分解思考</a></li><li><a href="https://zhuanlan.zhihu.com/p/28905707/edit" target="_blank" rel="external">从 React 绑定 this,看 JS 语言发展和框架设计</a></li><li><a href="https://zhuanlan.zhihu.com/p/28004982" target="_blank" rel="external">React 服务端渲染如此轻松 从零开始构建前后端应用</a></li><li><a href="http://link.zhihu.com/?target=http%3A//www.jianshu.com/p/49029b49f2b4" target="_blank" rel="external">做出Uber移动网页版还不够 极致性能打造才见真章**</a></li><li><a href="http://link.zhihu.com/?target=http%3A//www.jianshu.com/p/cde3cf7e2760" target="_blank" rel="external">React+Redux打造“NEWS EARLY”单页应用 一个项目理解最前沿技术栈真谛**</a></li></ul><p>Happy Coding!<br>PS: 作者 <a href="http://link.zhihu.com/?target=https%3A//github.com/HOUCe" target="_blank" rel="external">Github仓库**</a> 和 <a href="https://www.zhihu.com/people/lucas-hc/answers" target="_blank" rel="external">知乎问答链接</a> 欢迎各种形式交流。</p>]]></content>
<summary type="html">
<p>今天上午组内小朋友们谈到 React 实践,提到 React 模态框(弹窗)的使用。我发现很多一些 React 开发者对于 React 模态框的具体设计思路和实现存在一些疑惑。因而特写此文,分享我对<strong>模态框这个“重要且典型”的前端交互,在 React 框架里实
</summary>
<category term="js" scheme="https://exp-team.github.io/categories/js/"/>
<category term="js" scheme="https://exp-team.github.io/tags/js/"/>
</entry>
<entry>
<title>React Redux 中间件思想遇见 Web Worker 的灵感(附demo)</title>
<link href="https://exp-team.github.io/blog/2017/11/04/js/react-worker/"/>
<id>https://exp-team.github.io/blog/2017/11/04/js/react-worker/</id>
<published>2017-11-03T16:00:00.000Z</published>
<updated>2017-11-05T02:50:04.000Z</updated>
<content type="html"><![CDATA[<h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><p>原文首发于作者的知乎专栏:<a href="https://zhuanlan.zhihu.com/p/28525821" target="_blank" rel="external">React Redux 中间件思想遇见 Web Worker 的灵感(附demo)</a>,感兴趣的同学可以知乎关注,进行交流。</p><p>熟悉 React 技术栈的同学,想必对 Redux 数据流框架并不陌生。其倡导的单向数据流等思想独树一帜,虽然样板代码会有一定程度上的增多,但是对于开发效率和调试效率的提高是显著的。同时还带来了很多诸如 “时间旅行”,“ undo/redo ” 等黑魔法。</p><p>其实这还只是表象。如果你深入去了解 Redux 的设计理念,探索中间件奥秘,玩转高阶 reducer 等等,迎接你的就会是另一扇门。透过它,函数式编程思想之光倾斜如注。</p><h2 id="思想背景"><a href="#思想背景" class="headerlink" title="思想背景"></a>思想背景</h2><p>但是随着这个 web app 复杂度的提升,数据计算量压力徒增,你所设计的 Reducer 变得臃肿不堪。好吧,我们可以拆分 Reducer 使得代码看上去更加舒服。可是计算量呢?也许有一些“梦魇”,瓶颈般永远无法消除。</p><p>冥冥之中,“各种处理计算既然注定在同一时空,那么能否永远平行?”</p><p><strong>曾几何时,你是否听说过 JS 单线程异步?听说过浏览器卡顿或卡死?听说过 60 fps?</strong></p><p>其实一个很严峻的事实是:根据 60 fps 计算,每一帧留给我们 JS 执行的时间为 16ms(甚至更少)。那么一旦当 Reducer 计算时间过长,必然会影响浏览器渲染。</p><h2 id="多线程思路"><a href="#多线程思路" class="headerlink" title="多线程思路"></a>多线程思路</h2><p>关于浏览器主线程、render queue、event loop、call stack 等内容,本文不再复述,因为里面的知识完全都够写一本书了。假定读者对其有一二认知,那么你也不难理解我们即将登场的救星—— Web Worker!</p><p><strong>我们先来简单认识一下 web worker:</strong></p><p>2008 年 W3C 制定出第一个 HTML5 草案开始,HTML5 承载了越来越多崭新的特性和功能。其中,最重要的一个便是对多线程的支持。在 HTML5 中提出了工作线程(Web Worker)的概念,并且规范出 Web Worker 的三大主要特征:</p><ul><li>能够长时间运行(响应);</li><li>理想的启动性能;</li><li>以及理想的内存消耗。</li></ul><p>Work 线程可以执行任务而不干扰用户界面。</p><p><strong>于是,脑洞大开,能否将我们的 Redux Reducer 计算状态部分放进 Worker 线程中处理呢?</strong></p><p><strong>答案是肯定的。</strong><br><strong>那么要如何实施呢?</strong></p><p>我们先来看一下经典的 Redux workflow,如下图:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-32fe6806781bfedb?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="redux 流程图"></p><p>如果要接入 Web Work,那么我们改动流程图如下:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-17b02c39b2127514?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="redux + worker 流程图"></p><h2 id="具体实现和一个demo"><a href="#具体实现和一个demo" class="headerlink" title="具体实现和一个demo"></a>具体实现和一个demo</h2><p>当然,有了思路,还需要在实战中演练。</p><p>我使用 <a href="http://blog.csdn.net/hackbuteer1/article/details/6657109" target="_blank" rel="external">“N-皇后问题”</a> 模拟大型计算,并且实现的 demo 中可以任意设置 n 值,增加计算耗时。<br>如果你不理解此算法也没有关系,只需要知道N-皇后问题这个算法的计算耗时很长,且和 n 值相关:n 越大,计算成本越大。</p><p>除了一个极其耗时的计算,页面中还运行这么几个模块,来实现复杂的渲染逻辑操作:</p><ul><li>一个实时每16毫秒,显示计数(每秒增加1)的 blinker 模块;</li><li>一个定时每500毫秒,更新背景颜色的 counter 模块;</li><li>一个永久往复运动的 slider 模块;</li><li>一个每16毫秒翻转5度的 spinner 模块</li></ul><p><img src="http://upload-images.jianshu.io/upload_images/4363003-2c1b80daa49e9530?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="页面过程"></p><p>这些模块都定时频繁地更新 dom 样式,进行大量复杂的渲染计算。正常情况下,由于 JS 主线程进行N-皇后计算,这些渲染过程都将被卡顿。</p><p>同时,我设置“N-皇后问题”的 n 值,来观察在计算时这些模块的表现(是否卡顿)。在不开启 Work 线程的情况下,n 设置为13时,有 gif 图,左半部分:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-13bd10000ade2648?imageMogr2/auto-orient/strip" alt="off.gif"></p><p>我们非常清晰地看到:由于浏览器 call stack 进行 n=13 的皇后问题计算,而无法“按时”渲染,所以造成了这几个模块的卡顿,这些模块都无法更新状态。<strong>在这个卡顿过程中,用户的任何事件(如点击,敲键盘等)都无法被浏览器响应。</strong>这就是用户体会到的“慢”!</p><p>如果我把 n 值设置的大与13呢,比如24?<br>千万不要这么做!因为你的浏览器会被卡死!我使用 Mac Pro 8G 内存情况下,设置到14,浏览器就无法响应了。</p><p>在开启 Work 线程时,请参考上 gif 图右半部分,几个模块的渲染丝毫不受影响。完美达到了我们的目的。</p><p>因为 Reducer 的超级耗时计算被放入 Worker 线程当中,所以丝毫没有影响浏览器的渲染和响应。完全解决了用户觉得“电脑慢”的问题。</p><p>看到了如此完美的对比,也许你想问 Web Worker 的兼容性如何呢?</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-b5c95e172045d809?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="兼容性"></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>其实,这篇文章的意义并不在于这个 demo 和应用。而是在启发一种新的想法的同时,review 了很多 JS 当中关键概念和基本知识。比如:单线程异步、宿主环境、60 fps、一个算法等等。</p><p>更值得一提的是,如果你去深入 demo 代码,你更会发现 Redux 设计精妙的思想,比如我们将 Web Worker 的应用抽象出一个公共库:<strong>Redux-Worker,并包装为 Redux 的中间件(middleware),所有 React Redux 都可以无侵入,采用中间件的思想使用:</strong></p><pre><code>import { applyWorker } from 'redux-worker';const enhancerWithWorker = compose( applyMiddleware(thunk, logger), applyWorker(worker));const store = createStore(rootReducer, {}, enhancerWithWorker);</code></pre><p>当然,Redux-Worker 这个中间件的实现原理更是巧妙,这里不再展开。感兴趣的同学可以参考我的<a href="https://github.com/HOUCe/redux-worker-demo" target="_blank" rel="external">此项目 Github 仓库。</a>我 fork 了此<a href="https://github.com/chikeichan/redux-worker" target="_blank" rel="external">库源码</a>,并在核心逻辑加入了中文注释,感兴趣的同学可以关注。</p><p> <strong>我的其他关于 React 文章:</strong></p><ul><li><a href="https://zhuanlan.zhihu.com/p/27825741" target="_blank" rel="external">通过实例,学习编写 React 组件的“最佳实践”</a><ul><li><a href="https://zhuanlan.zhihu.com/p/27727292" target="_blank" rel="external">React 组件设计和分解思考</a></li><li><a href="">从 React 绑定 this,看 JS 语言发展和框架设计</a></li></ul></li><li><a href="https://zhuanlan.zhihu.com/p/28004982" target="_blank" rel="external">React 服务端渲染如此轻松 从零开始构建前后端应用</a></li><li><a href="http://www.jianshu.com/p/49029b49f2b4" target="_blank" rel="external">做出Uber移动网页版还不够 极致性能打造才见真章</a></li><li><a href="http://www.jianshu.com/p/7a56ac1de2a8" target="_blank" rel="external">解析Twitter前端架构 学习复杂场景数据设计</a></li><li><a href="http://www.jianshu.com/p/83c86dd0802d" target="_blank" rel="external">React Conf 2017 干货总结1: React + ES next = ♥</a></li><li><a href="http://www.jianshu.com/p/cde3cf7e2760" target="_blank" rel="external">React+Redux打造“NEWS EARLY”单页应用 一个项目理解最前沿技术栈真谛</a></li><li><a href="http://www.jianshu.com/p/8e28be0e7ab1" target="_blank" rel="external">一个react+redux工程实例</a></li></ul><p>Happy Coding!</p><p>PS:<br>作者<a href="https://github.com/HOUCe" target="_blank" rel="external">Github仓库</a> 和 <a href="https://www.zhihu.com/people/lucas-hc/answers" target="_blank" rel="external">知乎问答链接</a><br>欢迎各种形式交流。</p>]]></content>
<summary type="html">
<h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><p>原文首发于作者的知乎专栏:<a href="https://zhuanlan.zhihu.com/p/28525821" t
</summary>
<category term="js" scheme="https://exp-team.github.io/categories/js/"/>
<category term="js" scheme="https://exp-team.github.io/tags/js/"/>
</entry>
<entry>
<title>JS冻结对象的《人间词话》 完美实现究竟有几层境界?</title>
<link href="https://exp-team.github.io/blog/2017/11/04/js/freeze/"/>
<id>https://exp-team.github.io/blog/2017/11/04/js/freeze/</id>
<published>2017-11-03T16:00:00.000Z</published>
<updated>2017-11-05T02:47:30.000Z</updated>
<content type="html"><![CDATA[<p>王国维在《人间词话》里谈到了治学经验,他说:“古今之成大事业、大学问者,必经过三种之境界。”</p><p>巧合的是,最近受 <a href="http://gitbook.cn/" target="_blank" rel="external">git chat / git book</a> 邀请,做了一个分享。<br>其中谈到JS中冻结一个对象几种由浅入深的实践。想想也暗合国学大师所谓的三重境界。 具体分享内容,还没有上车的同学们可以<a href="http://gitbook.cn/m/mazi/article/59115363b55ad30f704c0252?isLogArticle=yes&readArticle=yes" target="_blank" rel="external">点击这里,参看实录。欢迎一起讨论。</a>相关文章,<a href="http://gitbook.cn/books/5919ab35c370474b2fa23006/index.html" target="_blank" rel="external">点击这里也可以查看。</a></p><p>这篇文章由浅入深讨论JS中对象的一些锁定特性。但都是一些基础语法的实现,相信即便是前端小白也可以大体领会。不过需要读者预先了解JS中对象的特性,尤其是对象自身属性的描述符:configurable、writable…</p><p>另外,如果您对JS中对象操作、不可变数据、函数式编程感兴趣,同样推荐我的其他一些相关文章:</p><ul><li><a href="http://www.jianshu.com/p/11fc75f28302" target="_blank" rel="external">如何优雅安全地在深层数据结构中取值</a></li><li><a href="http://www.jianshu.com/p/89f1d4245b20" target="_blank" rel="external">从JS对象开始,谈一谈“不可变数据”和函数式编程</a></li><li><a href="http://www.jianshu.com/p/7a56ac1de2a8" target="_blank" rel="external">解析Twitter前端架构 学习复杂场景数据设计</a></li></ul><p>等等。</p><h2 id="昨夜西风凋碧树-独上高楼-望尽天涯路"><a href="#昨夜西风凋碧树-独上高楼-望尽天涯路" class="headerlink" title="昨夜西风凋碧树 独上高楼 望尽天涯路"></a>昨夜西风凋碧树 独上高楼 望尽天涯路</h2><p>第一种境界:“昨夜西风凋碧树,独上高楼,望尽天涯路。”<br>该词句出自晏殊的《蝶恋花》,原意是说,“我”上高楼眺望所见的更为萧飒的秋景,西风黄叶,山阔水长,案书何达?</p><p>王国维此句中解成:做学问成大事业者,首先要有执着的追求,登高望远,瞰察路径,明确目标与方向,了解事物的概貌。</p><p> <strong>我们就从最基本的场景说起,究竟为什么要冻结一个对象?</strong></p><ul><li><p>场景一:<br>我们造了一个轮子,对外暴露一个对象,开放出来给第三方使用。同时需要保证这个对外暴露的对象完全安全,不能被业务代码所改写覆盖或下钩子(hook)函数。</p></li><li><p>场景二:<br>如果你看过Vue 2.* 版本源码,你会发现冻结一个对象的操作频繁出现。</p></li></ul><p><strong>我们先来看冻结对象的第一层实现 —— 扩展特性锁:</strong></p><p>他包含了两个基本方法:</p><ul><li>Object.isExtensible</li><li>Object.preventExtensions</li></ul><p>如果一个对象可以添加新的属性,则这个对象是可扩展的。扩展特性锁就是让这个对象变的不可扩展,也就是不能再有新的属性。</p><h3 id="Object-isExtensible"><a href="#Object-isExtensible" class="headerlink" title="Object.isExtensible"></a>Object.isExtensible</h3><p>MDN上内容概述:</p><pre><code>概述 Object.isExtensible() 方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。语法 Object.isExtensible(obj)参数 obj 需要检测的对象</code></pre><p>例如,我们正常使用对象字面量声明的对象都是可扩展的:</p><pre><code>var person1 = {};person1.name = "Lucas";console.log(person1);// {name: "Lucas"}</code></pre><p>同时:</p><pre><code>Object.isExtensible(person1) === true; // true</code></pre><p>你可能要问了,那么使用Object.create方法声明的对象,并对该对象属性进行配置是什么情况呢?<br>我们知道,用上面对象字面量声明的对象相当于:</p><pre><code>var person1 = Object.create({},{ "name":{ value : "Lucas", configurable : true, //可配置 enumerable : true , //可枚举 writable : true //可写 }});</code></pre><p>即便尝试将configurable设置为false:</p><pre><code>var person1 = Object.create({},{ "name":{ value : "Lucas", configurable : false, //不可配置 enumerable : true, //可枚举 writable : true //可写 }});</code></pre><p>仍然得到:</p><pre><code>Object.isExtensible(person1) === true; // true</code></pre><h2 id="Object-preventExtensions"><a href="#Object-preventExtensions" class="headerlink" title="Object.preventExtensions"></a>Object.preventExtensions</h2><p>当然,我们还是有方法可以使得一个对象变的不可扩展。</p><p>MDN上内容概述:</p><pre><code>概述 Object.preventExtensions() 方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。语法 Object.preventExtensions(obj)参数 obj 将要变得不可扩展的对象</code></pre><p>几个注意点包括但不限于:</p><ul><li>不可扩展的对象的属性通常<strong>仍然可以被删除。</strong></li><li>尝试给一个不可扩展对象添加新属性的操作将会失败,不过<strong>可能是静默失败,也可能会抛出 TypeError 异常</strong>(严格模式下)。</li><li>Object.preventExtensions 只能阻止一个对象不能再添加新的自身属性,<strong>仍然可以为该对象的原型添加属性。</strong></li></ul><p>比如:</p><pre><code>var person1 = { name: "Lucas"}Object.preventExtensions(person1);person1.age = 18;// 非严格模式下,这里不会有报错,属于静默失败person1.age // undefined// 扩展新属性失败了</code></pre><p>仍然可以向原型链添加属性:</p><pre><code>person1.__proto__.age = 18;person1.age // 18// 可以从原型链上取到</code></pre><p>同样也可以复写一些属性:</p><pre><code>person1.name = "Eros";person1.name // "Eros"</code></pre><p>也可以删除已有属性:</p><pre><code>person1.name; // "Eros",delete person1.name;person1.name; // undefined</code></pre><p>通过以上方法,我们实现了对一个对象属性扩展的冻结。但是同样也认识到,这并不是全面的保护:例如可以随意改动去覆盖已有属性,在对象原型链上增加属性也还是难以屏蔽。</p><h2 id="衣带渐宽终不悔-为伊消得人憔悴"><a href="#衣带渐宽终不悔-为伊消得人憔悴" class="headerlink" title="衣带渐宽终不悔 为伊消得人憔悴"></a>衣带渐宽终不悔 为伊消得人憔悴</h2><p>第二种境界:“衣带渐宽终不悔,为伊消得人憔悴。”<br>这引用的是北宋柳永《蝶恋花》最后两句词,原词是表现作者对爱的艰辛和爱的无悔。若把“伊”字理解为词人所追求的理想和毕生从事的事业,亦无不可。王国维则别有用心,以此两句来比喻成大事业、大学问者,不是轻而易举,随便可得的,必须坚定不移,经过一番辛勤劳动,废寝忘食,孜孜以求,直至人瘦带宽也不后悔。</p><p><strong>下面介绍一个更深一层的做法:密封特性。</strong></p><p>密封对象是指那些不能添加新的属性,不能删除已有属性,以及不能修改已有属性的可枚举性(enumerable)、可配置性(configurable)、可写性(writable),但<strong>可能可以修改已有属性的值的对象。</strong></p><p>他同样包含了两个基本方法:</p><ul><li>Object.isSealed</li><li>Object.seal</li></ul><h3 id="Object-isSealed"><a href="#Object-isSealed" class="headerlink" title="Object.isSealed"></a>Object.isSealed</h3><p>MDN上内容概述:</p><pre><code>概述 Object.isSealed() 方法判断一个对象是否是密封的(sealed)。语法 Object.isSealed(obj)参数 obj 将要检测的对象</code></pre><p>正常对象字面量声明的对象是不被密封的:</p><pre><code>var person1 = { name: "Lucas"}Object.isSealed(person1); // false</code></pre><p>当将这个对象禁止扩展时,它也不会变成密封的:</p><pre><code>var person1 = { name: "Lucas"}Object.preventExtensions(person1);Object.isSealed(person1); // false</code></pre><p>但是在此基础上,使用Object.defineProperty方法,把属性变得不可配置(configurable),则这个对象也就成了密封对象:</p><pre><code>var person1 = { name: "Lucas"}Object.defineProperty(person1, "name", {configurable : false});Object.isSealed(person1); // true</code></pre><p>此时,我们有:</p><pre><code>Object.getOwnPropertyDescriptor(person1, 'name');// 得到:Object { value: "Lucas", writable: true, enumerable: true, configurable: false}</code></pre><p>根据这个getOwnPropertyDescriptor,我们可以更加深入的理解密封特性:被密封的对象,就是在不可扩展基础上讲属性描述符configurable设置为false; 同时,<strong>被密封的对象,仍然有机会改变属性的值。</strong>只不过对于此对象本身而言,<strong>不可以再扩展新的属性,不可以更改已有属性的配置信息。</strong></p><h3 id="Object-seal"><a href="#Object-seal" class="headerlink" title="Object.seal"></a>Object.seal</h3><p>相对应我们也有一个方法将一个对象密封。</p><p>MDN上内容概述:</p><pre><code>概述 Object.seal() 方法可以让一个对象密封,并返回被密封后的对象。语法 Object.seal(obj)参数 obj 将要被密封的对象</code></pre><p>比如:</p><pre><code>var person1 = { name: "Lucas"}Object.getOwnPropertyDescriptor(person1, 'name');// 得到:Object { value: "Lucas", writable: true, enumerable: true, configurable: true}</code></pre><p>将此对象密封后:</p><pre><code>Object.seal(person1);Object.getOwnPropertyDescriptor(person1, 'name');// 得到:Object { value: "Lucas", writable: true, enumerable: true, configurable: false}</code></pre><p>也就是说:</p><pre><code>person1.age = 18;person1.age; // undefined// 扩展新属性失败// 同时调用defineProperty失败Object.defineProperty(person1,"name",{get : function(){return "g";}});// 抛出异常</code></pre><p>任何除更改属性值以外的操作,非严格模式下都会静默失败,如上并如下:</p><pre><code>delete person1.name;person1.name; // "Lucas"</code></pre><p>而更改属性值可以成功:</p><pre><code>person1.name = "Eros";person1.name; // "Eros"</code></pre><p>怎么理解这样的现象呢?牢记,被密封的对象拥有如下的属性描述符:</p><pre><code>Object { value: "Lucas", writable: true, enumerable: true, configurable: false}</code></pre><p><strong>而删除属性属于configurable,更改属性才属于writable;</strong></p><h3 id="一点延伸"><a href="#一点延伸" class="headerlink" title="一点延伸"></a>一点延伸</h3><p>借助于此,我们其实已经可以完成冻结对象的第三重境界:达到即密封又不可修改原属性值。因为可以这样做:</p><pre><code>var person1 = {name: "Lucas"};Object.defineProperty(person1, "name", {configurable: false, writable: false});Object.preventExtensions(person1);</code></pre><p>总结下就是设置:</p><blockquote><p>configurable: false + writable: false + preventExtensions</p></blockquote><p>或者因为</p><blockquote><p>configurable: false+ preventExtensions = seal</p></blockquote><p>所以也可以设置:</p><blockquote><p>seal + writable: false</p></blockquote><h2 id="众里寻他千百度,蓦然回首,那人却在,灯火阑珊处"><a href="#众里寻他千百度,蓦然回首,那人却在,灯火阑珊处" class="headerlink" title="众里寻他千百度,蓦然回首,那人却在,灯火阑珊处"></a>众里寻他千百度,蓦然回首,那人却在,灯火阑珊处</h2><p>第三种境界:“众里寻他千百度,蓦然回首,那人却在,灯火阑珊处。”<br>这是引用南宋辛弃疾《青玉案》词中的最后四句。梁启超称此词“自怜幽独,伤心人别有怀抱”。这是借词喻事,与文学赏析已无交涉。王国维已先自表明,“吾人可以无劳纠葛”。他以此词最后的四句为“境界”之第三,即最终最高境界。</p><p>这虽不是辛弃疾的原意,但也可以引出悠悠的远意:做学问、成大事业者,要达到第三境界,必须有专注的精神。反复追寻、研究,下足功夫,自然会豁然贯通,有所发现,有所发明,就能够从必然王国进入自由王国。</p><p>上边那种冻结对象的方法,其实也有原生实现,可谓:“众里寻他千百度,蓦然回首,那人却在,灯火阑珊处”</p><p>我们这里所说的一个对象的冻结(frozen)是指它不可扩展,所有属性都是不可配置的(non-configurable),且所有数据属性(data properties)都是不可写的(non-writable)。</p><p>或者说,冻结对象是指那些不能添加新的属性,不能修改已有属性的值,不能删除已有属性,以及不能修改已有属性的可枚举性、可配置性、可写性的对象。也就是说,这个对象永远是不可变的。</p><p>同样,包含了两个基本方法:</p><ul><li>Object.isFrozen</li><li>Object.freeze</li></ul><h3 id="Object-isFrozen"><a href="#Object-isFrozen" class="headerlink" title="Object.isFrozen"></a>Object.isFrozen</h3><p>MDN上内容概述:</p><pre><code>概述 Object.isFrozen() 方法判断一个对象是否被冻结(frozen)。语法 Object.isFrozen(obj)参数 obj 被检测的对象</code></pre><h3 id="Object-freeze-方法"><a href="#Object-freeze-方法" class="headerlink" title="Object.freeze 方法"></a>Object.freeze 方法</h3><p>MDN上内容概述:</p><pre><code>概述 Object.freeze() 方法可以冻结一个对象。语法 Object.freeze(obj)参数 obj 将要被冻结的对象</code></pre><p>可以先理解为,这是最高一层的冻结对象:</p><pre><code>var person1 = { name: "Lucas"}Object.freeze(person1);</code></pre><p>此时,我们有:</p><pre><code>Object.getOwnPropertyDescriptor(person1, 'name')Object { value: "Lucas", writable: false, enumerable: true, configurable: false}// 对冻结对象的任何操作都会失败person1.name = "Eros"; // 改写属性值,非严格模式下静默失败;person1.age = 18; // 扩展属性值,非严格模式下静默失败;Object.defineProperty(person1,"name",{value: "Eros"}); // 使用defineProperty会直接报错</code></pre><p>改写属性值,扩展新属性,调用defineProperty,全部都会失败。</p><p>但是,<strong>这种层面的冻结,只是浅冻结。</strong>如果对象里面还嵌套有对象,那么这个内部对象丝毫不受影响。</p><pre><code>var person1 = { name: "Lucas", family: { brother: "Eros" }}Object.freeze(person1);person1.family.brother = "Tim";person1.family.brother // "Tim"</code></pre><h3 id="终极实现"><a href="#终极实现" class="headerlink" title="终极实现"></a>终极实现</h3><p>那么,如果我们想深层次冻结一个对象呢?思路和深拷贝暗合,使用递归:</p><pre><code>Object.prototype.deepFreeze = Object.prototype.deepFreeze || function (o){ var prop, propKey; Object.freeze(o); // 首先冻结第一层对象 for (propKey in o){ prop = o[propKey]; if(!o.hasOwnProperty(propKey) || !(typeof prop === "object") || Object.isFrozen(prop)){ continue; } deepFreeze(prop); // 递归 }}</code></pre><p>这样子,我们再回过头来看:</p><pre><code>var person1 = { name: "Lucas", family: { brother: "Eros" }}Object.deepFreeze(person1);person1.family.brother = "Tim";person1.family.brother // "Eros"</code></pre><p>已经达到了深层次对象属性的冻结。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文先后介绍了关于冻结一个对象的三种进阶方法。他们层层递进,却又相互关联。关系如图:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-55750ea1c4e62989.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="关系图"></p><p>文章部分概念粘取了MDN语法介绍和<a href="https://segmentfault.com/a/1190000003894119" target="_blank" rel="external">Tomson的文章。</a></p><p>在《文学小言》一文中,王国维把上述三境界说成“三种之阶级”。并说:“未有不阅第一第二阶级而能遽跻第三阶级者,文学亦然。此有文学上之天才者,所以又需莫大之修养也。”</p><p>与大家共勉。</p><p>Happy coding!</p><p>PS: 作者<a href="https://github.com/HOUCe" target="_blank" rel="external">Github仓库</a>,欢迎通过代码各种形式交流。</p>]]></content>
<summary type="html">
<p>王国维在《人间词话》里谈到了治学经验,他说:“古今之成大事业、大学问者,必经过三种之境界。”</p>
<p>巧合的是,最近受 <a href="http://gitbook.cn/" target="_blank" rel="external">git chat / git
</summary>
<category term="js" scheme="https://exp-team.github.io/categories/js/"/>
<category term="js" scheme="https://exp-team.github.io/tags/js/"/>
</entry>
<entry>
<title>来自百度经验前端 —— 超级 sexy 的手势库</title>
<link href="https://exp-team.github.io/blog/2017/11/04/js/exp-touch/"/>
<id>https://exp-team.github.io/blog/2017/11/04/js/exp-touch/</id>
<published>2017-11-03T16:00:00.000Z</published>
<updated>2017-11-06T02:31:26.000Z</updated>
<content type="html"><![CDATA[<blockquote><p>一个页面,日均亿次围观,数以百计终端承载;<br>跳跃指尖,点按触碰拖拽,丝般顺滑,极致体验;</p><p>你还在为<strong>手势交互开发</strong>困扰吗,你还在为<strong>碎片化兼容性</strong>心塞吗?<br>去 <a href="">exp touch</a> 看看:</p><p>丰富手势交互封装,完美 demo 尽现。<br>让我们共同见证,指尖滑起的瞬间。</p></blockquote><p><img src="http://upload-images.jianshu.io/upload_images/4363003-013d341c9151767c.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="Martin Garrix 在 TomorrowLand 2017"></p><p>本文是一篇纯软文,<strong>PM、运营、后端、老板、水果大哥、酸奶小妹、保洁阿姨,阅读统统绝无障碍</strong>(技术细节文章会令写),底部超多可爱的 gif 图 demos。如果你已经厌倦营销文章的自吹自擂,那么作为前端程序员,以下内容,我想先和你谈谈产品和交互体验。</p><p>首先 exp touch 是一个超级 tiny & sexy 的前端手势库,而且 <strong><a href="">exp touch</a> 要开源啦</strong>!这是百度经验前端自研的交互类库。</p><p><strong>包含:</strong>单点、双击、长摁、拖拽、滑动、旋转等交互;<br><strong>覆盖:</strong>轮播图、抽奖转盘、3D 旋转、捏合缩放、上提(下拉)刷新等 demos;<br><strong>采用:</strong> ES Next 语法以及面向对象思想开发,辅以大量数学计算;<br><strong>最终:</strong>以<strong>极致用户体验</strong>和<strong>高效性能</strong>为亮点,横空出世!</p><h2 id="Wap-VS-Native-天空是否有边际"><a href="#Wap-VS-Native-天空是否有边际" class="headerlink" title="Wap VS Native: 天空是否有边际"></a>Wap VS Native: 天空是否有边际</h2><p>前端 Wap 开发,不同于我们认知的 Native App。天生决定,我们开发的宿主是浏览器。这个绝好舞台的另一面,隐藏着我们带镣铐起舞的尴尬。没错,<strong>我有多爱浏览器,就有多恨浏览器</strong>。</p><p>尤其是移动端,主流浏览器内核夹杂国产各种山寨,同时安卓和苹果两大手机系统天然鸿沟,种种碎片化导致事件规范不一致,最终成为产品体验的绊脚石。</p><p><strong>尤其关于触摸事件</strong>,更是一部血泪史。为此,我会另起技术文章分析,这里不在展开。说回产品,直面我们的痛楚:可爱的 PM 童鞋,在设计交互大开脑洞(褒义)的同时,对齐标准完全是 Native App!想想在评审时,wuli 我 PM 总会说: <strong>“就是这样,XX App 你用过的吧,你们像 XX App 一样做就可以了!”</strong></p><p>真的就可以吗?每次都想以:“你了解 App 和 Wap 的技术实现差别吗?”这种正当的技术理由怼回去,可是转念就压在心里,仅供五脏六腑之间交流。同时理智占据上风,信念控制住<strong>马上要抽起藏在抽屉中砍刀</strong>的麒麟臂,老老实实把胳膊肘放回到键盘上。</p><p>夜深人静时,我也想:“Wap 端真的就不可以实现 Native 那样的触控操作吗?”为此,我遍访了众多 Wap 页面,无奈都是<strong>饮恨而归</strong>。比如,我从来没有看到过: <strong>Wap 端手指捏和缩放图片时,是以手指中心为焦点:图片放大的同时图片自身也进行位移,保持预期中的放大焦点始终在双指中心,不脱离屏幕。</strong>如同在微信朋友圈中的图片缩放效果那般自然。可是在 Wap 页上,真的很少有实现!</p><p>我一直不甘心,直到这次的<strong>百度经验步骤页改版</strong>,让我向不可能发起挑战。来,慢慢跟你说。</p><h2 id="新版步骤页:拥抱视频化-奢华体验升华"><a href="#新版步骤页:拥抱视频化-奢华体验升华" class="headerlink" title="新版步骤页:拥抱视频化 + 奢华体验升华"></a>新版步骤页:拥抱视频化 + 奢华体验升华</h2><p>你也许没听说过百度经验,但很有可能无意中受益于他。打开搜索引擎,查找方法窍门,常识妙招:步骤化的阅读,左右滑动间,获益匪浅。</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-f958f6fc5c689189.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="经验步骤页"></p><p>百度经验步骤页是百度经验最重要的页面。无论从 PV、UV 还是变现能力来看,都处在毫无争议的核心地位。这次百度经验步骤页改版,最重要的目的除了拥抱视频化以外,就是打造更加完美、流畅的使用体验。</p><p>前端开发,在视频化方面也面临严重的机遇和挑战,由于此文推销主题原因,暂且不表,同样也会有技术文章稍后奉上。这里主要谈谈手势上的交互体验。</p><p>负责任地说,我从来没见到过像百度经验步骤页一样,极尽手势掌控之能事,尤其是在天生畸形的浏览器上,我们看看它都实现了什么:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-ab94bcae4206c75d.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="丑图"></p><p>上图中的文字仅仅涵盖了图片画廊手势交互一部分,各种自适应以及各种物理惰性回弹、缩放摩擦系数、缩放阈值、缓冲区域、回弹系数等等也就不一一列举了。</p><p>同时,图片画廊并不是一个新的页面,全部以原页面加遮罩形式出现。你可以理解为 SPA(单页面应用),各种消息交互,手势触控矛盾处理耦合,继续如图:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-1e8692c29fe81ee1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="组件消息通信"></p><p>单纯的双指缩放其实并不难,你可能也会想到就是二维坐标轴的各种计算罢了。</p><p><strong>可是:</strong>单击双击时间差如何区分;有角度的滑动算左滑还是右滑;指定元素上 touch move 期间滑动出了手机屏要不要触发 touch end 事件;一根手指和两根手指和多根手指放在不同的元素上要怎么划分?</p><p>诸如此类太多逻辑和细节交织,这酸爽足以醉人。再加上隔着浏览器兼容性的壁垒,这都构成 Wap 页面很少有大规模手势操控实现的原因。不信你去找找,Wap 上图片画廊支持切换图片且支持手指中心图片缩放的 case……额,找到有奖,找到源代码?有大奖!</p><p>可是这次的步骤页改版,我们偏偏要把这些实现。</p><h2 id="新版步骤页和新新版步骤页和新新新新新版步骤页"><a href="#新版步骤页和新新版步骤页和新新新新新版步骤页" class="headerlink" title="新版步骤页和新新版步骤页和新新新新新版步骤页"></a>新版步骤页和新新版步骤页和新新新新新版步骤页</h2><p>经过呕心吐血的骚操作,在去年步骤页改版全量上线之后,不管是 PV、UV、还是广告收益都有大幅度上涨。可是改版常年有,难道以后年年折腾一遍?</p><p>我选择拒绝,之前的开发心得并没有白积累。我利用周末时间,基于 AlloyTouch 等良心作品,<strong>开发了 exp touch 这个百度经验手势库</strong>。</p><p>它采用 ES Next 完全面向对象,封装了大量必要的计算过程,同时对外暴露出各种贴心的回调。我个人很厌恶功能大而全的 UI 库,限定死了太多内容而无法拓展。因此 exp touch 只进行兼容性处理以及数学计算。同时为了增强可用性和学习成本,基于 exp touch 手势库,我实现了海量 demos,第三方开发者完全可以复制粘贴,分分钟解锁各种姿势。</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-7037997de4248d4e.gif?imageMogr2/auto-orient/strip" alt="解锁各种姿势"></p><h2 id="海量-demo-即插即用"><a href="#海量-demo-即插即用" class="headerlink" title="海量 demo 即插即用"></a>海量 demo 即插即用</h2><p>那我们直接看看 Gif 图好咯:</p><p>demo: 这是一个简单的滚动。但是不出现滚动条,因为完全不依靠浏览器 scroll,而是根据 touchmove 实时计算 transformY,同时包含了缓冲区域设置:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-e581965b3dcdc7c1.gif?imageMogr2/auto-orient/strip" alt="神奇的滚动"></p><p>demo: 这是一个常见的移动端轮播图</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-1a322e443bcb7cad.gif?imageMogr2/auto-orient/strip" alt="仿百度经验轮首页播图"></p><p>demo: 这是一个常见的整屏幕翻动 H5:<br><img src="http://upload-images.jianshu.io/upload_images/4363003-7037997de4248d4e.gif?imageMogr2/auto-orient/strip" alt="H5 Slides"></p><p>demo: 这是一个信息流展现,包含下拉刷新,和头部动画:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-49101851198effe4.gif?imageMogr2/auto-orient/strip" alt="综合 demo"></p><p>demo: 这是一个 2D 抽奖转盘,手势转动开启;</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-271c8382ab827c83.gif?imageMogr2/auto-orient/strip" alt="转盘 demo"></p><p>demo: 这是一个拖拽:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-55a0fa5998dceb84.gif?imageMogr2/auto-orient/strip" alt="拖拽 demo"></p><p>(这些 demo 灵感来自 AlloyTeam 团队,我只是照着自己实现了一下)</p><h2 id="最后几句话"><a href="#最后几句话" class="headerlink" title="最后几句话"></a>最后几句话</h2><ol><li>这个库当然还存在一些不完善的地方,大家后续尽可以提 PR,或者当面交流;</li><li>强烈支持采用 exp touch,玩出更多花样,做出的最终页面欢迎反馈给我;</li><li>它将会在新版步骤页上线后正式开源。在此之前,任何有兴趣的,可以与我一起开发。虽然我已经实现了 99%,剩下 1% 就等你一个 contributor;</li><li><strong>多多关注百度经验,我们是一个小而美的团队</strong>;</li><li>写这个其实挺没意思的,我更喜欢 React 技术栈啊啊啊啊;</li><li>前些时间有几家出版社找到我约书,最终我选择和电子工业出版社签订约稿合同,在写一本关于 React 技术栈的书;</li><li>如果你对 React 以及出书感兴趣,我已经拉上了厂内外两个大佬做校审,也强烈欢迎你来跟我一起写,出谋划策或者审阅;</li><li>如果你单纯对 React 感兴趣,可以关注<a href="https://www.zhihu.com/people/lucas-hc/activities" target="_blank" rel="external">我的知乎</a>,最近半年不定期在发表一些技术心得(我是来骗粉的)。</li><li><strong>同 4;</strong></li><li><strong>同上。</strong></li></ol>]]></content>
<summary type="html">
<blockquote>
<p>一个页面,日均亿次围观,数以百计终端承载;<br>跳跃指尖,点按触碰拖拽,丝般顺滑,极致体验;</p>
<p>你还在为<strong>手势交互开发</strong>困扰吗,你还在为<strong>碎片化兼容性</strong>心塞吗?<br>去 <
</summary>
<category term="js" scheme="https://exp-team.github.io/categories/js/"/>
<category term="js" scheme="https://exp-team.github.io/tags/js/"/>
</entry>
<entry>
<title></title>
<link href="https://exp-team.github.io/blog/2017/06/27/js/decomposing_React_components/"/>
<id>https://exp-team.github.io/blog/2017/06/27/js/decomposing_React_components/</id>
<published>2017-06-27T07:12:25.000Z</published>
<updated>2017-06-27T07:14:03.000Z</updated>
<content type="html"><![CDATA[<p>之前分享过几篇关于React技术栈的原创文章:</p><ul><li><a href="http://www.jianshu.com/p/7a56ac1de2a8" target="_blank" rel="external">解析Twitter前端架构 学习复杂场景数据设计</a></li><li><a href="http://www.jianshu.com/p/83c86dd0802d" target="_blank" rel="external">React Conf 2017 干货总结1: React + ES next = ♥</a></li><li><a href="http://www.jianshu.com/p/cde3cf7e2760" target="_blank" rel="external">React+Redux打造“NEWS EARLY”单页应用 一个项目理解最前沿技术栈真谛</a></li><li><a href="http://www.jianshu.com/p/8e28be0e7ab1" target="_blank" rel="external">一个react+redux工程实例</a></li><li>……</li></ul><p>今天进一步剖析一个实际案例:<strong>Uber APP 移动网页版。</strong></p><p>如果你对React技术栈没有多大兴趣,或者不是很了解,也没有关系。因为读下来,你会发现,这篇文章的真谛其实在于<strong>性能优化</strong>上。</p><p>本文灵感和主体内容翻译自Narendra N Shetty的<a href="https://hackernoon.com/how-i-built-a-super-fast-uber-clone-for-mobile-web-863680d2100f" target="_blank" rel="external">文章:How I built a super fast Uber clone for mobile web</a>,同时进行了大量扩充以及深挖。</p><h2 id="出发点和产品雏形"><a href="#出发点和产品雏形" class="headerlink" title="出发点和产品雏形"></a>出发点和产品雏形</h2><p>很早以来,相信大家都会认同一个观点:<strong>移动端流量超越PC端是不争的事实。</strong>对于前端开发者来说,移动端web的开发同样非常有趣,也充满挑战。</p><p>这不,Uber最近发布了最新版本APP,全新样式,体验超棒。于是,笔者决定使用React来从零开始构建一个新的属于自己的Uber。</p><p>开发期间,笔者花费了很多时间在基础组件和样式搭建上。这环节中,主要应用了<a href="https://github.com/uber/react-map-gl" target="_blank" rel="external">Uber官方开放的React地图库</a>,并在地图上“目的地”和“起始点”之间采用svg-overlay和html-overlay去绘制路线。</p><p>最终的基本交互可以参考下面Gif图:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-59027620881f3e91.gif?imageMogr2/auto-orient/strip" alt="uber.gif"></p><h2 id="走上优化之路"><a href="#走上优化之路" class="headerlink" title="走上优化之路"></a>走上优化之路</h2><p>现在,我们有基本的产品形态了。目前面临的问题在于提高产品的各方面性能体验。我使用了Chrome Lighthouse去检验产品的性能表现。最终得到的结果为:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-ea0f448ffd3ae4f1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="结果1.png"></p><p>wow…<br>第一次绘制时间就已经<strong>接近2秒</strong>,后面的时间惨不忍睹就不要看了吧。<br>想象一下,一个用户拿出手机,企图叫车。主屏时间的绘制就超过了19189.9ms,这是极其不能忍受的。</p><p>接下来,什么也不说了,撸起袖子,想办法去优化吧。</p><h2 id="优化方法1-代码分离(Code-Splitting)"><a href="#优化方法1-代码分离(Code-Splitting)" class="headerlink" title="优化方法1-代码分离(Code Splitting)"></a>优化方法1-代码分离(Code Splitting)</h2><p>我最开始想到并使用的方法就是:Code Splitting(代码分离),正好我们可以借助webpack来实现这项技术。<br>什么是webpack code splitting呢? 您可以参考<a href="https://webpack.github.io/docs/code-splitting.html" target="_blank" rel="external">这里</a>,如果英语阅读吃力,可以参考下面引文:</p><blockquote><p>code splitting就是指将文件分割为块(chunk),webpack使我们可以定义一些分割点(split point),根据这些分割点对文件进行分块,并实现按需加载。</p></blockquote><p>因为笔者使用了React技术栈,并采用了react-router,所以代码的划分(split)就可以按照路由和加载时机进行。具体操作可以使用react-router的getComponent api来实现:</p><pre><code><Route path="home" name="home" getComponent={(nextState, cb) => { require.ensure([], (require) => { cb(null, require('../components/Home').default); }, 'HomeView');}}> </code></pre><p>只有当对应路由被请求时,相应的组件才会被加载呈现。</p><p>同时,笔者使用了webpack的CommonChunkPlugin插件提取第三方代码。这是出于什么考虑呢?</p><p>细心的读者可能会发现上面的code splitting也许会存在一个问题:<br><strong>按需(按路由)引入资源后,这些资源可能存在大量重复代码。尤其是我们使用的第三方资源。</strong><br>想明白这个问题,这时候,你应该就会明白CommonChunkPlugin这个插件的意义了。关于这个插件配置方法有多种,这里我们采用了:有选择性的提取(对象方式传参):</p><pre><code>{ 'entry': { 'app': './src/index.js', 'vendor': [ 'react', 'react-redux', 'redux', 'react-router', 'redux-thunk' ] }, 'output': { 'path': path.resolve(__dirname, './dist'), 'publicPath': '/', 'filename': 'static/js/[name].[hash].js', 'chunkFilename': 'static/js/[name].[hash].js' }, 'plugins': [ new webpack.optimize.CommonsChunkPlugin({ name: ['vendor'], // 公共块的块名称 minChunks: Infinity, // 最小被引用次数,最小是2。传递Infinity只是创建公共块,但不移动模块。 filename: 'static/js/[name].[hash].js', // 公共块的文件名 }), ]}</code></pre><p>这样子,我们把公共代码(react、react-redux、redux、react-router、redux-thunk)专门抽取到vendor模块中。</p><p>通过上述方法,笔者欣喜地发现:<br>First meaningful paint时间由19189.9ms缩短到4584.3ms:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-b7384bf2a9c9bb5a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="结果2"></p><p>这无疑是激动人心的。</p><h2 id="优化方法2-Server-side-rendering(服务端直出)"><a href="#优化方法2-Server-side-rendering(服务端直出)" class="headerlink" title="优化方法2-Server side rendering(服务端直出)"></a>优化方法2-Server side rendering(服务端直出)</h2><p>也许你一直在听说过“服务端渲染”或者“服务端直出”这样的名词。但是从未实践过,也从来没有了解过他的意义。好吧,这里我先描述一下,到底什么是服务端直出。</p><p>服务端直出,其实简单总结为服务器在接到来自浏览器第一次请求时,便返回一个“初步最终”HTML文档。这个HTML文档已经进行了数据拼接。这样用户能以最快的时间看到首屏的效果,当然这个效果是“阉割版”的,非最终版本。</p><p>这种方式主要是针对“前后分离”的传统模式。传统模式中,服务器返回HTML文档,之后浏览器解析文档标签,拉取CSS,之后拉取JS文件。JS文件加载完成之后,执行JS内容,并发送请求获取数据。最终,将数据渲染在页面上。</p><p>由此,Server side rendering方式将JS请求数据的过程放在了服务器上,甚至对于数据与HTML结合处理也可以在服务器上做。</p><p>这样一来,<strong>主要就是加快了首屏渲染时间。</strong>当然,使用服务端渲染,还能够优化前端渲染难以克服的SEO问题。</p><p>理论理解起来很简单,难处就在于服务器端环境的前端脚本如何处理,如何与客户端保持一致。</p><p>在这个项目中,我使用了Express作为nodeJS框架,结合react-router完成:</p><pre><code>server.use((req, res)=> { match({ 'routes': routes, 'location': req.url }, (error, redirectLocation, renderProps) => { if (error) { res.status(500).send(error.message); } else if (redirectLocation) { res.redirect(302, redirectLocation.pathname + redirectLocation.search); } else if (renderProps) { // Create a new Redux store instance const store = configureStore(); // Render the component to a string const html = renderToString(<Provider store={store}><RouterContext {...renderProps} /></Provider>); const preloadedState = store.getState(); fs.readFile('./dist/index.html', 'utf8', function (err, file) { if (err) { return console.log(err); } let document = file.replace(/<div id="app"><\/div>/, `<div id="app">${html}</div>`); document = document.replace(/'preloadedState'/, `'${JSON.stringify(preloadedState)}'`); res.setHeader('Cache-Control', 'public, max-age=31536000'); res.setHeader("Expires", new Date(Date.now() + 2592000000).toUTCString()); res.send(document); }); } else { res.status(404).send('Not found') } });});</code></pre><p>通过上述方法,我们欣喜地发现:<br>First meaningful paint时间已经缩短到921.5ms:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-b4c9aa1eff18344c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="结果3"></p><p>这无疑是令人振奋的。</p><h2 id="优化方法3-Compressed-static-assets(压缩静态文件)"><a href="#优化方法3-Compressed-static-assets(压缩静态文件)" class="headerlink" title="优化方法3-Compressed static assets(压缩静态文件)"></a>优化方法3-Compressed static assets(压缩静态文件)</h2><p>压缩文件,当然是一个容易想到而且行之有效的措施。为此,我使用了webpack的CompressionPlugin插件:</p><pre><code>{ 'plugins': [ new CompressionPlugin({ test: /\.js$|\.css$|\.html$/ }) ]}</code></pre><p>同时,使用express-static-gzip来对服务端进行配置:</p><pre><code>server.use('/static', expressStaticGzip('./dist/static', { 'maxAge': 31536000, setHeaders: function(res, path, stat) { res.setHeader("Expires", new Date(Date.now() + 2592000000).toUTCString()); return res; }}));</code></pre><p>express-static-gzip是一个处于express.static之上的中间件。如果对于指定路径的文件没有找到压缩版本,就使用为压缩版本进行返回。</p><p>经过此处理,我们缩短了400ms时间,OK,现在First meaningful paint时间为546.6ms.</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-29234133ac7f9edd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="结果4"></p><h2 id="优化方法4-Caching(缓存)"><a href="#优化方法4-Caching(缓存)" class="headerlink" title="优化方法4-Caching(缓存)"></a>优化方法4-Caching(缓存)</h2><p>截止到此,我们已经从最初的19189.9ms已经优化到546ms,我们当然继续可以在客户端进行<strong>静态文件缓存</strong>来使得加载时间变得更短。</p><p>笔者使用了<a href="https://github.com/GoogleChrome/sw-toolbox" target="_blank" rel="external">sw-toolbox</a>搭配service workers进行。</p><blockquote><p>sw-toolbox:A collection of service worker tools for offlining runtime requests.<br>Service Worker Toolbox provides some simple helpers for use in creating your own service workers. Specifically, it provides common caching strategies for dynamic content, such as API calls, third-party resources, and large or infrequently used local resources that you don’t want precached.</p></blockquote><p>简单翻译下:<br>Service Worker实现常见运行时缓存模式,例如动态内容、API调用以及第三方资源,实现方法就像编写README一样简单。</p><p>也许到这里你一头雾水,没关系,我们从最初开始,了解一下什么是service worker:</p><blockquote><p>在2014年,W3C公布了service worker的草案,service worker提供了很多新的能力,使得web app拥有与native app相同的离线体验、消息推送体验。<br>service worker是一段脚本,与web worker一样,也是在后台运行。<br>作为一个独立的线程,运行环境与普通脚本不同,所以不能直接参与web交互行为。native app可以做到离线使用、消息推送、后台自动更新,service worker的出现是正是为了使得web app也可以具有类似的能力。</p></blockquote><p>而sw-toolbox,顾名思义,就是service worker一个toolbox,具体我们看代码:</p><pre><code>toolbox.router.get('(.*).js', toolbox.fastest, { 'origin':/.herokuapp.com|localhost|maps.googleapis.com/, 'mode':'cors', 'cache': { 'name': `js-assets-${VERSION}`, 'maxEntries': 50, 'maxAgeSeconds': 2592e3 }});</code></pre><p>上面代码的意思是,我们对于get类型的请求,当请求内容为js脚本时,应用toolbox.fastest handler处理。<br>toolbox.fastest指示:对于这个请求,我们既从缓存中获取,也同时通过正常的请求network获取。<strong>这两种方式哪个返回快,就应用哪一个。</strong><br>另外,toolbox.router.get的第三个参数表示配置项。</p><p>考虑周到的读者可能会想,上面是对于支持Service worker的浏览器,那么对于不支持的浏览器呢?我们干脆设置:</p><pre><code>res.setHeader("Expires", new Date(Date.now() + 2592000000).toUTCString());</code></pre><p>通过这样处理,我们来直观感受一下页面加载瀑布流:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-1a5ca5dd910efece.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="使用Service worker"></p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-456cb5a07f1acd0b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="不使用Service worker"></p><h2 id="优化方法5-Preload-and-then-load(预加载/延后加载)"><a href="#优化方法5-Preload-and-then-load(预加载/延后加载)" class="headerlink" title="优化方法5-Preload and then load(预加载/延后加载)"></a>优化方法5-Preload and then load(预加载/延后加载)</h2><p>如果你还没听说过“Preload”,不要紧。我们这就来了解一下:</p><blockquote><p>Preload作为一个新的web标准,旨在提高性能和为web开发人员提供更细粒度的加载控制。Preload使开发者能够自定义资源的加载逻辑,且无需忍受基于脚本的资源加载器带来的性能损失。</p></blockquote><p>换成你能听明白的话来说:<br><strong>preload建议允许始终预加载某些资源,浏览器必须请求preload标记的资源。</strong></p><p>这样子,究竟有什么意义呢?<br>举个例子:比如一些隐藏在CSS和Javascript中的资源。<br>当浏览器发现自己需要这些资源时已经为时已晚,所以大多数情况,这些资源的加载都会对页面渲染造成延迟。</p><p>preload的出现就是为了优化这个过程。<br>对于preload的兼容性,可以参考<a href="http://caniuse.com/#search=preload" target="_blank" rel="external">这里。</a></p><p>对于不支持preload的浏览器,笔者使用了prefetch来处理。<br>但于preload不同,prefetch的作用是告诉浏览器加载下一页面可能会用到的资源,注意,是下一页面,而不是当前页面。因此该方法的加载优先级非常低。</p><p>这些新标准其实很有意思,里面的内容远不止这些。有兴趣的同学可以自行了解,也欢迎与我讨论。</p><p>回到正题,我在head标签中使用:</p><pre><code><link rel="preload" ... as="script"></code></pre><p>最终优化的结果如图:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-8823ee4b69444430.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="最终结果"></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>其实,使用React+Webpack做出一个Uber已经不是重点了。真正激动人心的是整套流程的优化之路。我们使用了大量成熟的、未成熟(新技术),希望对读者有所启发!</p><p>Happy Coding!</p><p>PS: 作者<a href="https://github.com/HOUCe" target="_blank" rel="external">Github仓库</a>,欢迎通过代码各种形式交流。</p>]]></content>
<summary type="html">
<p>之前分享过几篇关于React技术栈的原创文章:</p>
<ul>
<li><a href="http://www.jianshu.com/p/7a56ac1de2a8" target="_blank" rel="external">解析Twitter前端架构 学习复杂场景数
</summary>
</entry>
<entry>
<title></title>
<link href="https://exp-team.github.io/blog/2017/06/18/js/react_bind_this/"/>
<id>https://exp-team.github.io/blog/2017/06/18/js/react_bind_this/</id>
<published>2017-06-18T07:13:22.000Z</published>
<updated>2017-06-18T07:13:22.000Z</updated>
<content type="html"><![CDATA[<p>在 Javascript 语言中,关于 this 这个关键字的行为一直以来困扰着一代又一代初级开发者。但是请别误会,这篇文章并不会对 this 的基本特征进行全方位讲解,因为这些内容都可以在各种前端书籍中找到答案。我试图结合 React 事件处理函数对 this 的绑定演化史,谈一谈这个框架以及 javascript 语言在这一细节上的不断进步和完善。同时对比 this 绑定的不同方案,让大家对 React 、ES next 有一个更清晰的认识。</p><p>React 处理 this 上下文环境已经有至少五年历史了。期间方案辈出,我们先来总结一下。</p><h2 id="方法一:React-createClass自动绑定"><a href="#方法一:React-createClass自动绑定" class="headerlink" title="方法一:React.createClass自动绑定"></a>方法一:React.createClass自动绑定</h2><p>React 中创建组件的方式已经很多,比较古老的诸如 React.createClass 应该很多人并不陌生。当然,从 React 0.13 开始,可以使用 ES6 Class 代替 React.createClass 了,这应该是今后推荐的方法。但是需要知道,React.createClass 创建的组件内,可以自动绑定 this。也就是说,this 这个关键字会自动绑定在组件实例上面。</p><pre><code>// This magically works with React.createClass// because `this` is bound for you.onChange = {this.handleChange}</code></pre><p>当然很遗憾,截至目前,官方已经明确指出更推荐使用 class 声明组件或 functional 无状态组件:</p><blockquote><p>Later, classes were added to the language as part of ES2015, so we added the ability to create React components using JavaScript classes. Along with functional components, JavaScript classes are now the preferred way to create components in React.<br>For your existing createClass components, we recommend that you migrate them to JavaScript classes. </p></blockquote><p>我认为,这其实是 React 框架本身的自我完善和准确迎合未来,是框架和语言发展的大势所趋。</p><h2 id="方法二:渲染时绑定"><a href="#方法二:渲染时绑定" class="headerlink" title="方法二:渲染时绑定"></a>方法二:渲染时绑定</h2><p>通过前文,我们知道传统的组件创建方式不会有 this 绑定的困扰。接下来,我们假定所有的组件都采取 ES6 classes 方式声明。这种情况下,this 无法自动绑定。一个常见的解决方案便是:</p><pre><code>onChange = {this.handleChange.bind(this)}</code></pre><p>这种方法简明扼要,但是有一个潜在的性能问题:当组件每次重新渲染时,都会有一个新的函数创建。这听上去貌似是一个很大的问题,但是其实在真正的开发场景中因此引发的性能问题往往不值一提(除非是大型组件消费类应用或游戏)。</p><h2 id="方法三:箭头函数绑定"><a href="#方法三:箭头函数绑定" class="headerlink" title="方法三:箭头函数绑定"></a>方法三:箭头函数绑定</h2><p>这种方法其实和第二种类似,拜 ES6 箭头函数所赐,我们可以隐式绑定 this:</p><pre><code>onChange = {e => this.handleChange(e)}</code></pre><p>当然,它同样存在潜在的性能问题。<br>下面将要介绍的两种方法,可以有效规避此问题,请继续关注。</p><h2 id="方法四:Constructor-内绑定"><a href="#方法四:Constructor-内绑定" class="headerlink" title="方法四:Constructor 内绑定"></a>方法四:Constructor 内绑定</h2><p>constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。<br>所以我们可以:</p><pre><code>constructor(props) { super(props); this.handleChange = this.handleChange.bind(this);}</code></pre><p>这种方式往往被推荐为“最佳实践”,也是笔者最为常用的方法。<br>但是就个人习惯而言,我认为与前两种方法相比,constructor 内绑定在可读性和可维护性上也许有些欠缺。<br>同时,我们知道在 constructor 声明的方法不会存在实例的原型上,而落于实例本身的方法。每个实例都有同样一个 handleChange 方法,本身也是一种重复和浪费。</p><p>如果你对 ES next 一直抱有开放的思想,且能够使用 stage-2 的特性,不妨尝试一下最后一种方案。</p><h2 id="方法五:Class-属性中使用-和箭头函数"><a href="#方法五:Class-属性中使用-和箭头函数" class="headerlink" title="方法五:Class 属性中使用 = 和箭头函数"></a>方法五:Class 属性中使用 = 和箭头函数</h2><p>这个方法依赖于 ES next 的新特性,请参考<a href="https://tc39.github.io/proposal-class-public-fields/" target="_blank" rel="external">tc 39: Public Class Fields</a></p><pre><code>handleChange = () => { // call this function from render // and this.whatever in here works fine.};</code></pre><p>我们来总结一下这种方式的优点:</p><ul><li>使用箭头函数,有效绑定了 this;</li><li>没有第二种方法和第三种方法的潜在性能问题;</li><li>避免了方法四的组件实例重复问题;</li><li>我们可以直接从 ES5 createClass 重构。</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文在对比 React 绑定 this 的五种方法同时,我们也由远及近了解了 javascript 语言的发展:从 ES5 的 bind, 到 ES6 的箭头函数,再到 ES next 对 class 的改进。React 作为蓬勃发展的框架也同样在与时具进,不断完善。</p><p>最后,我们通过这张图片来完整回顾:</p><p>本文参考了 Cory House 的文章:<a href="https://medium.freecodecamp.com/react-binding-patterns-5-approaches-for-handling-this-92c651b5af56" target="_blank" rel="external">5 Approaches for Handling <code>this</code></a>,并在此基础上进行延伸。</p>]]></content>
<summary type="html">
<p>在 Javascript 语言中,关于 this 这个关键字的行为一直以来困扰着一代又一代初级开发者。但是请别误会,这篇文章并不会对 this 的基本特征进行全方位讲解,因为这些内容都可以在各种前端书籍中找到答案。我试图结合 React 事件处理函数对 this 的绑定演化
</summary>
</entry>
<entry>
<title>JS冻结对象的《人间词话》 完美实现究竟有几层?</title>
<link href="https://exp-team.github.io/blog/2017/06/09/js/freeze-obj/"/>
<id>https://exp-team.github.io/blog/2017/06/09/js/freeze-obj/</id>
<published>2017-06-08T16:00:00.000Z</published>
<updated>2017-06-06T08:34:06.000Z</updated>
<content type="html"><![CDATA[<p>王国维在《人间词话》里谈到了治学经验,他说:“古今之成大事业、大学问者,必经过三种之境界。”</p><p>巧合的是,最近受 <a href="http://gitbook.cn/" target="_blank" rel="external">git chat / git book</a> 邀请,做了一个分享。<br>其中谈到JS中冻结一个对象几种由浅入深的实践。想想也暗合国学大师所谓的三重境界。</p><p>这篇文章由浅入深讨论JS中对象的一些锁定特性。但都是一些基础语法的实现,相信即便是前端小白也可以大体领会。不过需要读者预先了解JS中对象的特性,尤其是对象自身属性的描述符:configurable、writable…</p><p>另外,如果您对JS中对象操作、不可变数据、函数式编程感兴趣,同样推荐我的其他一些相关文章:</p><ul><li><a href="http://www.jianshu.com/p/11fc75f28302" target="_blank" rel="external">如何优雅安全地在深层数据结构中取值</a></li><li><a href="http://www.jianshu.com/p/89f1d4245b20" target="_blank" rel="external">从JS对象开始,谈一谈“不可变数据”和函数式编程</a></li><li><a href="http://www.jianshu.com/p/7a56ac1de2a8" target="_blank" rel="external">解析Twitter前端架构 学习复杂场景数据设计</a></li></ul><p>等等。</p><h2 id="昨夜西风凋碧树-独上高楼-望尽天涯路"><a href="#昨夜西风凋碧树-独上高楼-望尽天涯路" class="headerlink" title="昨夜西风凋碧树 独上高楼 望尽天涯路"></a>昨夜西风凋碧树 独上高楼 望尽天涯路</h2><p>第一种境界:“昨夜西风凋碧树,独上高楼,望尽天涯路。”<br>该词句出自晏殊的《蝶恋花》,原意是说,“我”上高楼眺望所见的更为萧飒的秋景,西风黄叶,山阔水长,案书何达?</p><p>王国维此句中解成:做学问成大事业者,首先要有执着的追求,登高望远,瞰察路径,明确目标与方向,了解事物的概貌。</p><p> <strong>我们就从最基本的场景说起,究竟为什么要冻结一个对象?</strong></p><ul><li><p>场景一:<br>我们造了一个轮子,对外暴露一个对象,开放出来给第三方使用。同时需要保证这个对外暴露的对象完全安全,不能被业务代码所改写覆盖或下钩子(hook)函数。</p></li><li><p>场景二:<br>如果你看过Vue 2.* 版本源码,你会发现冻结一个对象的操作频繁出现。</p></li></ul><p><strong>我们先来看冻结对象的第一层实现 —— 扩展特性锁:</strong></p><p>他包含了两个基本方法:</p><ul><li>Object.isExtensible</li><li>Object.preventExtensions</li></ul><p>如果一个对象可以添加新的属性,则这个对象是可扩展的。扩展特性锁就是让这个对象变的不可扩展,也就是不能再有新的属性。</p><h3 id="Object-isExtensible"><a href="#Object-isExtensible" class="headerlink" title="Object.isExtensible"></a>Object.isExtensible</h3><p>MDN上内容概述:</p><pre><code>概述 Object.isExtensible() 方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。语法 Object.isExtensible(obj)参数 obj 需要检测的对象</code></pre><p>例如,我们正常使用对象字面量声明的对象都是可扩展的:</p><pre><code>var person1 = {};person1.name = "Lucas";console.log(person1);// {name: "Lucas"}</code></pre><p>同时:</p><pre><code>Object.isExtensible(person1) === true; // true</code></pre><p>你可能要问了,那么使用Object.create方法声明的对象,并对该对象属性进行配置是什么情况呢?<br>我们知道,用上面对象字面量声明的对象相当于:</p><pre><code>var person1 = Object.create({},{ "name":{ value : "Lucas", configurable : true, //不可配置 enumerable : true , //可枚举 writable : true //可写 }});</code></pre><p>即便尝试将configurable设置为false:</p><pre><code>var person1 = Object.create({},{ "name":{ value : "Lucas", configurable : false, //不可配置 enumerable : true, //可枚举 writable : true //可写 }});</code></pre><p>仍然得到:</p><pre><code>Object.isExtensible(person1) === true; // true</code></pre><h2 id="Object-preventExtensions"><a href="#Object-preventExtensions" class="headerlink" title="Object.preventExtensions"></a>Object.preventExtensions</h2><p>当然,我们还是有方法可以使得一个对象变的不可扩展。</p><p>MDN上内容概述:</p><pre><code>概述 Object.preventExtensions() 方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。语法 Object.preventExtensions(obj)参数 obj 将要变得不可扩展的对象</code></pre><p>几个注意点包括但不限于:</p><ul><li>不可扩展的对象的属性通常<strong>仍然可以被删除。</strong></li><li>尝试给一个不可扩展对象添加新属性的操作将会失败,不过<strong>可能是静默失败,也可能会抛出 TypeError 异常</strong>(严格模式下)。</li><li>Object.preventExtensions 只能阻止一个对象不能再添加新的自身属性,<strong>仍然可以为该对象的原型添加属性。</strong></li></ul><p>比如:</p><pre><code>var person1 = { name: "Lucas"}Object.preventExtensions(person1);person1.age = 18;// 非严格模式下,这里不会有报错,属于静默失败person1.age // undefined// 扩展新属性失败了</code></pre><p>仍然可以向原型链添加属性:</p><pre><code>person1.__proto__.age = 18;person1.age // 18// 可以从原型链上取到</code></pre><p>同样也可以复写一些属性:</p><pre><code>person1.name = "Eros";person1.name // "Eros"</code></pre><p>也可以删除已有属性:</p><pre><code>person1.name; // "Eros",delete person1.name;person1.name; // undefined</code></pre><p>通过以上方法,我们实现了对一个对象属性扩展的冻结。但是同样也认识到,这并不是全面的保护:例如可以随意改动去覆盖已有属性,在对象原型链上增加属性也还是难以屏蔽。</p><h2 id="衣带渐宽终不悔-为伊消得人憔悴"><a href="#衣带渐宽终不悔-为伊消得人憔悴" class="headerlink" title="衣带渐宽终不悔 为伊消得人憔悴"></a>衣带渐宽终不悔 为伊消得人憔悴</h2><p>第二种境界:“衣带渐宽终不悔,为伊消得人憔悴。”<br>这引用的是北宋柳永《蝶恋花》最后两句词,原词是表现作者对爱的艰辛和爱的无悔。若把“伊”字理解为词人所追求的理想和毕生从事的事业,亦无不可。王国维则别有用心,以此两句来比喻成大事业、大学问者,不是轻而易举,随便可得的,必须坚定不移,经过一番辛勤劳动,废寝忘食,孜孜以求,直至人瘦带宽也不后悔。</p><p><strong>下面介绍一个更深一层的做法:密封特性。</strong></p><p>密封对象是指那些不能添加新的属性,不能删除已有属性,以及不能修改已有属性的可枚举性(enumerable)、可配置性(configurable)、可写性(writable),但<strong>可能可以修改已有属性的值的对象。</strong></p><p>他同样包含了两个基本方法:</p><ul><li>Object.isSealed</li><li>Object.seal</li></ul><h3 id="Object-isSealed"><a href="#Object-isSealed" class="headerlink" title="Object.isSealed"></a>Object.isSealed</h3><p>MDN上内容概述:</p><pre><code>概述 Object.isSealed() 方法判断一个对象是否是密封的(sealed)。语法 Object.isSealed(obj)参数 obj 将要检测的对象</code></pre><p>正常对象字面量声明的对象是不被密封的:</p><pre><code>var person1 = { name: "Lucas"}Object.isSealed(person1); // false</code></pre><p>当将这个对象禁止扩展时,它也不会变成密封的:</p><pre><code>var person1 = { name: "Lucas"}Object.preventExtensions(person1);Object.isSealed(person1); // false</code></pre><p>但是在此基础上,使用Object.defineProperty方法,把属性变得不可配置(configurable),则这个对象也就成了密封对象:</p><pre><code>var person1 = { name: "Lucas"}Object.defineProperty(person1, "name", {configurable : false});Object.isSealed(person1); // true</code></pre><p>此时,我们有:</p><pre><code>Object.getOwnPropertyDescriptor(person1, 'name');// 得到:Object { value: "Lucas", writable: true, enumerable: true, configurable: false}</code></pre><p>根据这个getOwnPropertyDescriptor,我们可以更加深入的理解密封特性:被密封的对象,就是在不可扩展基础上讲属性描述符configurable设置为false; 同时,<strong>被密封的对象,仍然有机会改变属性的值。</strong>只不过对于此对象本身而言,<strong>不可以再扩展新的属性,不可以更改已有属性的配置信息。</strong></p><h3 id="Object-seal"><a href="#Object-seal" class="headerlink" title="Object.seal"></a>Object.seal</h3><p>相对应我们也有一个方法将一个对象密封。</p><p>MDN上内容概述:</p><pre><code>概述 Object.seal() 方法可以让一个对象密封,并返回被密封后的对象。语法 Object.seal(obj)参数 obj 将要被密封的对象</code></pre><p>比如:</p><pre><code>var person1 = { name: "Lucas"}Object.getOwnPropertyDescriptor(person1, 'name');// 得到:Object { value: "Lucas", writable: true, enumerable: true, configurable: true}</code></pre><p>将此对象密封后:</p><pre><code>Object.seal(person1);Object.getOwnPropertyDescriptor(person1, 'name');// 得到:Object { value: "Lucas", writable: true, enumerable: true, configurable: false}</code></pre><p>也就是说:</p><pre><code>person1.age = 18;person1.age; // undefined// 扩展新属性失败// 同时调用defineProperty失败Object.defineProperty(person1,"name",{get : function(){return "g";}});// 抛出异常</code></pre><p>任何除更改属性值以外的操作,非严格模式下都会静默失败,如上并如下:</p><pre><code>delete person1.name;person1.name; // "Lucas"</code></pre><p>而更改属性值可以成功:</p><pre><code>person1.name = "Eros";person1.name; // "Eros"</code></pre><p>怎么理解这样的现象呢?牢记,被密封的对象拥有如下的属性描述符:</p><pre><code>Object { value: "Lucas", writable: true, enumerable: true, configurable: false}</code></pre><p><strong>而删除属性属于configurable,更改属性才属于writable;</strong></p><h3 id="一点延伸"><a href="#一点延伸" class="headerlink" title="一点延伸"></a>一点延伸</h3><p>借助于此,我们其实已经可以完成冻结对象的第三重境界:达到即密封又不可修改原属性值。因为可以这样做:</p><pre><code>var person1 = {name: "Lucas"};Object.defineProperty(person1, "name", {configurable: false, writable: false});Object.preventExtensions(o);</code></pre><p>总结下就是设置:</p><blockquote><p>configurable: false + writable: false + preventExtensions</p></blockquote><p>或者因为</p><blockquote><p>configurable: false+ preventExtensions = seal</p></blockquote><p>所以也可以设置:</p><blockquote><p>seal + writable: false</p></blockquote><h2 id="众里寻他千百度,蓦然回首,那人却在,灯火阑珊处"><a href="#众里寻他千百度,蓦然回首,那人却在,灯火阑珊处" class="headerlink" title="众里寻他千百度,蓦然回首,那人却在,灯火阑珊处"></a>众里寻他千百度,蓦然回首,那人却在,灯火阑珊处</h2><p>第三种境界:“众里寻他千百度,蓦然回首,那人却在,灯火阑珊处。”<br>这是引用南宋辛弃疾《青玉案》词中的最后四句。梁启超称此词“自怜幽独,伤心人别有怀抱”。这是借词喻事,与文学赏析已无交涉。王国维已先自表明,“吾人可以无劳纠葛”。他以此词最后的四句为“境界”之第三,即最终最高境界。</p><p>这虽不是辛弃疾的原意,但也可以引出悠悠的远意:做学问、成大事业者,要达到第三境界,必须有专注的精神。反复追寻、研究,下足功夫,自然会豁然贯通,有所发现,有所发明,就能够从必然王国进入自由王国。</p><p>上边那种冻结对象的方法,其实也有原生实现,可谓:“众里寻他千百度,蓦然回首,那人却在,灯火阑珊处”</p><p>我们这里所说的一个对象的冻结(frozen)是指它不可扩展,所有属性都是不可配置的(non-configurable),且所有数据属性(data properties)都是不可写的(non-writable)。</p><p>或者说,冻结对象是指那些不能添加新的属性,不能修改已有属性的值,不能删除已有属性,以及不能修改已有属性的可枚举性、可配置性、可写性的对象。也就是说,这个对象永远是不可变的。</p><p>同样,包含了两个基本方法:</p><ul><li>Object.isFrozen</li><li>Object.freeze</li></ul><h3 id="Object-isFrozen"><a href="#Object-isFrozen" class="headerlink" title="Object.isFrozen"></a>Object.isFrozen</h3><p>MDN上内容概述:</p><pre><code>概述 Object.isFrozen() 方法判断一个对象是否被冻结(frozen)。语法 Object.isFrozen(obj)参数 obj 被检测的对象</code></pre><h3 id="Object-freeze-方法"><a href="#Object-freeze-方法" class="headerlink" title="Object.freeze 方法"></a>Object.freeze 方法</h3><p>MDN上内容概述:</p><pre><code>概述 Object.freeze() 方法可以冻结一个对象。语法 Object.freeze(obj)参数 obj 将要被冻结的对象</code></pre><p>可以先理解为,这是最高一层的冻结对象:</p><pre><code>var person1 = { name: "Lucas"}Object.freeze(person1);</code></pre><p>此时,我们有:</p><pre><code>Object.getOwnPropertyDescriptor(person1, 'name')Object { value: "Lucas", writable: false, enumerable: true, configurable: false}// 对冻结对象的任何操作都会失败person1.name = "Eros"; // 改写属性值,非严格模式下静默失败;person1.age = 18; // 扩展属性值,非严格模式下静默失败;Object.defineProperty(person1,"name",{value: "Eros"}); // 使用defineProperty会直接报错</code></pre><p>改写属性值,扩展新属性,调用defineProperty,全部都会失败。</p><p>但是,<strong>这种层面的冻结,只是浅冻结。</strong>如果对象里面还嵌套有对象,那么这个内部对象丝毫不受影响。</p><pre><code>var person1 = { name: "Lucas", family: { brother: "Eros" }}Object.freeze(person1);person1.family.brother = "Tim";person1.family.brother // "Tim"</code></pre><h3 id="终极实现"><a href="#终极实现" class="headerlink" title="终极实现"></a>终极实现</h3><p>那么,如果我们想深层次冻结一个对象呢?思路和深拷贝暗合,使用递归:</p><pre><code>Object.prototype.deepFreeze = Object.prototype.deepFreeze || function (o){ var prop, propKey; Object.freeze(o); // 首先冻结第一层对象 for (propKey in o){ prop = o[propKey]; if(!o.hasOwnProperty(propKey) || !(typeof prop === "object") || Object.isFrozen(prop)){ continue; } deepFreeze(prop); // 递归 }}</code></pre><p>这样子,我们再回过头来看:</p><pre><code>var person1 = { name: "Lucas", family: { brother: "Eros" }}Object.deepFreeze(person1);person1.family.brother = "Tim";person1.family.brother // "Eros"</code></pre><p>已经达到了深层次对象属性的冻结。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文先后介绍了关于冻结一个对象的三种进阶方法。他们层层递进,却又相互关联。关系如图:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-55750ea1c4e62989.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="关系图"></p><p>文章部分概念粘取了MDN语法介绍和<a href="https://segmentfault.com/a/1190000003894119" target="_blank" rel="external">Tomson的文章。</a></p><p>在《文学小言》一文中,王国维把上述三境界说成“三种之阶级”。并说:“未有不阅第一第二阶级而能遽跻第三阶级者,文学亦然。此有文学上之天才者,所以又需莫大之修养也。”</p><p>与大家共勉。</p><p>Happy coding!</p><p>PS: 作者<a href="https://github.com/HOUCe" target="_blank" rel="external">Github仓库</a>,欢迎通过代码各种形式交流。</p>]]></content>
<summary type="html">
<p>王国维在《人间词话》里谈到了治学经验,他说:“古今之成大事业、大学问者,必经过三种之境界。”</p>
<p>巧合的是,最近受 <a href="http://gitbook.cn/" target="_blank" rel="external">git chat / git
</summary>
<category term="js" scheme="https://exp-team.github.io/categories/js/"/>
<category term="js" scheme="https://exp-team.github.io/tags/js/"/>
</entry>
<entry>
<title>通过React Ref细小知识点 谈谈前端工程师的进阶</title>
<link href="https://exp-team.github.io/blog/2017/06/04/js/react-ref/"/>
<id>https://exp-team.github.io/blog/2017/06/04/js/react-ref/</id>
<published>2017-06-03T16:00:00.000Z</published>
<updated>2017-06-01T09:31:51.000Z</updated>
<content type="html"><![CDATA[<p>熟悉React的同学可能对其中的Ref并不陌生。不知道您是否听说”最新的React版本(v15.5.4)已经对这个API进行了修改并更新”。如果您一直保持对React框架的跟进与学习,相信会很清楚,<strong>新版本中”ref改为使用回调函数的方式去引用”。</strong></p><p>这是一个非常细小但是微妙的改动。当我得知时,第一个念头就是<strong> “这样的改动意义在哪里?”</strong></p><p>带着这个疑问,下班后我花了一晚上近2个小时时间去探究这一个微小改动,收获颇丰的同时,可谓”眼界大开”。</p><p>这篇文章我会记录一步一步探究的过程,以及总结分析最终答案。同时,对react框架及其周边技术栈感兴趣的同学可以参阅斧正我其他文章:</p><ul><li><a href="http://www.jianshu.com/p/7a56ac1de2a8" target="_blank" rel="external">解析Twitter前端架构 学习复杂场景数据设计</a></li><li><a href="http://www.jianshu.com/p/83c86dd0802d" target="_blank" rel="external">React Conf 2017 干货总结1: React + ES next = ♥</a></li><li><a href="http://www.jianshu.com/p/cde3cf7e2760" target="_blank" rel="external">React+Redux打造“NEWS EARLY”单页应用 一个项目理解最前沿技术栈真谛</a></li><li><a href="http://www.jianshu.com/p/8e28be0e7ab1" target="_blank" rel="external">一个react+redux工程实例</a></li><li>……</li></ul><h2 id="古来青史谁不见-今见功名胜古人"><a href="#古来青史谁不见-今见功名胜古人" class="headerlink" title="古来青史谁不见,今见功名胜古人"></a>古来青史谁不见,今见功名胜古人</h2><p>在对Ref使用方式的改动分析前,我们先要对其有一个全面的了解和认识。当然,最直接最有效的方式就是去<a href="https://facebook.github.io/react/docs/refs-and-the-dom.html" target="_blank" rel="external">官网</a>学习。</p><p>因为React本身更迭迅速,关于Ref的文档信息也经历过多次修正。官网上保持着最新版的输出,正所谓”古来青史谁不见,今见功名胜古人”。</p><p>这里先总结一下基本概念。</p><p><em>*</em> Refs诞生背景</p><p>我们都知道React讲究的是<strong>单项数据流</strong>,也就是说父组件与其子组件之间的通信依靠props来实现。如果你想通过父组件去更改一个子组件,那么就要从父组件处传递一个新的props值,已达到让子组件re-render的目的。</p><p>但是在一些情况下,这样严格略显教条的数据流或通信方式并不能满足我们需求。这时候,就是Ref派上用场的时候了。</p><p>那么我们在具体哪些场景下会用到Ref呢?</p><ul><li>处理输入框聚焦,文本选择,或其他多媒体反馈信息时;</li><li>触发需要的动画时;</li><li>与第三方操作DOM的类库交互时。</li></ul><p><strong>其他情况下,为了避免破坏React的哲学思想,都是不被建议使用的。</strong> </p><p>比如,一个弹框组件,我们不应该暴露类似 open(), close()这样的方法,以通过ref控制来调用。更合理的做法是通过一个类似 isOpen的prop去在组件之间传递协调。</p><h3 id="Refs使用方法"><a href="#Refs使用方法" class="headerlink" title="Refs使用方法"></a>Refs使用方法</h3><p>正如先前所说,ref属性目前接收一个回掉函数。<strong>当相应组件mounten或者unmounted时,该回掉函数会被立即调用</strong></p><p>比如,我们想让一个输入框组件在渲染后,自动聚焦,可以:</p><p>上述内容是在组件内引用Ref, 同样,类似父子组件通过props通信,我们可以完成<strong>父组件通过Ref控制子组件行为</strong>:</p><p>更多的用法和注意事项不再过多介绍,这并非此文主题,读者可以通过官网学到所有的内容。</p><h2 id="问渠哪得清如许?为有源头活水来。"><a href="#问渠哪得清如许?为有源头活水来。" class="headerlink" title="问渠哪得清如许?为有源头活水来。"></a>问渠哪得清如许?为有源头活水来。</h2><p>老版本关于Ref的用法远没有这么复杂。Ref完全可以通过字符串来定义。同样的功能,我们可以这样实现:</p><p>回到我们的探索之路,初期我是很难理解这样的变动有什么好处?并且更让我困惑的是,官方的一段说法:</p><blockquote><p>If you worked with React before, you might be familiar with an older API where the ref attribute is a string. <strong>We advise against it because string refs are considered legacy, and are likely to be removed in one of the future releases</strong>. Although string refs are not deprecated, they are considered legacy, and will likely be deprecated at some point in the future. Callback refs are preferred.</p></blockquote><p>官方认为之前的”字符串模式”是一种”反模式” ,在未来版本中很有可能会被废除。强烈向大家推荐了回掉函数式的用法。</p><p>带着疑惑,我Google了此问题(原谅我搜商较低):</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-00ebd947dadb6beb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="搜索内容"></p><p>不要吐槽我”连句英文都说不清楚”,因为我认为这样的关键字搜索方式更有利于找到有价值的内容。(您真当您跟搜索引擎对话呢?)</p><p>不出所料,搜索结果很多且杂乱。<br>第一条结果属于React官网,下面便有Stack Overflow的相关信息。<br>第一条:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-b7d617b5b0016416.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="类似问题"></p><p>此提问者还加了一句:</p><blockquote><p>NB: I’m looking for the “official” answer to the statement in the documentation, I’m not asking about personal preferences and so on.</p></blockquote><p>最高分的答案贡献了一下内容:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-b31dafe4c9fe8d8e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="问题答案"></p><p><a href="https://stackoverflow.com/questions/30710076/react-saving-a-component-in-the-ref-callback" target="_blank" rel="external">此答案</a>列举了7条老版本用法的问题。其实我在工作当中,并不是用React技术栈,所有知识储备以理论为主。所以,其中的很多条我并不能完全赞同。</p><p>并且答案也没有具体的代码示例,虽然没一条目英语层面上一清二白,但是具体在说什么某些条目上并没有完全理解。</p><p>先存起来,后边慢慢消化尝试理解,继续寻找答案。 <strong>变换策略,我直接去React Github仓库寻找答案。</strong>果然,里面非常多详尽的干货。</p><p>我在<a href="https://github.com/facebook/react/pull/8333" target="_blank" rel="external">第8333号Pull Requests中</a>,找到了Redux原作者,现在已经加入Facebook React团队的Dan Abramov的”官方回答”:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-292bb281019f1405.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="Dan Abramov"></p><p>解答内容为 (意译非直译):</p><ul><li>老版本基于字符串的Ref会追踪其组件,因为不利于React的运行速度和性能保证;</li><li>Ref在深层次的组件中,难以满足使用者对其的调用需求;</li><li>老版本基于字符串的Ref并不像新版本采用回掉方式带来的可组合(Composable)益处。</li></ul><p>虽然是男神解惑,可是反应迟钝智商拙急的我还是很难彻底理解。</p><p>难道就这样睡去吗?下班回家后已经持续研究了一个小时了,女朋友貌似在下一秒就要爆发。。。</p><p><strong>我当然是不甘心的,追求答案在此刻优先级已经溢出!</strong></p><p>紧接着,我翻到了Facebook React团队成员Sebastian Markbåge早在2014年的<a href="https://github.com/facebook/react/issues/1373" target="_blank" rel="external">对Ref的 “吐槽”</a>,此issues一共多达30多个comments, 延续到了2015年。并最终在2014年底,对Ref的改进写入了react-future,具体信息 <a href="https://github.com/reactjs/react-future/blob/master/01%20-%20Core/06%20-%20Refs.js" target="_blank" rel="external">点击这里可以追溯</a>,当时的代码注释信息写的非常清晰:</p><blockquote><p>This is a refs proposal that uses callbacks instead of strings. The callback can be easily statically typed and solves most use cases.<br>When a ref is attached, it gets passed the instance as the first argument.<br>When a ref is detached, the callback is invoked with null as the argument.</p></blockquote><p>接着,我找到了2015年9月react源码提交的<a href="https://github.com/facebook/react/commit/5ee8a93280987bf1547687f5d8665be89058f321#all_commit_comments" target="_blank" rel="external">commit信息</a>,最关键的内容提炼出来:</p><blockquote><p>Callback refs are preferred. We plan to deprecate string refs at some point in the future, but just haven’t gotten around to it yet.<br>String refs are very “magical” and is not idiomatic javascript, which is bad on principal. But there are also some practical reasons…<br>String refs could never be implemented in user space (callbacks can; just have the component call the callback). The ramifications of this are far-reaching. For instance, if you wanted to create a HOC (Higher Order Component) that transparently wraps another component, you could forward all the props… but you couldn’t forward a string ref. Or if you wanted a parent and a grandparent to both have a ref to a component, callback refs allow you to wrap the callback and pass it down, but string refs do not. The list goes on, but in the interest of time, I’ll truncate it here.</p></blockquote><p>建议读者先自己尝试去理解,下文中我会进行汉语总结。<br>同时我也看到了facebook react团队另外一名成员Jim的话:</p><blockquote><p>Basically, our team has had countless discussions on this topic and arrived at the consensus that string refs should be phased out in favor of callback refs.</p></blockquote><p>可见,关于Ref这个API设计调整的问题,源码团队也是经历了无数次讨论与争执。此时,我更加迫不及待地去挖掘体会更多信息。</p><h2 id="卷地风来忽吹散,望湖楼下水如天"><a href="#卷地风来忽吹散,望湖楼下水如天" class="headerlink" title="卷地风来忽吹散,望湖楼下水如天"></a>卷地风来忽吹散,望湖楼下水如天</h2><p>说来有趣,最终让我把众多信息”融会贯通”的契机来自于<a href="https://twitter.com/dan_abramov" target="_blank" rel="external"><strong>Dan Abramov的一条Twitter:</strong> </a></p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-df4edea15393570c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="Dan's Twitter"></p><p>通过这条Twitter以及该Twitter下的评论和回复,尤其是附带的代码截图,我瞬间理解了之前的很多信息。</p><p>先发给大家体会:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-db606be5928801bd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="代码1"></p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-ae8681234a038bf7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="代码2"></p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-9c40dbff15d1122a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="代码3"></p><p>仔细揣摩,你能体会到什么呢?</p><p>其实Dan Abramov私人还维护着一个React组件库:React DnD,这个组件库其实就是一个 <a href="https://facebook.github.io/react/docs/higher-order-components.html" target="_blank" rel="external">HOC(Higher-Order Components)</a>,浅显来说本质上,DnD对外暴露一个高阶组件,这个组件接收用户定义的组件,并通过”一系列魔法”,又返回用户传入的自定义的组件,此时这个组件可拖拽。</p><p>用最简单的代码展示,类似:</p><p>我们看代码1,dragSourceRef函数作为自定义(即需要实现拖拽的组件)组件的ref回掉函数,在React DnD组件库中,变获取了这个组件instance;</p><p>如果还是字符串方式,在React DnD组件库中只能预先定义协商好的ref值,以方便该组件库进行”魔法处理”。<br>但是如果需要进行可拖拽化的组件很多时,ref值就面临冲突的问题。<br>当然,我们可以通过一个预留的数组,在组件库和业务代码里传递refs值。但是这样的实现显然是很丑陋的。</p><p>参考 <a href="https://github.com/facebook/react/issues/8734" target="_blank" rel="external">facebook react 8734号issue</a>:</p><p><img src="http://upload-images.jianshu.io/upload_images/4363003-597c140d4d6d5613.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="facebook react 8734号issue"></p><p>这显然,已经是新Ref API变革带来的巨大好处。</p><p>同时仔细揣摩上面代码2,3截图,便是对composable的有力说明。当然,通过老版本字符串方式可以实现同样的功能(有内存管理的瑕疵,具体见下文说明),那么composable的优势,我认为可能只会体现在”先进的思想层面和灵活性层面”。</p><h2 id="纸上得来终觉浅-绝知此事要躬行"><a href="#纸上得来终觉浅-绝知此事要躬行" class="headerlink" title="纸上得来终觉浅 绝知此事要躬行"></a>纸上得来终觉浅 绝知此事要躬行</h2><p>除了上述React 仓库commit、issue、pull requests之外,我又参考了更多信息。</p><p>关于官方团队给出的优势,我认为很多条目是在向Functional Programming靠拢,更多意义上是一种思维模式的转变。</p><p>比如说,我想实现”在三层嵌套(Grandparent->Parent->child)结构中,Grandparent组件操控child组件”,这样的场景需求新老两种版本都可以实现:<br>回掉方式:</p><p>老版本字符串方式:</p><p>但是仔细对比这两种方式,在老版本中的实现,先不说我们使用了</p><pre><code>this.refs.myInput2.refs.myInput1.refs.myInput</code></pre><p>这么一长串才能拿到instance,更应该注意到为了拿到第三层的instance,我们被迫新增了两个只用于中间传递的refs。</p><p>这还没有完,前文提到过内存管理。我们再来思考一下,当child unmount时,基于字符串的ref方式无法感知。注意这都是在内存层面,即反映在虚拟DOM的开销上。他并不是真正的DOM节点,自然不会有依赖于浏览器的垃圾回收机制。</p><p>当程序中有大量类似场景时,大量unmount组件出现,就会有大量的无意义内存占用。</p><p>如果使用新的Ref API方式,我们可以在track的组件unmount时得到通知。因为:</p><blockquote><p>The ref attribute takes a callback function, and <strong>the callback will be executed immediately after the component is mounted or unmounted.</strong></p></blockquote><p>注意<strong>the callback will be executed immediately after the component is mounted or unmounted.</strong>我们只需要在child组件中进行监测,unmount后将this.input = null,即可完成手动垃圾回收:</p>]]></content>
<summary type="html">
<p>熟悉React的同学可能对其中的Ref并不陌生。不知道您是否听说”最新的React版本(v15.5.4)已经对这个API进行了修改并更新”。如果您一直保持对React框架的跟进与学习,相信会很清楚,<strong>新版本中”ref改为使用回调函数的方式去引用”。</stro
</summary>
<category term="js" scheme="https://exp-team.github.io/categories/js/"/>
<category term="js" scheme="https://exp-team.github.io/tags/js/"/>
</entry>
<entry>
<title>expfe技术周刊第11110期</title>
<link href="https://exp-team.github.io/blog/2017/05/20/weekly/weekly-11110/"/>
<id>https://exp-team.github.io/blog/2017/05/20/weekly/weekly-11110/</id>
<published>2017-05-19T16:00:00.000Z</published>
<updated>2017-11-05T02:36:55.000Z</updated>
<content type="html"><![CDATA[<p>5.15-5.20,浏览器支持ES6原生模块啦!</p><a id="more"></a><h2 id="本期推荐"><a href="#本期推荐" class="headerlink" title="本期推荐"></a>本期推荐</h2><h3 id="什么才是你心目中的前端圈"><a href="#什么才是你心目中的前端圈" class="headerlink" title="什么才是你心目中的前端圈?"></a><a href="https://www.zhihu.com/question/59758480" target="_blank" rel="external">什么才是你心目中的前端圈?</a></h3><h3 id="比较与理解React的Components,Elements和Instances"><a href="#比较与理解React的Components,Elements和Instances" class="headerlink" title="比较与理解React的Components,Elements和Instances"></a><a href="https://mp.weixin.qq.com/s?__biz=MjM5MTA1MjAxMQ==&mid=2651226492&idx=1&sn=4a20787048f4269b11548689fe73a47e&chksm=bd4958f88a3ed1eed2eccf59912268f6cd2bb13ed699b5b427e62c4f6cc8dbe1cd11fe15f58d&mpshare=1&scene=1&srcid=0515EYgoDmAu1dVKeNhAn9A8" target="_blank" rel="external">比较与理解React的Components,Elements和Instances</a></h3><h3 id="为何-ES-Module-如此姗姗来迟"><a href="#为何-ES-Module-如此姗姗来迟" class="headerlink" title="为何 ES Module 如此姗姗来迟"></a><a href="https://segmentfault.com/a/1190000004940294" target="_blank" rel="external">为何 ES Module 如此姗姗来迟</a></h3><h2 id="前端"><a href="#前端" class="headerlink" title="前端"></a>前端</h2><h3 id="WebSocket-教程"><a href="#WebSocket-教程" class="headerlink" title="WebSocket 教程"></a><a href="http://www.ruanyifeng.com/blog/2017/05/websocket.html" target="_blank" rel="external">WebSocket 教程</a></h3><h3 id="Babel-从入门到插件开发"><a href="#Babel-从入门到插件开发" class="headerlink" title="Babel 从入门到插件开发"></a><a href="http://web.jobbole.com/91277/" target="_blank" rel="external">Babel 从入门到插件开发</a></h3><h3 id="如何才能跨过前端的高级级别,譬如腾讯的t3-1-或者是阿里的p7。平时的前端知识并不足够跨过这个门槛"><a href="#如何才能跨过前端的高级级别,譬如腾讯的t3-1-或者是阿里的p7。平时的前端知识并不足够跨过这个门槛" class="headerlink" title="如何才能跨过前端的高级级别,譬如腾讯的t3-1,或者是阿里的p7。平时的前端知识并不足够跨过这个门槛?"></a><a href="https://www.zhihu.com/question/59747367" target="_blank" rel="external">如何才能跨过前端的高级级别,譬如腾讯的t3-1,或者是阿里的p7。平时的前端知识并不足够跨过这个门槛?</a></h3>]]></content>
<summary type="html">
<p>5.15-5.20,浏览器支持ES6原生模块啦!</p>
</summary>
<category term="weekly" scheme="https://exp-team.github.io/categories/weekly/"/>
<category term="weekly" scheme="https://exp-team.github.io/tags/weekly/"/>
</entry>
<entry>
<title>我们能从一场“数据设计的撕逼”中学到哪些精华</title>
<link href="https://exp-team.github.io/blog/2017/05/14/js/Converting%20an%20Array%20of%20Objects%20to%20an%20Object/"/>
<id>https://exp-team.github.io/blog/2017/05/14/js/Converting an Array of Objects to an Object/</id>
<published>2017-05-13T16:00:00.000Z</published>
<updated>2017-05-11T06:40:58.000Z</updated>
<content type="html"><![CDATA[<p>最近看了一篇Chris Burgin的<a href="https://medium.com/dailyjs/rewriting-javascript-converting-an-array-of-objects-to-an-object-ec579cafbfc7" target="_blank" rel="external">文章:Rewriting JavaScript: Converting an Array of Objects to an Object</a>,其中对比讨论了两种数据设计的不同点。并推广从“一个包含对象的数组”迁移到扁平化的“对象”存储结构。</p><p>这不,遥相呼应的是,我也在YouTube上看到了一篇类似的视频:<a href="https://www.youtube.com/watch?v=aJxcVidE0I0" target="_blank" rel="external">[Redux] The Best Way to Store Data</a></p><p>我们知道React+Redux技术栈由于其设计思路,使得前端应用并管理复杂数据越来越重要。比如,读者朋友可以参考我之前分析过的Twitter前端数据架构<a href="http://www.jianshu.com/p/7a56ac1de2a8" target="_blank" rel="external">一文:解析Twitter前端架构 学习复杂场景数据设计。</a></p><p>但是,上面提的数据设计方式到底是指的什么?当我们在说扁平化的设计时,到底在说什么?这样的设计真的“百利而无一害”吗?</p><p>别急,下文我讲细细道来。</p><h2 id="通过场景来看透设计"><a href="#通过场景来看透设计" class="headerlink" title="通过场景来看透设计"></a>通过场景来看透设计</h2><p>假设我们需要维护一个婚恋交友用户列表,包含了所有注册用户。每个用户有唯一id作为辨识,同时含有用户名和年龄信息。<br>一个很常见的做法是:</p><pre><code>const userArray = [ { id: 123, name: 'LucasHC', age: 18 }, { id: 456, name: 'maxiao', age: 38 }, { id: 789, name: 'chengwen', age: 22 }, { id: 101, name: 'yanhaijing', age: 28 }, { id: 102, name: 'zhaowenlin', age: 8 }]</code></pre><p>这样的储存设计方式非常的简单,也符合逻辑。</p><p>但是,如果我们想选出id为101用户,那就必须要遍历这个数组。通常的做法包括但不限于:</p><pre><code>let idToSelect = 101;let selectedUser;for (let user of userArray) { if (user.id === idToSelect) { selectedUser = user; // ... break; }}</code></pre><p>我们使用了ES6当中的for…of方法。<br>当然除此之外,使用数组的filter方法、find方法等等都是可行的。比如:</p><pre><code>const selectedUser = userArray.find(user => user.id === idToSelect)</code></pre><p>但是不可避免的就是需要遍历这个数组。<br>想象一下如果这个用户数组比较大,参加婚恋交友的用户很多,性能上先不说,这样的更新用户信息方式也是比较复杂的。</p><p>值得注意的是,Chris Burgin的原文当中,观点还有</p><blockquote><p>This is fairly simple and will traverse the array and assign the correct person to selectedPerson. This presents many problems though, mainly it introduces mutation into your code. Scary!</p></blockquote><p>不过,据我理解,上述的操作方式如果非要这么理解所谓的“mutation”的话,Chris Burgin可能想表达user赋值给selectedUser属于引用赋值,确实会存在“共享”的问题。但是他提供的方法(见下文),也同样没有规避这样的问题。</p><p>当然,不止我注意了这样的细节。原文下面的评论,也有很多读者留言表示:</p><blockquote><p>I don’t see a mutation here? </p><p>I don’t understand how it mutates the array. Does this portion “let person of peopleArray” somehow change the array?</p></blockquote><p>看到这里,如果您有自己对于“mutation”的看法,也欢迎和我交流。</p><h2 id="更好的方式"><a href="#更好的方式" class="headerlink" title="更好的方式"></a>更好的方式</h2><p>更理想的方式是把刚才的userArray数组转换为对象来存储。就像下面所做的这样:</p><pre><code>const userObject = { "123": { id: 123, name: "LucasHC", age: 18 }, "456": { id: 456, name: "maxiao", age: 38 }, "789": { id: 789, name: "chengwen", age: 22 }, "101": { id: 101, name: "yanhaijing", age: 28 }, "102": { id: 102, name: "zhaowenlin", age: 8 }}</code></pre><p>这种方式下,我们想要选出id为101用户的操作更简单了:</p><pre><code>const idToSelect = "101";const selectedUser = userObject[idToSelect];// ...</code></pre><p>只需要按照唯一id便可达到我们的目的。完全不再需要遍历一个数组!</p><p>这种方式更加简洁清晰。按照Chris Burgin的说法,同样还可以“removes mutation”。当然,我对此持保留态度。</p><h2 id="数据转换"><a href="#数据转换" class="headerlink" title="数据转换"></a>数据转换</h2><p>当然,在某些场景下就如上文所讲,数组转换为对象来存储数据是一种极好的方式。可是有时候我们并没有设计数据的主动权。比如从后端返回的数据就是</p><pre><code> const userArray = [ { id: 123, name: 'LucasHC', age: 18 }, { id: 456, name: 'maxiao', age: 38 }, { id: 789, name: 'chengwen', age: 22 }, { id: 101, name: 'yanhaijing', age: 28 }, { id: 102, name: 'zhaowenlin', age: 8 }]</code></pre><p>这种形式。这就需要我们进行手动转换。其中一种JS原生转换方式为:</p><pre><code>const arrayToObject = (array) => array.reduce((obj, item) => { obj[item.id] = item; return obj }, {})const idToSelect = "101";const userObject = arrayToObject(userArray);console.log(userObject[idToSelect]);</code></pre><p>关于reduce函数的用法,如果您还不熟悉,建议去补一补课。这个极具函数式风情的API绝对值得学习。</p><p>当然我们可以将转换函数做的更加通用一些:</p><pre><code>const arrayToObject = (array, keyField) => array.reduce((obj, item) => { obj[item[keyField]] = item;return obj }, {})</code></pre><p>我们接受keyField参数作为提取因子。</p><p>当然,我们还可以换一种姿势进行:</p><pre><code>const arrayToObject = (arr, keyField) => Object.assign({}, ...arr.map(item => ({[item[keyField]]: item})))</code></pre><p>这些都能完全达到我们的目的。</p><h2 id="一些思考"><a href="#一些思考" class="headerlink" title="一些思考"></a>一些思考</h2><p>实现了我们所有想要的结果,但是我们还应该进行更进一步的思考。<br>关于两种方式的比较,我们思考这么一个问题:如果场景需求我们在遍历数据时保证顺序性,或者存储的数据有严格的顺序要求。这时候也许使用数组是一种更加可靠的方法。</p><p>另外,我们实现了arrayToObject,但是有时候arrayToMap,使用Map结构来存取数据也许更加必要。ES6为我们提供了Map数据结构。它是一个”value-value”的对应。关于使用Object还是Map,这两种数据结构的比较,我给大家推荐<a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map#Objects_and_maps_compared" target="_blank" rel="external">这里,进行了解</a>,毕竟某种情况下,Map的取值相比Object还是有优势的。</p><p>另外,关于arrayToMap的实现,也很简单:</p><pre><code>const arrayToMap = (array, keyField) => new Map( array.map((item) => [ item[keyField], item ]))</code></pre><p>最后,关于广受争议的”Mutation”之争,我也给出我的想法。Chris Burgin的前后两种实现,我认为是否存在Mutation.关键在于:取出元素之后,接下来会如何操作去更新userObject/userArray.<br>拿update data举例,如下:</p><pre><code>const userObject = { "123": { id: 123, name: "LucasHC", age: 18 }, "456": { id: 456, name: "maxiao", age: 38 }, "789": { id: 789, name: "chengwen", age: 22 }, "101": { id: 101, name: "yanhaijing", age: 28 }, "102": { id: 102, name: "zhaowenlin", age: 8 }}const idToSelect = "101";const selectedUser = userObject[idToSelect];selectedUser.name = "xulinfeng";const userObject2 = Object.assign({}, userObject, { "101": selectedUser});</code></pre><p>这种更改,我们得到更新后的userObject2同时,也更改了原有的userObject;</p><p>但是为了演示,我们如果嵌套两层Object.assign,那么就不会有类似问题。</p><pre><code>const userObject = { "123": { id: 123, name: "LucasHC", age: 18 }, "456": { id: 456, name: "maxiao", age: 38 }, "789": { id: 789, name: "chengwen", age: 22 }, "101": { id: 101, name: "yanhaijing", age: 28 }, "102": { id: 102, name: "zhaowenlin", age: 8 }}const idToSelect = "101";const selectedUser = userObject[idToSelect];const userObject2 = Object.assign({}, userObject, { "101": Object.assign({}, selectedUser, {name: "xulinfeng"})});</code></pre><p>当然,存在更优雅的方法,比如使用对象的解构去实现。</p><p>最后,关于数据扁平化的设计上,大家可以关注<a href="https://github.com/paularmstrong/normalizr" target="_blank" rel="external">normalizr类库</a></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>我想,由Chris Burgin的文章,学习到的知识不少;更重要的是由此引出的思考。希望这篇文章对于大家在前端数据存储的设计上、Javascript语言特性上、甚至函数式编程思想上等都有不同程度的启发。<br>欢迎与我讨论。</p><p>Happy Coding!</p><p>PS: 作者<a href="https://github.com/HOUCe" target="_blank" rel="external">Github仓库</a>,欢迎通过代码各种形式交流。</p>]]></content>
<summary type="html">
<p>最近看了一篇Chris Burgin的<a href="https://medium.com/dailyjs/rewriting-javascript-converting-an-array-of-objects-to-an-object-ec579cafbfc7" tar
</summary>
<category term="js" scheme="https://exp-team.github.io/categories/js/"/>
<category term="js" scheme="https://exp-team.github.io/tags/js/"/>
</entry>
<entry>
<title>expfe技术周刊第11101期</title>
<link href="https://exp-team.github.io/blog/2017/05/12/weekly/weekly-11101/"/>
<id>https://exp-team.github.io/blog/2017/05/12/weekly/weekly-11101/</id>
<published>2017-05-11T16:00:00.000Z</published>
<updated>2017-11-05T02:36:55.000Z</updated>
<content type="html"><![CDATA[<p>5.8-5.12,你的英语水平制约你的学习了吗?</p><a id="more"></a><h2 id="本期推荐"><a href="#本期推荐" class="headerlink" title="本期推荐"></a>本期推荐</h2><h3 id="程序员拿什么来学英语"><a href="#程序员拿什么来学英语" class="headerlink" title="程序员拿什么来学英语"></a><a href="http://www.jianshu.com/p/5c3f19c78f25" target="_blank" rel="external">程序员拿什么来学英语</a></h3><h3 id="老生常谈-从输入url到页面展示到底发生了什么"><a href="#老生常谈-从输入url到页面展示到底发生了什么" class="headerlink" title="老生常谈-从输入url到页面展示到底发生了什么"></a><a href="http://www.cnblogs.com/xianyulaodi/p/6547807.html" target="_blank" rel="external">老生常谈-从输入url到页面展示到底发生了什么</a></h3><h3 id="在-2017-年学习-React-Redux-的一些建议(上篇)"><a href="#在-2017-年学习-React-Redux-的一些建议(上篇)" class="headerlink" title="在 2017 年学习 React + Redux 的一些建议(上篇)"></a><a href="https://github.com/iuap-design/blog/issues/178" target="_blank" rel="external">在 2017 年学习 React + Redux 的一些建议(上篇)</a></h3><h3 id="CSS"><a href="#CSS" class="headerlink" title="CSS"></a>CSS</h3><h3 id="CSS-变量教程"><a href="#CSS-变量教程" class="headerlink" title="CSS 变量教程"></a><a href="http://www.ruanyifeng.com/blog/2017/05/css-variables.html" target="_blank" rel="external">CSS 变量教程</a></h3><h3 id="JavaScript"><a href="#JavaScript" class="headerlink" title="JavaScript"></a>JavaScript</h3><h3 id="怎么用目前最好的工具来调试Node-js"><a href="#怎么用目前最好的工具来调试Node-js" class="headerlink" title="怎么用目前最好的工具来调试Node.js"></a><a href="http://zcfy.cc/article/how-to-debug-node-js-with-the-best-tools-available-risingstack-2710.html" target="_blank" rel="external">怎么用目前最好的工具来调试Node.js</a></h3><h3 id="构建-React-js-应用的十佳-UI-框架,都在这了!"><a href="#构建-React-js-应用的十佳-UI-框架,都在这了!" class="headerlink" title="构建 React.js 应用的十佳 UI 框架,都在这了!"></a><a href="https://my.oschina.net/editorial-story/blog/897707" target="_blank" rel="external">构建 React.js 应用的十佳 UI 框架,都在这了!</a></h3><h3 id="React-高阶组件-HOC-入门指南"><a href="#React-高阶组件-HOC-入门指南" class="headerlink" title="React 高阶组件(HOC)入门指南"></a><a href="https://juejin.im/post/5914fb4a0ce4630069d1f3f6" target="_blank" rel="external">React 高阶组件(HOC)入门指南</a></h3><h3 id="ES6-模块原生支持在浏览器中落地,是时候该重新考虑打包了吗?"><a href="#ES6-模块原生支持在浏览器中落地,是时候该重新考虑打包了吗?" class="headerlink" title="ES6 模块原生支持在浏览器中落地,是时候该重新考虑打包了吗?"></a><a href="https://juejin.im/post/590a990a5c497d005852cf61" target="_blank" rel="external">ES6 模块原生支持在浏览器中落地,是时候该重新考虑打包了吗?</a></h3><h3 id="javascript中那些折磨人的面试题"><a href="#javascript中那些折磨人的面试题" class="headerlink" title="javascript中那些折磨人的面试题"></a><a href="https://segmentfault.com/a/1190000006129337" target="_blank" rel="external">javascript中那些折磨人的面试题</a></h3>]]></content>
<summary type="html">
<p>5.8-5.12,你的英语水平制约你的学习了吗?</p>
</summary>
<category term="weekly" scheme="https://exp-team.github.io/categories/weekly/"/>
<category term="weekly" scheme="https://exp-team.github.io/tags/weekly/"/>
</entry>
<entry>
<title>expfe技术周刊第11100期</title>
<link href="https://exp-team.github.io/blog/2017/05/05/weekly/weekly-11100/"/>
<id>https://exp-team.github.io/blog/2017/05/05/weekly/weekly-11100/</id>
<published>2017-05-04T16:00:00.000Z</published>
<updated>2017-11-05T02:36:55.000Z</updated>
<content type="html"><![CDATA[<p>5.1-5.5,你没学好,不只是败给了时间</p><a id="more"></a><h2 id="本期推荐"><a href="#本期推荐" class="headerlink" title="本期推荐"></a>本期推荐</h2><h3 id="HTTP-HTTP2-0-SPDY-HTTPS看这篇就够了"><a href="#HTTP-HTTP2-0-SPDY-HTTPS看这篇就够了" class="headerlink" title="HTTP,HTTP2.0,SPDY,HTTPS看这篇就够了"></a><a href="http://m.sanwen8.cn/p/3edfj7S.html" target="_blank" rel="external">HTTP,HTTP2.0,SPDY,HTTPS看这篇就够了</a></h3><h3 id="你没学好,不只是败给了时间"><a href="#你没学好,不只是败给了时间" class="headerlink" title="你没学好,不只是败给了时间"></a><a href="https://mp.weixin.qq.com/s?__biz=MjM5ODQ2MDIyMA==&mid=2650713198&idx=1&sn=c59420502eeedcb1c1c28c4ecebe9799&chksm=bec0623d89b7eb2b5b0efe26597f7aa186e44fcbbeba09343ba81f5e061d2b8286794b2dc866&mpshare=1&scene=1&srcid=0427JqH0mUhXFREOJQRjCH9n#rd" target="_blank" rel="external">你没学好,不只是败给了时间</a></h3><h3 id="我是这样学习前端的"><a href="#我是这样学习前端的" class="headerlink" title="我是这样学习前端的"></a><a href="https://github.com/icepy/we-writing/issues/39" target="_blank" rel="external">我是这样学习前端的</a></h3><h2 id="Web"><a href="#Web" class="headerlink" title="Web"></a>Web</h2><h3 id="React-Conf-2017-干货总结1-React-ES-next-♥"><a href="#React-Conf-2017-干货总结1-React-ES-next-♥" class="headerlink" title="React Conf 2017 干货总结1: React + ES next = ♥"></a><a href="http://www.jianshu.com/p/83c86dd0802d" target="_blank" rel="external">React Conf 2017 干货总结1: React + ES next = ♥</a></h3><h3 id="函数式编程入门教程"><a href="#函数式编程入门教程" class="headerlink" title="函数式编程入门教程"></a><a href="http://www.ruanyifeng.com/blog/2017/02/fp-tutorial.html" target="_blank" rel="external">函数式编程入门教程</a></h3><h3 id="百度靠它回霸主地位?MIP、PWA两大招改变搜索"><a href="#百度靠它回霸主地位?MIP、PWA两大招改变搜索" class="headerlink" title="百度靠它回霸主地位?MIP、PWA两大招改变搜索"></a><a href="https://mp.weixin.qq.com/s?__biz=MzI2NzEzNDg2Mg==&mid=2650195907&idx=1&sn=cd8a699951b0ce31b828226fc03cf18c&chksm=f2811522c5f69c348ea4bf6ab86bbd3a46fd2cf37f79a054cd9f1e6bbe06b094636c2d3ea776&mpshare=1&scene=1&srcid=0426qjKqiI5HJunPHMGNoDtc#rd" target="_blank" rel="external">百度靠它回霸主地位?MIP、PWA两大招改变搜索</a></h3><h3 id="通过-PWA-我们让新用户转化率提升了-104"><a href="#通过-PWA-我们让新用户转化率提升了-104" class="headerlink" title="通过 PWA 我们让新用户转化率提升了 104%"></a><a href="https://zhuanlan.zhihu.com/p/26445223" target="_blank" rel="external">通过 PWA 我们让新用户转化率提升了 104%</a></h3><h3 id="单页式应用性能优化-首屏数据渐进式预加载"><a href="#单页式应用性能优化-首屏数据渐进式预加载" class="headerlink" title="单页式应用性能优化 - 首屏数据渐进式预加载"></a><a href="https://juejin.im/entry/58ff0ea78d6d810058a6a3c5" target="_blank" rel="external">单页式应用性能优化 - 首屏数据渐进式预加载</a></h3><h3 id="高性能滚动-scroll-及页面渲染优化"><a href="#高性能滚动-scroll-及页面渲染优化" class="headerlink" title="高性能滚动 scroll 及页面渲染优化"></a><a href="http://web.jobbole.com/86158/" target="_blank" rel="external">高性能滚动 scroll 及页面渲染优化</a></h3><h3 id="如何用-JavaScript-实现真正的私有属性"><a href="#如何用-JavaScript-实现真正的私有属性" class="headerlink" title="如何用 JavaScript 实现真正的私有属性"></a><a href="https://mp.weixin.qq.com/s?__biz=MzA4NjE3MDg4OQ==&mid=2650964432&idx=1&sn=fcc4e6b1e3b82f98c309bbd52e9d46ee&chksm=843aedb6b34d64a0c32a53ad8ba877385d6edb78cbda73454755dd4d7b505e8f3aae04f9a541&mpshare=1&scene=1&srcid=0418PcXD5JXFklIavqSXvGEQ" target="_blank" rel="external">如何用 JavaScript 实现真正的私有属性</a></h3><h3 id="为什么你统计-PV-的方式是错的?"><a href="#为什么你统计-PV-的方式是错的?" class="headerlink" title="为什么你统计 PV 的方式是错的?"></a><a href="https://zhuanlan.zhihu.com/p/26341409" target="_blank" rel="external">为什么你统计 PV 的方式是错的?</a></h3><h2 id="Other"><a href="#Other" class="headerlink" title="Other"></a>Other</h2><h3 id="我为什么不在乎人工智能"><a href="#我为什么不在乎人工智能" class="headerlink" title="我为什么不在乎人工智能"></a><a href="http://www.yinwang.org/blog-cn/2017/04/23/ai" target="_blank" rel="external">我为什么不在乎人工智能</a></h3>]]></content>
<summary type="html">
<p>5.1-5.5,你没学好,不只是败给了时间</p>
</summary>
<category term="weekly" scheme="https://exp-team.github.io/categories/weekly/"/>
<category term="weekly" scheme="https://exp-team.github.io/tags/weekly/"/>
</entry>
</feed>