├── .gitignore
├── DOM_Diff
├── README.md
└── demo
│ ├── vdom2.js
│ └── 递归.html
└── Virtual_Dom
├── README.md
└── static
├── Vdom.png
└── Vdom2.png
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
--------------------------------------------------------------------------------
/DOM_Diff/README.md:
--------------------------------------------------------------------------------
1 | # Diff
2 |
3 | ## diff:用于比较两个文件的内容差异,可以被制作成补丁文件,使用 patch 命令对相应的文件打补丁
4 |
5 | 
6 |
7 | ### DIFF 算法在执行时有三个维度,分别是 Tree DIFF、Component DIFF 和 Element DIFF,执行时按顺序依次执行,它们的差异仅仅因为 DIFF 粒度不同、执行先后顺序不同。
8 |
9 | ### Tree DIFF 是对树的每一层进行遍历,如果某组件不存在了,则会直接销毁。如图所示,左边是旧属,右边是新属,第一层是 R 组件,一模一样,不会发生变化;第二层进入 Component DIFF,同一类型组件继续比较下去,发现 A 组件没有,所以直接删掉 A、B、C 组件;继续第三层,重新创建 A、B、C 组件。
10 |
11 | 
12 |
13 | ### 如图所示,第一层遍历完,进行第二层遍历时,D 和 G 组件是不同类型的组件,不同类型组件直接进行替换,将 D 删掉,再将 G 重建
14 |
15 | 
16 |
17 | ### 作用
18 |
19 | - 计算出 Virtual DOM 中真正变化的部分,并只针对该部分进行原生 DOM 操作,而非重新渲染整个页面。
20 |
21 | ### 传统 diff 算法
22 |
23 | - 通过循环递归对节点进行依次对比,算法复杂度达到 O(n^3) ,n 是树的节点数,这个有多可怕呢?——如果要展示 1000 个节点,得执行上亿次比较。。即便是 CPU 快能执行 30 亿条命令,也很难在一秒内计算出差异。
24 |
25 | ## Vue
26 |
27 | ### 当数据发生变化时,vue 是怎么更新节点的?
28 |
29 | - 要知道渲染真实 DOM 的开销是很大的,比如有时候我们修改了某个数据,如果直接渲染到真实 dom 上会引起整个 dom 树的重绘和重排,有没有可能我们只更新我们修改的那一小块 dom 而不要更新整个 dom 呢?diff 算法能够帮助我们。
30 |
31 | - 我们先根据真实 DOM 生成一颗 virtual DOM,当 virtual DOM 某个节点的数据改变后会生成一个新的 Vnode,然后 Vnode 和 oldVnode 作对比,发现有不一样的地方就直接修改在真实的 DOM 上,然后使 oldVnode 的值为 Vnode。
32 |
33 | - diff 的过程就是调用名为 patch 的函数,比较新旧节点,一边比较一边给真实的 DOM 打补丁。
34 |
35 | ### virtual DOM 和真实 DOM 的区别?
36 |
37 | - virtual DOM 是将真实的 DOM 的数据抽取出来,以对象的形式模拟树形结构。比如 dom 是这样的:
38 |
39 | ```html
40 |
43 | ```
44 |
45 | - 对应的 virtual DOM(伪代码):
46 |
47 | ```js
48 | var Vnode = {
49 | tag: "div",
50 | children: [{ tag: "p", text: "123" }]
51 | };
52 | ```
53 |
54 | ### diff 的比较方式?
55 |
56 | - 在采取 diff 算法比较新旧节点的时候,比较只会在同层级进行, 不会跨层级比较。
57 |
58 | ```html
59 |
62 |
63 |
64 | 456
65 |
66 | ```
67 |
68 | - 上面的代码会分别比较同一层的两个 div 以及第二层的 p 和 span,但是不会拿 div 和 span 作比较。在别处看到的一张很形象的图:
69 |
70 | 
71 |
72 | ### diff 流程图
73 |
74 | - 当数据发生改变时,set 方法会让调用 Dep.notify 通知所有订阅者 Watcher,订阅者就会调用 patch 给真实的 DOM 打补丁,更新相应的视图。
75 |
76 | 
77 |
78 | ### Key
79 |
80 | - 可以标识组件的唯一性,为了更好地区别各个组件 key 的作用主要是为了高效的更新虚拟 DOM。另外 vue 中在使用相同标签名元素的过渡切换时,也会使用到 key 属性,其目的也是为了让 vue 可以区分它们,否则 vue 只会替换其内部属性而不会触发过渡效果。
81 |
82 | ## React
83 |
84 | ### render 执行的结果得到的不是真正的 DOM 节点.结果仅仅是轻量级的 JavaScript 对象, 我们称之为 virtual DOM.
85 |
86 | ### React 工作流程
87 |
88 | ```js
89 | var MyComponent = React.createClass({
90 | render: function() {
91 | if (this.props.first) {
92 | return (
93 |
94 | A Span
95 |
96 | );
97 | } else {
98 | return (
99 |
102 | );
103 | }
104 | }
105 | });
106 | ```
107 |
108 | - React 要从这个表现形式当中尝试找到前一个渲染结果到后一个的最小步数.
109 | 比如, 当我们挂载了
110 |
111 | ```html
112 |
113 | ```
114 |
115 | , 然后用
116 |
117 | ```html
118 |
119 | ```
120 |
121 | 替换, 然后又取消挂载,
122 | 这样一个过程的 DOM 的指令是这样的:
123 |
124 | - 从没有到第一步
125 |
126 | - 创建节点:
127 |
128 | ```html
129 | A Span
130 | ```
131 |
132 | - 第一步到第二步
133 |
134 | - 替换属性: className="first" 到 className="second"
135 |
136 | - 替换节点:
137 |
138 | ```html
139 | A Span 到
140 | A Paragraph
141 | ```
142 |
143 | - 第二步到没有
144 |
145 | - 删除节点:
146 |
147 | ```html
148 |
149 | ```
150 |
151 | ### 调和:将 Virtual DOM 树转换成 actual DOM 树的最少操作的过程 称为 调和 。
152 |
153 | ### React diff 算法:diff 算法是调和的具体实现。
154 |
155 | ### diff 策略
156 |
157 | #### React 用 三大策略 将 O(n^3)复杂度 转化为 O(n)复杂度
158 |
159 | - 策略一(tree diff):
160 | Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计。
161 | - 策略二(component diff):
162 | 拥有相同类的两个组件 生成相似的树形结构,
163 | 拥有不同类的两个组件 生成不同的树形结构。
164 | - 策略三(element diff):
165 | 对于同一层级的一组子节点,通过唯一 id 区分。
166 |
167 | ### component diff
168 |
169 | #### React 对不同的组件间的比较,有三种策略
170 |
171 | - 同一类型的两个组件,按原策略(层级比较)继续比较 Virtual DOM 树即可。
172 | - 同一类型的两个组件,组件 A 变化为组件 B 时,可能 Virtual DOM 没有任何变化,如果知道这点(变换的过程中,Virtual DOM 没有改变),可节省大量计算时间,所以 用户 可以通过 shouldComponentUpdate() 来判断是否需要 判断计算。
173 | - 不同类型的组件,将一个(将被改变的)组件判断为 dirty component(脏组件),从而替换 整个组件的所有节点
174 | - 注意:如果组件 D 和组件 G 的结构相似,但是 React 判断是 不同类型的组件,则不会比较其结构,而是删除 组件 D 及其子节点,创建组件 G 及其子节点。
175 |
176 | ### element diff
177 |
178 | #### 当节点处于同一层级时,diff 提供三种节点操作:删除、插入、移动。
179 |
180 | - 插入:组件 C 不在集合(A,B)中,需要插入
181 | - 删除:(1)组件 D 在集合(A,B,D)中,但 D 的节点已经更改,不能复用和更新,所以需要删除 旧的 D ,再创建新的。
182 | (2)组件 D 之前在 集合(A,B,D)中,但集合变成新的集合(A,B)了,D 就需要被删除。
183 | 移动:组件 D 已经在集合(A,B,C,D)里了,且集合更新时,D 没有发生更新,只是位置改变,如新集合(A,D,B,C),D 在第二个,无须像传统 diff,让旧集合的第二个 B 和新集合的第二个 D 比较,并且删除第二个位置的 B,再在第二个位置插入 D,而是 (对同一层级的同组子节点) 添加唯一 key 进行区分,移动即可。
184 |
185 | ### 如果 DOM 节点出现了跨层级操作,diff 会咋办呢?
186 |
187 | - 答:diff 只简单考虑同层级的节点位置变换,如果是跨层级的话,只有创建节点和删除节点的操作。
188 |
189 | ### 重点说下移动的逻辑:
190 |
191 | #### 情形一:新旧集合中存在相同节点但位置不同时,如何移动节点
192 |
193 | 
194 |
195 | - 看着上图的 B,React 先从新中取得 B,然后判断旧中是否存在相同节点 B,当发现存在节点 B 后,就去判断是否移动 B。
196 | B 在旧 中的 index=1,它的 lastIndex=0,不满足 index < lastIndex 的条件,因此 B 不做移动操作。此时,一个操作是,lastIndex=(index,lastIndex)中的较大数=1.
197 | 注意:lastIndex 有点像浮标,或者说是一个 map 的索引,一开始默认值是 0,它会与 map 中的元素进行比较,比较完后,会改变自己的值的(取 index 和 lastIndex 的较大数)。
198 | - 看着 A,A 在旧的 index=0,此时的 lastIndex=1(因为先前与新的 B 比较过了),满足 index < lastIndex,因此,对 A 进行移动操作,此时 lastIndex=max(index,lastIndex)=1。
199 | - 看着 D,同(1),不移动,由于 D 在旧的 index=3,比较时,lastIndex=1,所以改变 lastIndex=max(index,lastIndex)=3
200 | - 看着 C,同(2),移动,C 在旧的 index=2,满足 index < lastIndex(lastIndex=3),所以移动。
201 | 由于 C 已经是最后一个节点,所以 diff 操作结束。
202 |
203 | #### 情形二:新集合中有新加入的节点,旧集合中有删除的节点
204 |
205 | 
206 |
207 | - B 不移动,不赘述,更新 l astIndex=1
208 | - 新集合取得 E,发现旧不存在,故在 lastIndex=1 的位置 创建 E,更新 lastIndex=1
209 | - 新集合取得 C,C 不移动,更新 lastIndex=2
210 | - 新集合取得 A,A 移动,同上,更新 lastIndex=2
211 | - 新集合对比后,再对旧集合遍历。判断 新集合 没有,但 旧集合 有的元素(如 D,新集合没有,旧集合有),发现 D,删除 D,diff 操作结束。
212 |
213 | #### diff 的不足与待优化的地方
214 |
215 | 
216 |
217 | - 看图的 D,此时 D 不移动,但它的 index 是最大的,导致更新 lastIndex=3,从而使得其他元素 A,B,C 的 index < lastIndex,导致 A,B,C 都要去移动。
218 | - 理想情况是只移动 D,不移动 A,B,C。因此,在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,会影响 React 的渲染性能。
219 |
220 | ## 总结
221 |
222 | - 尽量不要跨层级的修改 dom
223 | - 设置 key 可以最大化的利用节点
224 | - diff 的效率并不是每种情况下都是最优的
225 |
--------------------------------------------------------------------------------
/DOM_Diff/demo/vdom2.js:
--------------------------------------------------------------------------------
1 | // 1.请用递归的方式遍历树形数据结构中的每一个节点
2 | const options = [
3 | {
4 | value: 'zhejiang',
5 | label: 'Zhejiang',
6 | children: [
7 | {
8 | value: 'hangzhou',
9 | label: 'Hangzhou',
10 | children: [
11 | {
12 | value: 'xihu',
13 | label: 'West Lake'
14 | }
15 | ]
16 | }
17 | ]
18 | },
19 | {
20 | value: 'jiangsu',
21 | label: 'Jiangsu',
22 | children: [
23 | {
24 | value: 'nanjing',
25 | label: 'Nanjing',
26 | children: [
27 | {
28 | value: 'zhonghuamen',
29 | label: 'Zhong Hua Men'
30 | }
31 | ]
32 | }
33 | ]
34 | }
35 | ];
36 |
37 | function recursion (data,ck){
38 | if(!Array.isArray(data)){
39 | typeof ck === 'function' && ck(data);
40 | }else {
41 | data.forEach(item=>{
42 | typeof ck === 'function' && ck(item);
43 | if(item.children){
44 | recursion(item.children,ck);
45 | }
46 | })
47 | }
48 | }
49 |
50 | recursion (options,(data)=>{
51 | console.log(data,'1')
52 | })
53 |
54 | // 2.将类似以下JSON表示的树状结构(可以无限层级)通过parseDOM函数(使用document.createElement,
55 | // document.createTextNode,appendChild等方法)生成一颗DOM树(返回一个element元素)
56 | const JsonTree= {
57 | "tagName":"body", // dom树的跟节点
58 | "children":[{
59 | "tagName":"ul",
60 | "props":{
61 | "className":"list",
62 | "data-name":"jsontree"
63 | },
64 | },{
65 | "tagName":"a",
66 | "props":{
67 | "href":"https://www.aliyun.com",
68 | "target":"_blank"
69 | },
70 | "children":[
71 | {
72 | "tagName":'div',
73 | "props":{
74 | "className":"getit"
75 | },
76 | "children":[
77 | {
78 | "tagName":"h1",
79 | }
80 | ]
81 | },
82 | {
83 | "tagName":"div",
84 | "props":{
85 | "data-id":"3424"
86 | },
87 | "children":"hello world"
88 | }
89 | ]
90 | } ]
91 | }
92 | function createDom (tagName,props,children){
93 | let dom = document.createElement(tagName);
94 | if(!props){
95 | return dom;
96 | }
97 | let prop = Object.entries(props);
98 | prop.forEach(item=>{
99 | dom.setAttribute(item[0],item[1]);
100 | })
101 | if(!children){
102 | return dom;
103 | }
104 | if(typeof children === 'string'||typeof children === 'number'){
105 | dom.innerHTML = children;
106 | }else if(typeof children === 'object'){
107 | if(Array.isArray(children)){
108 | children.forEach(item=>{
109 | dom.appendChild(item);
110 | })
111 | }else{
112 | dom.appendChild(children);
113 | }
114 | }
115 | return dom;
116 | }
117 | function createTree (JsonTree){ // 递归树
118 | let domtree = {};
119 | domtree = JsonTree.map(item=>{
120 | console.log(item,'2');
121 | if(!item.children){
122 | return createDom(item.tagName,item.props)
123 | }else if(!Array.isArray(item.children)){
124 | return createDom(item.tagName,item.props,item.children)
125 | }else if(Array.isArray(item.children)){
126 | return createDom(item.tagName,item.props,createTree(item.children));
127 | }
128 | })
129 | return domtree;
130 | }
131 |
132 | function parseDom(JsonTree){
133 | let html = createDom(JsonTree.tagName)
134 | let b = createTree(JsonTree.children);
135 | console.log(b,'22');
136 | b.forEach(item=>{
137 | html.appendChild(item);
138 | });
139 | return html;
140 | }
141 |
142 | let a = parseDom(JsonTree);
143 | console.log(a,'222');
--------------------------------------------------------------------------------
/DOM_Diff/demo/递归.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
88 |
--------------------------------------------------------------------------------
/Virtual_Dom/README.md:
--------------------------------------------------------------------------------
1 | ###虚拟Dom
2 | (1)什么是虚拟Dom
3 | > Vdom可以看作是一个使用javascript模拟DOM结构的属性结构,这个树结构包含整个DOM结构的信息,如下图:
4 |
5 | 
6 | 
7 |
8 |
9 | > 可见左边的DOM结构,不论是标签还是标签的属性或标签的子级,都会对应在右边的树结构中
10 |
11 | (2)为什么要用虚拟DOM?
12 | 之前使用原生js或者jquery写页面的时候会发现操作DOM是一件非常麻烦的一件事情,往往是DOM标签和js逻辑同时写在js文件里,数据交互时不时还要写很多的input隐藏域,如果没有好的代码规范的话会显得代码非常冗余混乱,耦合性高并且难以维护。
13 |
14 | 另外一方面在浏览器里一遍又一遍的渲染DOM是非常非常消耗性能的,常常会出现页面卡死的情况;所以尽量减少对DOM的操作成为了优化前端性能的必要手段
15 | > vdom就是将DOM的对比放在了js层,通过对比不同之处来选择新渲染DOM节点,从而提高渲染效率。
16 |
--------------------------------------------------------------------------------
/Virtual_Dom/static/Vdom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emperorY/React_study/a99bcb9ee19fd3100fbde8f06ed0f6db81a2320f/Virtual_Dom/static/Vdom.png
--------------------------------------------------------------------------------
/Virtual_Dom/static/Vdom2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emperorY/React_study/a99bcb9ee19fd3100fbde8f06ed0f6db81a2320f/Virtual_Dom/static/Vdom2.png
--------------------------------------------------------------------------------