-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
1221 lines (1132 loc) · 70.2 KB
/
Copy pathatom.xml
File metadata and controls
1221 lines (1132 loc) · 70.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>https://evansalien.github.io</id>
<title>Evans Alien</title>
<updated>2020-03-20T05:25:33.166Z</updated>
<generator>https://github.com/jpmonette/feed</generator>
<link rel="alternate" href="https://evansalien.github.io"/>
<link rel="self" href="https://evansalien.github.io/atom.xml"/>
<subtitle>温故而知新</subtitle>
<logo>https://evansalien.github.io/images/avatar.png</logo>
<icon>https://evansalien.github.io/favicon.ico</icon>
<rights>All rights reserved 2020, Evans Alien</rights>
<entry>
<title type="html"><![CDATA[Vue生命周期]]></title>
<id>https://evansalien.github.io/post/vue-sheng-ming-zhou-qi-xi-lie-yi/</id>
<link href="https://evansalien.github.io/post/vue-sheng-ming-zhou-qi-xi-lie-yi/">
</link>
<updated>2020-03-19T06:45:37.000Z</updated>
<content type="html"><![CDATA[<h2 id="概念">概念</h2>
<p>一个从Vue实例的创建到销毁的过程。</p>
<h2 id="生命周期阶段与钩子">生命周期阶段与钩子</h2>
<figure data-type="image" tabindex="1"><img src="https://evansalien.github.io/post-images/1584601074831.png" alt="" loading="lazy"></figure>
<pre><code>如图可以看到每一个阶段中的钩子命名都很好记忆,阶段开始前使用 beforeXxx,阶段后结束后使用xxxed
</code></pre>
<p>除这 8 个核心钩子,另外还有 3 个新增功能型钩子,目前总共是 11 个钩子 顺带提一下这 3 个钩子的功能</p>
<ol>
<li>组件缓存,activated 与 deactivated,这两个钩子也是一对的,分别表示被 keep-alive 缓存的组件激活和停用时调用。</li>
<li>组件错误捕获,errorCaptured,对组件中出现对异常错误进行处理,使用较少。</li>
</ol>
<h2 id="图解生命周期">图解生命周期</h2>
<p>借鉴官方图解</p>
<figure data-type="image" tabindex="2"><img src="https://cn.vuejs.org/images/lifecycle.png" alt="" loading="lazy"></figure>
<p>对于上面的理解,需要对Vue源码进行大概梳理,如下图</p>
<figure data-type="image" tabindex="3"><img src="https://evansalien.github.io/post-images/1584604698149.png" alt="" loading="lazy"></figure>
<p>可以看到,从实例创建、组件挂载、渲染的一些过程中有明显的的周期节点。</p>
<h2 id="简化理解">简化理解</h2>
<figure data-type="image" tabindex="4"><img src="https://evansalien.github.io/post-images/1584604788789.png" alt="" loading="lazy"></figure>
<p>如图出现一些问题</p>
<ol>
<li>什么时期创建 el ?</li>
<li>什么时期挂载 data ?</li>
<li>什么时期可以访问 dom ?</li>
<li>什么情况下组件会更新?更新是同步更新还是异步更新?</li>
<li>什么情况下组件会被销毁?</li>
<li>销毁组件后,还可以访问哪些内容?</li>
</ol>
<pre><code class="language-javascript">export default {
data() {
return {
message: 1
}
},
methods: {
printInfo(name) {
console.log(name);
console.log('el', this.$el);
console.log('data', this.$data);
console.log('watch', this.$watch)
}
},
beforeCteate() {
this.printInfo('beforeCreate');
// 无法访问 this 中的 data、method, 只能访问到watch
},
created() {
this.message = this.message + 1;
this.printComponentInfo('created')
// 能访问到 data,但是访问不到 el
},
beforeMount() {
this.printComponentInfo('beforeMount')
// 可以访问 data,但是拿不到 el
},
mounted() {
this.printComponentInfo('mounted')
// 可以访问 el
},
beforeUpdate() {
this.printComponentInfo('beforeUpdate')
// 获取跟新前 data ,el的属性
},
updated() {
this.printComponentInfo('updated')
// 当 data被修改发生在 created 阶段是,不触发, 但是el 和 data 的值都会改变
// 如果发生在mounted 阶段, 触发, 相当于异步更新
},
beforeDestroy() {
this.printComponentInfo('beforeDestroy')
// 触发方式: 1. 手动调用 $destroy
// 2. v-if, v-for
// 3. 路由切换或者刷新页面
// 还是能访问 el, data, watch
},
destroyed() {
this.printComponentInfo('destroyed')
// // 还是能访问 el, data, watch
},
}
</code></pre>
<h2 id="生命周期应用场景">生命周期应用场景</h2>
<ol>
<li>
<p>beforeCreate 谨慎操作this:beforeCreate 无法访问 this 中的 data、method</p>
</li>
<li>
<p>created:可以访问 this, 但是无法访问dom, dom未挂载,初始化渲染的请求可以放在这里</p>
</li>
<li>
<p>mounted: 可以操作DOM代码, 如需根据数据改变布局的请求可放在这里进行</p>
</li>
</ol>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Vue初始化到最终渲染过程的分析(二)]]></title>
<id>https://evansalien.github.io/post/vue-chu-shi-hua-dao-zui-zhong-xuan-ran-guo-cheng-de-fen-xi-er/</id>
<link href="https://evansalien.github.io/post/vue-chu-shi-hua-dao-zui-zhong-xuan-ran-guo-cheng-de-fen-xi-er/">
</link>
<updated>2020-03-18T08:49:51.000Z</updated>
<content type="html"><![CDATA[<h2 id="mount">$mount</h2>
<p>紧接着上篇讲的vue在init后,就会进行<span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>m</mi><mi>o</mi><mi>u</mi><mi>n</mi><mi>t</mi><mi mathvariant="normal">挂</mi><mi mathvariant="normal">载</mi><mi mathvariant="normal">过</mi><mi mathvariant="normal">程</mi><mi mathvariant="normal">。</mi></mrow><annotation encoding="application/x-tex">mount 挂载过程。</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.61508em;vertical-align:0em;"></span><span class="mord mathdefault">m</span><span class="mord mathdefault">o</span><span class="mord mathdefault">u</span><span class="mord mathdefault">n</span><span class="mord mathdefault">t</span><span class="mord cjk_fallback">挂</span><span class="mord cjk_fallback">载</span><span class="mord cjk_fallback">过</span><span class="mord cjk_fallback">程</span><span class="mord cjk_fallback">。</span></span></span></span>mount 这个方法的实现是和平台、构建方式都相关的。 我们分析带 compiler 版本的 $mount 实现。在 Vue 2.0 版本中,所有 Vue 的组件最终都会转换成 render 方法。</p>
<ul>
<li>
<p><strong>第一点</strong>:挂载的时候,它对el做了限制,Vue不能挂载到body、html这样的根节点上。</p>
</li>
<li>
<p><strong>第二点</strong>:如果没有定义render方法,则会调用compileToFunctions方法把el或者template字符串转换成render方法。</p>
</li>
<li>
<p><strong>第三点</strong>: <strong>mountComponent</strong>核心就是先实例化一个Watcher,在它的回调函数中会调用 updateComponent 方法,在此方法中调 用 vm._render 方法先生成虚拟 Node,最终调用 vm._update 更 新 DOM。</p>
</li>
<li>
<p><strong>第四点</strong>:将 vm._isMounted 设置为 true,表示已经挂载。</p>
</li>
<li>
<p><strong>第五点</strong>:执行 mounted 钩子函数:callHook(vm, 'mounted')。</p>
</li>
</ul>
<h2 id="complie">complie</h2>
<ul>
<li>在 Vue 2.0 版本中,所有 Vue 的组件的渲染最终都需要 render 方法,无论 我们是用单文件 .vue 方式开发组件,还是写了 el 或者 template 属性,最终 都会转换成 render 方法,那么这个过程是 Vue 的一个“在线编译”的过程,它 是调用 compileToFunctions 方法实现的。</li>
</ul>
<h2 id="render">render</h2>
<p>Vue 的 _render 方法是实例一个私有方法,最终会把实例渲染成一个虚拟 Node。</p>
<ul>
<li>vm_render最终是通过执行一个createElement方法并返回的是vnode,它是一个虚拟Node</li>
</ul>
<h2 id="virtual-dom虚拟dom">Virtual DOM(虚拟dom)</h2>
<p>其本质是一个原生的JS对象,用class定义出来的。</p>
<ul>
<li>
<p><strong>第一点</strong>:<strong>核心定义</strong> 几个关键属性,标签名、数据、子节点、键值等,其它属性都是用来扩展 VNode的灵活以实现一些特殊 feature 的。</p>
</li>
<li>
<p><strong>第二点</strong>:映射到真实DOM, 实际上要经历 VNode 的 create、diff、patch等过程。</p>
</li>
<li>
<p><strong>第三点</strong>:<strong>cretateElement创建VNode</strong></p>
</li>
</ul>
<h2 id="update">update</h2>
<p>其核心是通过 Vue 的 _ update 方法,<em>update 方 法的作用是把 VNode 渲 染成真实的 DOM。</em> update 的核心就是调用 vm.<strong>patch</strong> 方法,__ patch__在不同的平台,比 如 web 和 weex 上的定义 是不一样的。</p>
<h3 id="首次渲染">首次渲染</h3>
<ul>
<li>
<p>通过 createElm 方法,把虚拟节 点创建真实的 DOM 并插入到它的父 节点中。</p>
</li>
<li>
<p>然后调用 createChildren 方法去创 建子元素,实际上是遍历子虚拟节 点,递归调用 createElm。</p>
</li>
<li>
<p>接着再调用 invokeCreateHooks 方法执行所 有的 create 的钩子并把 vnode push 到 insertedVnodeQueue</p>
</li>
<li>
<p>最后调用 insert 方法把 DOM 插入到父节点中,因为 是递归调用,子元素会优先 调用 insert,所以整个 vnode 树节点的插入顺序是 先子后父。</p>
</li>
</ul>
<h3 id="数据跟新">数据跟新</h3>
<h2 id="dom">DOM</h2>
<p>Vue最终创建的 DOM</p>
<h2 id="总结">总结</h2>
<p>**初始化 Vue 到最终渲染的整个过程:**new Vue => init => $mounted => compile => render => vnode => patch => DOM<br>
<img src="https://evansalien.github.io/post-images/1584600744290.png" alt="" loading="lazy"></p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Vue初始化到最终渲染的过程分析(一)]]></title>
<id>https://evansalien.github.io/post/vue-fen-xing-zong-jie/</id>
<link href="https://evansalien.github.io/post/vue-fen-xing-zong-jie/">
</link>
<updated>2020-03-16T06:01:29.000Z</updated>
<content type="html"><![CDATA[<h2 id="来源">来源</h2>
<p>从我最近面试的情况来看(三四年的经验),项目中用Vue的时间也不少,我抛开常见的问题,问了他们对于Vue的熟悉程度,都回答很熟悉,什么工程化、全家桶、脚手架系列的。我就问了下 Vue的渲染过程或者在Object.prototype.toString.call(Vue)是什么? 😱WHAT ? 额,很多人一下回答不上来。不急,下面我们来慢慢的探究下这到底是个什么东东。</p>
<h2 id="项目目录分析">项目目录分析</h2>
<figure data-type="image" tabindex="1"><img src="https://evansalien.github.io/post-images/1584343182945.png" alt="" loading="lazy"></figure>
<h3 id="具体我们来分析-srccore目录下的东西">具体我们来分析 src/core目录下的东西</h3>
<p><strong>在src/core/index.js</strong><br>
import Vue from './instance/index'</p>
<p><strong>在src/core/instance/index.js</strong></p>
<pre><code class="language-javascript">import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue(options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
</code></pre>
<ul>
<li>initMixin :对于各种vue实例各种属性进行初始化</li>
<li>stateMixin :Vue原型上绑定state相关的方法和属性,data、props等</li>
<li>eventsMixin :Vue原型上绑定事件相关方法</li>
<li>lifecycleMixin :Vue原型上绑定生命周期相关方法,比如_update、<span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>f</mi><mi>o</mi><mi>r</mi><mi>c</mi><mi>e</mi><mi>U</mi><mi>p</mi><mi>d</mi><mi>a</mi><mi>t</mi><mi>e</mi><mi mathvariant="normal">、</mi></mrow><annotation encoding="application/x-tex">forceUpdate、</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;"></span><span class="mord mathdefault" style="margin-right:0.10764em;">f</span><span class="mord mathdefault">o</span><span class="mord mathdefault" style="margin-right:0.02778em;">r</span><span class="mord mathdefault">c</span><span class="mord mathdefault">e</span><span class="mord mathdefault" style="margin-right:0.10903em;">U</span><span class="mord mathdefault">p</span><span class="mord mathdefault">d</span><span class="mord mathdefault">a</span><span class="mord mathdefault">t</span><span class="mord mathdefault">e</span><span class="mord cjk_fallback">、</span></span></span></span>destroy</li>
<li>renderMixin : Vue原型上绑定和渲染相关的方法</li>
</ul>
<h4 id="initmixin">initMixin</h4>
<p><strong>在src/core/instance/init.js</strong></p>
<ul>
<li><strong>第一点</strong>:initMixin 是Vue的一些初始化实例的方法,在还没有构造一个对象前是不会进入到这个方法内部,当通过new出一个对象后才会进入,原因如下:</li>
</ul>
<pre><code class="language-javascript">Vue.prototype._init = function (options?: Object) {}
</code></pre>
<p>这里有一个options?:object的校验,刚开始即只是引入<script src="../../dist/vue.js"></script>这个文件,当var vm = new Vue()之后才进入_init方法内部。</p>
<ul>
<li><strong>第二点</strong>:_init方法中对于$options设置:</li>
</ul>
<pre><code class="language-javascript"> vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
</code></pre>
<ul>
<li><strong>第三点</strong>:_init方法中其他初始化方法:</li>
</ul>
<pre><code class="language-javascript"> initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
</code></pre>
<p>接下来将会一个个初始化方法说明,初次之外_init方法还有一些变量的初始化,比如_uid、_isVue、_name、_renderProxy的初始化</p>
<ul>
<li><strong>第四点</strong>:最后在_init方法中需要注意</li>
</ul>
<pre><code class="language-javascript"> if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
</code></pre>
<p>调用$mount 挂在根元素上</p>
<h4 id="statemixin">stateMixin</h4>
<p><strong>src/core/instance/state.js</strong></p>
<ul>
<li>
<p><strong>第一点</strong>: stateMixin是对于Vue原型对象(Vue.prototype)加上<span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>d</mi><mi>a</mi><mi>t</mi><mi>a</mi><mi mathvariant="normal">、</mi></mrow><annotation encoding="application/x-tex">data、</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathdefault">d</span><span class="mord mathdefault">a</span><span class="mord mathdefault">t</span><span class="mord mathdefault">a</span><span class="mord cjk_fallback">、</span></span></span></span>props、<span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>d</mi><mi>e</mi><mi>l</mi><mi>e</mi><mi>t</mi><mi>e</mi><mi mathvariant="normal">、</mi></mrow><annotation encoding="application/x-tex">delete、</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathdefault">d</span><span class="mord mathdefault">e</span><span class="mord mathdefault" style="margin-right:0.01968em;">l</span><span class="mord mathdefault">e</span><span class="mord mathdefault">t</span><span class="mord mathdefault">e</span><span class="mord cjk_fallback">、</span></span></span></span>watch、<span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>s</mi><mi>e</mi><mi>t</mi><mi mathvariant="normal">属</mi><mi mathvariant="normal">性</mi><mi mathvariant="normal">。</mi><mi mathvariant="normal">并</mi><mi mathvariant="normal">且</mi><mi mathvariant="normal">通</mi><mi mathvariant="normal">过</mi><mi>O</mi><mi>b</mi><mi>j</mi><mi>e</mi><mi>c</mi><mi>t</mi><mi mathvariant="normal">.</mi><mi>d</mi><mi>e</mi><mi>f</mi><mi>i</mi><mi>n</mi><mi>e</mi><mi>P</mi><mi>r</mi><mi>o</mi><mi>p</mi><mi>e</mi><mi>r</mi><mi>t</mi><mi>y</mi><mi mathvariant="normal">对</mi></mrow><annotation encoding="application/x-tex">set属性。并且通过Object.defineProperty对</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;"></span><span class="mord mathdefault">s</span><span class="mord mathdefault">e</span><span class="mord mathdefault">t</span><span class="mord cjk_fallback">属</span><span class="mord cjk_fallback">性</span><span class="mord cjk_fallback">。</span><span class="mord cjk_fallback">并</span><span class="mord cjk_fallback">且</span><span class="mord cjk_fallback">通</span><span class="mord cjk_fallback">过</span><span class="mord mathdefault" style="margin-right:0.02778em;">O</span><span class="mord mathdefault">b</span><span class="mord mathdefault" style="margin-right:0.05724em;">j</span><span class="mord mathdefault">e</span><span class="mord mathdefault">c</span><span class="mord mathdefault">t</span><span class="mord">.</span><span class="mord mathdefault">d</span><span class="mord mathdefault">e</span><span class="mord mathdefault" style="margin-right:0.10764em;">f</span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">e</span><span class="mord mathdefault" style="margin-right:0.13889em;">P</span><span class="mord mathdefault" style="margin-right:0.02778em;">r</span><span class="mord mathdefault">o</span><span class="mord mathdefault">p</span><span class="mord mathdefault">e</span><span class="mord mathdefault" style="margin-right:0.02778em;">r</span><span class="mord mathdefault">t</span><span class="mord mathdefault" style="margin-right:0.03588em;">y</span><span class="mord cjk_fallback">对</span></span></span></span>data、$props属性进行set和get。</p>
</li>
<li>
<p><strong>第二点</strong>:initState方法是在init.js中调用,即实例化之后才调用的,是个实例对象添加属性。</p>
</li>
</ul>
<pre><code class="language-javascript">export function initState(vm: Component) {
// 首先在vm上初始化一个_watchers数组,缓存这个vm上的所有watcher
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
</code></pre>
<p>对于实例对象进行相关属性的初始化,另外data、props因为需要双向绑定,在initData、initProps中都有一个proxy方法对这两个属性进行set和get的设置</p>
<h4 id="eventmixin">eventMixin</h4>
<ul>
<li>
<p><strong>第一点</strong>: eventsMixin是对于Vue原型对象(Vue.prototype)绑定一些事件方法,比如<span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>o</mi><mi>n</mi><mi mathvariant="normal">、</mi></mrow><annotation encoding="application/x-tex">on、</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.43056em;vertical-align:0em;"></span><span class="mord mathdefault">o</span><span class="mord mathdefault">n</span><span class="mord cjk_fallback">、</span></span></span></span>once、<span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>o</mi><mi>f</mi><mi>f</mi><mi mathvariant="normal">、</mi></mrow><annotation encoding="application/x-tex">off、</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;"></span><span class="mord mathdefault">o</span><span class="mord mathdefault" style="margin-right:0.10764em;">f</span><span class="mord mathdefault" style="margin-right:0.10764em;">f</span><span class="mord cjk_fallback">、</span></span></span></span>emit。</p>
</li>
<li>
<p><strong>第二点</strong>: initEvents是对于实例对象初始化事件</p>
</li>
</ul>
<pre><code class="language-javascript">export function initEvents(vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events 初始化父级相关事件
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
</code></pre>
<p>创建_events一个空对象之后用来存放事件,_hasHookEvent是一个优化标记(可以暂时不理会),然后初始化父级事件。根据是否有父级监听事件,如果有则更新父级事件</p>
<h4 id="lifecyclemixin">lifecycleMixin</h4>
<ul>
<li><strong>第一点</strong>: lifecycleMixin是对Vue原型对象(Vue.prototype)绑定_update、<span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>f</mi><mi>o</mi><mi>r</mi><mi>c</mi><mi>e</mi><mi>U</mi><mi>p</mi><mi>d</mi><mi>a</mi><mi>t</mi><mi>e</mi><mi mathvariant="normal">、</mi></mrow><annotation encoding="application/x-tex">forceUpdate、</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;"></span><span class="mord mathdefault" style="margin-right:0.10764em;">f</span><span class="mord mathdefault">o</span><span class="mord mathdefault" style="margin-right:0.02778em;">r</span><span class="mord mathdefault">c</span><span class="mord mathdefault">e</span><span class="mord mathdefault" style="margin-right:0.10903em;">U</span><span class="mord mathdefault">p</span><span class="mord mathdefault">d</span><span class="mord mathdefault">a</span><span class="mord mathdefault">t</span><span class="mord mathdefault">e</span><span class="mord cjk_fallback">、</span></span></span></span>destroy三个生命周期方法。_update方法中通过调用__patch__方法更新虚拟dom;</li>
</ul>
<pre><code class="language-javascript">if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
</code></pre>
<p><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>f</mi><mi>o</mi><mi>r</mi><mi>c</mi><mi>e</mi><mi>U</mi><mi>p</mi><mi>d</mi><mi>a</mi><mi>t</mi><mi>e</mi><mi mathvariant="normal">强</mi><mi mathvariant="normal">制</mi><mi mathvariant="normal">重</mi><mi mathvariant="normal">新</mi><mi mathvariant="normal">渲</mi><mi mathvariant="normal">染</mi><mi mathvariant="normal">实</mi><mi mathvariant="normal">例</mi><mi mathvariant="normal">本</mi><mi mathvariant="normal">身</mi><mi mathvariant="normal">和</mi><mi mathvariant="normal">插</mi><mi mathvariant="normal">入</mi><mi mathvariant="normal">插</mi><mi mathvariant="normal">槽</mi><mi mathvariant="normal">内</mi><mi mathvariant="normal">容</mi><mi mathvariant="normal">的</mi><mi mathvariant="normal">子</mi><mi mathvariant="normal">组</mi><mi mathvariant="normal">件</mi><mi mathvariant="normal">;</mi></mrow><annotation encoding="application/x-tex">forceUpdate强制重新渲染实例本身和插入插槽内容的子组件;</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;"></span><span class="mord mathdefault" style="margin-right:0.10764em;">f</span><span class="mord mathdefault">o</span><span class="mord mathdefault" style="margin-right:0.02778em;">r</span><span class="mord mathdefault">c</span><span class="mord mathdefault">e</span><span class="mord mathdefault" style="margin-right:0.10903em;">U</span><span class="mord mathdefault">p</span><span class="mord mathdefault">d</span><span class="mord mathdefault">a</span><span class="mord mathdefault">t</span><span class="mord mathdefault">e</span><span class="mord cjk_fallback">强</span><span class="mord cjk_fallback">制</span><span class="mord cjk_fallback">重</span><span class="mord cjk_fallback">新</span><span class="mord cjk_fallback">渲</span><span class="mord cjk_fallback">染</span><span class="mord cjk_fallback">实</span><span class="mord cjk_fallback">例</span><span class="mord cjk_fallback">本</span><span class="mord cjk_fallback">身</span><span class="mord cjk_fallback">和</span><span class="mord cjk_fallback">插</span><span class="mord cjk_fallback">入</span><span class="mord cjk_fallback">插</span><span class="mord cjk_fallback">槽</span><span class="mord cjk_fallback">内</span><span class="mord cjk_fallback">容</span><span class="mord cjk_fallback">的</span><span class="mord cjk_fallback">子</span><span class="mord cjk_fallback">组</span><span class="mord cjk_fallback">件</span><span class="mord cjk_fallback">;</span></span></span></span>destroy销毁一个实例,清理它与其它实例的连接,解绑它的全部指令及事件监听器,触发 beforeDestroy 和 destroyed 的钩子</p>
<ul>
<li><strong>第二点</strong>: initLifecycle是在_init方法中调用,是实例生命周期的初始化,其中会包括很多变量</li>
</ul>
<pre><code class="language-javascript">export function initLifecycle(vm: Component) {
const options = vm.$options
// locate first non-abstract parent 创建第一个非抽象父组件,抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中,例如<keep-alive>
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null // watcher对象
vm._inactive = null // 和keep-alive中组件状态有关系
vm._directInactive = false // 和keep-alive中组件状态有关系
vm._isMounted = false //当前实例是否被挂载
vm._isDestroyed = false // 当前实例是否被销毁
vm._isBeingDestroyed = false // 当前实例是否正在被销毁或者没销毁完全
}
</code></pre>
<ul>
<li><strong>第三点</strong>: callHook是在_init方法中调用,这个方法是直接调用钩子,调用形式如下</li>
</ul>
<pre><code class="language-javascript">callHook(vm, 'beforeCreate')
callHook(vm, 'created')
</code></pre>
<h4 id="renderminix">RenderMinix</h4>
<p><strong>src/core/instance/render.js</strong></p>
<ul>
<li><strong>第一点</strong>: renderMixin方法主要是给Vue原型对象绑定$nextTick、_render两个方法,其中_render方法代码如下:</li>
</ul>
<pre><code class="language-javascript">Vue.prototype._render = function (): VNode {
……
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode
// render self
let vnode
try {
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render`)
if (process.env.NODE_ENV !== 'production') {
if (vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
} catch (e) {
handleError(e, vm, `renderError`)
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
}
……
return vnode
}
</code></pre>
<p>在这个方法中主要是try……catch这里创建了vnode。<br>
vnode = render.call(vm._renderProxy, vm.$createElement) 创建一个vnode并且返回,如果失败则返回一个空的vnode vnode = createEmptyVNode()</p>
<ul>
<li><strong>第二点</strong>: initRender是在_init方法中调用,进行实例渲染属性的绑定并且对一些属性的监听</li>
</ul>
<pre><code class="language-javascript">export function initRender(vm: Component) {
……
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
……
}
</code></pre>
<p>这里着重关注一下createElement方法,传入vnode以及dom的属性创建真正dom节点。</p>
<h2 id="源码构建基于-rollup">源码构建,基于 Rollup</h2>
<figure data-type="image" tabindex="2"><img src="https://evansalien.github.io/post-images/1584343201997.png" alt="" loading="lazy"></figure>
<p>总结:<strong>Runtime Only:通常需要借助如 webpack 的 vue-loader 工具把 .vue 文件编译成JavaScript,因为是在编译阶段做 的,所以它只包含运行时的 Vue.js 代码,因此代码体积 也会更轻量。 Runtime + Compiler:我们如果没有对代码做预编译,但 又使用了 Vue 的 template 属性并传入一个字符串,则需 要在客户端编译模板。 Vue.js 2.0 中,最终渲染都是通 过 render 函数,如果写 template 属性,则需要编译成 render 函数,那么这个编译过程会发生运行时,所以需 要带有编译器的版本。</strong></p>
<h2 id="vue的本质构造函数">Vue的本质:构造函数</h2>
<pre><code class="language-javascript"> function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
</code></pre>
<p>总结:<strong>vue 本质上就是一个用 Function 实现的 Class,然后在它 的原型 prototype 以及它本身上扩展了一系列的方法和属 性。Vue 不用 ES6 的 Class 去实现的原因:按功能区分,把 功能扩展分散到多个模块中去实现,然后挂载中 vue 的 原型 prototype 上,也有在 Vue 这个对象本身 上。而不是在一个模块里实现所有,这种方式是用 Class 难以 实现的。这么做的好处是非常方便代码的维护和管理</strong></p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[浏览器-渲染篇(下)]]></title>
<id>https://evansalien.github.io/post/liu-lan-qi-xuan-ran-pian-xia/</id>
<link href="https://evansalien.github.io/post/liu-lan-qi-xuan-ran-pian-xia/">
</link>
<updated>2020-03-12T03:05:16.000Z</updated>
<content type="html"><![CDATA[<h1 id="浏览的回流与重绘">浏览的回流与重绘</h1>
<p><strong>接着上回文章结尾的,来讲讲浏览器的回流和重绘</strong></p>
<h2 id="回流">回流</h2>
<p>对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)。</p>
<h2 id="重绘">重绘</h2>
<p>对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。这个过程叫做重绘。</p>
<p><strong>重绘不一定导致回流,回流一定会导致重绘</strong> ,回流所需的成本比重绘高的多,改变深层次的节点很可能导致父节点的一系列回流。</p>
<p>下面动作坑呢过会导致性能问题:</p>
<ol>
<li>改变window大小</li>
<li>改变字体</li>
<li>添加或删除样式</li>
<li>文字改变</li>
<li>定位或者浮动</li>
<li>盒模型</li>
</ol>
<h2 id="重绘和回流与-event-loop-有关">重绘和回流与 Event loop 有关</h2>
<ol>
<li>当 Event loop 执行完 Microtasks 后,会判断 document 是否需要更新。因为浏览器是 60Hz 的刷新率,每 16ms 才会更新一次。</li>
<li>然后判断是否有 resize 或者 scroll ,有的话会去触发事件,所以 resize 和 scroll 事件也是至少 16ms 才会触发一次,并且自带节流功能。</li>
<li>判断是否触发了 media query</li>
<li>更新动画并且发送事件</li>
<li>判断是否有全屏操作事件</li>
<li>执行 requestAnimationFrame 回调</li>
<li>执行 IntersectionObserver 回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好</li>
<li>更新界面</li>
<li>以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行 requestIdleCallback 回调。</li>
</ol>
<h2 id="减少重绘和回流">减少重绘和回流</h2>
<ol>
<li>使用<strong>translate</strong>代替<strong>top</strong> 避免回流</li>
</ol>
<figure data-type="image" tabindex="1"><img src="https://evansalien.github.io/post-images/1583983883698.png" alt="" loading="lazy"></figure>
<ol start="2">
<li>
<p>使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)</p>
</li>
<li>
<p>把 DOM 离线后修改,比如:先把 DOM 给 display:none (有一次 Reflow),然后你修改100次,然后再把它显示出来</p>
</li>
<li>
<p>不要把 DOM 结点的属性值放在一个循环里当成循环里的变量</p>
</li>
<li>
<p>动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame</p>
</li>
<li>
<p>CSS 选择符从右往左匹配查找,避免 DOM 深度过深</p>
</li>
<li>
<p>将频繁运行的动画变为图层,图层能够阻止该节点回流影响别的元素。比如对于 video 标签,浏览器会自动将该节点变为图层。</p>
</li>
</ol>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[浏览器-渲染篇(上)]]></title>
<id>https://evansalien.github.io/post/liu-lan-qi-xuan-ran-pian/</id>
<link href="https://evansalien.github.io/post/liu-lan-qi-xuan-ran-pian/">
</link>
<updated>2020-03-10T07:03:53.000Z</updated>
<content type="html"><![CDATA[<h1 id="浏览器渲染机制一般分为以下步骤">浏览器渲染机制一般分为以下步骤</h1>
<ol>
<li>处理HTML并构建DOM树。</li>
<li>处理CSS构建CSSOM树。</li>
<li>将DOM与CSSOM合成一个渲染树。</li>
<li>根据渲染树来布局,计算每个节点的位置。</li>
<li>调用GPU绘制,合成图层,绘制在浏览器上。</li>
</ol>
<figure data-type="image" tabindex="1"><img src="https://evansalien.github.io/post-images/1583830191702.jpg" alt="" loading="lazy"></figure>
<ul>
<li>
<p><strong>DOM树</strong>:解析 HTML 以创建的是 DOM 树(DOM tree ):渲染引擎开始解析 HTML 文档,转换树中的标签到 DOM 节点,它被称为“内容树”。</p>
</li>
<li>
<p><strong>CSSOM树</strong>:解析 CSS(包括外部 CSS 文件和样式元素)创建的是 CSSOM 树。CSSOM 的解析过程与 DOM 的解析过程是并行的。</p>
</li>
<li>
<p><strong>渲染树</strong>:CSSOM 与 DOM 结合,之后我们得到的就是渲染树(Render tree )。</p>
</li>
<li>
<p><strong>布局渲染树</strong>:从根节点递归调用,计算每一个元素的大小、位置等,给每个节点所应该出现在屏幕上的精确坐标,我们便得到了基于渲染树的布局渲染树(Layout of the render tree)。</p>
</li>
<li>
<p><strong>绘制渲染树</strong>:遍历渲染树,每个节点将使用 UI 后端层来绘制。整个过程叫做绘制渲染树(Painting the render tree)。</p>
</li>
</ul>
<h3 id="总结下渲染过程">总结下渲染过程</h3>
<p>开始是基于 HTML 构建一个 DOM 树,这棵 DOM 树与 CSS 解释器解析出的 CSSOM 相结合,就有了布局渲染树。最后浏览器以布局渲染树为蓝本,去计算布局并绘制图像,我们页面的初次渲染就大功告成了。<br>
之后每当一个新元素加入到这个 DOM 树当中,浏览器便会通过 CSS 引擎查遍 CSS 样式表,找到符合该元素的样式规则应用到这个元素上,然后再重新去绘制它。</p>
<h2 id="css的优化">CSS的优化</h2>
<p>在CSS选择器方面的优化建议之前,先说一下CSS引擎查找样式表,对每条写的规则都是从右往左的顺序去匹配的,如:</p>
<pre><code>#list li { }
</code></pre>
<p>这样的写法其实很常见。大家平时习惯了从左到右阅读的文字阅读方式,会本能地以为浏览器也是从左到右匹配 CSS 选择器的,因此会推测这个选择器并不会费多少力气:#list 是一个 id 选择器,它对应的元素只有一个,查找起来应该很快。定位到了 list 元素,等于是缩小了范围后再去查找它后代中的 li 元素,没毛病。</p>
<p>事实上,CSS 选择符是从右到左进行匹配的。我们这个看似“没毛病”的选择器,实际开销相当高:浏览器必须遍历页面上每个 li 元素,并且每次都要去确认这个 li 元素的父元素 id 是不是 list,你说坑不坑!是否还记得:*<strong>{}</strong> 这个坑,但这个家伙很恐怖,它会匹配所有元素,所以浏览器必须去遍历每一个元素!我们低头看看自己页面里的元素个数,感觉心凉了——这得计算多少次呀!</p>
<p><strong>CSS性能优化方案</strong></p>
<ul>
<li>
<p>避免使用通配符,只对需要用到的元素进行选择。</p>
</li>
<li>
<p>关注可以通过继承实现的属性,避免重复匹配重复定义。</p>
</li>
<li>
<p>少用标签选择器。如果可以,用类选择器替代。</p>
</li>
</ul>
<h2 id="css-与-js-的加载顺序优化">CSS 与 JS 的加载顺序优化</h2>
<p>其实 HTML, CSS,JS, 都具有阻塞渲染的特性。</p>
<p>HTML 阻塞,天经地义——没有 HTML,何来 DOM?没有 DOM,渲染和优化,都是空谈。</p>
<p>只能是针对 CSS 和 JS 的阻塞去了解。</p>
<h3 id="css-的阻塞">CSS 的阻塞</h3>
<p>在刚刚的过程中,我们提到 DOM 和 CSSOM 合力才能构建渲染树。这一点会给性能造成严重影响:默认情况下,CSS 是阻塞的资源。浏览器在构建 CSSOM 的过程中,不会渲染任何已处理的内容。即便 DOM 已经解析完毕了,只要 CSSOM 不 OK,那么渲染这个事情就不 OK(这主要是为了避免没有 CSS 的 HTML 页面丑陋地“裸奔”在用户眼前)。</p>
<p>我们知道,只有当我们开始解析 HTML 后、解析到 link 标签或者 style 标签时,CSS 才登场,CSSOM 的构建才开始。很多时候,DOM 不得不等待 CSSOM。因此我们可以这样总结:</p>
<pre><code>CSS 是阻塞渲染的资源。需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间。
</code></pre>
<p>事实上,现在很多团队都已经做到了尽早(将 CSS 放在 head 标签里)和尽快(启用 CDN 实现静态资源加载速度的优化)。这个“把 CSS 往前放”的动作,对人来说已经内化为一种编码习惯。那么现在我们还应该知道,这个“习惯”不是空穴来风,它是由 CSS 的特性决定的。</p>
<h3 id="js的阻塞">JS的阻塞</h3>
<p>在首次渲染过程中,JS 并不是一个非登场不可的角色——没有 JS,CSSOM 和 DOM 照样可以组成渲染树,页面依然会呈现——即使它死气沉沉、毫无交互。</p>
<p>JS 的作用在于修改,它帮助我们修改网页的方方面面:内容、样式以及它如何响应用户交互。这“方方面面”的修改,本质上都是对 DOM 和 CSSOM 进行修改。因此 JS 的执行会阻止 CSSOM,在不作显式声明的情况下,它也会阻塞 DOM。</p>
<p>前面说过,JS 引擎是独立于渲染引擎存在的。 JS 代码在文档的何处插入,就在何处执行。当 HTML 解析器遇到一个 script 标签时,它会暂停渲染过程,将控制权交给 JS 引擎。JS 引擎对内联的 JS 代码会直接执行,对外部 JS 文件还要先获取到脚本、再进行执行。等 JS 引擎运行完毕,浏览器又会把控制权还给渲染引擎,继续 CSSOM 和 DOM 的构建。 因此与其说是 JS 把 CSS 和 HTML 阻塞了,不如说是 JS 引擎抢走了渲染引擎的控制权。</p>
<p>现在理解了阻塞的表现与原理,开始思考一个问题。浏览器之所以让 JS 阻塞其它的活动,是因为它不知道 JS 会做什么改变,担心如果不阻止后续的操作,会造成混乱。但是写 JS 的人,知道 JS 会做什么改变。假如可以确认一个 JS 文件的执行时机并不一定非要是此时此刻,就可以通过对它使用 defer 和 async 来避免不必要的阻塞,这里就引出了外部 JS 的三种加载方式。</p>
<h4 id="js的三种加载方式">JS的三种加载方式</h4>
<ol>
<li>正常方式</li>
</ol>
<pre><code class="language-html"><script src='app.js'></script>
</code></pre>
<p>这种情况下 JS 会阻塞浏览器,浏览器必须等待 index.js 加载和执行完毕才能去做其它事情。</p>
<ol start="2">
<li>async方式</li>
</ol>
<pre><code class="language-html"><script async src='app.js'></script>
</code></pre>
<p>async 模式下,JS 不会阻塞浏览器做任何其它的事情。它的加载是异步的,当它加载结束,JS 脚本会立即执行。</p>
<ol>
<li>defer方式</li>
</ol>
<pre><code class="language-html"><script defer src='app.js'></script>
</code></pre>
<p>defer 模式下,JS 的加载是异步的,执行是被推迟的。等整个文档解析完成、DOMContentLoaded 事件即将被触发时,被标记了 defer 的 JS 文件才会开始依次执行。</p>
<p>从应用的角度来说,一般当脚本与 DOM 元素和其它脚本之间的依赖关系不强时,会选用 async;当脚本依赖于 DOM 元素和其它脚本的执行结果时,会选用 defer。</p>
<p>通过审时度势地向 script 标签添加 async/defer,就可以告诉浏览器在等待脚本可用期间不阻止其它的工作,这样可以显著提升性能。</p>
<h2 id="小结">小结</h2>
<p>我们知道,当 JS 登场时,往往意味着对 DOM 的操作。DOM 操作所导致的性能开销的“昂贵”,大家可能早就有所耳闻,雅虎军规里很重要的一条就是“尽量减少 DOM 访问”。</p>
<p>那么 DOM 到底为什么慢,我们如何去规避这种慢呢?这里我们就引出了下一个章节需要重点解释的两个概念:CSS 中的回流(Reflow)与重绘(Repaint)。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[浏览器的组成]]></title>
<id>https://evansalien.github.io/post/liu-lan-qi-de-zu-cheng/</id>
<link href="https://evansalien.github.io/post/liu-lan-qi-de-zu-cheng/">
</link>
<updated>2020-03-10T02:39:03.000Z</updated>
<content type="html"><![CDATA[<h2 id="浏览器的主要结构">浏览器的主要结构</h2>
<ul>
<li>
<p>用户界面</p>
</li>
<li>
<p>浏览器引擎(负责窗口的管理、tab进程管理...)</p>
</li>
<li>
<p>渲染引擎(内核,负责HTML解析,页面渲染)</p>
</li>
<li>
<p>JS引擎(JS解释器,V8, chakra, javascrriptcore...)</p>
</li>
</ul>
<p><strong>主讲浏览器核心的渲染引擎和JS引擎</strong></p>
<h2 id="从进程和线程角度来了解浏览器工作">从进程和线程角度来了解浏览器工作</h2>
<h3 id="进程和线程">进程和线程</h3>
<ul>
<li>
<p>进程是cpu资源分配的最小单位(能拥有资源和独立运行的最小单位)</p>
</li>
<li>
<p>线程是cpu调度的最小单位(线程是建立在进程基础上的一个程序运行单位,一个进程可以拥有多个线程)</p>
</li>
</ul>
<p><strong>进程可以类比为工厂,线程就是工厂里面的工人,一个工厂可以包含一个或者多个工人,工人之间可以相互协作,并且共享工作空间</strong></p>
<h3 id="浏览器的多进程架构">浏览器的多进程架构</h3>
<p>现代的浏览器采用的都是多进程架构,主要包含以下三种进程:</p>
<ul>
<li>
<p>Browser进程<br>
<strong>浏览器的主线程,主要负责浏览器的页面管理、书签、前进后退、资源下载管理等,整个浏览器应用程序只有一个,对应上述浏览器组成中的浏览器引擎。</strong></p>
</li>
<li>
<p>渲染进程<br>
<strong>内核进程、负责页面渲染、JS执行,对应的是上述的渲染引擎和JS引擎,一个浏览器可以包含多个渲染进程,每个Tab窗口页对应一个渲染进程</strong></p>
</li>
<li>
<p>GPU进程<br>
<strong>负责GPU渲染,整个浏览器应用程序只有一个</strong></p>
</li>
<li>
<p>插件进程<br>
<strong>浏览器安装的插件(扩展程序),每个插件会创建一个进程</strong></p>
</li>
</ul>
<p>如下图:<br>
<img src="https://evansalien.github.io/post-images/1583809456713.jpg" alt="" loading="lazy"></p>
<p><strong>拥有3个插件进程,3个浏览器进程1个GPU进程1个网络进程</strong></p>
<p>这种多进程浏览器架构的优势 :</p>
<ul>
<li>
<p>避免单个页面奔溃影响整个浏览器</p>
</li>
<li>
<p>避免第三方插件奔溃影响整个浏览器</p>
</li>
<li>
<p>充分利用多核优势</p>
</li>
</ul>
<h3 id="浏览器的渲染进程">浏览器的渲染进程</h3>
<ul>
<li>
<p>浏览器有多个渲染进程、一个Tab页面一个(相同的Tab页面会被合并)</p>
</li>
<li>
<p>一个渲染进程包含多个线程</p>
</li>
</ul>
<p><strong>一个渲染进程主要包括如下线程:</strong></p>
<p>1.GUI线程(主要负责解析HTML、CSS和渲染页面)</p>
<p>2.JS引擎线程(负责解析和执行JS代码)</p>
<p>3.事件线程(控制事件循环)</p>
<p>4.定时器线程(处理定时器相关逻辑)</p>
<p>5.异步请求线程(发起Ajax时会生成该线程)</p>
<p><strong>线程规则如下</strong></p>
<ul>
<li>
<p><strong>GUI渲染线程与JS引擎线程是互斥的</strong>,当JS引擎执行时GUI线程会被挂起,页面的更新操作会等到JS引擎空闲时执行,涉及任务和微任务相关知识</p>
</li>
<li>
<p>一个渲染进程同时只有一个JS解析线程在运行</p>
</li>
<li>
<p>JS引擎线程不停的处理事件线程推送到事件队列中的任务</p>
</li>
<li>
<p>定时器和异步请求最终生成的回调事件也有事件线程来控制和管理了解了浏览器的渲染进程之后我们再来看看JS引擎。</p>
</li>
</ul>
<h3 id="从实践循环的角度理解js引擎工作的过程">从实践循环的角度理解JS引擎工作的过程</h3>
<p><strong>同步与异步</strong></p>
<p>同步是代码执行后可以获得想要的结果,异步是指代码执行后不能立即获得想要的结果</p>
<p><strong>同步任务和异步任务</strong></p>
<p>同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而是由事件线程调度,在满足执行条件的时候放到事件队列中,等待主线程(JS线程)的执行。</p>
<p><strong>循环事件的概念</strong></p>
<p>JS包含异步操作(Ajax, 定时器),这些异步操作完成之后要通知JS引擎来处理异步操作返回的结果,(Ajax的CallBack)。这些异步操作什么时候完成的是不确定的,所以需要一个事件队列,事件线程将已经完成的异步操作的回调任务加载到事件队列中,JS引擎在执行完当前的同步任务之后循环从事件队列中取事件执行。</p>
<p><strong>异步任务</strong></p>
<pre><code>setTimeout, setTnterval, Promise, process.nextTick(NodeJs), Ajax
</code></pre>
<p>异步任务统一有事件线程管理,当异步任务完成的时候会被放入到事件队列中,JS在顺序执行完当前的代码之后会从事件队列中读取任务,再重复整个流程,判断该任务是同步还是异步</p>
<figure data-type="image" tabindex="1"><img src="https://evansalien.github.io/post-images/1583817990649.png" alt="" loading="lazy"></figure>
<p><strong>异步任务的优先级</strong></p>
<p>如果按照上述的简化理解,所有异步任务都按照满足执行条件的顺序放到事件队列中,世界很和平,先来先到,但是在ES6当中,引入了microtask的概念,microtask会在当前的任务执行完成之后立即执行。因为我们将异步任务分为task和microtask,我们又称为宏任务和微任务。</p>
<pre><code>task: setTImeout、setInterval、ajax
microtask:MutationObserve、promise、process.nextTick(Node.js)
</code></pre>
<p>这样子加了优先级的话JS的执行又会变得再复杂一点,如下图所示,异步任务执行完成之后会判断他是task还是microtask,再分别加到不同的时间队列中,JS当前任务执行完成之后优先清空当前的microtask队列,而且在每次执行完宏任务的时候都会去清空微任务。</p>
<figure data-type="image" tabindex="2"><img src="https://evansalien.github.io/post-images/1583818100784.png" alt="" loading="lazy"></figure>
<p>如下demo:<br>
<img src="https://evansalien.github.io/post-images/1583818807797.png" alt="" loading="lazy"></p>
<p>执行后</p>
<ol>
<li>click</li>
<li>promise</li>
<li>timeout</li>
</ol>
<p><strong>UI渲染线程什么时候工作</strong></p>
<p>UI渲染线程会在当前的Task执行完成之后,下一个Task执行之前执行,微任务会优先于UI渲染线程,这就意味着我们使用微任务更新的DOM能更快的被渲染出来。另外Vue.js最新版本数据变更的时候采用的是promise和MutationObserver创建微任务</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[浏览器-事件机制]]></title>
<id>https://evansalien.github.io/post/liu-lan-qi-shi-jian-ji-zhi/</id>
<link href="https://evansalien.github.io/post/liu-lan-qi-shi-jian-ji-zhi/">
</link>
<updated>2020-03-09T09:24:21.000Z</updated>
<content type="html"><![CDATA[<h2 id="事件触发三个阶段">事件触发三个阶段</h2>
<h3 id="事件触发有三个阶段">事件触发有三个阶段</h3>
<ul>
<li><strong>window</strong>往事件触发出传播,遇到注册的捕获事件会触发</li>
<li>传播到事件触发处往<strong>window</strong>传播,遇到注册的冒泡事件会触发</li>
</ul>
<p>事件触发一般来说会按照上面的顺序进行,但是也有特例,如果给一个目标节点同时注册冒泡和捕获事件,事件触发会按照注册的顺序执行。<br>
<img src="https://evansalien.github.io/post-images/1583746493173.png" alt="" loading="lazy"></p>
<h2 id="注册事件">注册事件</h2>
<p>通常我们使用 addEventListener 注册事件,该函数的第三个参数可以是布尔值,也可以是对象。对于布尔值 useCapture 参数来说,该参数默认值为 false 。useCapture 决定了注册的事件是捕获事件还是冒泡事件。对于对象参数来说,可以使用以下几个属性:</p>
<ul>
<li>capture,布尔值,和 useCapture 作用一样</li>
<li>once,布尔值,值为 true 表示该回调只会调用一次,调用后会移除监听</li>
<li>passive,布尔值,表示永远不会调用 preventDefault</li>
</ul>
<p>一般来说,我们只希望事件只触发在目标上,这时候可以使用 stopPropagation 来阻止事件的进一步传播。通常我们认为 stopPropagation 是用来阻止事件冒泡的,其实该函数也可以阻止捕获事件。stopImmediatePropagation 同样也能实现阻止事件,但是还能阻止该事件目标执行别的注册事件。<br>
<img src="https://evansalien.github.io/post-images/1583746844308.png" alt="" loading="lazy"></p>
<h2 id="事件代理">事件代理</h2>
<p>如果一个节点中的子节点是动态生成的,那么子节点需要注册事件的话应该注册在父节点上</p>
<figure data-type="image" tabindex="1"><img src="https://evansalien.github.io/post-images/1583746938029.png" alt="" loading="lazy"></figure>
<p>事件代理的方式相对于直接给目标注册事件来说,有以下优点</p>
<ul>
<li>节省内存</li>
<li>不需要给子节点注销事件</li>
</ul>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[EventLoop]]></title>
<id>https://evansalien.github.io/post/eventloop/</id>
<link href="https://evansalien.github.io/post/eventloop/">
</link>
<updated>2020-03-09T05:11:41.000Z</updated>
<content type="html"><![CDATA[<h1 id="如何理解eventloop的宏任务和微任务">如何理解EventLoop的宏任务和微任务</h1>
<h2 id="宏任务-macrotask-引入">宏任务 (MacroTask) 引入</h2>
<p><strong>在JS中,大部分的任务都是主线程上执行,常见的任务有:</strong></p>
<pre><code>1. 渲染事件
2. 用户交互事件
3. js脚本执行
4. 网络请求、文件读写完成事件等等
bool keep_running = true;
void MainTherad(){
for(;;){
//执行队列中的任务
Task task = task_queue.takeTask();
ProcessTask(task);
//执行延迟队列中的任务
ProcessDelayTask()
if(!keep_running) //如果设置了退出标志,那么直接退出线程循环
break;
}
}
</code></pre>
<p><strong>为了让这些事件有条不紊地进行,JS引擎需要对之执行的顺序做一定的安排,V8 其实采用的是一种队列的方式来存储这些任务, 即先进来的先执行 ,但是其中包含了两种任务队列,除了上述提到的任务队列, 还有一个延迟队列,它专门处理诸如setTimeout/setInterval这样的定时器回调任务</strong></p>
<h2 id="微任务-microtask-引入">微任务 (MicroTask) 引入</h2>
<p><strong>对于每个宏任务而言,其内部都有一个微任务队列。那为什么要引入微任务?微任务在什么时候执行呢?</strong><br /><br>
<strong>其实引入微任务的初衷是为了解决异步回调的问题。想一想,对于异步回调的处理,有多少种方式?总结起来有两点:</strong></p>
<pre><code>1. 将异步回调进行宏任务队列的入队操作。
2. 将异步回调放到当前宏任务的末尾。
</code></pre>
<p><strong>如果采用第一种方式,那么执行回调的时机应该是在前面所有的宏任务完成之后,倘若现在的任务队列非常长,那么回调迟迟得不到执行,造成应用卡顿。</strong><br>
<strong>为了规避这样的问题,V8 引入了第二种方式,这就是微任务的解决方式。在每一个宏任务中定义一个微任务队列,当该宏任务执行完成,会检查其中的微任务队列,如果为空则直接执行下一个宏任务,如果不为空,则依次执行微任务,执行完成才去执行下一个宏任务。</strong></p>
<p>常见的微任务有MutationObserver、Promise.then(或.reject) 以及以 Promise 为基础开发的其他技术(比如fetch API), 还包括 V8 的垃圾回收过程。</p>
<p><strong>Ok, 这便是宏任务和微任务的概念,接下来正式介绍JS非常重要的运行机制——EventLoop。</strong></p>
<h2 id="如何理解eventloop浏览器">如何理解EventLoop——浏览器</h2>
<pre><code class="language-javascript"> console.log('start');
setTimeout(() => {
console.log('timeout');
});
Promise.resolve().then(() => {
console.log('resolve');
});
console.log('end');
// console.log('start')
// async function test1 () {
// console.log('test1 start')
// await test2()
// console.log('test1 end')
// }
// setTimeout(() => {
// console.log('setTimeout')
// }, 0)
// new Promise(function (resolve) {
// console.log('promise start')
// resolve();
// }).then(res => {
// console.log('promise end')
// })
// async function test2() {
// console.log('test2 start')
// }
// test1()
// console.log('end')
// start
// promise start
// test1 start
// test2 start
// end
// promise end
// test1 end
// setTimeout
</code></pre>
<p><strong>我们来分析一下:</strong></p>
<pre><code>1. 刚开始整个脚本作为一个宏任务来执行,对于同步代码直接压入执行栈中,进行执行,因此先打印start和end
2. setTimeout 作为一个宏任务放入宏任务队列
3. Promise.then作为一个为微任务放入到微任务队列
4. 当本次宏任务执行完,检查微任务队列,发现一个Promise.then, 执行
5. 接下来进入到下一个宏任务——setTimeout, 执行
</code></pre>
<p>所以输出的是:</p>
<pre><code>// start
// end
// resolve
// timeout
</code></pre>
<p><strong>这样就带大家直观地感受到了浏览器环境下 EventLoop 的执行流程。不过,这只是其中的一部分情况,接下来我们来做一个更完整的总结</strong></p>
<pre><code>1. 一开始整段脚本作为第一个宏任务执行
2. 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
3. 当前宏任务执行完出队,检查微任务队列,如果有则依次执行,直到微任务队列为空
4. 执行浏览器 UI 线程的渲染工作
5. 检查是否有Web worker任务,有则执行
6. 执行队首新的宏任务,回到2,依此循环,直到宏任务和微任务队列都为空
</code></pre>
<p><strong>经典面试题</strong></p>
<pre><code class="language-javascript">async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
return 'async return';
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function() {
console.log("setTimeout");
}, 0);
async1().then(function (message) { console.log(message) });
new Promise(function(resolve) {
console.log("promise1");
resolve();
}).then(function() {
console.log("promise2");
});
console.log("script end");
</code></pre>
<h2 id="如何理解eventloopnodejs">如何理解EventLoop——nodejs</h2>
<p><strong>nodejs和浏览器的eventLoop还是有很大的差别的,先来看一张node的eventLoop流程图吧</strong><br>
<img src="https://evansalien.github.io/post-images/1583730932501.png" alt="" loading="lazy"></p>
<p><strong>别说你,我第一时间看这个也是一面懵,不急慢慢来拆分nodej的事件循环机制</strong></p>
<h3 id="三大关键阶段">三大关键阶段</h3>
<p>首先,梳理一下 nodejs 三个非常重要的执行阶段:</p>
<pre><code>1. 执行 定时器回调 的阶段。检查定时器,如果到了时间,就执行回调。这些定时器就是setTimeout、setInterval。这个阶段暂且叫它timer。
2. 轮询(英文叫poll)阶段。因为在node代码中难免会有异步操作,比如文件I/O,网络I/O等等,那么当这些异步操作做完了,就会来通知JS主线程,怎么通知呢?就是通过'data'、'connect'等事件使得事件循环到达 poll 阶段。到达了这个阶段后:
如果当前已经存在定时器,而且有定时器到时间了,拿出来执行,eventLoop 将回到timer阶段。
如果没有定时器, 会去看回调函数队列。
(1) 如果队列不为空,拿出队列中的方法一次执行
(2) 如果队列为空,检查是否有 setImmdiate 的回调
a. 有则前往check阶段(下面会说)
b. 没有则继续等待,相当于阻塞了一段时间(阻塞时间是有上限的), 等待 callback 函数加入队列,加入后会立刻执行。一段时间后自动进入 check 阶段
1. check 阶段。这是一个比较简单的阶段,直接执行 setImmdiate 的回调。
</code></pre>
<p><strong>这三个阶段为一个循环过程。不过现在的eventLoop并不完整,我们现在就来一一地完善</strong></p>
<h3 id="完善">完善</h3>
<p><strong>首先,当第 1 阶段结束后,可能并不会立即等待到异步事件的响应,这时候 nodejs 会进入到 I/O异常的回调阶段。比如说 TCP 连接遇到ECONNREFUSED,就会在这个时候执行回调。</strong></p>
<p><strong>并且在 check 阶段结束后还会进入到 关闭事件的回调阶段。如果一个 socket 或句柄(handle)被突然关闭,例如 socket.destroy(), 'close' 事件的回调就会在这个阶段执行。</strong></p>
<p><strong>梳理一下,nodejs 的 eventLoop 分为下面的几个阶段:</strong></p>
<pre><code>1. timer 阶段
2. I/O 异常回调阶段
3. 空闲、预备状态(第2阶段结束,poll 未触发之前)
4. poll 阶段
5. check 阶段
6. 关闭事件的回调阶段
</code></pre>
<pre><code class="language-javascript"> setTimeout(()=>{
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
setTimeout(()=>{
console.log('timer2')
Promise.resolve().then(function() {
console.log('promise2')
})
}, 0)
// timer1
// promise1
// time2
// promise2
</code></pre>
<h3 id="nodejs-和-浏览器关于eventloop的主要区别">nodejs 和 浏览器关于eventLoop的主要区别</h3>
<p><strong>两者最主要的区别在于浏览器中的微任务是在每个相应的宏任务中执行的,而nodejs中的微任务是在不同阶段之间执行的。</strong></p>
<h3 id="关于processnexttick的一点说明">关于process.nextTick的一点说明</h3>
<p><strong>process.nextTick 是一个独立于 eventLoop 的任务队列。在每一个 eventLoop 阶段完成后会去检查这个队列,如果里面有任务,会让这部分任务优先于微任务执行。</strong></p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[JavaScript 中的 call, apply, bind]]></title>
<id>https://evansalien.github.io/post/javascript_call-apply-bind/</id>
<link href="https://evansalien.github.io/post/javascript_call-apply-bind/">
</link>
<updated>2020-03-06T06:57:10.000Z</updated>
<content type="html"><![CDATA[<h1 id="call-apply-bin区别">call、apply 、 bin区别</h1>
<h2 id="call-call-方法在使用一个指定的-this-值和若干个指定的参数值的前提下调用某个函数或方法">call: call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。</h2>
<pre><code class="language-javascript"> var foo = {
value: 1,
};
function bar() {
console.log(this.value);
}
bar.call(foo); // 1
// 1. call 改变了this的指向,指向到了foo
// 2. bar 函数执行了
</code></pre>
<p><strong>模拟实现call</strong></p>
<pre><code class="language-javascript"> // 如何模拟上面这两个步骤呢?
// 试想调用call的时候,把foo对象该找成如下效果
var foo = {
value: 1,
bar: function () {
console.log(this.value)
}
};
foo.bar(); // 1
// 这个时候 this 就指向了 foo,是不是很简单呢?
// 但是这样却给 foo 对象本身添加了一个属性,这可不行呐!
// 不过也不用担心,我们用 delete 再删除它不就好了~
// 所以可以模拟拆分步骤
// 1. 将函数设为对象的属性 foo.fn = bar
// 2. 执行该函数 foo.fn()
// 3. 删除该函数 delete foo.fn
Function.prototype.call1 = function(context) {
context.fn = this;
context.fn();
delete context.fn;
}
// 测试一下
var foo = {