-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
498 lines (288 loc) · 168 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>SPA 路由原理及 react-router 浅析</title>
<link href="/2018/11/02/SPA-%E8%B7%AF%E7%94%B1%E5%8E%9F%E7%90%86%E5%8F%8A-react-router-%E6%B5%85%E6%9E%90/"/>
<url>/2018/11/02/SPA-%E8%B7%AF%E7%94%B1%E5%8E%9F%E7%90%86%E5%8F%8A-react-router-%E6%B5%85%E6%9E%90/</url>
<content type="html"><![CDATA[<blockquote><p>对于单页应用来说,如果没有路由系统,就会出现进行多次跳转后由于 url 未发生变化,页面刷新后又回到最初始的状态,用户体验很不友好;并且由于缺少路由,对网站的 SEO 也很不友好。</p></blockquote><h1 id="SPA-路由原理"><a href="#SPA-路由原理" class="headerlink" title="SPA 路由原理"></a>SPA 路由原理</h1><h2 id="Hash-路由原理及简单实现"><a href="#Hash-路由原理及简单实现" class="headerlink" title="Hash 路由原理及简单实现"></a>Hash 路由原理及简单实现</h2><p>url 上的 hash 以 # 开头,主要作为锚点来使用,方便页面定位到相应位置。每次 # 后内容改变不会引起页面刷新,而且每次变化都会触发 hashChange 函数,可以利用这个特性来实现一个简单的 SPA hash 路由。</p><a id="more"></a><pre><code class="js">class Router { constructor() { this.routes = {} // 保存每个路由对应的回调函数 this.currentUrl = '' // 当前路由 } route(path, callback) { this.routes[path] = callback || function() {} } updateView() { this.currentUrl = location.hash.slice(1) || '/home' this.routes[this.currentUrl] && this.routes[this.currentUrl]() } init() { window.addEventListener('load', this.updateView.bind(this), false) // 页面 onload 后,刷新页面 window.addEventListener('hashchange', this.updateView.bind(this), false) // 将 updateView 设置为 hashchange 的回调函数,每次 hash 变化都触发页面刷新 }}</code></pre><p><a href="https://codepen.io/pororo/pen/YOXwWZ?editors=1000" target="_blank" rel="noopener">Hash 简单实现 demo</a></p><h2 id="history-路由原理及简单实现"><a href="#history-路由原理及简单实现" class="headerlink" title="history 路由原理及简单实现"></a>history 路由原理及简单实现</h2><p>History 路由是基于 HTML5 规范,在 HTML5 规范中提供了 <code>history.pushState</code> 和 <code>history.replaceState</code> 来进行路由控制。</p><p>执行 <code>history.pushState({}, null, '/about')</code> <code>history.replaceState({}, null, '/about')</code>时,虽然页面 url 改变,但是不会刷新页面。</p><p>参数说明:</p><p>state:存储 JSON 字符串,可以用在 popstate 事件中</p><p>title:现在大多浏览器忽略这个参数,直接用 null 代替</p><p>url:任意有效的 URL,用于更新浏览器的地址栏</p><p>hash 的改变会触发 onhashchange 事件,但是 history 的情况比较复杂。</p><p>所以,需要知道可能触发 url 改变的情景并进行拦截,从而达到监听 url 改变的目的。</p><p>浏览器的前进或后退按钮(HTML5 规范中有相应的 onpopstate 事件来监听)<br>a 标签(将进行路由跳转的链接用 data-href 来表示,而不是用 href)<br>history.pushState、history.replaceState函数(调用它们并不会触发任何事件,所以调用完之后需要做手动刷新)</p><pre><code class="js">class Router { constructor() { this.routes = {} this.currentUrl = '' } route(path, callback) { this.routes[path] = callback || function() {} } updateView(url) { this.currentUrl = url || '/home' this.routes[this.currentUrl] && this.routes[this.currentUrl]() } bindLink() { const allLink = document.querySelectorAll('a[data-href]') for (let i = 0, len = allLink.length; i < len; i++) { const current = allLink[i] current.addEventListener( 'click', e => { e.preventDefault() const url = current.getAttribute('data-href') history.pushState({}, null, url) this.updateView(url) }, false ) } } init() { this.bindLink() window.addEventListener('popstate', e => { this.updateView(window.location.pathname) }) window.addEventListener('load', () => this.updateView(), false) }}</code></pre><p><a href="https://codepen.io/pororo/pen/LJVNPO" target="_blank" rel="noopener">History 简单实现 demo</a></p><h2 id="优缺点"><a href="#优缺点" class="headerlink" title="优缺点"></a>优缺点</h2><h3 id="hash-路由"><a href="#hash-路由" class="headerlink" title="hash 路由"></a>hash 路由</h3><h4 id="优点:"><a href="#优点:" class="headerlink" title="优点:"></a>优点:</h4><ul><li>兼容性好,可以兼容到 IE8;</li><li>不需要后端支持。</li></ul><h4 id="缺点:"><a href="#缺点:" class="headerlink" title="缺点:"></a>缺点:</h4><ul><li>对于需要重定向的操作,服务器端无法获取 hash 部分,无法取得 url 中的数据,url 部分数据会丢失;</li><li>服务器端无法准确跟踪前端路由信息;</li><li>锚点定位的需求会导致与 hash 路由机制冲突。</li></ul><h3 id="history-路由"><a href="#history-路由" class="headerlink" title="history 路由"></a>history 路由</h3><h4 id="优点:-1"><a href="#优点:-1" class="headerlink" title="优点:"></a>优点:</h4><ul><li>重定向过程中不会丢失 url 中的参数,服务器端可以拿数据;</li><li>服务器端可以准确跟踪路由信息;</li><li>可以通过 history.state 获取当前 url 对应的状态信息。</li></ul><h4 id="缺点:-1"><a href="#缺点:-1" class="headerlink" title="缺点:"></a>缺点:</h4><ul><li>从 IE10 往后兼容,不如 hash 路由兼容性好;</li><li>需要服务器端支持,每次返回 html 文档。</li></ul><h1 id="react-router-V4-原理"><a href="#react-router-V4-原理" class="headerlink" title="react-router V4 原理"></a>react-router V4 原理</h1><p>react 本身作为一个 view 层框架,本身不会对路由进行控制,所以需要通过第三方来进行路由管理。</p><p>在上面我们实现的简单的路由实现都是直接操作 DOM,但是在 react 中我们不能直接操作 DOM,而是管理抽象的 VDOM,也就是管理组件的生命周期,不同路由显示不同组件。</p><p>这里我们以 react-router V4 为例(其实主要功能还是依赖于 history,这里不做详细的介绍),在 V4 中路由都是以组件的形式组织(Just Component API)。</p><p>react-router V4 分成了三个包:react-router-dom(for web)、react-router-native(for native)、react-router(core)</p><p>ps: 其实还有一个 react-router-redux 但已停止维护,用 connected-react-router 来替代,是一个单独的库,并不包含在 react-router V4 中。</p><h2 id="Router"><a href="#Router" class="headerlink" title="Router"></a>Router</h2><p>Router 源码 </p><pre><code class="js">import warning from 'warning'import invariant from 'invariant'import React from 'react'import PropTypes from 'prop-types'/** * The public API for putting history on context. */class Router extends React.Component { // 有两个 props : history 和 chidlren static propTypes = { history: PropTypes.object.isRequired, children: PropTypes.node, } static contextTypes = {router: PropTypes.object} static childContextTypes = {router: PropTypes.object.isRequired} getChildContext() { return { router: { ...this.context.router, history: this.props.history, route: {location: this.props.history.location, match: this.state.match}, }, } } state = {match: this.computeMatch(this.props.history.location.pathname)} computeMatch(pathname) { return {path: '/', url: '/', params: {}, isExact: pathname === '/'} } componentWillMount() { const {children, history} = this.props invariant( children == null || React.Children.count(children) === 1, 'A <Router> may have only one child element' ) // Do this here so we can setState when a <Redirect> changes the location in componentWillMount. This happens e.g. when doing server rendering using a <StaticRouter>. this.unlisten = history.listen(() => { this.setState({match: this.computeMatch(history.location.pathname)}) }) } componentWillReceiveProps(nextProps) { warning( // props.history初始化后,不能再次更改 this.props.history === nextProps.history // "You cannot change <Router history>" ) } componentWillUnmount() { this.unlisten() } render() { const {children} = this.props return children ? React.Children.only(children) : null // Router 下只能有一个子元素 }}export default Router</code></pre><h3 id="类型"><a href="#类型" class="headerlink" title="类型"></a>类型</h3><p>Router 组件有两种类型:</p><ul><li>BrowserRouter:通过 pushState 和 replaceState 构建路由</li><li>HashRouter:通过 location.hash 和 hashchange 构建路由</li></ul><p>它们是高阶组件,在内部创建一个全局的 history 对象(可以监听整个路由的变化),并将 history 作为 props 传递给 Router 组件。</p><pre><code class="js">// BrowerRouter import { createBrowserHistory as createHistory } from "history";...history = createHistory(this.props);... render() { return <Router history={this.history} children={this.props.children} />; } // HashRouterimport { createHashHistory as createHistory } from "history"; ... history = createHistory(this.props); ... render() { return <Router history={this.history} children={this.props.children} />; }</code></pre><p>history 是 react-router 的实现的核心。</p><p>BrowserRouter 和 HashRouter 组件主要的区别就是:从 history 中导入的 <code>createHistory</code> 不同,创建了不同的 history 对象。</p><p>BroeserRouter 中利用 <code>createBrowserHistory</code> 创建 history 对象,HashRouter 中利用 <code>createHashHistory</code> 创建 history 对象。</p><p>无论使用哪种方式创建 history 对象,都会得到拥有相同属性和方法的 history 对象。</p><pre><code class="js">// createBrowserHistoryconst history = { length: globalHistory.length, action: 'POP', location: initialLocation, createHref, push, replace, go, goBack, goForward, block, listen,}// createHashHistoryconst history = { length: globalHistory.length, action: 'POP', location: initialLocation, createHref, push, replace, go, goBack, goForward, block, listen,}</code></pre><p>两者的主要区别是:根据 url 创建 location 的对象不同,见下面的对比:</p><pre><code class="js">// 提供如下URL url = 'http://www.zhihu.com/home?key=value#hash' // browser history 创建的 location 对象: { pathname: '/home', search: '?key=value', hash: '#hash' } //hash history 创建的 location 对象: { pathname: 'hash', search: '', hash: '' }</code></pre><p>browser history 使用完整 url,而 hash history 只使用第一个 hash 后的 url。</p><h3 id="特性"><a href="#特性" class="headerlink" title="特性"></a>特性</h3><p>Router 有两个 props : history 和 children。<br>Router 下只能有一个子元素(通过 React.Children.only 进行限制)<br>props 中 history 初始化后,不能再次更改</p><h3 id="通信方式"><a href="#通信方式" class="headerlink" title="通信方式"></a>通信方式</h3><p>Router 组件与子组件通信通过旧的 context API 来进行通信。<br>需要在父组件(Router)设置: getChildContext,childContextTypes,子组件需要设置: contextTypes,利用 context API 来实现 Router 下 history 实例的共享。</p><p>对子组件暴露一个 router 对象</p><pre><code class="js">router: { ...this.context.router, history: this.props.history, // 当前 history 实例 route: { // 当前路由匹配信息 location: this.props.history.location, // 当前 history 实例的 location match: this.state.match } }</code></pre><p>Router 只通过 history 实例中的 listen 函数来监听路由变化,每次变化后在 Router 中都执行 setState 更新 state.match。由于 context 中的 match 指向的是 state.match,从而造成 context 发生变化,触发子组件(Route)的 <code>componentWillReceiveProps</code> 函数,触发重新渲染(下面会讲到)。之所以把这个 <code>history.listen</code> 函数写在 <code>componentWillMount</code> 里,是为了 SSR 的时候,也能够使用 Redirect。</p><h2 id="Route"><a href="#Route" class="headerlink" title="Route"></a>Route</h2><p>Route 源码</p><pre><code class="js">import warning from "warning";import invariant from "invariant";import React from "react";import PropTypes from "prop-types";import matchPath from "./matchPath";const isEmptyChildren = children => React.Children.count(children) === 0;/** * The public API for matching a single path and rendering. */class Route extends React.Component { static propTypes = { computedMatch: PropTypes.object, // private, from <Switch> path: PropTypes.string, exact: PropTypes.bool, strict: PropTypes.bool, sensitive: PropTypes.bool, component: PropTypes.func, render: PropTypes.func, children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), location: PropTypes.object }; static contextTypes = { router: PropTypes.shape({ history: PropTypes.object.isRequired, route: PropTypes.object.isRequired, staticContext: PropTypes.object }) }; static childContextTypes = { router: PropTypes.object.isRequired }; getChildContext() { return { router: { ...this.context.router, route: { location: this.props.location || this.context.router.route.location, match: this.state.match } } }; } state = { match: this.computeMatch(this.props, this.context.router) }; computeMatch( { computedMatch, location, path, strict, exact, sensitive }, router ) { if (computedMatch) return computedMatch; // <Switch> already computed the match for us invariant( router, "You should not use <Route> or withRouter() outside a <Router>" ); const { route } = router; const pathname = (location || route.location).pathname; return matchPath(pathname, { path, strict, exact, sensitive }, route.match); } componentWillMount() { warning( !(this.props.component && this.props.render), "You should not use <Route component> and <Route render> in the same route; <Route render> will be ignored" ); warning( !( this.props.component && this.props.children && !isEmptyChildren(this.props.children) ), "You should not use <Route component> and <Route children> in the same route; <Route children> will be ignored" ); warning( !( this.props.render && this.props.children && !isEmptyChildren(this.props.children) ), "You should not use <Route render> and <Route children> in the same route; <Route children> will be ignored" ); } componentWillReceiveProps(nextProps, nextContext) { warning( !(nextProps.location && !this.props.location), '<Route> elements should not change from uncontrolled to controlled (or vice versa). You initially used no "location" prop and then provided one on a subsequent render.' ); warning( !(!nextProps.location && this.props.location), '<Route> elements should not change from controlled to uncontrolled (or vice versa). You provided a "location" prop initially but omitted it on a subsequent render.' ); this.setState({ match: this.computeMatch(nextProps, nextContext.router) }); } render() { const { match } = this.state; const { children, component, render } = this.props; const { history, route, staticContext } = this.context.router; const location = this.props.location || route.location; const props = { match, location, history, staticContext }; if (component) return match ? React.createElement(component, props) : null; if (render) return match ? render(props) : null; if (typeof children === "function") return children(props); if (children && !isEmptyChildren(children)) return React.Children.only(children); return null; }}export default Route;</code></pre><p>上面说到在 Router 中「由于 context 中的 match 指向的是 state.match,从而造成 context 发生变化,触发子组件(Route)的 <code>componentWillReceiveProps</code> 函数,触发重新渲染」。</p><p>Route 接受上层的 Router 传入的 context,Router 中的 history 监听着整个页面的路由变化。由于 context 中的 match 指向的是 Router 中的 state.match,当页面发生跳转时,history 触发监听事件,Router 中的 state.match 变化,从而 context 变化,触发子组件(Route)的 <code>componentWillReceiveProps</code> 函数,传入新的 context 对象作为 nextContext 参数,并执行 setState(),更新当前 Route 中的state.match,重新判断是否渲染对应组件。</p><p>简单概括就是:每次路由变化 -> 触发顶层 Router 的回调事件 -> Router 进行 setState -> 向下传递 nextContext(context 中含有最新的 location)-> 下面的 Route 路由组件获取新的 nextContext 判断是否进行渲染。</p><p>Route 提供了三种渲染组件的方法:<em>component props</em>,<em>render props</em> 和 <em>children props</em>。<br>渲染的优先级也是依次按照顺序,可以分成四种情况:</p><ul><li>component (props) : 由于使用 React.createElement 创建,所以可以传入一个 class component。</li><li>render (props) : 直接调用 render() 展开子元素,所以需要传入 stateless function component。</li><li>children (props): 其实和 render 差不多,区别是不判断 match,总是会被渲染。</li><li>children(子元素): 如果以上都没有,那么会默认渲染子元素,但是只能有一个子元素。</li></ul><h2 id="matchPath-函数"><a href="#matchPath-函数" class="headerlink" title="matchPath 函数"></a>matchPath 函数</h2><p>Route 中是否匹配就是使用 computeMatch 函数。</p><p>matchPath 源码</p><pre><code class="js">import pathToRegexp from "path-to-regexp";const patternCache = {};const cacheLimit = 10000;let cacheCount = 0;const compilePath = (pattern, options) => { const cacheKey = `${options.end}${options.strict}${options.sensitive}`; const cache = patternCache[cacheKey] || (patternCache[cacheKey] = {}); if (cache[pattern]) return cache[pattern]; const keys = []; const re = pathToRegexp(pattern, keys, options); const compiledPattern = { re, keys }; if (cacheCount < cacheLimit) { cache[pattern] = compiledPattern; cacheCount++; } return compiledPattern;};/** * Public API for matching a URL pathname to a path pattern. */const matchPath = (pathname, options = {}, parent) => { if (typeof options === "string") options = { path: options }; const { path, exact = false, strict = false, sensitive = false } = options; if (path == null) return parent; const { re, keys } = compilePath(path, { end: exact, strict, sensitive }); const match = re.exec(pathname); if (!match) return null; const [url, ...values] = match; const isExact = pathname === url; if (exact && !isExact) return null; return { path, // the path pattern used to match url: path === "/" && url === "" ? "/" : url, // the matched portion of the URL isExact, // whether or not we matched exactly params: keys.reduce((memo, key, index) => { memo[key.name] = values[index]; return memo; }, {}) };};export default matchPath;</code></pre><p>它依赖于 path-to-regexp,根据输入的 path 生成简单的 url 正则表达式,来判断当前 pathname 是否与 path 符合。</p><pre><code class="js">var keys = []var re = pathToRegexp('/foo/:bar', keys)// re = /^\/foo\/([^\/]+?)\/?$/i// keys = [{ name: 'bar', prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '[^\\/]+?' }]re.exec('/test/route')//=> nullvar re = pathToRegexp('/:foo/:bar')// keys = [{ name: 'foo', prefix: '/', ... }, { name: 'bar', prefix: '/', ... }]re.exec('/test/route')//=> ['/test/route', 'test', 'route']</code></pre><p>如果当前 pathname 与 path 不符合 matchPath 函数,返回 null;符合的话,返回的是一个如下结构的对象:</p><pre><code class="js">{ path, // 用来进行匹配的路径,其实是直接导出的传入 matchPath 的 options 中的 path url: path === "/" && url === "" ? "/" : url, // 整个的 URL isExact, // url 与 path 是否是 exact 的匹配 isExact, // 返回的是一个键值对的映射 // 比如 path 是 /users/:id,然后匹配的 pathname 是 /user/123 // 那么 params 的返回值就是 {id: '123'} params: keys.reduce((memo, key, index) => { memo[key.name] = values[index]; return memo; }, {}) }</code></pre><p>这些信息将作为匹配的参数传递给 Route 和 Switch。</p><p>在 matchPath 内部 compilePath 时,有</p><pre><code class="js">const patternCache = {};const cacheLimit = 10000; let cacheCount = 0;</code></pre><p>作为 pathToRegexp 的缓存,因为 ES6 的 import 模块导出的是值的引用,所以将 patternCache 可以理解为一个全局变量缓存,缓存以 {option:{pattern: }} 的形式存储,之后如果需要匹配相同 pattern 和 option 的 path,则可以直接从缓存中获得正则表达式和 keys。</p><p>加缓存的原因是路由页面大部分情况下都是相似的,比如要访问 <code>/user/123</code>或 <code>/users/234</code>,都会使用 <code>/user/:id</code> 这个 path 去匹配,没有必要每次都生成一个新的正则表达式。SPA 在页面整个访问的过程中都维护着这份缓存。</p><h2 id="Switch"><a href="#Switch" class="headerlink" title="Switch"></a>Switch</h2><p>Switch 是嵌套在 Route 的外面,当 Switch 中的第一个 Route 匹配之后就不会再渲染其他的 Route(Switch 只是一个代理,它的作用还是渲染 Route,Switch 计算得到的 <code>computedMatch</code> 会传递给要渲染的 Route,此时 Route 将直接使用这个 <code>computedMatch</code> 而不需要再自己来计算)。</p><p>Switch 源码</p><pre><code class="js">import React from "react";import PropTypes from "prop-types";import warning from "warning";import invariant from "invariant";import matchPath from "./matchPath";/** * The public API for rendering the first <Route> that matches. */class Switch extends React.Component { static contextTypes = { router: PropTypes.shape({ route: PropTypes.object.isRequired }).isRequired }; static propTypes = { children: PropTypes.node, location: PropTypes.object }; componentWillMount() { invariant( this.context.router, "You should not use <Switch> outside a <Router>" ); } componentWillReceiveProps(nextProps) { warning( !(nextProps.location && !this.props.location), '<Switch> elements should not change from uncontrolled to controlled (or vice versa). You initially used no "location" prop and then provided one on a subsequent render.' ); warning( !(!nextProps.location && this.props.location), '<Switch> elements should not change from controlled to uncontrolled (or vice versa). You provided a "location" prop initially but omitted it on a subsequent render.' ); } render() { const { route } = this.context.router; const { children } = this.props; const location = this.props.location || route.location; let match, child; React.Children.forEach(children, element => { if (match == null && React.isValidElement(element)) { const { path: pathProp, exact, strict, sensitive, from } = element.props; const path = pathProp || from; child = element; match = matchPath( location.pathname, { path, exact, strict, sensitive }, route.match ); } }); return match ? React.cloneElement(child, { location, computedMatch: match }) : null; }}export default Switch;</code></pre><p>Switch 也是通过 matchPath 这个函数来判断是否匹配成功,一直按照 Switch 中 children 的顺序依次遍历子元素,如果匹配失败则 match 为 null,如果匹配成功则标记这个子元素和它对应的 <code>location</code>、<code>computedMatch</code>。在最后的时候使用 <code>React.cloneElement</code> 渲染,如果没有匹配到的子元素则返回 null。</p><h2 id="Link"><a href="#Link" class="headerlink" title="Link"></a>Link</h2><p>Link 源码</p><pre><code class="js">import React from "react";import PropTypes from "prop-types";import invariant from "invariant";import { createLocation } from "history";const isModifiedEvent = event => !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);/** * The public API for rendering a history-aware <a>. */class Link extends React.Component { static propTypes = { onClick: PropTypes.func, target: PropTypes.string, replace: PropTypes.bool, to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, innerRef: PropTypes.oneOfType([PropTypes.string, PropTypes.func]) }; static defaultProps = { replace: false }; static contextTypes = { router: PropTypes.shape({ history: PropTypes.shape({ push: PropTypes.func.isRequired, replace: PropTypes.func.isRequired, createHref: PropTypes.func.isRequired }).isRequired }).isRequired }; handleClick = event => { if (this.props.onClick) this.props.onClick(event); if ( !event.defaultPrevented && // onClick prevented default event.button === 0 && // ignore everything but left clicks !this.props.target && // let browser handle "target=_blank" etc. !isModifiedEvent(event) // ignore clicks with modifier keys ) { event.preventDefault(); const { history } = this.context.router; const { replace, to } = this.props; if (replace) { history.replace(to); } else { history.push(to); } } }; render() { const { replace, to, innerRef, ...props } = this.props; // eslint-disable-line no-unused-vars invariant( this.context.router, "You should not use <Link> outside a <Router>" ); invariant(to !== undefined, 'You must specify the "to" property'); const { history } = this.context.router; const location = typeof to === "string" ? createLocation(to, null, null, history.location) : to; const href = history.createHref(location); return ( <a {...props} onClick={this.handleClick} href={href} ref={innerRef} /> ); }}export default Link;</code></pre><p>Link 最终创建一个 a 标签来包裹住要跳转的元素,为了使 a 标签不发生页面的跳转,在这个 a 标签的 <code>handleClick</code> 中 <code>preventDefault</code> 禁止默认的跳转。所以这里的 href 并没有作用,但有更好的 html 语义。</p><p>在 <code>handleClick</code> 中,只对单纯的左击进行 <code>preventDefault</code> 和 <code>history.push</code>。是因为:「路由的变化」与「页面的跳转」是不互相关联的,应区别开来。</p><p>在 Link 中调用了 history 中的 push 方法引起路由改变,Router 中的 listen 监听到路由的变化,然后通过 context 更新 props 和 nextContext 让下层的 Route 去重新匹配,完成需要渲染部分的更新。</p><h2 id="Redirect"><a href="#Redirect" class="headerlink" title="Redirect"></a>Redirect</h2><p>Redirect 源码</p><pre><code class="js">import React from "react";import PropTypes from "prop-types";import warning from "warning";import invariant from "invariant";import { createLocation, locationsAreEqual } from "history";import generatePath from "./generatePath";/** * The public API for updating the location programmatically * with a component. */class Redirect extends React.Component { static propTypes = { computedMatch: PropTypes.object, // private, from <Switch> push: PropTypes.bool, from: PropTypes.string, to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired }; static defaultProps = { push: false }; static contextTypes = { router: PropTypes.shape({ history: PropTypes.shape({ push: PropTypes.func.isRequired, replace: PropTypes.func.isRequired }).isRequired, staticContext: PropTypes.object }).isRequired }; isStatic() { return this.context.router && this.context.router.staticContext; } componentWillMount() { invariant( this.context.router, "You should not use <Redirect> outside a <Router>" ); if (this.isStatic()) this.perform(); } componentDidMount() { if (!this.isStatic()) this.perform(); } componentDidUpdate(prevProps) { const prevTo = createLocation(prevProps.to); const nextTo = createLocation(this.props.to); if (locationsAreEqual(prevTo, nextTo)) { warning( false, `You tried to redirect to the same route you're currently on: ` + `"${nextTo.pathname}${nextTo.search}"` ); return; } this.perform(); } computeTo({ computedMatch, to }) { if (computedMatch) { if (typeof to === "string") { return generatePath(to, computedMatch.params); } else { return { ...to, pathname: generatePath(to.pathname, computedMatch.params) }; } } return to; } perform() { const { history } = this.context.router; const { push } = this.props; const to = this.computeTo(this.props); if (push) { history.push(to); } else { history.replace(to); } } render() { return null; }}export default Redirect;</code></pre><p>Redirect 支持在客户端和服务器端调用。客户端 mount 阶段会在 <code>componentDidMount</code> 进行重定向,update 阶段会在 <code>componentDidUpdate</code> 中进行重定向;在服务器端由于不会执行 <code>componentDidMount</code>(服务器端只会执行到 <code>componentWillMount</code> ) ,所以用 <code>componentWillMount</code> 来代替进行重定向。通过 <code>this.isStatic()</code> 来判断重定向是发生在客户端还是服务器端。</p><p>这个组件 render方法 return null,是因为 react-router 4 遵循 Just Components API 的原则,将 Redirect 写成组件而不是函数。</p><h2 id="withRouter"><a href="#withRouter" class="headerlink" title="withRouter"></a>withRouter</h2><p>withRouter 源码</p><pre><code class="js">import React from "react";import PropTypes from "prop-types";import hoistStatics from "hoist-non-react-statics";import Route from "./Route";/** * A public higher-order component to access the imperative API */const withRouter = Component => { const C = props => { const { wrappedComponentRef, ...remainingProps } = props; return ( <Route children={routeComponentProps => ( <Component {...remainingProps} {...routeComponentProps} ref={wrappedComponentRef} /> )} /> ); }; C.displayName = `withRouter(${Component.displayName || Component.name})`; C.WrappedComponent = Component; C.propTypes = { wrappedComponentRef: PropTypes.func }; return hoistStatics(C, Component);};export default withRouter;</code></pre><p>withRouter 的作用是:在普通的非直接嵌套在 Route 中的组件也能获得路由的信息。它是一个用 Route 包裹了原本 Component 的 HOC。</p><p>创建 Route 有三种方法,这里直接采用了传递 children props(children 为 function)的方法,因为该 HOC 要原封不动的渲染 wrappedComponent,即:不判断 match,总需要被渲染。</p><p>最后返回 HOC 时,使用了 hoistStatics 方法,作用是保留原本 Component 类的静态方法。由于该 HOC 是在原本 Component 的外层包了一层 Route,需要要将原本 Component 类的静态方法转移给新的 Route。</p><h2 id="StaticRouter"><a href="#StaticRouter" class="headerlink" title="StaticRouter"></a>StaticRouter</h2><p>StaticRouter 源码</p><pre><code class="js">import warning from "warning";import invariant from "invariant";import React from "react";import PropTypes from "prop-types";import { createLocation, createPath } from "history";import Router from "./Router";const addLeadingSlash = path => { return path.charAt(0) === "/" ? path : "/" + path;};const addBasename = (basename, location) => { if (!basename) return location; return { ...location, pathname: addLeadingSlash(basename) + location.pathname };};const stripBasename = (basename, location) => { if (!basename) return location; const base = addLeadingSlash(basename); if (location.pathname.indexOf(base) !== 0) return location; return { ...location, pathname: location.pathname.substr(base.length) };};const createURL = location => typeof location === "string" ? location : createPath(location);const staticHandler = methodName => () => { invariant(false, "You cannot %s with <StaticRouter>", methodName);};const noop = () => {};/** * The public top-level API for a "static" <Router>, so-called because it * can't actually change the current location. Instead, it just records * location changes in a context object. Useful mainly in testing and * server-rendering scenarios. */class StaticRouter extends React.Component { static propTypes = { basename: PropTypes.string, context: PropTypes.object.isRequired, location: PropTypes.oneOfType([PropTypes.string, PropTypes.object]) }; static defaultProps = { basename: "", location: "/" }; static childContextTypes = { router: PropTypes.object.isRequired }; getChildContext() { return { router: { staticContext: this.props.context } }; } createHref = path => addLeadingSlash(this.props.basename + createURL(path)); handlePush = location => { const { basename, context } = this.props; context.action = "PUSH"; context.location = addBasename(basename, createLocation(location)); context.url = createURL(context.location); }; handleReplace = location => { const { basename, context } = this.props; context.action = "REPLACE"; context.location = addBasename(basename, createLocation(location)); context.url = createURL(context.location); }; handleListen = () => noop; handleBlock = () => noop; componentWillMount() { warning( !this.props.history, "<StaticRouter> ignores the history prop. To use a custom history, " + "use `import { Router }` instead of `import { StaticRouter as Router }`." ); } render() { const { basename, context, location, ...props } = this.props; const history = { createHref: this.createHref, action: "POP", location: stripBasename(basename, createLocation(location)), push: this.handlePush, replace: this.handleReplace, go: staticHandler("go"), goBack: staticHandler("goBack"), goForward: staticHandler("goForward"), listen: this.handleListen, block: this.handleBlock }; return <Router {...props} history={history} />; }}export default StaticRouter;</code></pre><p>StaticRouter 主要用在测试环境和服务端环境。由于在 server 端路由是静态的,所以并不支持一些在客户端才能进行的路由操作,比如:go, goBack, goForward、listen、block 方法。在 StaticRouter 组件内使用这些方法不进行操作。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>最后可以总结一下 react-router V4 中路有变化的整个过程:</p><p>初始化 Router 的时候,Router 在 componentWillMount 中 listen 了一个回调函数,该函数来自 history 实例,每次路由改变都会触发该回调函数,进而触发 setState。<br>ps:每次路由变化 -> 触发顶层 Router 的监听事件 -> Router 触发 setState -> 向下传递新的 nextContext(nextContext 中含有最新的 location)。</p><p>下层的 Route 拿到新的 nextContext 通过 matchPath 函数来判断 path 是否与 location 匹配,如果匹配则渲染,不匹配则不渲染。</p>]]></content>
<categories>
<category> React </category>
</categories>
<tags>
<tag> react-router </tag>
</tags>
</entry>
<entry>
<title>Promise之串行执行的实现</title>
<link href="/2018/04/05/Promise%E7%9A%84%E4%B8%B2%E8%A1%8C%E6%89%A7%E8%A1%8C/"/>
<url>/2018/04/05/Promise%E7%9A%84%E4%B8%B2%E8%A1%8C%E6%89%A7%E8%A1%8C/</url>
<content type="html"><![CDATA[<blockquote><p><a href="http://www.ituring.com.cn/article/66566" target="_blank" rel="noopener">Promises/A+规范</a>了解一下?</p></blockquote><a id="more"></a><h1 id="误区"><a href="#误区" class="headerlink" title="误区"></a>误区</h1><p>通常如果说要实现 Promise 的串行执行,对于初学者(比如说我这种小菜鸟)来说,会不假思索的写出如下代码:</p><pre><code class="js">function executePromises(promises) { var result = Promise.resolve(); promises.forEach(function (promise) { result = result.then(promise); }); return result;}</code></pre><p>但其实发现,和用 <code>Promise.all()</code> 实现的的效果是一样,都是并行执行。</p><p>那这是什么原因呢?</p><p><strong>依照 promises 规范,一旦一个 promise 被创建,它就被执行了。</strong>promises.forEach本意是顺序执行promise,也就是依次的,然而上面的代码,实际上promises并不会等待上一个promise结束就会开始执行。</p><h1 id="正解"><a href="#正解" class="headerlink" title="正解"></a>正解</h1><p>所以实现的关键点就在于,在执行 <code>executePromise</code> 之前不会创建 promise。</p><p>所以可以创建一个类似于下面的函数:</p><pre><code class="js">function createPromise() { return new Promise((resolve)=>{ doSomthing() resolve() });}</code></pre><p>然后将函数 <code>executePromises</code> 修改为:</p><pre><code class="js">function executePromises(createPromises) { var result = Promise.resolve(); createPromises.forEach(function (createPromise) { result = result.then(createPromise); }); return result;}</code></pre><p>有了这两个函数,我们就可以使多个 Promise 串执行~</p><h1 id="解释"><a href="#解释" class="headerlink" title="解释"></a>解释</h1><p>为什么这样可以呢?</p><p>因为一个 createPromise 在被执行之前并不会创建 promise。它和一个 then 函数一样。</p><p>如果你查看上面的 <code>executePromises()</code> 函数,然后想象 <code>createPromise</code> 被包裹在 result.then(…) 之中,就像下面的样子:</p><pre><code class="js">result.resolve().then(createPromise).then(createPromise)...</code></pre><p>是不是就秒懂啦~这样就实现了 Promise 的串行执行~</p>]]></content>
<categories>
<category> Promise </category>
</categories>
<tags>
<tag> Promise串行执行 </tag>
</tags>
</entry>
<entry>
<title>CSS中的层叠规则</title>
<link href="/2018/04/05/CSS%E4%B8%AD%E7%9A%84%E5%B1%82%E5%8F%A0%E8%A7%84%E5%88%99/"/>
<url>/2018/04/05/CSS%E4%B8%AD%E7%9A%84%E5%B1%82%E5%8F%A0%E8%A7%84%E5%88%99/</url>
<content type="html"><![CDATA[<blockquote><p>默认情况下,网页的内容是没有偏移角的垂直视觉呈现,当内容发生层叠时,就会产生一个有先后顺序的层叠。</p></blockquote><h1 id="z-index"><a href="#z-index" class="headerlink" title="z-index"></a>z-index</h1><p>一般说到层叠首先就很容易想到 z-index 属性。</p><p>z-index 属性只和 position 不为 static 的元素在一起时才生效,可为负值也可为正值。</p><p>但是,在新出现的 CSS3 中,flex 盒子的子元素也可以设置 z-index 属性。</p><h1 id="层叠上下文和层叠水平"><a href="#层叠上下文和层叠水平" class="headerlink" title="层叠上下文和层叠水平"></a>层叠上下文和层叠水平</h1><h2 id="层叠上下文"><a href="#层叠上下文" class="headerlink" title="层叠上下文"></a>层叠上下文</h2><p>层叠上下文(stacking context)是 HTML 中的一个三维的概念。可以理解为,某个元素含有层叠上下文那它就在 z 轴(当你看电脑屏幕时,视线和显示器之前的这条垂直线)上就会高于其他非层叠上下文元素。</p><p>层叠上下文和块级格式化上下文类似,每个层叠上下文中可能有其他的层叠上下文,而自身也有可能处于一个层叠上下文中。</p><h2 id="层叠水平"><a href="#层叠水平" class="headerlink" title="层叠水平"></a>层叠水平</h2><p>层叠水平(stacking level)决定了同一个层叠上下文中元素在 z 轴上的显示顺序。</p><p>包括层叠上下文元素和普通元素,网页上的每个元素都有层叠水平。只不过普通元素的层叠水平是无法和层叠上下文元素的层叠水平相较量的。</p><p>需要注意,不要把层叠水平和 CSS 的 z-index 属性混为一谈。z-index 对层叠水平的影响仅限于定位元素和 flex 盒子元素,而层叠水平适用于所有元素。</p><h1 id="元素的层叠顺序"><a href="#元素的层叠顺序" class="headerlink" title="元素的层叠顺序"></a>元素的层叠顺序</h1><p>层叠顺序(stacking order)表示发生层叠时垂直显示顺序。</p><a id="more"></a><p>层叠规则可以类似这样表示:</p><p><img src="http://oy5zqmv54.bkt.clouddn.com/z-index.png" alt="层叠顺序"></p><p>以上的层叠顺序规则仅适于当前层叠上下文。</p><p>注意:z-index: 0 和 z-index: auto,可以看成是一样的,但在层叠上下文中有着根本性的差异。</p><p>上图的层叠顺序从底到上可以理解为:装饰 => 布局 => 内容。</p><p><a href="https://codepen.io/anon/pen/EEBEZP" target="_blank" rel="noopener">看山 example</a></p><h1 id="层叠准则"><a href="#层叠准则" class="headerlink" title="层叠准则"></a>层叠准则</h1><ol><li>谁的值大谁在上:在一个层叠上下文中,层叠水平大的元素会覆盖层叠水平小的元素。</li><li>后来元素在上:在同一个层叠上下文中,层叠水平一致、层叠顺序相同时,后面的元素会覆盖前面的元素。</li></ol><p><a href="https://codepen.io/anon/pen/NMpOox" target="_blank" rel="noopener">inline-block、block example</a></p><h1 id="深入理解层叠上下文"><a href="#深入理解层叠上下文" class="headerlink" title="深入理解层叠上下文"></a>深入理解层叠上下文</h1><h2 id="层叠上下文的特性"><a href="#层叠上下文的特性" class="headerlink" title="层叠上下文的特性"></a>层叠上下文的特性</h2><ol><li>层叠上下文的层叠水平要比普通元素高</li><li>层叠上下文可以阻断元素的混和模式 CSS3 isolation: isolate、mix-blend-mode</li><li>层叠上下文可以嵌套,内部的层叠上下文及其子元素受制于外部的层叠上下文</li><li>每个层叠上下文和兄弟元素独立。</li><li>每个层叠上下文是有体系的,当元素发生层叠时,整个元素被认为是在父层叠上下文的层叠顺序中。</li></ol><h2 id="层叠上下文的创建"><a href="#层叠上下文的创建" class="headerlink" title="层叠上下文的创建"></a>层叠上下文的创建</h2><ol><li>默认的:页面根元素默认具有层叠上下文,即根层叠上下文。</li><li>z-index:z-index 值为数值的定位元素的传统“层叠上下文”。</li><li>CSS3 的一些属性</li></ol><h3 id="根层叠上下文"><a href="#根层叠上下文" class="headerlink" title="根层叠上下文"></a>根层叠上下文</h3><p>根层叠上下文是页面根元素,即<html>元素。所以,页面中所有的元素一定处于至少一个层叠上下文中。</html></p><h3 id="定位元素和传统层叠上下文"><a href="#定位元素和传统层叠上下文" class="headerlink" title="定位元素和传统层叠上下文"></a>定位元素和传统层叠上下文</h3><p>当定位元素的 z-index 的值不为 auto 时,会创建层叠上下文。</p><p><a href="https://codepen.io/anon/pen/MGpzXX" target="_blank" rel="noopener">一只刘看山 example</a></p><p><a href="https://codepen.io/anon/pen/yKdKde" target="_blank" rel="noopener">两只刘看山 example</a></p><p>position:fixed 属性使元素天然具有层叠上下文(在 blink、webkit 内核浏览器中)。</p><h3 id="CSS3-中的层叠上下文"><a href="#CSS3-中的层叠上下文" class="headerlink" title="CSS3 中的层叠上下文"></a>CSS3 中的层叠上下文</h3><ol><li>元素为 flex 布局元素同时子元素 z-index 值不为 auto,层叠上下文元素是flex子元素 <a href="https://codepen.io/anon/pen/qYrQGB" target="_blank" rel="noopener">example</a></li><li>元素的 opacity 值不是 1 <a href="https://codepen.io/anon/pen/OZprVx" target="_blank" rel="noopener">example</a></li><li>元素的 transform 值不是 none <a href="https://codepen.io/anon/pen/VxpqeK" target="_blank" rel="noopener">example</a></li><li>元素的 filter 不是 none <a href="https://codepen.io/anon/pen/wjJRom" target="_blank" rel="noopener">example</a></li><li>元素的 mix-blend-mode 值不是 normal <a href="https://codepen.io/pororo/pen/deVpex" target="_blank" rel="noopener">example</a> 默认情况下是会混合所有比其层叠顺序低的元素的</li><li>元素的 isolation 值是 isolate 完善 之所以可以阻断混合模式的进行,本质上是因为isolation:isolate创建一个新的层叠上下文(stacking context)。<a href="https://codepen.io/pororo/pen/BxwLOp" target="_blank" rel="noopener">example</a></li><li>元素的 will-change 属性为 2~6 中任意一个 <a href="http://www.zhangxinxu.com/wordpress/2015/11/css3-will-change-improve-paint/" target="_blank" rel="noopener">参考</a></li><li>元素的 -webkit-overflow-scrolling 为 touch <a href="https://codepen.io/anon/pen/GdWPjv" target="_blank" rel="noopener">example</a></li></ol><h2 id="层叠上下文和层叠顺序"><a href="#层叠上下文和层叠顺序" class="headerlink" title="层叠上下文和层叠顺序"></a>层叠上下文和层叠顺序</h2><ol><li>如果层叠上下文元素不依赖 z-index 数值,即,z-index:auto 相当于 z-index:0</li><li>如果层叠上下文元素依赖 z-index 数值,则层叠顺序由 z-index 决定。</li></ol><p>元素一旦成为定位元素,就会自动具有 z-index:auto 属性。</p><p>不支持 z-index 的层叠上下文元素是天然 z-index:auto,和定位元素是一个层叠顺序,当它们发生层叠时,遵守“后来元素在上”的规则。</p><p>层叠顺序可以更新一下;</p><p><img src="http://oy5zqmv54.bkt.clouddn.com/z-index-2.png" alt="z-index-2"></p><p><a href="https://codepen.io/anon/pen/yKdQWY" target="_blank" rel="noopener">1-auto</a></p><p><a href="https://codepen.io/anon/pen/vjxvdJ" target="_blank" rel="noopener">2-flex</a></p><p>应用:</p><p><a href="https://codepen.io/anon/pen/JvWxga" target="_blank" rel="noopener">图片淡入</a></p><h1 id="z-index负值深入理解"><a href="#z-index负值深入理解" class="headerlink" title="z-index负值深入理解"></a>z-index负值深入理解</h1><p>由之前的层叠顺序图可以看出来,z-index 负值元素的层级在层叠上下文上面、block 元素下面。</p><p>所以在一个层级上下文里不管 z-index 的负值再小,都不会突破当前层级上下文。</p><p>z-index 负值渲染的过程就是一个寻找第一个层叠上下文元素的过程,然后层叠顺序止步于这个层叠上下文元素。</p><p><a href="https://codepen.io/anon/pen/bvPQXP?editors=1000" target="_blank" rel="noopener">example</a></p><p><a href="https://codepen.io/anon/pen/GxbwPX" target="_blank" rel="noopener">flex-example</a></p><p>应用:</p><p><a href="https://codepen.io/anon/pen/VXJqPE" target="_blank" rel="noopener">卷起效果</a></p><p>.container灰色背景通过position:relative;z-index:0创建了层叠上下文,.page仅有position:relative而没有设置z-index值,因此只能算z-index:auto程度的定位元素,于是,z-index:-1两个边角阴影就完美地藏在了层叠上下文(灰色背景)之上、普通定位元素(黄色纸张)之下, 隐藏了丑陋的细节,展示了完美的边角阴影,实现了最终细腻的样式效果。</p><h1 id="关于-z-index-注意事项"><a href="#关于-z-index-注意事项" class="headerlink" title="关于 z-index 注意事项"></a>关于 z-index 注意事项</h1><p>对于非浮层元素,避免设置 z-index 值,z-index 值没有任何道理需要超过2。</p><ol><li>定位元素一旦设置了 z-index 值,就从普通定位元素变成了层叠上下文元素,相互间的层叠顺序就发生了根本的变化,很容易出现设置了巨大的 z-index 值也无法覆盖其他元素的问题。</li><li>避免 z-index “一山比一山高”的样式混乱问题。</li></ol><p>DOM 顺序无法调整,不得不用 z-index 时,一定不要超过 2,如果超过 2,可以尝试用“relative 最小化原则”来实现和利用元素原生的层叠顺序进行层及控制等。</p><p>但是当元素是 js 驱动的浮层组件时,</p><p>需要借助“层级计数器”来管理,原因如下:</p><p>(1)总会遇到意想不到的高层级元素;</p><p>(2)组件的覆盖规则具有动态性。</p><p>所谓“层级计数器”,实际上就是一段 JavaScript 脚本,会遍历所有<body>处于显示状态的子元素,并得到最大 z-index 值,和默认的 z-index 做比较。如果超出,则显示的组件的 z-index 自动加1,这样就不会出现有组件被其他组件覆盖的问题;如果不超出,就使用默认的 z-index 值。</body></p>]]></content>
<categories>
<category> CSS </category>
</categories>
<tags>
<tag> CSS层叠规则 </tag>
</tags>
</entry>
<entry>
<title>countdownHOC实现</title>
<link href="/2017/11/21/countdownHOC%E5%AE%9E%E7%8E%B0/"/>
<url>/2017/11/21/countdownHOC%E5%AE%9E%E7%8E%B0/</url>
<content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>最近写了一个countdown的倒计时的高阶组件。现在大概有了个样子,实现了最基本的倒计时,但是后续会继续更新使更加完备、好用。</p><a id="more"></a><h1 id="demo简介"><a href="#demo简介" class="headerlink" title="demo简介"></a>demo简介</h1><p>对于高阶组件的概念和相关内容不再概述,不懂得可以查一查。</p><p>本demo是基于高阶函数以函数为子组件的模式写的,该模式的优点是:没有对被增强的组件有任何props的要求,只是传递一个参数,至于参数的使用完全由子组件的函数决定。</p><p>核心代码如下:</p><pre><code class="JavaScript">class Countdown extends Component { constructor () { super(...arguments) this.state = { count: this.props.startCount } } componentDidMount () { this.intervalHandle = setInterval(() => { const newCount = this.state.count - 1 if (newCount >= 0) { this.setState({count: newCount}) } else { window.clearInterval(this.intervalHandle) } }, 1000) } componentWillUnmount () { if(this.intervalHandle) { window.clearInterval(this.intervalHandle) } } render () { return this.props.children(this.state.count) }}Countdown.propTypes = { children: PropTypes.func.isRequired, startCount: PropTypes.number.isRequired}export default Countdown</code></pre><p>这是最初版本的代码。</p><p>然后使用方法如下:</p><pre><code class="JavaScript"><Countdown startCount={60}> { (count) => <div>{count}</div> }</Countdown></code></pre><p>上面代码中的<code>div</code>也可以替换成任何的组件,用法十分灵活。</p><h1 id="关于性能优化"><a href="#关于性能优化" class="headerlink" title="关于性能优化"></a>关于性能优化</h1><p>以函数为子组件的模式的缺点就是:性能难以优化。</p><ul><li>问题一:每次外层组件渲染组件都要调用函数,无法利用shouldComponentUpdate()来避免渲染浪费的问题。</li><li>问题二:虽然高阶函数函数形式的子组件可以直接使用shouldComponentUpdate(),但是每次渲染都会重新定义一个新的函数。由于两个函数不一样,每次必定会重新渲染。</li></ul><p>解决方法:</p><ul><li>外层组件可以定制属于外层组件自己的shouldComponentUpdate()来避免渲染浪费,但是,不同的外层组件要定制不同的shouldComponentUpdate(),无疑增加了不必要的麻烦。</li><li><p>对于高阶组件本身,每次使用时,不可以使用匿名函数,即本demo中的箭头函数。需要在高阶组件里定义一个showCount函数,接受一个count参数。</p><p> 然后就把showCount作为子组件使用。代码如下:</p><pre><code class="JavaScript"> <Countdown startCount={60}> { showCount } </Countdown></code></pre></li></ul><p>这样的话代码就失去了灵活性。</p><p>虽然以函数为子组件的模式有潜在性能问题,但依然阻挡不了它的灵广泛使用。我们熟知的react-motion动画库中大量使用这种模式,而且用户也没有反应性能问题,所以,以函数为子组件是性能和灵活性恰当折中的模式。</p>]]></content>
<categories>
<category> HOC </category>
</categories>
<tags>
<tag> HOC </tag>
<tag> react </tag>
</tags>
</entry>
<entry>
<title>数组中的逆序对思路分析</title>
<link href="/2017/11/21/%E5%89%91%E6%8C%87offer%E4%B9%8B%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%80%86%E5%BA%8F%E5%AF%B9%E6%80%9D%E8%B7%AF%E5%88%86%E6%9E%90/"/>
<url>/2017/11/21/%E5%89%91%E6%8C%87offer%E4%B9%8B%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%80%86%E5%BA%8F%E5%AF%B9%E6%80%9D%E8%B7%AF%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<h1 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h1><blockquote><p>在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。</p></blockquote><a id="more"></a><h1 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h1><p>我们以数组{7, 5, 6, 4}为例来分析统计逆序对的过程。每次扫描到一个数字的时候,我们不能拿它和后面的每一个数字作比较,否则时间复杂度就是O(n^5),因此我们可以考虑先比较两个相邻的数字。</p><p><img src="http://oy5zqmv54.bkt.clouddn.com/51.png" alt="图5.1"></p><p>如图5 . 1 ( a )和图5.1 ( b)所示,我们先把数组分解成两个长度为2的子数组, 再把这两个子数组分别拆分成两个长度为1 的子数组。接下来一边合并相邻的子数组, 一边统计逆序对的数目。在第一对长度为1 的子数组{7}、{5}中7 大于5 , 因此(7, 5)组成一个逆序对。同样在第二对长度为1 的子数组{6}、{4}中也有逆序对(6, 4)。由于我们已经统计了这两对子数组内部的逆序对,因此需要把这两对子数组排序( 图5.1 ( c)所示),以免在以后的统计过程中再重复统计。<br>逆序对的总数=左边数组中的逆序对的数量+右边数组中逆序对的数量+左右结合成新的顺序数组时中出现的逆序对的数量;</p><p><img src="http://oy5zqmv54.bkt.clouddn.com/second.png" alt="图5.2"></p><blockquote><p>注 图中省略了最后一步, 即复制第二个子数组最后剩余的4 到辅助数组中.</p><ol><li>P1指向的数字大于P2指向的数字,表明数组中存在逆序对.P2 指向的数字是第二个子数组的第二个数字, 因此第二个子数组中有两个数字比7 小. 把逆序对数目加2,并把7 复制到辅助数组,向前移动P1和P3. </li><li>P1指向的数字小子P2 指向的数字,没有逆序对.把P2 指向的数字复制到辅助数组,并向前移动P2 和P3 . </li><li>P1指向的数字大于P2 指向的数字,因此存在逆序对. 由于P2 指向的数字是第二个子数组的第一个数字,子数组中只有一个数字比5 小. 把逆序对数目加1 ,并把5复制到辅助数组,向前移动P1和P3.接下来我们统计两个长度为2 的子数组之间的逆序对。我们在图5.2 中细分图5.1 ( d)的合并子数组及统计逆序对的过程。</li></ol></blockquote><p> 我们先用两个指针分别指向两个子数组的末尾,并每次比较两个指针指向的数字。如果第一个子数组中的数字大于第二个子数组中的数字,则构成逆序对,并且逆序对的数目等于第二个子数组中剩余数字的个数(如图5.2 (a)和图5.2 (c)所示)。如果第一个数组中的数字小于或等于第二个数组中的数字,则不构成逆序对(如图5.2 (b)所示〉。每一次比较的时候,我们都把较大的数字从·后往前复制到一个辅助数组中去,确保辅助数组中的数字是递增排序的。在把较大的数字复制到辅助数组之后,把对应的指针向前移动一位,接下来进行下一轮比较。<br> 经过前面详细的诗论, 我们可以总结出统计逆序对的过程:先把数组分隔成子数组, 先统计出子数组内部的逆序对的数目,然后再统计出两个相邻子数组之间的逆序对的数目。在统计逆序对的过程中,还需要对数组进行排序。如果对排序贺,法很熟悉,我们不难发现这个排序的过程实际上就是归并排序 </p><h1 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h1><pre><code>class Solution {public:int InversePairs(vector<int> data) { if(data.size()<=1) return 0;//如果少于等于1个元素,直接返回0 int* copy=new int[data.size()]; //初始化该数组,该数组作为存放临时排序的结果,最后要将排序的结果复制到原数组中 for(unsigned int i=0;i<data.size();i++) copy[i]=0; //调用递归函数求解结果 int count=InversePairCore(data,copy,0,data.size()-1); delete[] copy;//删除临时数组 return count; } //程序的主体函数 int InversePairCore(vector<int>& data,int*& copy,int start,int end) { if(start==end) { copy[start]=data[start]; return 0; } //将数组拆分成两部分 int length=(end-start)/2;//这里使用的下标法,下面要用来计算逆序个数;也可以直接使用mid=(start+end)/2 //分别计算左边部分和右边部分 int left=InversePairCore(data,copy,start,start+length)%1000000007; int right=InversePairCore(data,copy,start+length+1,end)%1000000007; //进行逆序计算 int i=start+length;//前一个数组的最后一个下标 int j=end;//后一个数组的下标 int index=end;//辅助数组下标,从最后一个算起 int count=0; while(i>=start && j>=start+length+1) { if(data[i]>data[j]) { copy[index--]=data[i--]; //统计长度 count+=j-start-length; if(count>=1000000007)//数值过大求余 count%=1000000007; } else { copy[index--]=data[j--]; } } for(;i>=start;--i) { copy[index--]=data[i]; } for(;j>=start+length+1;--j) { copy[index--]=data[j]; } //排序 for(int i=start; i<=end; i++) { data[i] = copy[i]; } //返回最终的结果 return (count+left+right)%1000000007; }};</code></pre>]]></content>
<categories>
<category> 算法 </category>
</categories>
<tags>
<tag> 算法 </tag>
</tags>
</entry>
<entry>
<title>整数中一的个数笔记</title>
<link href="/2017/11/18/%E6%95%B4%E6%95%B0%E4%B8%AD%E4%B8%80%E7%9A%84%E4%B8%AA%E6%95%B0-%E5%89%91%E6%8C%87offer-%E7%AC%94%E8%AE%B0/"/>
<url>/2017/11/18/%E6%95%B4%E6%95%B0%E4%B8%AD%E4%B8%80%E7%9A%84%E4%B8%AA%E6%95%B0-%E5%89%91%E6%8C%87offer-%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<h1 id="C-代码"><a href="#C-代码" class="headerlink" title="C++代码"></a>C++代码</h1><pre><code class="JavaScript">int countDigitOne(int n) { int ones = 0; for (long long m = 1; m <= n; m *=10) ones += (n/m +8) /10 * m + (n/m % 10 == 1) * (n%m + 1); return ones;} </code></pre><a id="more"></a><h1 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h1><p>为了使解释更加完美,我们需要使用变量 <code>a</code>,<code>b</code>。</p><pre><code class="JavaScript">int countDigitOne(int n) { int ones = 0; for (long long m = 1; m <= n; m *=10) int a = n/m, b = n%m; ones += (a +8) /10 * m + (a % 10 == 1) * (b + 1); return ones;} </code></pre><p>通过使<code>m</code>乘1,10,100,1000…来遍历数字的位数。</p><p>对于每一位,将十进制数拆分成两个部分。例如:在<code>m = 100</code>分析百分位时,将<code>n=3141592</code>分成<code>a=31415</code>和<code>b=92</code>。然后我们就可以知道从“0000”到“3141”<code>n</code>的百分位为1,即3142次。每次都是一个周期。由于是百分位,所以每一周期长度为100。因此,百分位出现1的次数为<code>(a / 10 + 1) * 100</code>。</p><p>接下来考虑百分位,即,<code>m = 1000</code>.可以将<code>n=3141592</code>分成<code>a=3141</code>和<code>b=592</code>。然后我们就可以知道从“0000”到“314”<code>n</code>的百分位为1,即315次。并且每一个周期都包含1000个数。但是由于千分位是1,在最后一个周期里不是一千个数而是593个数(从“000”到”592“)。因此,千分位出现1的次数为<code>(a / 10 * 1000) + (b + 1)</code>。</p><p>当前的位数/位置结果在0,1,>=2之间的情况可以很简单的在一个表达式里实现。用<code>(a+8)/10</code>可以得到完整的一个周期,用<code>a%10 == 1</code>来判断是否添加部分周期。</p>]]></content>
<categories>
<category> 算法 </category>
</categories>
<tags>
<tag> 算法 </tag>
</tags>
</entry>
<entry>
<title>Ajax与Comet</title>
<link href="/2017/11/15/Ajax%E4%B8%8EComet/"/>
<url>/2017/11/15/Ajax%E4%B8%8EComet/</url>
<content type="html"><![CDATA[<h1 id="Ajax"><a href="#Ajax" class="headerlink" title="Ajax"></a>Ajax</h1><p>Ajax技术的核心是XMLHttpRequest对象(XHR)。</p><h2 id="XMLHttpRequest对象-XHR"><a href="#XMLHttpRequest对象-XHR" class="headerlink" title="XMLHttpRequest对象(XHR)"></a>XMLHttpRequest对象(XHR)</h2><p>IE5是第一款引入XHR对象的浏览器,在IE中可能会遇到三种不同版本的XHR。因此,要在IE中使用XHR对象,可以向如下代码一样:</p><pre><code>function createXHR(){ if(typeof XMLHttpRequest != "undefined"){ return new XMLHttpRequest(); }else if (typeof ActiveXObeject != "undefined"){ if(typeof arguments.callee.activeXString != "string"){ var versions = { "MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp" }, len, i; for(i=0, len=arguments.length; i < len; i++){ try{ new ActiveXObkect(versions[i]); arguments.callee.activeXString = versions[i]; break; } catch (ex){ // 跳过 } } } return new ActiveXObject(arguments.callee.activeXstring); } else{ return new Error("No XHR object available.") }}</code></pre><p>这个函数先检测原生XHR对象是否存在,如果存在就返回它的一个实例。如果原生对象不存在,则检测ActiveX对象。如果都不存在,就抛出一个错误。最后所有浏览器中就可以创建XHR对象了。</p><pre><code>var xhr = createXHR();</code></pre><a id="more"></a><h2 id="XHR用法"><a href="#XHR用法" class="headerlink" title="XHR用法"></a>XHR用法</h2><p>在使用XHR对象时,要调用的第一个方法是:open(),它接受三个参数:请求类型,请求URL,请求是否异步。</p><pre><code>xhr.open("method", "url", false)xhr.send(null);</code></pre><p>下面的send()方法接受一个参数,即作为请求主体发送的数据,若没有就必须传入null。</p><p>上面是同步请求,在收到响应后,响应的数据会自动填充XHR对象的属性,相关属性如下:</p><ul><li>responseText: 作为响应主体被返回的文本</li><li>responseXML: 如果响应内容类型为“text/xml”或“application/xml”,这个属性将保存包含着响应数据的XML DOM文档;其他类型值为null</li><li>status: 响应的HTTP状态</li><li>statusText:HTTP状态的说明</li></ul><p>对于异步请求,可以检测XHR的readyState属性,该属性表示请求/响应过程中的当前活动阶段。取值如下:</p><ul><li>0:为初始化。尚未调用open()方法。</li><li>1:启动。已调用open()方法,未调用send()方法。</li><li>2:发送。已调用send()方法,但未收到响应。</li><li>3:接收。已经接收到部分响应数据。</li><li>4:完成。已经接收到全部响应数据,而且已经可以在客户端使用。</li></ul><p>而且在接收到响应之前可以调用absort()方法取消异步请求。</p><pre><code>xhr.absort();</code></pre><p>之后应该对XHR对象进行解引用操作。由于内存原因不建议重用XHR对象。</p><h2 id="HTTP头部信息"><a href="#HTTP头部信息" class="headerlink" title="HTTP头部信息"></a>HTTP头部信息</h2><p>每个HTTP请求和响应都会带有相应的头部信息。XHR对象也提供了操作请求头部和响应头部信息的方法。</p><p>在发送XHR请求的同时,还会发送头部信息。</p><p>要成功发送头部信息必须在调用open()方法之后且在调用send()方法之前调用setRequestHeader().</p><p>要取得相应的响应头部信息可以调用getResponseHeader()方法;取得一个包含所有头部信息的长字符串可以调用getAllResponseHeader().</p><h1 id="Comet"><a href="#Comet" class="headerlink" title="Comet"></a>Comet</h1><p>Comet指的是一种更高级的Ajax技术(也有人称之为“服务器推送”)。它与Ajax不同,可以实现服务器向页面推送数据的功能。有两种实现Comet的方式:长轮询和流。</p><p>长轮询:页面发起一个到服务器的请求,然后服务器一直保持连接打开,直到有数据可发送。发送完数据后,随即由发起一个到服务器的请求。这一过程在页面打开期间一直持续不断。</p><p>流:浏览器向服务器发送一个请求,而服务器保持连接打开,然后周期性地想浏览器发送数据。</p><p>浏览器社区认为Comet是未来Web的一个重要组成部分,为了化简这一技术,又为Comet创建了两个新接口。</p><h2 id="服务器发送事件"><a href="#服务器发送事件" class="headerlink" title="服务器发送事件"></a>服务器发送事件</h2><p>SSE(Server-Sent Events, 服务器发送事件)支持:短轮询、长轮询和HTTP流。</p><h2 id="Web-Socket"><a href="#Web-Socket" class="headerlink" title="Web Socket"></a>Web Socket</h2><p>Web Socket的目标是在一个单独的持久连接上提供全双工、双向通信。它可以在服务器与客户端之间发送非常少量的数据,而不必担心HTTP那样字节级的开销。使用标准的HTTP服务器无法实现Web Socket,只有支持Web Socket协议的专门服务器才能实现。</p><p>在JavaScript中创建了Web Socket之后,会有一个HTTP请求发送到浏览器以发起连接。在取得服务器响应后,建立的连接会使用HTTP从HTTP协议交换为Web Socket协议。</p><p>由于Web Socket使用了自定义的协议,所以URL模式也略有不同。连接是ws://和wss://。</p>]]></content>
<categories>
<category> Ajax </category>
</categories>
<tags>
<tag> Ajax </tag>
<tag> Comet </tag>
</tags>
</entry>
<entry>
<title>form脚本特性及其中的HTML5新特性</title>
<link href="/2017/11/13/form%E8%84%9A%E6%9C%AC%E7%89%B9%E6%80%A7%E5%8F%8A%E5%85%B6%E4%B8%AD%E7%9A%84HTML5%E6%96%B0%E7%89%B9%E6%80%A7/"/>
<url>/2017/11/13/form%E8%84%9A%E6%9C%AC%E7%89%B9%E6%80%A7%E5%8F%8A%E5%85%B6%E4%B8%AD%E7%9A%84HTML5%E6%96%B0%E7%89%B9%E6%80%A7/</url>
<content type="html"><![CDATA[<h1 id="表单"><a href="#表单" class="headerlink" title="表单"></a>表单</h1><h2 id="表单基础知识"><a href="#表单基础知识" class="headerlink" title="表单基础知识"></a>表单基础知识</h2><p>在HTML中表单是由<code><form></code>元素来表示,而在JavaScript中,表单则对应的是HTMLFormElement类型,因而与其他HTML元素具有相同的默认属性。但HTMLFormElement也有自己的独有的属性和方法。详见:<a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement" target="_blank" rel="noopener">HTMLFormElement</a></p><p>取得<code><form></code>元素引用常见的方式有:</p><ol><li>为其添加id特性,然后通过<code>var form = document.getElementById("form1")</code>来找到它。</li><li><p>通过<code>document.forms</code>来取得页面中的所有表单。</p><ul><li><code>var firstForm = document.form[0]</code>取的页面中第一个表单</li><li><code>var myForm = document.form["form2"]</code>取得页面中名为“form2”的表单。</li></ul></li></ol><a id="more"></a><h2 id="提交表单"><a href="#提交表单" class="headerlink" title="提交表单"></a>提交表单</h2><p>提交表单可以通过<code><input></code> <code><button></code>来定义提交按钮。这种方法提交表单时,浏览器会在请求发送给服务器前触发submit事件,我们可以通过addHandler()来验证表单数据。preventDefault()可以用来阻止表单的提交。</p><p>表单提交也可以通过JavaScript调用submit()方法提交,但是不会触发submit事件,所以要记得在调用此方法之前验证表单。</p><h2 id="重置表单"><a href="#重置表单" class="headerlink" title="重置表单"></a>重置表单</h2><p>用户点击重置按钮时,表单被重置。</p><p>首先,重置可以通过使用type值为“reset”的 <code><input></code> <code><button></code> 创建。单机后会触发reset事件,在这个时事件内,我们可以在必要时通过preventDefault()取消重置操作。</p><p>与提交表单不一样,JavaScript中调用reset()方法也会触发submit事件。</p><h2 id="表单注意事项"><a href="#表单注意事项" class="headerlink" title="表单注意事项"></a>表单注意事项</h2><p>用户可能会重复点击表单提交按钮。比如:支付交费时,多次点击,导致费用翻倍。最常用的解决方法就是在第一次点击后就禁用submit事件,。只要监听submit事件,并在该事件放生时禁用提交按钮即可。<br>举个栗子:</p><pre><code>EventUtil.addHandler(form,"submit",function(event){ event=EventUtil.getEvent(event); var target=EventUtil.getTarget(event); var btn=target.elements["submit-btn"]; btn.disable=true;})</code></pre><p><strong>注意</strong>:不能通过onclick事件处理程序来实现这个功能,因为不同浏览器submit事件和click事件触发先后顺序不一样,有可能先触发click事件来禁用提交按钮,结果表单永远都不会提交。(但是这种方式不适合表单中不包含提交按钮的情况,只有在包含的情况下,才有可能触发表单的submit事件)</p><h1 id="表单中的HTML5特性"><a href="#表单中的HTML5特性" class="headerlink" title="表单中的HTML5特性"></a>表单中的HTML5特性</h1><ul><li>HTML5为表单字段新增了一个antofocus属性</li><li>为取得选择的文本新增selectStart和selectEnd属性</li><li>为取得选择文本框部分文本提供了setSelectionRange()方法</li><li>将剪贴板事件纳入规范<ul><li>beforecopy</li><li>copy</li><li>beforecut</li><li>cut</li><li>beforepaste</li><li>paste</li></ul></li><li>为表单验证添加约束API:<ul><li>必填字段:require</li><li>为<code><input></code>元素的type添加了email、url、number、range、datetime、datetime-local、data、montn、week、time等值</li><li>为文本字段新增pattern属性</li><li>checkValidity()检测表单中某个字段是否有效</li><li>novalidate属性告诉表单不进行验证</li></ul></li></ul>]]></content>
<categories>
<category> form </category>
</categories>
<tags>
<tag> form </tag>
<tag> HTML5 </tag>
</tags>
</entry>
<entry>
<title>JavaScript高级程序设计DOM拓展之HTML5</title>
<link href="/2017/10/23/JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1DOM%E6%8B%93%E5%B1%95%E4%B9%8BHTML5/"/>
<url>/2017/10/23/JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1DOM%E6%8B%93%E5%B1%95%E4%B9%8BHTML5/</url>
<content type="html"><![CDATA[<p>本文章讨论HTML5中DOM节点相关的内容</p><p>对于传统THML而言HTML5是一个叛逆。所有之前的版本对JavaScript接口的描述都不过三言两语,主要篇幅都用于定义标记,与JavaScript相关的内容一概交由DOM规范去定义。</p><p>而HTML5规范则围绕如何使用新增标记定义了大量JavaScript API。其中一些API与DOM重叠,定义了浏览器应该支持的DOM拓展。</p><a id="more"></a><h1 id="与类相关的拓充"><a href="#与类相关的拓充" class="headerlink" title="与类相关的拓充"></a>与类相关的拓充</h1><ol><li>getElementsByClassName():接受一个参数,返回带有指定类的所有元素的nodeList。</li><li><p>classList属性:给元素添加、删除和替换类名。该属性还拥有以下方法:</p><ul><li>add():将给定字符串添加到列表中,如果已存在就不添加。</li><li>contains():判断列表中是否存在给定的值。返回true则存在;false则不存在。</li><li>remove():从列表中删除指定的字符串。</li><li>toggle():如果列表里已存在给定值删除,不存在的添加给定值。</li></ul></li></ol><h1 id="焦点管理"><a href="#焦点管理" class="headerlink" title="焦点管理"></a>焦点管理</h1><p>HTML5添加了辅助管理DOM焦点的功能。</p><ul><li>document.activeElement属性:这个属性会始终引用DOM中当前获得了焦点的元素,默认情况下,文档刚加载完,其中保存的是document.body元素的引用。</li><li>document.hasFocus()方法:确定文档是否获得焦点。</li></ul><h1 id="HTMLDocument的变化"><a href="#HTMLDocument的变化" class="headerlink" title="HTMLDocument的变化"></a>HTMLDocument的变化</h1><ul><li><p>readyState属性:有两个可能的值:</p><ul><li>loading:正在加载文档</li><li>complete:已经加载完文档</li></ul></li><li><p>兼容模式:为document添加了一个名为compatMode的属性。</p><ul><li>在标准模式下,document.compatMode的值等于“CSS1Compat”</li><li>在混杂模式下,document.compatMode的值等于“BackCompat”</li></ul></li><li><p>新增document.head属性:引用文档的<head>元素。</head></p></li></ul><h1 id="字符集属性"><a href="#字符集属性" class="headerlink" title="字符集属性"></a>字符集属性</h1><ul><li>charset属性:表示文档中实际使用的字符集,也可以用来指定新的字符集。</li><li>defaultCharset属性:表示根据默认浏览器及操作系统的设置,当前文档默认的字符集应该是什么。</li></ul><h1 id="自定义数据属性"><a href="#自定义数据属性" class="headerlink" title="自定义数据属性"></a>自定义数据属性</h1><p>HTML5规定可以为元素添加非标准的属性,但要添加前缀data-,目的是为元素提供与渲染无关的信息,或者提供语义信息。</p><pre><code>var div = document.getElementById("myDiv");var appId = div.dataset.appId;var myName = div.dataset.myname;div.dataset.appId = 23456;div.dataset.myname = "Michael";</code></pre><h1 id="插入标记"><a href="#插入标记" class="headerlink" title="插入标记"></a>插入标记</h1><ol><li><p>innerHTML属性</p><p> 在读模式下,它返回与调用元素的所有子节点(包括元素、注释和文本节点)对应的HTML标记。在写模式下,innerHTML会根据指定的值创建新的DOM树,然后用这个DOM树完全替换元素原先的所有子节点。注意:写模式下,设置的包含HTML的字符串值与解析后innerHTML的值大不相同。</p><p> 不支持innerHTML属性的元素有:\<col>、\<colgroup>、\<frameset>、\<head>、\<html>、\<style>、\<table>、\<tbody>、\<thead>、\<tfoot>、\<tr>。</p></li><li><p>outerHTML属性</p><p> 在读模式下,它返回调用它的元素及所有子节点的HTML标签。在写模式下,会根据指定的HTML字符串创建新的DOM子树,然后用这个DOM子树完全替换掉用元素。</p></li><li><p>insertAdjacentHTML()方法</p><p> 插入标记的最后一个方式是insertAdjacentHTML()方法。它接受两个参数:插入的位置和要插入的HTML文本。第一个参数必须是下列值之一:</p><ul><li>“beforebegin”:当前元素之前插入一个紧邻的同辈元素</li><li>“afterbegin”:当前元素之下插入一个新的子元素或在第一个子元素之前在插入新的子元素。</li><li>“beforeend”:当前元素之下插入一个新的子元素或在最后一个子元素之前在插入新的子元素。</li><li>“afterend”:当前元素之后插入一个紧邻的同辈元素</li></ul></li></ol><ol start="4"><li><p>内存与性能问题</p><p> 如果用之前的属性将某事件处理程序的元素从文档树中删除,元素与事件处理程序之间的绑定关系在内存中并没有被删除。因此,在使用innerHTML、outerHTML属性和insertAdjacentHTML()方法时,最好先手工删除要被替换元素的所有事件处理和JavaScript对象属性。<br> 在使用innerHTML时,最好单独构建字符串,然后再一次性的将结果字符串赋值给innerHTML。</p></li></ol></style></html></head></frameset></colgroup></p></li></ol>]]></content>
<categories>
<category> HTML5DOM </category>
</categories>
<tags>
<tag> HTML5,DOM </tag>
</tags>
</entry>
<entry>
<title>JavaScript高级程序设计节点层次之Node类型</title>
<link href="/2017/10/22/JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1%E8%8A%82%E7%82%B9%E5%B1%82%E6%AC%A1%E4%B9%8BNode%E7%B1%BB%E5%9E%8B/"/>
<url>/2017/10/22/JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1%E8%8A%82%E7%82%B9%E5%B1%82%E6%AC%A1%E4%B9%8BNode%E7%B1%BB%E5%9E%8B/</url>
<content type="html"><![CDATA[<h1 id="节点层次"><a href="#节点层次" class="headerlink" title="节点层次"></a>节点层次</h1><pre><code><html> <head> <title>Simple Page</title> </head> <body> <p>HEllo World!</p> </body></html></code></pre><p>可以将这个HTML文档表示为一个层次结构。如下图:</p><p><img src="/images/HTMLnode.png" alt=""></p><p>文档节点是每个文档的根节点。节点对应的元素就称之为文档元素。一般的HTML文档的文档元素是<html>元素。</html></p><p>每一段标记都可以通过树中的一个节点来表示:HTML元素通过元素节点表示,特性通过特性节点表示,文档类型通过文档类型节点表示,注释则通过注释节点来表示。一共有12中节点类型,这些基类都继承自一个基类型。</p><a id="more"></a><h2 id="Node类型"><a href="#Node类型" class="headerlink" title="Node类型"></a>Node类型</h2><p>DOM1级定义了一个Node接口,该接口将由DOM中的所有节点类型实现。这个Node接口在JavaScript中是作为Node类型实现的(除了IE其他浏览器都可以访问到)。JavaScript中的所有节点类型都继承自Node类型,因此所有节点类型都共享着相同的基本属性和方法。</p><ul><li>Node.ELEMENT_NODE(1)</li><li>Node.ATTRIBUTE_NODE(2)</li><li>Node.TEXT_NODE(3)</li><li>Node.CDATA_SECTION_NODE(4)</li><li>Node.ENTITY_REFERENCE_NODE(5)</li><li>Node.ENTITY_NODE(6)</li><li>Node.PROCESSING_INSTRUCTION_NODE(7)</li><li>Node.COMMENT_NODE(8)</li><li>Node.DOCUMENT_NODE(9)</li><li>Node.DOCUMENT_TYPE_NODE(10)</li><li>Node.DOCUMENT_FRAGMENT_NODE(11)</li><li>Node.NOTETION_NODE(12)</li></ul><h3 id="nodeName和nodeValue属性"><a href="#nodeName和nodeValue属性" class="headerlink" title="nodeName和nodeValue属性"></a>nodeName和nodeValue属性</h3><p>这两个值完全取决于节点类型。nodeName中保存的是标签名,nodeValue的值在元素节点中为null。</p><h3 id="节点关系"><a href="#节点关系" class="headerlink" title="节点关系"></a>节点关系</h3><p>每个节点都有一个childNodes属性,其中保存着一个NodeList对象(类数组对象),它实际上是基于DOM结构动态执行查询的结果;每个节点也都拥有previousSibling和nextSibling属性,第一个节点previousSibling属性值为null,最后一个节点nextSibling属性值为null。每个节点也都有一个parentNode属性。</p><h3 id="操作关系"><a href="#操作关系" class="headerlink" title="操作关系"></a>操作关系</h3><ul><li>appendChild()用于向childNodes列表的末尾添加一个节点。添加节点后,更新关系指针,更新完成后,返回新增节点。</li></ul><pre><code>var returnedNode = someNode.appendChild(newChild);</code></pre><ul><li>insertBefore()用于把节点放入特定的位置,这个方法接受两个参数:要插入的节点和作为参照的节点;插入节点后,被插入的节点会变成参照节点的前一个同胞节点(previousSibling),同时方法被返回;如果参照节点是null,则与appendChild()执行相同的操作。</li></ul><pre><code>var returnedNode = someNode.insertBefore(newChild, someNode.firstNode);</code></pre><ul><li>replaceChild()方法接受的两个参数是:要插入的节点和要替换的节点;要替换的节点将由这个节点返回并从文档树中被移除,同时要插入的节点占据其位置。</li></ul><pre><code>var returnedNode = someNode.replaceChild(newChild);</code></pre><ul><li>removeChild()方法接受一个参数:要移除的节点。;被移除的节点将成为方法的返回值。</li></ul><pre><code>var returnedNode = someNode.removeChild(someNode.firstChild);</code></pre><ul><li>cloneNode()和 normalize() 是所有类型的节点都有的属性。<ul><li>cloneNode()方法接受一个布尔值参数。为true时执行深复制,即复制节点及其整个子节点数;为false时执行浅复制,即只复制节点本身。 </li><li>normalize()方法唯一作用就是处理文档树中的文本节点。 </li></ul></li></ul><h2 id="练习"><a href="#练习" class="headerlink" title="练习"></a>练习</h2><p>最近看到一个关于DOM操作的京东前端面试题,点击删除,删除本条信息,输入框里输入添加一条新的信息。附上源码:</p><pre><code><!doctype html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>增删</title> <style type="text/css"> body, html { padding: 0; margin: 0; font-size: 14px; color: #000000; } table { border-collapse: collapse; width: 100%; table-layout: fixed; } thead { background: #3d444c; color: #ffffff; } td, th { border: 1px solid #e1e1e1; padding: 0; height: 30px; line-height: 30px; text-align: center; } .add{ width: 100%; margin: 20px; } </style></head><body><div class="add"> <form id="form"> <input type="text" name="name" placeholder="商品名称"> <input type="text" name="price" placeholder="价格"> <input type="button" name="submit" value="添加" onclick="add()"> </form></div><table id="jsTrolley"> <thead> <tr> <th>名称</th> <th>价格</th> <th>操作</th> </tr> </thead> <tbody> <tr> <td>产品1</td> <td>10.00</td> <td><a href="javascript:void(0);">删除</a></td> </tr> <tr> <td>产品2</td> <td>30.20</td> <td><a href="javascript:void(0);">删除</a></td> </tr> <tr> <td>产品3</td> <td>20.50</td> <td><a href="javascript:void(0);">删除</a></td> </tr> </tbody> <tfoot> <tr> <th>总计</th> <td colspan="2">60.70(3件商品)</td> </tr> </tfoot></table><script type="text/javascript"> var tableObj = document.getElementById('jsTrolley') var sum = 60.70 /** * 添加一个商品 */ function add () { var form = document.getElementById('form') var name = form.name.value var price = form.price.value var add = document.createElement('tr') var src = document.getElementsByTagName('tbody') var s = src[0] /**添加商品并绑定函数**/ add.innerHTML = '<td>' + name + '</td><td>' + price + '</td><td><a href=\'javascript:void(0);\' onclick=\'foo(s,this)\'>删除</a></td>' src[0].appendChild(add) sum += parseFloat(price) var num = tableObj.rows.length - 2 tableObj.rows[tableObj.rows.length - 1].cells[1].innerText = sum + '(' + num + '件商品)' name = null price = null } /** * 绑定初始化数据 */ function bind () { var arr = document.getElementsByTagName('a') var src = document.getElementsByTagName('tbody') s = src[0] var len = arr.length for (var i = 0; i < len; i++) { arr[i].setAttribute('onclick', 'foo(s,this)') } } /** * 删除商品函数 * @param x * @param y */ function foo (x, y) { var tar = y.parentNode.parentNode var v = y.parentNode.previousElementSibling.innerHTML sum -= parseFloat(v) x.removeChild(tar) var num = tableObj.rows.length - 2 tableObj.rows[tableObj.rows.length - 1].cells[1].innerText = sum.toFixed(2) + '(' + num + '件商品)' } function show () { var tableRowInfo = '' for (var i = 0; i < tableObj.rows.length; i++) { for (var j = 0; j < tableObj.rows[i].cells.length; j++) { tableRowInfo += tableObj.rows[i].cells[j].innerText + ' ' } console.log(tableRowInfo) // 打印每行信息 tableRowInfo = '' } } show() bind()</script></body></html></code></pre>]]></content>
<categories>
<category> JavaScript高级程序设计 </category>
</categories>
<tags>
<tag> DOM </tag>
</tags>
</entry>
<entry>
<title>React:setState详解</title>
<link href="/2017/10/19/React%EF%BC%9AsetState%E8%AF%A6%E8%A7%A3/"/>
<url>/2017/10/19/React%EF%BC%9AsetState%E8%AF%A6%E8%A7%A3/</url>
<content type="html"><![CDATA[<p>首先我们来看一个栗子:</p><pre><code class="JavaScript">class Eg extends Component { contructor () { super() this.state = { value: 0, index: 0 } } componentDidMount () { this.setState({value: this.state.value + 1}) console.log(this.state.value) // 第一次输出 this.setState({value: this.state.value + 1}) console.log(this.state.value) // 第二次输出 setTimeout(() => { this.setState({value: this.state.value + 1}) console.log(this.state.value) // 第三次输出 this.setState({value: this.state.value + 1}) console.log(this.state.value) // 第四次输出 }, 0); this.refs.button.addEventListener('click', this.click) } click = () => { this.setState({value: this.state.index + 1}) this.setState({value: this.state.index + 1}) } render () { return ( <div><span>value: {this.state.value}index: {this.props.index}</span> <button ref="button" onClick={this.click}>点击</button> </div> ) }}</code></pre><p>这四次输出,按常理来说分别是: 1,2,3,4。但是,实际输出为: 0, 0, 2, 3。</p><a id="more"></a><p>在分析之前,需要知道setState的一些关键点。</p><h1 id="setState的关键点"><a href="#setState的关键点" class="headerlink" title="setState的关键点"></a>setState的关键点</h1><ol><li><p>setState不会立刻改变React组件中state的值</p></li><li><p>setState通过引发一次组件的更新过程来引发重新绘制</p><p> 重绘指的就是引起React的更新生命周期函数4个函数:</p><ul><li>shouldComponentUpdate(被调用时this.state没有更新;如果返回了false,生命周期被中断,虽然不调用之后的函数了,但是state仍然会被更新)</li><li>componentWillUpdate(被调用时this.state没有更新)</li><li>render(被调用时this.state得到更新)</li><li>componentDidUpdate</li></ul></li><li><p>多次setState函数调用产生的效果会合并。</p><pre><code class="JavaScript"> this.setState({name: 'Pororo'}) this.setState({age: 20})</code></pre><pre><code class="JavaScript"> this.setState({name: 'Pororo',age: 20})</code></pre><p> 上面两块代码的效果是一样的。如果每次调用都引发一次生命周期更新,那性能就会消耗很大了。所以,React会将多个this.setState产生的修改放进一个队列里,等差不多的时候就会引发一次生命周期更新。</p></li></ol><h1 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h1><blockquote><p>知道了这些之后就开始分析。</p></blockquote><h2 id="先来分析前两次setState:"><a href="#先来分析前两次setState:" class="headerlink" title="先来分析前两次setState:"></a>先来分析前两次setState:</h2><pre><code class="JavaScript">this.setState({value: this.state.val + 1});console.log(this.state.value); // 第一次输出this.setState({value: this.state.val + 1});console.log(this.state.value); // 第二次输出</code></pre><p>由于setState不会立即改变React组件中state的值,所以两次setState中this.state.value都是同一个值0,故而,这两次输出都是0。因而value只被加1。</p><p>既然这样,那么是不是可以直接操作<code>this.state</code>呢?比如:<code>this.state.value=this.state.value+1;</code>这样的确可以修改this.state.value的状态但是却不可以引发重复渲染。所以,就必须通过React设定的setState函数去改变this.state,从而引发重新渲染。 </p><h2 id="接下来分析setTimeout里面的两次setState:"><a href="#接下来分析setTimeout里面的两次setState:" class="headerlink" title="接下来分析setTimeout里面的两次setState:"></a>接下来分析setTimeout里面的两次setState:</h2><pre><code class="JavaScript">setTimeout(() => { this.setState({value: this.state.value + 1}) console.log(this.state.value) // 第三次输出 this.setState({value: this.state.value + 1}) console.log(this.state.value) // 第四次输出}, 0);</code></pre><p>这两次this.stat的值同步更新了,这是为什么的呢?</p><p>在React中,<strong>如果是由React引发的事件处理(比如:onClick引发的事件处理),调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.setState。</strong> “除此之外”指的是:绕过React通过addEventListener直接添加的事件处理函数和setTimeout/setInterval产生的异步调用。</p><p>那为什么会这样呢?</p><p><img src="/images/setState.png" alt=""></p><p>每次setState产生新的state会依次被存入一个队列,然后会根据<strong>isBathingUpdates</strong>变量判断是直接更新this.state还是放进dirtyComponent里回头再说。isBatchingUpdates默认是false,也就表示setState会同步更新this.state。但是,当React在调用事件处理函数之前就会调用batchedUpdates,这个函数会把isBatchingUpdates修改为true,造成的后果就是由React控制的事件处理过程setState不会同步更新this.state。</p><h1 id="同步更新state的办法—函数式setState"><a href="#同步更新state的办法—函数式setState" class="headerlink" title="同步更新state的办法—函数式setState"></a>同步更新state的办法—函数式setState</h1><p>如果this.setState的参数不是一个对象而是一个函数时,这个函数会接收到两个参数,第一个是当前的state值,第二个是当前的props,这个函数应该返回一个对象,这个对象代表想要对this.state的更改,换句话说,之前你想给this.setState传递什么对象参数,在这种函数里就返回什么对象。不过,计算这个对象的方法有些改变,不再依赖于this.state,而是依赖于输入参数state。</p><pre><code class="JavaScript">function increment(state, props) { return {count: state.count + 1};}</code></pre><pre><code class="JavaScript">function incrementMultiple() { this.setState(increment); this.setState(increment); this.setState(increment);}</code></pre><p>假如当前this.state.count的值是0,第一次调用this.setState(increment),传给increment的state参数是0,第二调用时,state参数是1,第三次调用是,参数是2,最终incrementMultiple让this.state.count变成了3。</p><p>对于多次调用函数式setState的情况,React会保证调用每次increment时,state都已经合并了之前的状态修改结果。</p><blockquote><p>要注意的是,在increment函数被调用时,this.state并没有被改变,依然,要等到render函数被重新执行时(或者shouldComponentUpdate函数返回false之后)才被改变。</p></blockquote><h1 id="把两种setState的用法混用,会有什么效果?"><a href="#把两种setState的用法混用,会有什么效果?" class="headerlink" title="把两种setState的用法混用,会有什么效果?"></a>把两种setState的用法混用,会有什么效果?</h1><p>我们把incrementMultiple改成这样。</p><pre><code class="JavaScript">function incrementMultiple() { this.setState(increment); this.setState(increment); this.setState({count: this.state.count + 1}); this.setState(increment);}</code></pre><p>在几个函数式setState调用中插入一个传统式setState调用,最后得到的结果是让this.state.count增加了2,而不是增加4。</p><p>这是因为React会依次合并所有setState产生的效果,虽然前两个函数式setState调用产生的效果是count加2,但是中间出现一个传统式setState调用,一下子强行把积攒的效果清空,用count加1取代。</p><p>所以,传统式setState与函数式setState一定不要混用。</p><p>总结自<a href="https://zhuanlan.zhihu.com/p/26069727" target="_blank" rel="noopener">setState何时同步更新状态</a>、<a href="https://zhuanlan.zhihu.com/p/25990883" target="_blank" rel="noopener">setState为什么不会同步更新组件状态</a></p>]]></content>
<categories>
<category> React </category>
</categories>
<tags>
<tag> setState </tag>
</tags>
</entry>
<entry>
<title>React之Diff</title>
<link href="/2017/10/17/React%E4%B9%8BDiff/"/>
<url>/2017/10/17/React%E4%B9%8BDiff/</url>
<content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>React之所以高效有一部分原因是因为Virtual DOM和diff算法的完美结合。React Diff会帮助我们计算出Virtual DOM真正变化的部分,只针对变化部分进行实际的DOM操作,并不对整个页面进行重新渲染,从而大大提高了渲染的效率。</p><p>但是React的Diff算法与传统的Diff算法又有不同,React技术团队对Diff算法进行优化。</p><p>传统Diff算法的复杂度达到了O(n^3)(n为树中的总结点数),而React将算法的复杂度降低到O(n),使Diff算法更加稳定高效。</p><a id="more"></a><h1 id="React-Diff"><a href="#React-Diff" class="headerlink" title="React Diff"></a>React Diff</h1><h2 id="tree-diff"><a href="#tree-diff" class="headerlink" title="tree diff"></a>tree diff</h2><p><strong>由于Web UI中DOM节点跨层级的移动特别少,可以忽略不计。</strong>React对树的算法进行优化,对树进行分层比较,只会比较两棵树的同一层级节点。当节点已不存在,则该层级节点被完全删除掉,不进行进一步比较。因而,一次遍历就可以完成整个DOM树的比较。</p><p><img src="/images/tree_diff.png" alt="tree——DOM"></p><p>献上源码</p><pre><code class="javascript">updateChildren: function(nextNestedChildrenElements, transaction, context) { updateDepth++; var errorThrown = true; try { this._updateChildren(nextNestedChildrenElements, transaction, context); errorThrown = false; } finally { updateDepth--; if (!updateDepth) { if (errorThrown) { clearQueue(); } else { processQueue(); } } }}</code></pre><p>那如果真的出现了DOM节点的跨层级移动操作,React diff会怎样操作?</p><p>此时,React diff的执行情况:createA->createB->createC->deleteA。因为React只考虑同层级节点的位置变换;而对于不同层级的节点,只有创建和删除。</p><p><img src="/images/DOM跨层级移动.png" alt="DOM跨层级移动"></p><p>所以,上图并没有把A节点及子节点移动,而是以A为根节点重新创建整个树。这是非常消耗性能的操作,因此,在项目里要避免。(DOM结构的稳定有助于性能的提升。)</p><h2 id="component-diff"><a href="#component-diff" class="headerlink" title="component diff"></a>component diff</h2><p>基本的思想是:<strong>拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。</strong></p><p>所以component diff分为两种情况:</p><ol><li>同一类型的组件,按照原策略继续比较virtual DOM tree;如果可以准确的知道virtual DOM有没有任何变化,就可以通过shouldComponentUpdate()来判断组件是否要进行diff。</li><li>不同类型的组件,则会将其判断为dirty component,并替换整个组件下的所有子节点。</li></ol><p><img src="/images/componentdiff.png" alt="componentdiff"></p><p>如上图,当component D变为component G时,即使他们结构是相似的,但是D和G是不同类型的组件,因而就不会比较二者的结构,而是直接删除D,重新创建D及其子节点。(component很少存在DOM tree相似的情况,所以就可以不考虑这种情况对性能的影响)</p><h2 id="element-diff"><a href="#element-diff" class="headerlink" title="element diff"></a>element diff</h2><p><strong>对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。</strong></p><p>React diff提供了三种节点操作:</p><ol><li>INSERT_MARKUP,新的 component 类型不在老集合里, 即是全新的节点,需要对新节点执行插入操作。</li><li>MOVE_EXISTING,在老集合有新 component 类型,且 element 是可更新的类型,generateComponentChildren 已调用 receiveComponent,这种情况下 prevChild=nextChild,就需要做移动操作,可以复用以前的 DOM 节点。</li><li>REMOVE_NODE,老 component 类型,在新集合里也有,但对应的 element 不同则不能直接复用和更新,需要执行删除操作,或者老 component 不在新集合里的,也需要执行删除操作。</li></ol><p>献上源码:</p><pre><code class="javascript">function enqueueInsertMarkup(parentInst, markup, toIndex) { updateQueue.push({ parentInst: parentInst, parentNode: null, type: ReactMultiChildUpdateTypes.INSERT_MARKUP, markupIndex: markupQueue.push(markup) - 1, content: null, fromIndex: null, toIndex: toIndex, });}function enqueueMove(parentInst, fromIndex, toIndex) { updateQueue.push({ parentInst: parentInst, parentNode: null, type: ReactMultiChildUpdateTypes.MOVE_EXISTING, markupIndex: null, content: null, fromIndex: fromIndex, toIndex: toIndex, });}function enqueueRemove(parentInst, fromIndex) { updateQueue.push({ parentInst: parentInst, parentNode: null, type: ReactMultiChildUpdateTypes.REMOVE_NODE, markupIndex: null, content: null, fromIndex: fromIndex, toIndex: null, });}</code></pre><p>如下图,老集合中包含节点:A、B、C、D,更新后的新集合中包含节点:B、A、D、C,此时新老集合进行 diff 差异化对比,发现 B != A,则创建并插入 B 至新集合,删除老集合 A;以此类推,创建并插入 A、D 和 C,删除 B、C 和 D。</p><p><img src="/images/elementtree.png" alt="elementtree"></p><p>React 发现这类操作繁琐冗余,因为这些都是相同的节点,但由于位置发生变化,导致需要进行繁杂低效的删除、创建操作,其实只要对这些节点进行位置移动即可。</p><p>针对这一现象,React 提出优化策略:允许开发者对同一层级的同组子节点,添加唯一 key 进行区分,虽然只是小小的改动,性能上却发生了翻天覆地的变化!</p><p>新老集合所包含的节点,如下图所示,新老集合进行 diff 差异化对比,通过 key 发现新老集合中的节点都是相同的节点,因此无需进行节点删除和创建,只需要将老集合中节点的位置进行移动,更新为新集合中节点的位置,此时 React 给出的 diff 结果为:B、D 不做任何操作,A、C 进行移动操作,即可。</p><p><img src="/images/elementtreekey.png" alt="elementtreekey"></p><pre><code>_updateChildren: function(nextNestedChildrenElements, transaction, context) { var prevChildren = this._renderedChildren; var nextChildren = this._reconcilerUpdateChildren( prevChildren, nextNestedChildrenElements, transaction, context ); if (!nextChildren && !prevChildren) { return; } var name; var lastIndex = 0; var nextIndex = 0; for (name in nextChildren) { if (!nextChildren.hasOwnProperty(name)) { continue; } var prevChild = prevChildren && prevChildren[name]; var nextChild = nextChildren[name]; if (prevChild === nextChild) { // 移动节点 this.moveChild(prevChild, nextIndex, lastIndex); lastIndex = Math.max(prevChild._mountIndex, lastIndex); prevChild._mountIndex = nextIndex; } else { if (prevChild) { lastIndex = Math.max(prevChild._mountIndex, lastIndex); // 删除节点 this._unmountChild(prevChild); } // 初始化并创建节点 this._mountChildAtIndex( nextChild, nextIndex, transaction, context ); } nextIndex++; } for (name in prevChildren) { if (prevChildren.hasOwnProperty(name) && !(nextChildren && nextChildren.hasOwnProperty(name))) { this._unmountChild(prevChildren[name]); } } this._renderedChildren = nextChildren;},// 移动节点moveChild: function(child, toIndex, lastIndex) { if (child._mountIndex < lastIndex) { this.prepareToManageChildren(); enqueueMove(this, child._mountIndex, toIndex); }},// 创建节点createChild: function(child, mountImage) { this.prepareToManageChildren(); enqueueInsertMarkup(this, mountImage, child._mountIndex);},// 删除节点removeChild: function(child) { this.prepareToManageChildren(); enqueueRemove(this, child._mountIndex);},_unmountChild: function(child) { this.removeChild(child); child._mountIndex = null;},_mountChildAtIndex: function( child, index, transaction, context) { var mountImage = ReactReconciler.mountComponent( child, transaction, this, this._nativeContainerInfo, context ); child._mountIndex = index; this.createChild(child, mountImage);},</code></pre><p>首先对新集合的节点进行循环遍历,for (name in nextChildren),通过唯一 key 可以判断新老集合中是否存在相同的节点,if (prevChild === nextChild),如果存在相同节点,则进行移动操作,但在移动前需要将当前节点在老集合中的位置与 lastIndex 进行比较,if (child._mountIndex < lastIndex),则进行节点移动操作,否则不执行该操作。这是一种顺序优化手段,lastIndex 一直在更新,表示访问过的节点在老集合中最右的位置(即最大的位置),如果新集合中当前访问的节点比 lastIndex 大,说明当前访问节点在老集合中就比上一个节点位置靠后,则该节点不会影响其他节点的位置,因此不用添加到差异队列中,即不执行移动操作,只有当访问的节点比 lastIndex 小时,才需要进行移动操作。</p><p>以上图为例,可以更为清晰直观的描述 diff 的差异对比过程:</p><ol><li>从新集合中取得 B,判断老集合中存在相同节点 B,通过对比节点位置判断是否进行移动操作,B 在老集合中的位置 B._mountIndex = 1,此时 lastIndex = 0,不满足 child._mountIndex < lastIndex 的条件,因此不对 B 进行移动操作;更新 lastIndex = Math.max(prevChild._mountIndex, lastIndex),其中 prevChild._mountIndex 表示 B 在老集合中的位置,则 lastIndex = 1,并将 B 的位置更新为新集合中的位置prevChild._mountIndex = nextIndex,此时新集合中 B._mountIndex = 0,nextIndex++ 进入下一个节点的判断。</li><li>从新集合中取得 A,判断老集合中存在相同节点 A,通过对比节点位置判断是否进行移动操作,A 在老集合中的位置 A._mountIndex = 0,此时 lastIndex = 1,满足 child._mountIndex < lastIndex的条件,因此对 A 进行移动操作enqueueMove(this, child._mountIndex, toIndex),其中 toIndex 其实就是 nextIndex,表示 A 需要移动到的位置;更新 lastIndex = Math.max(prevChild._mountIndex, lastIndex),则 lastIndex = 1,并将 A 的位置更新为新集合中的位置 prevChild._mountIndex = nextIndex,此时新集合中A._mountIndex = 1,nextIndex++ 进入下一个节点的判断。</li><li>从新集合中取得 D,判断老集合中存在相同节点 D,通过对比节点位置判断是否进行移动操作,D 在老集合中的位置 D._mountIndex = 3,此时 lastIndex = 1,不满足 child._mountIndex < lastIndex的条件,因此不对 D 进行移动操作;更新 lastIndex = Math.max(prevChild._mountIndex, lastIndex),则 lastIndex = 3,并将 D 的位置更新为新集合中的位置 prevChild._mountIndex = nextIndex,此时新集合中D._mountIndex = 2,nextIndex++ 进入下一个节点的判断。</li><li>从新集合中取得 C,判断老集合中存在相同节点 C,通过对比节点位置判断是否进行移动操作,C 在老集合中的位置 C._mountIndex = 2,此时 lastIndex = 3,满足 child._mountIndex < lastIndex 的条件,因此对 C 进行移动操作 enqueueMove(this, child._mountIndex, toIndex);更新 lastIndex = Math.max(prevChild._mountIndex, lastIndex),则 lastIndex = 3,并将 C 的位置更新为新集合中的位置 prevChild._mountIndex = nextIndex,此时新集合中 C._mountIndex = 3,nextIndex++ 进入下一个节点的判断,由于 C 已经是最后一个节点,因此 diff 到此完成。</li></ol><p>如果新集合中有新加入的节点且老集合存在需要删除的节点,那么 React diff 又是如何对比运作的呢?</p><p>以下图为例:</p><p><img src="/images/elementtreekeydel.png" alt="elementtreekeydel"></p><ol><li>从新集合中取得 B,判断老集合中存在相同节点 B,由于 B 在老集合中的位置 B._mountIndex = 1,此时lastIndex = 0,因此不对 B 进行移动操作;更新 lastIndex = 1,并将 B 的位置更新为新集合中的位置B._mountIndex = 0,nextIndex++进入下一个节点的判断。</li><li>从新集合中取得 E,判断老集合中不存在相同节点 E,则创建新节点 E;更新 lastIndex = 1,并将 E 的位置更新为新集合中的位置,nextIndex++进入下一个节点的判断。</li><li>从新集合中取得 C,判断老集合中存在相同节点 C,由于 C 在老集合中的位置C._mountIndex = 2,lastIndex = 1,此时 C._mountIndex > lastIndex,因此不对 C 进行移动操作;更新 lastIndex = 2,并将 C 的位置更新为新集合中的位置,nextIndex++ 进入下一个节点的判断。</li><li>从新集合中取得 A,判断老集合中存在相同节点 A,由于 A 在老集合中的位置A._mountIndex = 0,lastIndex = 2,此时 A._mountIndex < lastIndex,因此对 A 进行移动操作;更新 lastIndex = 2,并将 A 的位置更新为新集合中的位置,nextIndex++ 进入下一个节点的判断。</li><li>当完成新集合中所有节点 diff 时,最后还需要对老集合进行循环遍历,判断是否存在新集合中没有但老集合中仍存在的节点,发现存在这样的节点 D,因此删除节点 D,到此 diff 全部完成。</li></ol><p>当然,React diff 还是存在些许不足与待优化的地方,若新集合的节点更新为:D、A、B、C,与老集合对比只有 D 节点移动,而 A、B、C 仍然保持原有的顺序,理论上 diff 应该只需对 D 执行移动操作,然而由于 D 在老集合的位置是最大的,导致其他节点的 _mountIndex < lastIndex,造成 D 没有执行移动操作,而是 A、B、C 全部移动到 D 节点后面的现象。</p><p>所以,在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。</p>]]></content>
<categories>
<category> React </category>
</categories>
<tags>
<tag> Diff </tag>
</tags>
</entry>
<entry>
<title>js之call、apply、bind的模拟实现</title>
<link href="/2017/09/20/js%E4%B9%8Bcall%E3%80%81apply%E3%80%81bind%E7%9A%84%E6%A8%A1%E6%8B%9F%E5%AE%9E%E7%8E%B0/"/>
<url>/2017/09/20/js%E4%B9%8Bcall%E3%80%81apply%E3%80%81bind%E7%9A%84%E6%A8%A1%E6%8B%9F%E5%AE%9E%E7%8E%B0/</url>
<content type="html"><![CDATA[<p>本系列均是参考 <a href="https://github.com/mqyqingfeng/Blog" target="_blank" rel="noopener">冴羽的博客</a> 后为加深记忆做的总结并非原创。</p><p>只写出了最终的实现代码。具体详解见<a href="https://github.com/mqyqingfeng/Blog" target="_blank" rel="noopener">冴羽的博客</a></p><h1 id="call的模拟实现"><a href="#call的模拟实现" class="headerlink" title="call的模拟实现"></a>call的模拟实现</h1><pre><code class="javascript">Function.prototype.call2 = function (context) { var context = context || window; context.fn = this; var args = []; for(var i = 1, len = arguments.length; i < len; i++) { args.push('arguments[' + i + ']'); } var result = eval('context.fn(' + args +')'); delete context.fn return result;}</code></pre><a id="more"></a><h1 id="apply的模拟实现"><a href="#apply的模拟实现" class="headerlink" title="apply的模拟实现"></a>apply的模拟实现</h1><pre><code class="javascript">Function.prototype.apply = function (context, arr) { var context = Object(context) || window; context.fn = this; var result; if (!arr) { result = context.fn(); } else { var args = []; for (var i = 0, len = arr.length; i < len; i++) { args.push('arr[' + i + ']'); } result = eval('context.fn(' + args + ')') } delete context.fn return result;}</code></pre><h1 id="bind的模拟实现"><a href="#bind的模拟实现" class="headerlink" title="bind的模拟实现"></a>bind的模拟实现</h1><pre><code class="javascript">Function.prototype.bind2 = function (context) { if (typeof this !== "function") { throw new Error("Function.prototype.bind - what is trying to be bound is not callable"); } var self = this; var args = Array.prototype.slice.call(arguments, 1); var fNOP = function () {}; var fBound = function () { var bindArgs = Array.prototype.slice.call(arguments); self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs)); } fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound;}</code></pre>]]></content>
<categories>
<category> JavaScript </category>
</categories>
<tags>
<tag> call </tag>
<tag> apply </tag>
<tag> bind </tag>
</tags>
</entry>
<entry>
<title></title>
<link href="/2017/09/19/js%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87%E4%B9%8B%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87%E6%A0%88/"/>
<url>/2017/09/19/js%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87%E4%B9%8B%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87%E6%A0%88/</url>
<content type="html"><![CDATA[<hr><p>title: js执行上下文之执行上下文栈<br>date: 2017-09-16 20:17:25<br>categories: JavaScript</p><h2 id="tags-执行上下文-执行上下文栈"><a href="#tags-执行上下文-执行上下文栈" class="headerlink" title="tags: [执行上下文, 执行上下文栈]"></a>tags: [执行上下文, 执行上下文栈]</h2><h1 id="可执行代码"><a href="#可执行代码" class="headerlink" title="可执行代码"></a>可执行代码</h1><p>本系列均是参考 <a href="https://github.com/mqyqingfeng/Blog" target="_blank" rel="noopener">冴羽的博客</a> 后为加深记忆做的总结并非原创。</p><p>可分为三类:</p><ol><li>全局代码</li><li>函数代码</li><li>eval代码</li></ol><p>当代码执行到一个函数的时候,就会进行准备工作,即“执行上下文(execution context)”。</p><a id="more"></a><h1 id="执行上下文栈"><a href="#执行上下文栈" class="headerlink" title="执行上下文栈"></a>执行上下文栈</h1><p>JavaScript 引擎创建了执行上下文栈(Execution context stack,ECS)来管理执行上下文</p><p>为了模拟执行上下文栈的行为,让我们定义执行上下文栈是一个数组:</p><pre><code class="javascript"> ECStack = [ globalContext ];</code></pre><p>例子: </p><pre><code class="javascript"> function fun3() { console.log('fun3') } function fun2() { fun3(); } function fun1() { fun2(); } fun1();</code></pre><p>当执行一个函数的时候,就会创建一个执行上下文,并且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。</p><pre><code class="javascript"> // 伪代码 // fun1() ECStack.push(<fun1> functionContext); // fun1中竟然调用了fun2,还要创建fun2的执行上下文 ECStack.push(<fun2> functionContext); // 擦,fun2还调用了fun3! ECStack.push(<fun3> functionContext); // fun3执行完毕 ECStack.pop(); // fun2执行完毕 ECStack.pop(); // fun1执行完毕 ECStack.pop(); // javascript接着执行下面的代码,但是ECStack底层永远有个globalContext</code></pre>]]></content>
</entry>
<entry>
<title>js之闭包</title>
<link href="/2017/09/19/js%E4%B9%8B%E9%97%AD%E5%8C%85/"/>
<url>/2017/09/19/js%E4%B9%8B%E9%97%AD%E5%8C%85/</url>
<content type="html"><![CDATA[<h1 id="闭包定义"><a href="#闭包定义" class="headerlink" title="闭包定义"></a>闭包定义</h1><p>本系列均是参考 <a href="https://github.com/mqyqingfeng/Blog" target="_blank" rel="noopener">冴羽的博客</a> 后为加深记忆做的总结并非原创。</p><p>MDN对闭包的定义是:</p><blockquote><p>闭包是指那些能够访问自由变量的函数。</p></blockquote><p>而自由变量呢?</p><blockquote><p>自由变量是指在函数中使用的,但既不是函数的参数也不是函数的局部变量的变量。</p></blockquote><p>所以可以这么说:</p><blockquote><p>闭包 = 函数 + 函数能够访问的自由变量</p></blockquote><p>以上是理论上的闭包。</p><p>ECMAScript中,闭包指的是:</p><ol><li>从理论上角度:所有的函数。因为它们都在出创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。</li><li>从实践角度:闭包要满足以下两点:<ul><li>即使创建它的上下文已经销毁,它仍然存在。</li><li>在代码中引用了自由变量。</li></ul></li></ol><a id="more"></a><h1 id="闭包分析"><a href="#闭包分析" class="headerlink" title="闭包分析"></a>闭包分析</h1><pre><code class="javascript"> var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f; } var foo = checkscope(); foo();</code></pre><p>首先分析执行上下文栈和执行上下文的变化情况。</p><ol><li>进入全局代码,创建全局执行上下文,全局执行上下文压入执行上下文栈。</li><li>全局执行上下文初始化。</li><li>执行checkscope函数,创建checkscope函数执行上下文,checkscope执行上下文被压入执行上下文栈。</li><li>checkscope执行上下文初始化,创建变量对象、作用域链、this等。</li><li>checkscope函数执行完毕,checkscope执行上下文从执行上下文栈弹出。</li><li>执行f函数,创建f函数的执行上下文,f执行上下文被压入执行上下文栈。</li><li>f执行上下文初始化,创建变量对象、作用域链、this等。</li><li>f函数执行完毕,f执行上下文从执行上下文栈弹出。</li></ol><p>f执行上下文维护了一个作用域:</p><pre><code class="javascript"> fContext = { Scope: [AO, checkscopeContext.AO, globalContext.VO] }</code></pre><p>因为这个作用域链,f 函数依然可以读取到 checkscopeContext.AO 的值,说明当 f 函数引用了 checkscopeContext.AO 中的值的时候,即使 checkscopeContext 被销毁了,但是 JavaScript 依然会让 checkscopeContext.AO 活在内存中,f 函数依然可以通过 f 函数的作用域链找到它。</p><p>在原博里面还附上了简短的例子加深理解:</p><pre><code class="javascript"> var data = []; for (var i = 0; i < 3; i++) { data[i] = function () { console.log(i); }; } data[0](); data[1](); data[2]();</code></pre><p>答案都是:3</p><p>当执行到 data[0] 函数之前,此时全局上下文的 VO 为:</p><pre><code class="javascript"> globalContext = { VO: { data: [...], i: 3 } }</code></pre><p>当执行 data[0] 函数的时候,data[0] 函数的作用域链为:</p><pre><code class="javascript"> data[0]Context = { Scope: [AO, globalContext.VO] }</code></pre><p>data[0]Context 的 AO 并没有 i 值,所以会从 globalContext.VO 中查找,i 为 3,所以打印的结果就是 3。</p><p>data[1] 和 data[2] 是一样的道理。</p><p>所以让我们改成闭包看看:</p><pre><code class="javascript"> var data = []; for (var i = 0; i < 3; i++) { data[i] = (function (i) { return function(){ console.log(i); } })(i); } data[0](); data[1](); data[2]();</code></pre><p>当执行到 data[0] 函数之前,此时全局上下文的 VO 为:</p><pre><code class="javascript"> globalContext = { VO: { data: [...], i: 3 } }</code></pre><p>跟没改之前一模一样。</p><p>当执行 data[0] 函数的时候,data[0] 函数的作用域链发生了改变:</p><pre><code class="javascript"> data[0]Context = { Scope: [AO, 匿名函数Context.AO globalContext.VO] }</code></pre><p>匿名函数执行上下文的AO为:</p><pre><code class="javascript"> 匿名函数Context = { AO: { arguments: { 0: 0, length: 1 }, i: 0 } }</code></pre><p>data[0]Context 的 AO 并没有 i 值,所以会沿着作用域链从匿名函数 Context.AO 中查找,这时候就会找 i 为 0,找到了就不会往 globalContext.VO 中查找了,即使 globalContext.VO 也有 i 的值(值为3),所以打印的结果就是0。</p><p>data[1] 和 data[2] 是一样的道理。</p>]]></content>
<categories>
<category> JavaScript </category>
</categories>
<tags>
<tag> 闭包 </tag>
</tags>
</entry>
<entry>
<title>js执行上下文之具体执行分析</title>
<link href="/2017/09/18/js%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87%E4%B9%8B%E5%85%B7%E4%BD%93%E6%89%A7%E8%A1%8C%E5%88%86%E6%9E%90/"/>
<url>/2017/09/18/js%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87%E4%B9%8B%E5%85%B7%E4%BD%93%E6%89%A7%E8%A1%8C%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<p>我们分析第一段代码:</p><pre><code class="javascript"> var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f(); } checkscope();</code></pre><p>执行过程如下:</p><a id="more"></a><ol><li><p>执行全局代码,创建全局执行上下文,全局上下文被压入执行上下文栈</p><pre><code class="javascript"> ECStack = [ globalContext ];</code></pre></li><li><p>全局上下文初始化</p><pre><code class="javascript"> globalContext = { VO: [global, scope, checkscope], Scope: [globalContext.VO], this: globalContext.VO }</code></pre></li><li><p>初始化的同时,checkscope 函数被创建,保存作用域链到函数的内部属性[[scope]]</p><pre><code class="javascript"> checkscope.[[scope]] = [ globalContext.VO ];</code></pre></li><li><p>执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈</p><pre><code class="javascript"> ECStack = [ checkscopeContext, globalContext ];</code></pre></li><li><p>checkscope 函数执行上下文初始化:</p><ul><li>复制函数 [[scope]] 属性创建作用域链,</li><li>用 arguments 创建活动对象,</li><li>初始化活动对象,即加入形参、函数声明、变量声明,</li><li>将活动对象压入 checkscope 作用域链顶端。</li><li><p>同时 f 函数被创建,保存作用域链到 f 函数的内部属性[[scope]]</p><pre><code class="javascript">checkscopeContext = { AO: { arguments: { length: 0 }, scope: undefined, f: reference to function f(){} }, Scope: [AO, globalContext.VO], this: undefined}</code></pre></li></ul></li><li><p>执行 f 函数,创建 f 函数执行上下文,f 函数执行上下文被压入执行上下文栈</p><pre><code class="javascript"> ECStack = [ fContext, checkscopeContext, globalContext ];</code></pre></li><li><p>f 函数执行上下文初始化, 以下跟第 4 步相同:</p><ul><li>复制函数 [[scope]] 属性创建作用域链</li><li>用 arguments 创建活动对象</li><li>初始化活动对象,即加入形参、函数声明、变量声明</li><li><p>将活动对象压入 f 作用域链顶端</p><pre><code class="javascript">fContext = { AO: { arguments: { length: 0 }}, Scope: [AO, checkscopeContext.AO, globalContext.VO], this: undefined}</code></pre></li></ul></li><li><p>f 函数执行,沿着作用域链查找 scope 值,返回 scope 值</p></li><li><p>f 函数执行完毕,f 函数上下文从执行上下文栈中弹出</p><pre><code class="javascript"> ECStack = [ checkscopeContext, globalContext ];</code></pre></li><li><p>checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出</p><pre><code class="javascript"> ECStack = [ globalContext ];</code></pre></li></ol>]]></content>
<categories>
<category> JavaScript </category>
</categories>
<tags>
<tag> 执行上下文 </tag>
</tags>
</entry>
<entry>
<title>js执行上下文之this</title>
<link href="/2017/09/18/js%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87%E4%B9%8Bthis/"/>
<url>/2017/09/18/js%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87%E4%B9%8Bthis/</url>
<content type="html"><![CDATA[<h1 id="理解Rnference"><a href="#理解Rnference" class="headerlink" title="理解Rnference"></a>理解Rnference</h1><p>本系列均是参考 <a href="https://github.com/mqyqingfeng/Blog/issues/7" target="_blank" rel="noopener">冴羽的博客</a> 后为加深记忆做的总结并非原创。</p><p>reference是一种只存在于规范中的类型,它的作用是用来描述语言底层行为逻辑。(简单地说,在控制台你是无法输出的,他是抽象存在的)</p><p>reference由三个部分组成:</p><ol><li>base value: 它的值只有可能是undefined, an Object, a Boolean, a String, a Number, or an environment record 其中的一种。</li><li>referenced name</li><li>strict reference</li></ol><a id="more"></a><p>除此之外它还有三个方法:</p><ul><li>GetBase: 返回base的value</li><li>GetValue: 返回对象属性真正的值,但是要注意:调用 GetValue,返回的将是具体的值,而不再是一个 Reference</li><li>isPropertyReference: 如果 base value 是一个对象,就返回true。</li></ul><p>通过一个例子来介绍它们:</p><pre><code class="javascript"> var foo = 1; // 对应的fooReference是: var fooReference = { base: EnvironmentRecord, name: 'foo', strict: false };</code></pre><p>复杂点的例子:</p><pre><code class="javascript"> var foo = { bar: function () { return this; } }; foo.bar(); // foo // bar对应的Reference是: var BarReference = { base: foo, propertyName: 'bar', strict: false };</code></pre><p>确定this的具体方法:</p><ol><li>计算MemberExpression的结果赋值给ref</li><li>判断ref是不是一个Reference类型<ul><li>如果 ref 是 Reference,并且IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)</li><li>如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么this的值为 ImplicitThisValue(ref)</li><li>如果 ref 不是 Reference,那么 this 的值为 undefined</li></ul></li></ol><p>可以通过以下几个例子来理解:</p><pre><code class="javascript"> var foo = { bar: function () { return this; } }; foo() // MemberExpression是foo foo.bar() // MemberExpression是foo.bar</code></pre><p>简单理解 MemberExpression 其实就是()左边的部分。</p><pre><code class="javascript"> var value = 1; var foo = { value: 2, bar: function () { return this.value; } } //示例1 console.log(foo.bar()); //示例2 console.log((foo.bar)()); //示例3 console.log((foo.bar = foo.bar)()); //示例4 console.log((false || foo.bar)()); //示例5 console.log((foo.bar, foo.bar)());</code></pre><blockquote><p>foo.bar()</p></blockquote><p>该表达式返回了一个 Reference 类型</p><p>根据之前的内容,我们知道该值为:</p><pre><code class="javascript"> var Reference = { base: foo, name: 'bar', strict: false };</code></pre><p>接下来按照判断流程走:</p><ol><li><p>如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)<br>该值是 Reference 类型,那么 IsPropertyReference(ref) 的结果是多少呢?</p></li><li><p>前面我们已经铺垫了 IsPropertyReference 方法,如果 base value 是一个对象,结果返回 true。base value 为 foo,是一个对象,所以 IsPropertyReference(ref) 结果为 true。</p></li></ol><p>这个时候我们就可以确定 this 的值了:</p><pre><code class="javascript"> this = GetBase(ref),</code></pre><p>GetBase 也已经铺垫了,获得 base value 值,这个例子中就是foo,所以 this 的值就是 foo ,示例1的结果就是 2!</p><blockquote><p>(foo.bar)()</p></blockquote><p>实际上 () 并没有对 MemberExpression 进行计算,所以其实跟示例 1 的结果是一样的。</p><blockquote><p>(foo.bar = foo.bar)()</p></blockquote><p>有赋值操作符,所以返回的值不是 Reference 类型,</p><p>按照之前讲的判断逻辑:</p><p>如果 ref 不是Reference,那么 this 的值为 undefined<br>this 为 undefined,非严格模式下,this 的值为 undefined 的时候,其值会被隐式转换为全局对象。</p><blockquote><p>(false || foo.bar)()</p></blockquote><p>看示例4,逻辑与算法,返回的不是 Reference 类型,this 为 undefined</p><blockquote><p>(foo.bar, foo.bar)()</p></blockquote><p>看示例5,逗号操作符,所以返回的不是 Reference 类型,this 为 undefined</p><p>揭晓结果</p><p>所以最后一个例子的结果是:</p><pre><code class="javascript"> var value = 1; var foo = { value: 2, bar: function () { return this.value; } } //示例1 console.log(foo.bar()); // 2 //示例2 console.log((foo.bar)()); // 2 //示例3 console.log((foo.bar = foo.bar)()); // 1 //示例4 console.log((false || foo.bar)()); // 1 //示例5 console.log((foo.bar, foo.bar)()); // 1</code></pre><p>注意:以上是在非严格模式下的结果,严格模式下因为 this 返回 undefined,所以示例 3 会报错。</p>]]></content>
<categories>
<category> JavaScript </category>
</categories>
<tags>
<tag> 执行上下文 </tag>
<tag> this </tag>
</tags>
</entry>
<entry>
<title>js执行上下文之作用域链</title>
<link href="/2017/09/17/js%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87%E4%B9%8B%E4%BD%9C%E7%94%A8%E5%9F%9F%E9%93%BE/"/>
<url>/2017/09/17/js%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87%E4%B9%8B%E4%BD%9C%E7%94%A8%E5%9F%9F%E9%93%BE/</url>
<content type="html"><![CDATA[<h1 id="作用域链"><a href="#作用域链" class="headerlink" title="作用域链"></a>作用域链</h1><p>本系列均是参考 <a href="https://github.com/mqyqingfeng/Blog" target="_blank" rel="noopener">冴羽的博客</a> 后为加深记忆做的总结并非原创。</p><p>《JavaScript高级程序设计》中是这样解释作用域链的:</p><blockquote><p>当代码在一个执行上下文中执行时,会创建变量对象的一个<strong>作用域链</strong>(scope chain)。作用域链的用途是:保证对执行上下文有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在上下文的变量对象。作用域链中的下一个变量对象;来自包含的(外部)上下文,而再一个变量对象则来自下一个包含对象。这样,一直延续到全局执行上下文;全局执行上下文的变量对象始终都是作用域链中的最后一个对象。</p></blockquote><a id="more"></a><h1 id="作用域链的创建"><a href="#作用域链的创建" class="headerlink" title="作用域链的创建"></a>作用域链的创建</h1><p>这里用函数的作用域链的创建和变化为例</p><h2 id="函数的创建"><a href="#函数的创建" class="headerlink" title="函数的创建"></a>函数的创建</h2><p>函数的作用域在函数定义的时候就决定了。</p><p>这是因为函数有一个内部属性 [[scope]],当函数创建的时候,就会保存所有父变量对象到其中,你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!函数内部的[[scope]]属性是虚拟出来的一个属性,我们实际访问时访问不到这个属性的,这个属性是为了让我们更好的理解函数,虚拟出来的一个属性。</p><p>例子:</p><pre><code class="javascript"> function foo() { function bar() { ... } }</code></pre><p>函数创建时,上面例子各自的[[scope]]属性为:</p><pre><code class="javascript"> foo.[[scope]] = { globalContext.AO; } bar.[[scope]] = { fooContext.AO; globalContext.VO; }</code></pre><h2 id="函数的激活"><a href="#函数的激活" class="headerlink" title="函数的激活"></a>函数的激活</h2><p>当函数激活时,进入函数上下文,创建 VO/AO 后,就会将活动对象添加到作用链的前端。</p><p>这时候执行上下文的作用域链,我们命名为 Scope:</p><pre><code class="javascript"> Scope = [AO].concat([[Scope]]);</code></pre><p>至此,作用域链创建完毕。</p><p>上面例子中,共涉及到3个执行上下文。分别是:全局执行上下文、foo的执行上下文、bar的执行上下文。</p><p>它的作用域链可以用下图表示:</p><p><img src="/images/window.png" alt=""></p><p>内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。</p><p>每一个执行上下文都可以向上搜索作用域链,但任何执行上下文都不能通过向下搜索作用域链而进入另一个执行上下文。</p><h1 id="加深理解:"><a href="#加深理解:" class="headerlink" title="加深理解:"></a>加深理解:</h1><p>以下面的例子为例,结合着之前讲的变量对象和执行上下文栈,我们来总结一下函数执行上下文中作用域链和变量对象的创建过程:</p><pre><code class="javascript"> var scope = "global scope"; function checkscope(){ var scope2 = 'local scope'; return scope2; } checkscope();</code></pre><p>执行过程如下:</p><ol><li><p>checkscope 函数被创建,保存作用域链到 内部属性[[scope]]</p><pre><code class="javascript"> checkscope.[[scope]] = [ globalContext.VO ];</code></pre></li><li><p>执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈</p><pre><code class="javascript"> ECStack = [ checkscopeContext, globalContext ];</code></pre></li><li><p>checkscope 函数并不立刻执行,开始做准备工作,第一步:复制函数[[scope]]属性创建作用域链</p><pre><code class="javascript"> checkscopeContext = { Scope: checkscope.[[scope]], }</code></pre></li><li><p>第二步:用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明</p><pre><code class="javascript"> checkscopeContext = { AO: { arguments: { length: 0 }, scope2: undefined } }</code></pre></li><li><p>第三步:将活动对象压入 checkscope 作用域链顶端</p><pre><code class="javascript"> checkscopeContext = { AO: { arguments: { length: 0 }, scope2: undefined }, Scope: [AO, [[Scope]]] }</code></pre></li><li><p>准备工作做完,开始执行函数,随着函数的执行,修改 AO 的属性值</p><pre><code class="javascript"> checkscopeContext = { AO: { arguments: { length: 0 }, scope2: 'local scope' }, Scope: [AO, [[Scope]]] }</code></pre></li><li><p>查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出</p><pre><code class="javascript"> ECStack = [ globalContext ];</code></pre></li></ol>]]></content>
<categories>
<category> JavaScript </category>
</categories>
<tags>
<tag> 执行上下文 </tag>
<tag> 作用域链 </tag>
</tags>
</entry>
<entry>
<title>js执行上下文之变量对象</title>
<link href="/2017/09/17/js%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87%E4%B9%8B%E5%8F%98%E9%87%8F%E5%AF%B9%E8%B1%A1/"/>
<url>/2017/09/17/js%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87%E4%B9%8B%E5%8F%98%E9%87%8F%E5%AF%B9%E8%B1%A1/</url>
<content type="html"><![CDATA[<h1 id="执行上下文-执行环境-execution-context-的三个属性"><a href="#执行上下文-执行环境-execution-context-的三个属性" class="headerlink" title="执行上下文(执行环境 execution context)的三个属性"></a>执行上下文(执行环境 execution context)的三个属性</h1><p>本系列均是参考 <a href="https://github.com/mqyqingfeng/Blog" target="_blank" rel="noopener">冴羽的博客</a> 后为加深记忆做的总结并非原创。</p><ol><li>变量对象(Variable object, VO)</li><li>作用域链(Scope chain)</li><li>this</li></ol><blockquote><p>执行上下文是在执行函数时被创建的,编写代码时我们无法访问这个对象,但解析器在处理数据时会在后台使用它。</p></blockquote><h1 id="变量对象"><a href="#变量对象" class="headerlink" title="变量对象"></a>变量对象</h1><p>《JavaScript高级程序设计》里面是这样介绍变量对象的。</p><blockquote><p>每个执行环境都有一个与之关联的<strong>变量对象</strong>, 环境中定义的所有变量和函数都保存在这个对象中。</p></blockquote><p>变量对象又分为两类:</p><ol><li>全局上下文的变量对象</li><li>函数上下文的变量对象</li></ol><a id="more"></a><h2 id="全局上下文"><a href="#全局上下文" class="headerlink" title="全局上下文"></a>全局上下文</h2><p>首先看一下w3School中介绍的全局对象。</p><blockquote><p>全局对象是预定义的对象,作为 JavaScript 的全局函数和全局属性的占位符。通过使用全局对象,可以访问所有其他所有预定义的对象、函数和属性。全局对象不是任何对象的属性,所以它没有名称。</p></blockquote><p>在顶层 JavaScript 代码中,可以用关键字 this 引用全局对象。但通常不必用这种方式引用全局对象,因为全局对象是作用域链的头,这意味着所有非限定性的变量和函数名都会作为该对象的属性来查询。</p><blockquote></blockquote><p>例如,当JavaScript 代码引用 parseInt() 函数时,它引用的是全局对象的 parseInt 属性。全局对象是作用域链的头,还意味着在顶层 JavaScript 代码中声明的所有变量都将成为全局对象的属性。</p><blockquote></blockquote><p>全局对象只是一个对象,而不是类。既没有构造函数,也无法实例化一个新的全局对象。</p><blockquote></blockquote><p>在 JavaScript 代码嵌入一个特殊环境中时,全局对象通常具有环境特定的属性。实际上,ECMAScript 标准没有规定全局对象的类型,JavaScript 的实现或嵌入的 JavaScript 都可以把任意类型的对象作为全局对象,只要该对象定义了这里列出的基本属性和函数。例如,在允许通过 LiveConnect 或相关的技术来脚本化 Java 的 JavaScript 实现中,全局对象被赋予了这里列出的 java 和 Package 属性以及 getClass() 方法。而在客户端 JavaScript 中,全局对象就是 Window 对象,表示允许 JavaScript 代码的 Web 浏览器窗口。</p><p>总结一句就是:全局上下文中的变量对象就是全局对象。</p><pre><code class="javascript"> //通过this引用,在浏览器控制台中 全局对象就是Window对象 console.log(this)</code></pre><h2 id="函数上下文"><a href="#函数上下文" class="headerlink" title="函数上下文"></a>函数上下文</h2><p>每个函数都有自己的执行上下文。当执行流进入一个函数时, 函数的执行上下文就会被推入一个执行上下文栈中。而在函数执行后,栈将其上下文弹出,把控制权返回给之前的执行上下文。</p><p>在函数上下文中,将其活动对象(activation object)作为变量对象。</p><p>活动对象是在进入函数上下文时刻被创建的,它通过函数的 arguments 属性初始化。arguments 属性值是 Arguments 对象(这个对象在全局环境中是不存在的)。</p><h1 id="执行过程"><a href="#执行过程" class="headerlink" title="执行过程"></a>执行过程</h1><h2 id="进入执行上下文"><a href="#进入执行上下文" class="headerlink" title="进入执行上下文"></a>进入执行上下文</h2><blockquote><p>此时还未执行代码。<br>在进入执行上下文时,首先会处理函数声明,其次会处理变量声明,如果如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。</p></blockquote><p>此时的变量对象包括:</p><ol><li><p>函数的所有形参(在函数上下文的情况下)</p><ul><li>由名称和对应值组成的一个变量对象的属性被创建</li><li>没有参数时,属性值为 <code>undefined</code> </li></ul></li><li><p>函数声明</p><ul><li>由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建</li><li>如果变量对象已经存在相同名称的属性,则完全替换这个属性</li></ul></li><li><p>变量声明</p><ul><li>由名称和对应值(undefined)组成一个变量对象的属性被创建</li><li><p>如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性</p><p>例如:</p><pre><code class="javascript"> function foo(a) { var b = 2; function c() {} var d = function() {}; b = 3; } foo(1);</code></pre><p>进入执行上下文后,这时的活动对象(AO)是:</p><pre><code class="javascript"> AO = { arguments: { 0: 1, length: 1 }, a: 1, b: undefined, c: reference to function c(){}, d: undefined }</code></pre></li></ul></li></ol><h2 id="代码执行"><a href="#代码执行" class="headerlink" title="代码执行"></a>代码执行</h2><p>代码执行阶段会按顺序执行代码,根据代码,修改变量对象的值。</p><p>那上面的例子就会变成这样: </p><pre><code class="javascript"> AO = { arguments: { 0: 1, length: 1 }, a: 1, b: 3, c: reference to function c(){}, d: reference to FunctionExpression "d" }</code></pre><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>变量对象的创建过程简洁的总结为:</p><ul><li><p>全局上下文的变量对象初始化是全局对象</p></li><li><p>函数上下文的变量对象初始化只包括 Arguments 对象</p></li><li><p>在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值</p></li><li><p>在代码执行阶段,会再次修改变量对象的属性值</p></li></ul>]]></content>
<categories>
<category> JavaScript </category>
</categories>
<tags>
<tag> 执行上下文 </tag>
<tag> 变量对象 </tag>
</tags>
</entry>
<entry>
<title>滚动无限加载的页面渲染优化</title>
<link href="/2017/09/02/%E6%BB%9A%E5%8A%A8%E6%97%A0%E9%99%90%E5%8A%A0%E8%BD%BD%E7%9A%84%E9%A1%B5%E9%9D%A2%E6%B8%B2%E6%9F%93%E4%BC%98%E5%8C%96/"/>
<url>/2017/09/02/%E6%BB%9A%E5%8A%A8%E6%97%A0%E9%99%90%E5%8A%A0%E8%BD%BD%E7%9A%84%E9%A1%B5%E9%9D%A2%E6%B8%B2%E6%9F%93%E4%BC%98%E5%8C%96/</url>
<content type="html"><![CDATA[<h1 id="滚动无限加载优化原因"><a href="#滚动无限加载优化原因" class="headerlink" title="滚动无限加载优化原因"></a>滚动无限加载优化原因</h1><p>之前的项目是用react来写的,所以直接在componentDidMount里面直接加了监听事件。代码如下:</p><pre><code class="javascript"> window.addEventListener('scroll',function(){ //在此写滚动后进行的操作 },false);</code></pre><p>当它发生时,由于间隔很近,触发频次非常高,DOM开销十分高,所以页面就很容易掉帧。用户体验和性能都非常不好,十分有必要在此基础上进行优化。</p><a id="more"></a><h1 id="防抖和节流"><a href="#防抖和节流" class="headerlink" title="防抖和节流"></a>防抖和节流</h1><h2 id="防抖"><a href="#防抖" class="headerlink" title="防抖"></a>防抖</h2><p>在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行,总之,就是要等你触发完事件 n 秒内不再触发事件,才执行。</p><p>下面是封装后的代码</p><pre><code class="javascript"> <!DOCTYPE html> <html lang="zh-cmn-Hans"> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1"> <title>debounce</title> <style> #container{ width: 100%; height: 200px; line-height: 200px; text-align: center; color: #fff; background-color: #444; font-size: 30px; } </style> </head> <body> <div id="container"></div> <button id="button">点击取消debounce</button> <script type="application/javascript"> /** * 防抖动函数 * @param func 防抖函数所触发的函数 * @param wait 两次函数触发所间隔的时间 * @param immediate 是否立即触发函数 * @returns {debounced} */ function debounce(func, wait, immediate) { var timeout, result; return function () { var context = this; var args = arguments; if (timeout) clearTimeout(timeout); if (immediate) { // 如果已经执行过,不再执行 var callNow = !timeout; timeout = setTimeout(function(){ timeout = null; }, wait) if (callNow) result = func.apply(context, args) } else { timeout = setTimeout(function(){ func.apply(context, args) }, wait); } return result; } // 点击取消防抖函数 debounced.cancel = function() { clearTimeout(timeout); timeout = null; }; return debounced; } var count = 1; var container = document.getElementById('container'); function getUserAction() { container.innerHTML = count++; }; container.onmousemove = getUserAction; document.getElementById("button").addEventListener('click', function(){ setUseAction.cancel(); }) </script> </body> </html></code></pre><p>分析:</p><p> <code>var context = this</code> 和 <code>func.apply(context, args)</code></p><p> 使func的this指针指向和未使用debounce函数时this的指向一样。</p><p> <code>var args = arguments</code> 和 <code>func.apply(context, args)</code></p><p> 使func的事件处理函数中提供的事件对象event指向和未使用debounce函数事件对象event的指向一样。 </p><p> 可以尝试两种情况下的console.log(this)和console.log(args)</p><h2 id="节流"><a href="#节流" class="headerlink" title="节流"></a>节流</h2><p>实现防抖后就要解决在停止下滑时图片才能被加载出来的问题,这里就要用到节流的方法。节流函数保证了每次请求在一定时间内至少被执行了一次我们希望出发的事件。</p><pre><code class="javascript"><!DOCTYPE html><html><head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1"> <title>throttle</title> <style media="screen"> #container { width: 100%; height: 200px; line-height: 200px; text-align: center; color: #fff; background-color: #444; font-size: 30px; } </style></head><body><div id="container"></div><button id="button">点击取消</button><script type="application/javascript"> var count = 1 var container = document.getElementById('container') function getUserAction () { container.innerHTML = count++ } var setUseAction = throttle(getUserAction, 10000) container.onmousemove = setUseAction document.getElementById('button').addEventListener('click', function () { setUseAction.cancel() }) /** * 节流函数 * @param func * @param wait * @param options * @returns {throttled} */ function throttle (func, wait, options) { var timeout, context, args, result var previous = 0 if (!options) options = {} var later = function () { previous = options.leading === false ? 0 : new Date().getTime() timeout = null func.apply(context, args) if (!timeout) context = args = null } var throttled = function () { var now = new Date().getTime() if (!previous && options.leading === false) previous = now var remaining = wait - (now - previous) context = this args = arguments if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout) timeout = null } previous = now func.apply(context, args) if (!timeout) context = args = null } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining) } } throttled.cancel = function () { clearTimeout(timeout) previous = 0 timeout = null } return throttled }</script></body></html></code></pre><p>以上代码表示的是:在一段时间内 scroll 触发的间隔一直短于 500ms ,那么能保证事件我们希望调用的 handler 至少在 1000ms 内会触发一次。</p><h1 id="使用-rAF(requestAnimationFrame)触发滚动事件"><a href="#使用-rAF(requestAnimationFrame)触发滚动事件" class="headerlink" title="使用 rAF(requestAnimationFrame)触发滚动事件"></a>使用 rAF(requestAnimationFrame)触发滚动事件</h1><p>如果页面只需要兼容高版本浏览器或应用在移动端,又或者页面需要追求高精度的效果,那么可以使用浏览器的原生方法 rAF(requestAnimationFrame)。</p><h2 id="requestAnimationFrame"><a href="#requestAnimationFrame" class="headerlink" title="requestAnimationFrame"></a>requestAnimationFrame</h2><p>window.requestAnimationFrame() 这个方法是用来在页面重绘之前,通知浏览器调用一个指定的函数。这个方法接受一个函数为参,该函数会在重绘前调用。</p><p>rAF 常用于 web 动画的制作,用于准确控制页面的帧刷新渲染,让动画效果更加流畅,当然它的作用不仅仅局限于动画制作,我们可以利用它的特性将它视为一个定时器。(当然它不是定时器)</p><p>通常来说,rAF 被调用的频率是每秒 60 次,也就是 1000/60 ,触发频率大概是 16.7ms 。(当执行复杂操作时,当它发现无法维持 60fps 的频率时,它会把频率降低到 30fps 来保持帧数的稳定。)</p><p>简单而言,使用 requestAnimationFrame 来触发滚动事件,相当于上面的:</p><p><code>throttle(func, xx, 1000/60) //xx 代表 xx ms内不会重复触发事件 handler</code></p><pre><code class="javascript"> var ticking = false; // rAF 触发锁 function onScroll(){ if(!ticking) { requestAnimationFrame(realFunc); ticking = true; } } function realFunc(){ // do something... console.log("Success"); ticking = false; } // 滚动事件监听 window.addEventListener('scroll', onScroll, false);</code></pre><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ol><li>防抖动:防抖技术即是可以把多个顺序地调用合并成一次,也就是在一定时间内,规定事件被触发的次数。</li><li>节流函数:只允许一个函数在 X 毫秒内执行一次,只有当上一次函数执行后过了你规定的时间间隔,才能进行下一次该函数的调用。</li><li>rAF:16.7ms 触发一次 handler,降低了可控性,但是提升了性能和精确度。</li></ol><p>本文部分参考于 <a href="http://www.cnblogs.com/coco1s/p/5499469.html" target="_blank" rel="noopener">ChokCoco</a> (更多注意事项也可参考该文章)</p>]]></content>
<categories>
<category> JavaScript </category>
</categories>
<tags>
<tag> 节流 </tag>
<tag> 防抖 </tag>
</tags>
</entry>
<entry>
<title>react生命周期</title>
<link href="/2017/09/01/react%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/"/>
<url>/2017/09/01/react%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/</url>
<content type="html"><![CDATA[<p>每个react组件的生命周期分为三部分:实例化、存在期、销毁期。不同时期都有一些方法被调用,在接触react初期对于这些方法的使用可能并不频繁,但随着项目体积的增大,使用一些必要的生命周期方法就显得尤为重要了。</p><h1 id="实例化阶段"><a href="#实例化阶段" class="headerlink" title="实例化阶段"></a>实例化阶段</h1><p>实例化阶段又分为客户端和服务端</p><a id="more"></a><h2 id="1-getDefaultProps"><a href="#1-getDefaultProps" class="headerlink" title="1. getDefaultProps"></a>1. getDefaultProps</h2><p>设置 props 的默认数据,通常情况下组件都会定义部分默认数据,这样调用组件时可以不用将每一个参数都设置一遍。所有的实例化,只调用一次。</p><pre><code class="javascript"> getDefaultProps(){ return { name : "auto" }; }</code></pre><h2 id="2-getInitialState"><a href="#2-getInitialState" class="headerlink" title="2. getInitialState"></a>2. getInitialState</h2><p>对于组件的每个实例来说,这个方法的调用有且只有一次,用来初始化每个实例的 state,在这个方法里,可以访问组件的 props。state只存在组件的内部,props 在所有实例中共享。</p><pre><code class="javascript"> getInitialState(){ return { name : "auto" }; }</code></pre><p>每次修改 state,都会重新渲染组件,实例化后通过 state 更新组件,会依次调用下列方法:</p><ol><li>shouldComponentUpdate</li><li>componentWillUpdate</li><li>render</li><li>componentDidUpdate</li></ol><p>所以通过this.setState来改变state</p><h2 id="3-componentWillMount"><a href="#3-componentWillMount" class="headerlink" title="3. componentWillMount"></a>3. componentWillMount</h2><p>在第一次渲染之前调用,也是在 render 方法调用之前修改 state 的最后一个机会。</p><h2 id="4-render"><a href="#4-render" class="headerlink" title="4. render"></a>4. render</h2><p>会生成虚拟DOM,展示组件。(虚拟DOM显示的并不是一个真正的DOM,尽量避免DOM操作)<br>该方法的正确调用必须满足以下几点:</p><ol><li>只能通过this.props和this.state来访问数据</li><li>可以返回一个组件或者null或false</li><li>只能返回一个组件,即return内所有元素都必须被一个标签包裹(一个顶级组件)</li><li>不能改变组件状态</li><li>不能改变DOM输出 </li></ol><h2 id="5-componentDidMount"><a href="#5-componentDidMount" class="headerlink" title="5. componentDidMount"></a>5. componentDidMount</h2><p>在服务端渲染的时候该方法不会调用。该方法是在生成真实的DOM之后被调用,所以,在该方法内可以通过<code>ReactDOM.findDOMNode()</code>和 <code>this.refs.[refsName]</code>进行DOM操作。</p><h1 id="存在期"><a href="#存在期" class="headerlink" title="存在期"></a>存在期</h1><p>在该阶段主要进行与用户的一些交互行为和更新state(render重新渲染DOM)</p><h2 id="1-componentWillReceiveProps"><a href="#1-componentWillReceiveProps" class="headerlink" title="1. componentWillReceiveProps"></a>1. componentWillReceiveProps</h2><p>当组件的props属性的改变是由父组件引起是该方法被调用。</p><pre><code class="javascript"> componentWillReceiveProps: function(nextProps){ if(nextProps.checked !== undefined){ this.setState({ checked: nextProps.checked }) } }</code></pre><h2 id="2-shouldComponentUpdate"><a href="#2-shouldComponentUpdate" class="headerlink" title="2. shouldComponentUpdate"></a>2. shouldComponentUpdate</h2><p>通过该方法返回true或false来判断state或props改变时,render及componentWillUpdata和componentDidUpdate是否会调用</p><pre><code class="javascript"> shouldComponentUpdate: function(nextProps, nextState){ return this.state.checked === nextState.checked; }</code></pre><h2 id="3-componentWillUpdate"><a href="#3-componentWillUpdate" class="headerlink" title="3. componentWillUpdate"></a>3. componentWillUpdate</h2><p>当props和state发生变化时执行,在render方法之前执行(初始化render时不执行该方法),在这个函数里面,不能使用this.setState来修改状态。这个函数调用之后,就会把nextProps和nextState分别设置到this.props和this.state中。紧接着就会调用render()来更新DOM</p><pre><code class="javascript"> componentWillUpdate(object nextProps, object nextState)</code></pre><h2 id="4-componentDidUpdate"><a href="#4-componentDidUpdate" class="headerlink" title="4. componentDidUpdate"></a>4. componentDidUpdate</h2><p>组件更新结束之后执行,在初始化render时不执行</p><pre><code class="javascript"> componentDidUpdate(object prevProps, object prevState)</code></pre><h1 id="销毁时"><a href="#销毁时" class="headerlink" title="销毁时"></a>销毁时</h1><h2 id="componentWillUnmount"><a href="#componentWillUnmount" class="headerlink" title="componentWillUnmount"></a>componentWillUnmount</h2><p>在 componentDidMount 中添加的任务都需要再该方法中撤销,如创建的定时器或事件监听器。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p> react以上生命周期可以概括为下图:</p><p> <img src="/images/reactlifecycle.png" alt=""></p>]]></content>
<categories>
<category> React </category>
</categories>
<tags>
<tag> React生命周期 </tag>
</tags>
</entry>
<entry>
<title>JavaScript中的隐式类型转换</title>
<link href="/2017/05/06/JavaScript%E4%B8%AD%E7%9A%84%E9%9A%90%E5%BC%8F%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/"/>
<url>/2017/05/06/JavaScript%E4%B8%AD%E7%9A%84%E9%9A%90%E5%BC%8F%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/</url>
<content type="html"><![CDATA[<h1 id="前言:"><a href="#前言:" class="headerlink" title="前言:"></a>前言:</h1><p>最近写的一个项目因为js里的隐式类型转换踩了不少的坑,耽误了不少工时,下面就来总结一下js里的隐式类型转换</p><a id="more"></a><h1 id="运算符依次登场"><a href="#运算符依次登场" class="headerlink" title="运算符依次登场"></a>运算符依次登场</h1><h2 id="一元"><a href="#一元" class="headerlink" title="一元 +"></a>一元 +</h2><p>一元+ 将其操作数转换为Number类型</p><pre><code>let str = '11'let tem = + str</code></pre><p>的结果为Number类型的11</p><h2 id="一元-1"><a href="#一元-1" class="headerlink" title="一元 -"></a>一元 -</h2><p>一元 - 将其操作数转换为Number类型并反转其正负。</p><pre><code>let str = '11'let tem = - str</code></pre><p>其结果为Number类型的-11(+0产生-0;-0产生+0)</p><h2 id="加法运算符(-)"><a href="#加法运算符(-)" class="headerlink" title="加法运算符(+)"></a>加法运算符(+)</h2><p>基本规则:</p><ul><li>某个运算数是 NaN,那么结果为 NaN。</li><li>-Infinity 加 -Infinity,结果为 -Infinity。</li><li>Infinity 加 -Infinity,结果为 NaN。</li><li>+0 加 +0,结果为 +0。</li><li>-0 加 +0,结果为 +0。</li><li>-0 加 -0,结果为 -0。</li></ul><p>如果某个运算数是字符串,那么采用下列规则:</p><p>如果两个运算数都是字符串,把第二个字符串连接到第一个上。<br>如果只有一个运算数是字符串,把另一个运算数转换成字符串,结果是两个字符串连接成的字符串。</p><h3 id="栗子"><a href="#栗子" class="headerlink" title="栗子"></a>栗子</h3><h4 id="算术运算"><a href="#算术运算" class="headerlink" title="算术运算"></a>算术运算</h4><pre><code>let res = 1 + 1 // 2 </code></pre><p>number + number = number</p><h4 id="连接字符串"><a href="#连接字符串" class="headerlink" title="连接字符串"></a>连接字符串</h4><pre><code>let res = 'hello, ' + 'pororoJ' // 'hello, pororoJ'</code></pre><p>string + string = string</p><h4 id="数字和字符串"><a href="#数字和字符串" class="headerlink" title="数字和字符串"></a>数字和字符串</h4><pre><code>let res = 1 + '5' // '15'</code></pre><p>如果操作数中有一个是字符串,第二个操作数将转换成字符串,并且连接在一起转换为字符串</p><h4 id="数字和数组"><a href="#数字和数组" class="headerlink" title="数字和数组"></a>数字和数组</h4><pre><code>let res = [1,2,3] + 1 // ‘1,2,31’</code></pre><p>第一个操作数是数组,转换为原始值字符串(由于如果操作数中有一个是字符串,第二个操作数将转换成字符串,并且连接在一起转换为字符串),第二个操作数是数字,转换为字符串,然后两个字符串连接在一起。</p><h4 id="数字和布尔值"><a href="#数字和布尔值" class="headerlink" title="数字和布尔值"></a>数字和布尔值</h4><pre><code>let res = 1 + true // 2</code></pre><p>因为两个操作数都不是字符串,布尔值将转换为数字符,然后作数字加法运算。</p><h4 id="数字和对象"><a href="#数字和对象" class="headerlink" title="数字和对象"></a>数字和对象</h4><pre><code>let res = 1 + {} //'1[object Object]'</code></pre><p>第二个操作数是一个对象,转换为原始值字符串(‘[object Object]’)。第二个操作数转换之后是一个字符串,故数字也将转换为一个字符串,再把字符串连接在一起。</p><h4 id="数字和null"><a href="#数字和null" class="headerlink" title="数字和null"></a>数字和null</h4><pre><code>let res = 1 + null // 1</code></pre><p>如果操作数不是对象或字符串时,null会转换为数字,然后做数字加法运算。</p><h4 id="字符串和null"><a href="#字符串和null" class="headerlink" title="字符串和null"></a>字符串和null</h4><pre><code>let res = 'pororoJ' + null // 'pororoJnull'</code></pre><p>因为第一个操作数是一个字符串,所以null将转换为一个字符串”null”,然后两个把字符串连接在一起。</p><h4 id="数字和undefined"><a href="#数字和undefined" class="headerlink" title="数字和undefined"></a>数字和undefined</h4><pre><code>let res = 1 + undifined // NAN</code></pre><p>因为没有操作数是对象或字符串,undefined将转换为NaN。两个数字做加法运算,之所以要做加法,是因为typeof(NaN) == “number”,又因为任何一个数字和NaN做加法运算,所以等于NaN。</p><h2 id="减法运算符(-)"><a href="#减法运算符(-)" class="headerlink" title="减法运算符(-)"></a>减法运算符(-)</h2><ul><li>某个运算数是 NaN,那么结果为 NaN。</li><li>Infinity 减 Infinity,结果为 NaN。</li><li>-Infinity 减 -Infinity,结果为 NaN。</li><li>Infinity 减 -Infinity,结果为 Infinity。</li><li>-Infinity 减 Infinity,结果为 -Infinity。</li><li>+0 减 +0,结果为 +0。</li><li>-0 减 -0,结果为 -0。</li><li>+0 减 -0,结果为 +0。</li><li>某个运算符不是数字,那么结果为 NaN。</li></ul><h3 id="算术运算-1"><a href="#算术运算-1" class="headerlink" title="算术运算"></a>算术运算</h3><pre><code>let res = 1 - 1 // 0</code></pre><p>number - number = number</p><h1 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h1><p>既然隐式类型转换这么麻烦那如何避免呢?</p><p>使用全等运算符 === 来进行条件判断。全等运算符会先进行数据类型判断,并且不会发生隐式类型转换。</p>]]></content>
<categories>
<category> 运算符 </category>
</categories>
<tags>
<tag> 运算符类型转换 </tag>
</tags>
</entry>
<entry>
<title>About</title>
<link href="/about/index.html"/>
<url>/about/index.html</url>
<content type="html"><![CDATA[]]></content>
</entry>
<entry>
<title>categories</title>
<link href="/categories/index.html"/>
<url>/categories/index.html</url>
<content type="html"><![CDATA[]]></content>
</entry>
<entry>
<title>tags</title>
<link href="/tags/index.html"/>
<url>/tags/index.html</url>
<content type="html"><![CDATA[]]></content>
</entry>
</search>