├── README.md
├── img
├── 1.png
├── 2.png
├── 3.png
└── 4.png
├── step0
└── defineProperty_test.html
├── step1
├── XVue.js
└── index.html
├── step2
├── XVue.js
└── index.html
├── step3.1
├── XVue.js
├── compile.js
└── index.html
└── step3.2
├── XVue.js
├── compile.js
└── index.html
/README.md:
--------------------------------------------------------------------------------
1 | ## Vue.js 代码实现
2 |
3 | 检验学习效果的最好方法就是自己造轮子。最近在学习Vue源码,写了一个迷你版vue,实现数据响应式。从step1到step3.2,是开发步骤和实现思路,每一步都可以独立运行。
4 |
5 | 代码地址:https://github.com/dora-zc/miniature-vue
6 |
7 | 以上每个step文件夹对应下面的每一步骤,代表了代码实现的顺序,每个文件夹下的代码都可以独立运行。
8 |
9 | ### 1. 步骤一
10 |
11 | 创建XVue.js。
12 |
13 | 创建Vue类,通过Observer劫持监听所有属性。
14 |
15 | observe函数的作用:递归遍历data选项,它当中的defineReactive函数为data中每一个key定义getter和setter,达到数据劫持的目的。
16 |
17 | > 步骤一对应代码目录:step1
18 |
19 | ### 2. 步骤二
20 |
21 | 处理页面上的`
{{msg}}
`,也就是收集依赖,当msg的值发生变化时,视图需要做出相应的变化。因此需要创建依赖管理器,把所有依赖保存起来,当数据发生变化的时候再去更新对应的依赖。
22 |
23 | #### 2.1 创建Dep类
24 |
25 | Dep负责将视图中的所有依赖收集管理,包括依赖添加和派发通知
26 |
27 | 1- 在Dep类中创建数组deps=[],用来存放Watcher的实例
28 |
29 | 2-创建addDep方法,添加Watcher
30 |
31 | 3-创建notify方法,通知所有的Wather执行更新。遍历deps数组,调用每个Wather的更新方法
32 |
33 | #### 2.2 创建监听器Watcher类
34 |
35 | Watcher是具体的更新执行者。
36 |
37 | 1-将当前Watcher实例添加到Dep.target上。
38 |
39 | ```js
40 | Dep.target = this
41 | ```
42 |
43 | 之后在get时,就能通过Dep.target拿到当前Watcher的实例。
44 |
45 | 2-创建update方法
46 |
47 | 3-set方法中,调用dep.notify,让依赖管理器通知更新,则所有的Watcher会执行update方法
48 |
49 | 那么问题来了:Watcher在什么时候收集最合适?
50 |
51 | 在defineReactive函数的get方法中,get方法触发时,把Watcher放进Dep.target中。
52 |
53 | 那么问题又来了:为什么是在get方法中呢?
54 |
55 | 因为在扫描视图中的依赖时,如果扫描到`{{msg}}
`,此时一定会去访问msg的值,就会触发get。一旦get被触发,就能将Watcher放进dep中,实现依赖收集的目的。所以get是一个合适的时间点。
56 |
57 | 代码测试:在get中输出dep.deps,如果Watcher已经放进去了,并且控制台打印出Watcher中的update方法中的log,说明这一步操作成功了。
58 |
59 | 至此,已经完成的工作如下:
60 |
61 | 
62 |
63 | > 步骤二对应代码目录:step2
64 |
65 | 现在,Watcher发生变化时,视图还没有更新,下面我们将要完成视图更新的操作。
66 |
67 | 首先,需要Compile对界面模板解析指令,进行编译,编译的阶段实际是创建Watcher的阶段。Watcher是由编译器创建的。编译器在做依赖收集的时候,顺便把Watcher创建了。Watcher在创建的时候,立刻就能知道它将来要更新的是谁,它应该被谁管理,它发生变化以后值应该是什么。于是Watcher就知道调谁(Updater去做更新了)。
68 |
69 | 
70 |
71 | ### 3.步骤三
72 |
73 | 创建compile.js,用于扫描模板中所有依赖(指令、插值、绑定、事件…),创建更新函数和Watcher
74 |
75 | #### 3.1 扫描模板
76 |
77 | 1-创建编译器Compile类,接收两个参数,el(宿主元素或选择器)和vm(当前vue实例)
78 |
79 | 2-创建node2Fragment函数,将dom节点( $el )截成代码块( 转换为Fragment )来处理,而不是直接做dom操作,提高执行效率
80 |
81 | 3-创建compile函数,执行编译( 将模板中的动态值替换为真实的值 ),传入代码块
82 |
83 | 4-将生成的结果追加至宿主元素
84 |
85 | ##### 3.1.1 node2Fragment函数
86 |
87 | 创建一个新的fragment,将原生节点移动至fragment
88 |
89 | 返回fragment,传给编译函数进行编译
90 |
91 | ##### 3.1.2 compile函数
92 |
93 | 获取所有的孩子节点,进行遍历,判断节点类型,并作出相应的判断
94 |
95 | 处理元素节点
96 |
97 | 处理文本节点( 只处理{{msg}} 这种情况,其他的全部不处理)
98 |
99 | ...其他的节点类型暂时不判断了
100 |
101 | 遍历可能存在的子节点,往下递归
102 |
103 | 下面是compile函数中的两个核心方法
104 |
105 | 1-compileElement方法:编译元素节点
106 |
107 | ```html
108 | {{msg}}
109 | ```
110 |
111 | 拿到所有属性名称,进行遍历
112 |
113 | 2-compileText方法:编译文本节点
114 |
115 | 代码测试:
116 |
117 | 在XVue constructor中,创建编译器实例,将宿主元素el和当前vue实例作为参数传入。
118 |
119 | 如果compileElement和compileText两个函数能触发,控制台打印出"开始编译元素节点"和"开始编译文本节点",则说明功能正常,可以继续让下走了。
120 |
121 | > 对应代码:step3.1
122 |
123 |
124 |
125 | #### 3.2 编译元素节点和文本节点,并创建更新函数
126 |
127 | ##### 3.2.1 编译元素节点compileElement方法实现
128 |
129 | 获取节点所有属性,进行遍历。判断指令和事件,已经相应的处理方法。
130 |
131 | 指令只试着处理v-text,v-html,v-model三个,其他的暂不处理
132 |
133 | v-model:双向绑定还需要处理视图对模型的更新
134 |
135 | ##### 3.2.2 创建更新器函数
136 |
137 | 更新器函数:接收四个参数,node,vm,exp,dir(指令)
138 |
139 | 针对指令的更新器主要是在做dom操作
140 |
141 | 在更新器函数中创建Watcher实例,当Watcher监听到变化的时候,就能触发视图的更新。
142 |
143 | 至此,全部代码已经完成,双向数据绑定顺利实现!
144 |
145 | 
146 |
147 | > 对应代码:step3.2
148 |
149 |
150 |
151 | ## Vue.js 工作机制
152 |
153 | ### 初始化
154 |
155 | 在new Vue()之后,Vue会调用初始化函数,会初始化声明周期、事件、props、methods、data、computed和watcher等。其中最重要的是通过Object.defineProperty设置setter和getter,用来实现响应式和依赖收集。
156 |
157 | 初始化之后会调用$.mount挂载组件。
158 |
159 | ### 编译
160 |
161 | 编译模块分为三个阶段:
162 |
163 | #### 1-parse
164 |
165 | 使用正则解析模板中的vue的指令、变量等等,形成抽象语法树AST
166 |
167 | #### 2-optimize
168 |
169 | 标记一些静态节点,用作后面的性能优化,在diff的时候直接略过
170 |
171 | #### 3-generate
172 |
173 | 把第一步生成的AST转化为渲染函数 render function
174 |
175 | ### 响应式
176 |
177 | 这一块是vue最核心的内容。初始化的时候通过defineProperty进行绑定,设置通知的机制,当编译生成的渲染函数被实际渲染的时候,会触发getter进行依赖收集,在数据变化的时候,触发setter进行更新。
178 |
179 | ### 虚拟dom
180 |
181 | 虚拟dom是由react首创,Vue2开始支持,就是用JavaScript对象来描述dom结构,数据修改的时候,我们先修改虚拟dom中的数据,然后数组做diff算法,最后再汇总所有的diff,力求做最少的dom操作,毕竟js里对比很快,而真实的dom操作太慢了。
182 |
183 | ```html
184 |
187 | ```
188 |
189 | ```js
190 | // vdom
191 | {
192 | tag:'div',
193 | props:{
194 | name:'小菠萝',
195 | style: {color:red},
196 | onClick:xx
197 | },
198 | children:[
199 | {
200 | tag:'a',
201 | text:'click me'
202 | }
203 | ]
204 | }
205 | ```
206 |
207 | ### 更新视图
208 |
209 | 数据修改触发setter,然后监听器会通知进行修改,通过对比两个dom树,得到改变的地方,就是patch,然后只需要把这些差异修改即可。
210 |
211 | ### 编译
212 |
213 | compile的核心逻辑是获取dom,遍历dom,获取{{}}格式的变量,以及每个dom的属性,截取v-和@开头的部分来设置响应式。
214 |
215 | 
216 |
--------------------------------------------------------------------------------
/img/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenz-fe/miniature-vue/27ea40cf4eea037c9fc0c9517eeae9852dec71ba/img/1.png
--------------------------------------------------------------------------------
/img/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenz-fe/miniature-vue/27ea40cf4eea037c9fc0c9517eeae9852dec71ba/img/2.png
--------------------------------------------------------------------------------
/img/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenz-fe/miniature-vue/27ea40cf4eea037c9fc0c9517eeae9852dec71ba/img/3.png
--------------------------------------------------------------------------------
/img/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenz-fe/miniature-vue/27ea40cf4eea037c9fc0c9517eeae9852dec71ba/img/4.png
--------------------------------------------------------------------------------
/step0/defineProperty_test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 | hello,
11 |
12 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/step1/XVue.js:
--------------------------------------------------------------------------------
1 | class XVue {
2 | constructor(options) {
3 | this.$data = options.data
4 | this.observe(this.$data)
5 | }
6 |
7 | observe(value) {
8 | if (!value || typeof value !== 'object') {
9 | return
10 | }
11 | Object.keys(value).forEach(key => {
12 | this.defineReactive(value,key,value[key])
13 | })
14 | }
15 |
16 | defineReactive(obj, key, val) {
17 | // 递归查找嵌套属性
18 | this.observe(val)
19 | Object.defineProperty(obj, key, {
20 | enumerable: true,
21 | configurable: true,
22 | get() {
23 | return val
24 | },
25 | set(newVal) {
26 | if (newVal===val) {
27 | return
28 | }
29 | val = newVal
30 | console.log('====================================');
31 | console.log('数据发生变化');
32 | console.log('====================================');
33 | }
34 | })
35 | }
36 | }
--------------------------------------------------------------------------------
/step1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/step2/XVue.js:
--------------------------------------------------------------------------------
1 | class XVue {
2 | constructor(options) {
3 | this.$data = options.data;
4 | this.observe(this.$data);
5 |
6 | // step2测试代码:
7 | new Watcher();
8 | console.log('模拟compile', this.$data.test);
9 | }
10 |
11 | observe(value) {
12 | if (!value || typeof value !== 'object') {
13 | return;
14 | }
15 | Object.keys(value).forEach(key => {
16 | this.defineReactive(value, key, value[key]);
17 | });
18 | }
19 |
20 | defineReactive(obj, key, val) {
21 | // 递归查找嵌套属性
22 | this.observe(val);
23 |
24 | // 创建Dep(step2新增)
25 | const dep = new Dep();
26 |
27 | Object.defineProperty(obj, key, {
28 | enumerable: true,
29 | configurable: true,
30 | get() {
31 | // 收集依赖(step2新增)
32 | Dep.target && dep.addDep(Dep.target);
33 | console.log(dep.deps);
34 | return val;
35 | },
36 | set(newVal) {
37 | if (newVal === val) {
38 | return;
39 | }
40 | val = newVal;
41 | dep.notify();
42 | },
43 | });
44 | }
45 | }
46 |
47 | // 依赖管理器:负责将视图中所有依赖收集管理,包括依赖添加和通知
48 | class Dep {
49 | constructor() {
50 | // deps里面存放的是Watcher的实例
51 | this.deps = [];
52 | }
53 | addDep(dep) {
54 | this.deps.push(dep);
55 | }
56 | // 通知所有watcher执行更新
57 | notify() {
58 | this.deps.forEach(dep => {
59 | dep.update();
60 | });
61 | }
62 | }
63 |
64 | // Watcher: 具体的更新执行者
65 | class Watcher {
66 | constructor() {
67 | Dep.target = this;
68 | }
69 | update() {
70 | console.log('====================================');
71 | console.log('from Watcher update: 视图更新啦!!!');
72 | console.log('====================================');
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/step2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 | {{msg}}
11 |
12 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/step3.1/XVue.js:
--------------------------------------------------------------------------------
1 | class XVue {
2 | constructor(options) {
3 | this.$data = options.data;
4 | this.observe(this.$data);
5 |
6 | // step2测试代码:
7 | // new Watcher();
8 | // console.log('模拟compile', this.$data.test);
9 |
10 | // step3.1测试代码:
11 | new Compile(options.el, this)
12 | }
13 |
14 | observe(value) {
15 | if (!value || typeof value !== 'object') {
16 | return;
17 | }
18 | Object.keys(value).forEach(key => {
19 | this.defineReactive(value, key, value[key]);
20 | });
21 | }
22 |
23 | defineReactive(obj, key, val) {
24 | // 递归查找嵌套属性
25 | this.observe(val);
26 |
27 | // 创建Dep(step2新增)
28 | const dep = new Dep();
29 |
30 | Object.defineProperty(obj, key, {
31 | enumerable: true,
32 | configurable: true,
33 | get() {
34 | // 收集依赖(step2新增)
35 | Dep.target && dep.addDep(Dep.target);
36 | console.log(dep.deps);
37 | return val;
38 | },
39 | set(newVal) {
40 | if (newVal === val) {
41 | return;
42 | }
43 | val = newVal;
44 | dep.notify();
45 | },
46 | });
47 | }
48 | }
49 |
50 | // 依赖管理器:负责将视图中所有依赖收集管理,包括依赖添加和通知
51 | class Dep {
52 | constructor() {
53 | // deps里面存放的是Watcher的实例
54 | this.deps = [];
55 | }
56 | addDep(dep) {
57 | this.deps.push(dep);
58 | }
59 | // 通知所有watcher执行更新
60 | notify() {
61 | this.deps.forEach(dep => {
62 | dep.update();
63 | });
64 | }
65 | }
66 |
67 | // Watcher: 具体的更新执行者
68 | class Watcher {
69 | constructor() {
70 | Dep.target = this;
71 | }
72 | update() {
73 | console.log('====================================');
74 | console.log('from Watcher update: 视图更新啦!!!');
75 | console.log('====================================');
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/step3.1/compile.js:
--------------------------------------------------------------------------------
1 | // 扫描模板中所有依赖创建更新函数和watcher
2 | class Compile {
3 | // el是宿主元素或其选择器
4 | // vm当前Vue实例
5 | constructor(el, vm) {
6 | this.$el = document.querySelector(el);
7 | this.$vm = vm;
8 | if (this.$el) {
9 | // 将dom节点转换为Fragment提高执行效率
10 | this.$fragment = this.node2Fragment(this.$el);
11 | this.compile(this.$fragment);
12 | // 将生成的结果追加至宿主元素
13 | this.$el.appendChild(this.$fragment);
14 | }
15 | }
16 | node2Fragment(el) {
17 | // 创建一个新的Fragment
18 | const fragment = document.createDocumentFragment();
19 | let child;
20 | // 将原生节点移动至fragment
21 | while ((child = el.firstChild)) {
22 | fragment.appendChild(child);
23 | }
24 | return fragment;
25 | }
26 | compile(el) {
27 | let childNodes = el.childNodes;
28 | Array.from(childNodes).forEach(node => {
29 | // 判断node类型,做相应处理
30 | if (this.isElementNode(node)) {
31 | // 元素节点要识别v-xx或@xx
32 | this.compileElement(node);
33 | } else if (
34 | this.isTextNode(node) &&
35 | /\{\{(.*)\}\}/.test(node.textContent)
36 | ) {
37 | // 文本节点,只关心{{msg}}格式
38 | this.compileText(node, RegExp.$1); // RegExp.$1匹配{{}}之中的内容
39 | }
40 | // 遍历可能存在的子节点
41 | if (node.childNodes && node.childNodes.length) {
42 | this.compile(node);
43 | }
44 | });
45 | }
46 |
47 | compileElement() {
48 | console.log('开始编译元素节点');
49 | }
50 |
51 | compileText(node, exp) {
52 | console.log('开始编译文本节点');
53 | }
54 |
55 | isElementNode(node) {
56 | return node.nodeType == 1; //元素节点
57 | }
58 |
59 | isTextNode(node) {
60 | return node.nodeType == 3; //元素节点
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/step3.1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 | {{test}}
12 |
{{msg}}
13 |
14 |
15 |
16 |
17 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/step3.2/XVue.js:
--------------------------------------------------------------------------------
1 | class XVue {
2 | constructor(options) {
3 | this.$data = options.data;
4 | this.observe(this.$data);
5 | this.$options = options;
6 | // 执行编译
7 | new Compile(options.el, this);
8 | }
9 |
10 | observe(value) {
11 | if (!value || typeof value !== 'object') {
12 | return;
13 | }
14 | Object.keys(value).forEach(key => {
15 | this.defineReactive(value, key, value[key]);
16 | // 为vue的data做属性代理
17 | this.proxyData(key);
18 | });
19 | }
20 |
21 | defineReactive(obj, key, val) {
22 | // 递归查找嵌套属性
23 | this.observe(val);
24 |
25 | // 创建Dep
26 | const dep = new Dep();
27 |
28 | Object.defineProperty(obj, key, {
29 | enumerable: true,
30 | configurable: true,
31 | get() {
32 | // 收集依赖
33 | Dep.target && dep.addDep(Dep.target);
34 | // console.log(dep.deps);
35 | return val;
36 | },
37 | set(newVal) {
38 | if (newVal === val) {
39 | return;
40 | }
41 | val = newVal;
42 | dep.notify();
43 | },
44 | });
45 | }
46 |
47 | proxyData(key) {
48 | Object.defineProperty(this, key, {
49 | get() {
50 | return this.$data[key];
51 | },
52 | set(newVal) {
53 | this.$data[key] = newVal;
54 | },
55 | });
56 | }
57 | }
58 |
59 | // 依赖管理器:负责将视图中所有依赖收集管理,包括依赖添加和通知
60 | class Dep {
61 | constructor() {
62 | // deps里面存放的是Watcher的实例
63 | this.deps = [];
64 | }
65 | addDep(dep) {
66 | this.deps.push(dep);
67 | }
68 | // 通知所有watcher执行更新
69 | notify() {
70 | this.deps.forEach(dep => {
71 | dep.update();
72 | });
73 | }
74 | }
75 |
76 | // Watcher: 具体的更新执行者
77 | class Watcher {
78 | constructor(vm, key, cb) {
79 | this.vm = vm;
80 | this.key = key;
81 | this.cb = cb;
82 | // 将来 new 一个监听器时,将当前 Watcher 实例附加到 Dep.target
83 | // 将来通过 Dep.target 就能拿到当时创建的 Watcher 实例
84 | Dep.target = this;
85 | // 读取操作,主动触发 get,当前 Watcher 实例被添加到依赖管理器中
86 | this.vm[this.key];
87 | // 清空操作,避免不必要的重复添加(再次触发 get 就不需要再添加 watcher 了)
88 | Dep.target = null;
89 | }
90 | update() {
91 | // console.log('from Watcher update: 视图更新啦!!!');
92 | // 通知页面做更新
93 | this.cb.call(this.vm, this.vm[this.key]);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/step3.2/compile.js:
--------------------------------------------------------------------------------
1 | // 扫描模板中所有依赖(指令、插值、绑定、事件等)创建更新函数和watcher
2 | class Compile {
3 | // el是宿主元素或其选择器
4 | // vm当前Vue实例
5 | constructor(el, vm) {
6 | this.$el = document.querySelector(el);
7 | this.$vm = vm;
8 | if (this.$el) {
9 | // 将dom节点转换为Fragment提高执行效率
10 | this.$fragment = this.node2Fragment(this.$el);
11 | // 执行编译,编译完成以后所有的依赖已经替换成真正的值
12 | this.compile(this.$fragment);
13 | // 将生成的结果追加至宿主元素
14 | this.$el.appendChild(this.$fragment);
15 | }
16 | }
17 | node2Fragment(el) {
18 | // 创建一个新的Fragment
19 | const fragment = document.createDocumentFragment();
20 | let child;
21 | // 将原生节点移动至fragment
22 | while ((child = el.firstChild)) {
23 | // appendChild 是移动操作,移动一个节点,child 就会少一个,最终结束循环
24 | fragment.appendChild(child);
25 | }
26 | return fragment;
27 | }
28 | // 编译指定片段
29 | compile(el) {
30 | let childNodes = el.childNodes;
31 | Array.from(childNodes).forEach(node => {
32 | // 判断node类型,做相应处理
33 | if (this.isElementNode(node)) {
34 | // 元素节点要识别v-xx或@xx
35 | this.compileElement(node);
36 | } else if (
37 | this.isTextNode(node) &&
38 | /\{\{(.*)\}\}/.test(node.textContent)
39 | ) {
40 | // 文本节点,只关心{{msg}}格式
41 | this.compileText(node, RegExp.$1); // RegExp.$1匹配{{}}之中的内容
42 | }
43 | // 遍历可能存在的子节点
44 | if (node.childNodes && node.childNodes.length) {
45 | this.compile(node);
46 | }
47 | });
48 | }
49 |
50 | compileElement(node) {
51 | // console.log('编译元素节点');
52 | //
53 | const attrs = node.attributes;
54 | Array.from(attrs).forEach(attr => {
55 | const attrName = attr.name; // 获取属性名 v-text
56 | const exp = attr.value; // 获取属性值 test
57 | if (this.isDirective(attrName)) {
58 | // 指令
59 | const dir = attrName.substr(2); // text
60 | this[dir] && this[dir](node, this.$vm, exp);
61 | } else if (this.isEventDirective(attrName)) {
62 | // 事件
63 | const dir = attrName.substr(1); // click
64 | this.eventHandler(node, this.$vm, exp, dir);
65 | }
66 | });
67 | }
68 |
69 | compileText(node, exp) {
70 | // console.log('编译文本节点');
71 | this.text(node, this.$vm, exp);
72 | }
73 |
74 | isElementNode(node) {
75 | return node.nodeType == 1; //元素节点
76 | }
77 |
78 | isTextNode(node) {
79 | return node.nodeType == 3; //元素节点
80 | }
81 |
82 | isDirective(attr) {
83 | return attr.indexOf('v-') == 0;
84 | }
85 |
86 | isEventDirective(dir) {
87 | return dir.indexOf('@') == 0;
88 | }
89 |
90 | // 文本更新
91 | text(node, vm, exp) {
92 | this.update(node, vm, exp, 'text');
93 | }
94 |
95 | // 处理html
96 | html(node, vm, exp) {
97 | this.update(node, vm, exp, 'html');
98 | }
99 |
100 | // 双向绑定
101 | model(node, vm, exp) {
102 | this.update(node, vm, exp, 'model');
103 |
104 | let val = vm.exp;
105 | // 双绑还要处理视图对模型的更新
106 | node.addEventListener('input', e => {
107 | vm[exp] = e.target.value; // 这里相当于执行了 set
108 | });
109 | }
110 |
111 | // 更新
112 | // 能够触发这个 update 方法的时机有两个:1-编译器初始化视图时触发;2-Watcher更新视图时触发
113 | update(node, vm, exp, dir) {
114 | let updaterFn = this[dir + 'Updater'];
115 | updaterFn && updaterFn(node, vm[exp]); // 立即执行更新;这里的 vm[exp] 相当于执行了 get
116 | new Watcher(vm, exp, function (value) {
117 | // 每次创建 Watcher 实例,都会传入一个回调函数,使函数和 Watcher 实例之间形成一对一的挂钩关系
118 | // 将来数据发生变化时, Watcher 就能知道它更新的时候要执行哪个函数
119 | updaterFn && updaterFn(node, value);
120 | });
121 | }
122 |
123 | textUpdater(node, value) {
124 | node.textContent = value;
125 | }
126 |
127 | htmlUpdater(node, value) {
128 | node.innerHTML = value;
129 | }
130 |
131 | modelUpdater(node, value) {
132 | node.value = value;
133 | }
134 |
135 | eventHandler(node, vm, exp, dir) {
136 | let fn = vm.$options.methods && vm.$options.methods[exp];
137 | if (dir && fn) {
138 | node.addEventListener(dir, fn.bind(vm), false);
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/step3.2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 | {{ test }}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
46 |
47 |
48 |
--------------------------------------------------------------------------------