├── stage-1
├── index.html
└── vue-0.1.js
├── stage-2
├── index.html
└── vue-0.2.js
├── stage-3
├── index.html
└── vue-0.3.js
├── stage-4
├── index.html
└── vue-0.4.js
└── README.md
/stage-1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vue for learning
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/stage-2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vue for learning
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/stage-3/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vue for learning
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/stage-4/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vue for learning
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/stage-1/vue-0.1.js:
--------------------------------------------------------------------------------
1 | /**
2 | * mount virtual dom to real dom
3 | */
4 |
5 | ;(function () {
6 |
7 | function vnode (tag, data, children, text) {
8 | this.tag = tag;
9 | this.data = data;
10 | this.children = children;
11 | this.text = text;
12 | }
13 |
14 | function createElm (vnode) {
15 | var tag = vnode.tag;
16 | var data = vnode.data;
17 | var children = vnode.children;
18 |
19 | if (tag !== undefined) {
20 | vnode.elm = document.createElement(tag);
21 |
22 | if (data.attrs !== undefined) {
23 | var attrs = data.attrs;
24 | for (var key in attrs) {
25 | vnode.elm.setAttribute(key, attrs[key])
26 | }
27 | }
28 | if (children) {
29 | createChildren(vnode, children)
30 | }
31 | } else {
32 | vnode.elm = document.createTextNode(vnode.text);
33 | }
34 |
35 | return vnode.elm;
36 | }
37 |
38 | function createChildren (vnode, children) {
39 | for (var i = 0; i < children.length; ++i) {
40 | vnode.elm.appendChild(createElm(children[i]));
41 | }
42 | }
43 |
44 | function patch (oldVnode, vnode) {
45 |
46 | createElm(vnode)
47 |
48 | var isRealElement = oldVnode.nodeType !== undefined ; // virtual node has no `nodeType` property
49 | if (isRealElement) {
50 | var parent = oldVnode.parentNode;
51 | if (parent) {
52 | parent.insertBefore(vnode.elm, oldVnode);
53 | parent.removeChild(oldVnode);
54 | }
55 | }
56 |
57 | return vnode.elm
58 | }
59 |
60 | function render () {
61 | return new vnode(
62 | 'div',
63 | {
64 | attrs: {
65 | 'class': 'wrapper'
66 | }
67 | },
68 | [
69 | new vnode(
70 | 'p',
71 | {
72 | attrs: {
73 | 'class': 'inner'
74 | }
75 | },
76 | [new vnode(undefined, undefined, undefined, 'Hello world')]
77 | )
78 | ]
79 | )
80 | }
81 |
82 | function mount (el) {
83 | var vnode = render();
84 | patch(el, vnode)
85 | }
86 |
87 | mount(document.querySelector("#app"))
88 |
89 | })();
--------------------------------------------------------------------------------
/stage-2/vue-0.2.js:
--------------------------------------------------------------------------------
1 | /**
2 | * apply virtual dom to real dom
3 | */
4 |
5 | ;(function () {
6 |
7 | function vnode (tag, data, children, text, elm) {
8 | this.tag = tag;
9 | this.data = data;
10 | this.children = children;
11 | this.text = text;
12 | this.elm = elm;
13 | }
14 |
15 | function normalizeChildren (children) {
16 | if (typeof children === 'string') {
17 | return [createTextVNode(children)]
18 | }
19 | return children
20 | }
21 |
22 | function createTextVNode (val) {
23 | return new vnode(undefined, undefined, undefined, String(val))
24 | }
25 |
26 | function createElement (tag, data, children) {
27 | return new vnode(tag, data, normalizeChildren(children), undefined, undefined);
28 | }
29 |
30 | function createElm (vnode) {
31 | var tag = vnode.tag;
32 | var data = vnode.data;
33 | var children = vnode.children;
34 |
35 | if (tag !== undefined) {
36 | vnode.elm = document.createElement(tag);
37 |
38 | if (data.attrs !== undefined) {
39 | var attrs = data.attrs;
40 | for (var key in attrs) {
41 | vnode.elm.setAttribute(key, attrs[key])
42 | }
43 | }
44 |
45 | if (children) {
46 | createChildren(vnode, children)
47 | }
48 | } else {
49 | vnode.elm = document.createTextNode(vnode.text);
50 | }
51 |
52 | return vnode.elm;
53 | }
54 |
55 | function createChildren (vnode, children) {
56 | for (var i = 0; i < children.length; ++i) {
57 | vnode.elm.appendChild(createElm(children[i]));
58 | }
59 | }
60 |
61 | function initData (vm) {
62 | var data = vm.$data = vm.$options.data;
63 | var keys = Object.keys(data);
64 | var i = keys.length
65 | // proxy data so you can use `this.key` directly other than `this.$data.key`
66 | while (i--) {
67 | proxy(vm, keys[i])
68 | }
69 | }
70 |
71 | function proxy (vm, key) {
72 | Object.defineProperty(vm, key, {
73 | configurable: true,
74 | enumerable: true,
75 | get: function () {
76 | return vm.$data[key]
77 | },
78 | set: function (val) {
79 | vm.$data[key] = val
80 | }
81 | })
82 | }
83 |
84 | function Vue (options) {
85 | this.$options = options;
86 |
87 | initData(this);
88 | this.mount(document.querySelector(options.el))
89 | }
90 |
91 | Vue.prototype.mount = function (el) {
92 | this.$el = el;
93 | var vnode = this.$options.render.call(this)
94 | this.patch(this.$el, vnode)
95 | }
96 |
97 | Vue.prototype.patch = function (oldVnode, vnode) {
98 | var isRealElement = oldVnode.nodeType !== undefined; // virtual node has no `nodeType` property
99 |
100 | if (isRealElement) {
101 | createElm(vnode);
102 | var parent = oldVnode.parentNode;
103 | parent.insertBefore(vnode.elm, oldVnode);
104 | parent.removeChild(oldVnode);
105 | }
106 |
107 | return vnode.elm
108 | }
109 |
110 | new Vue({
111 | el: '#app',
112 | data: {
113 | message: 'Hello world'
114 | },
115 | render () {
116 | return createElement(
117 | 'div',
118 | {
119 | attrs: {
120 | 'class': 'wrapper'
121 | }
122 | },
123 | [
124 | createElement(
125 | 'p',
126 | {
127 | attrs: {
128 | 'class': 'inner'
129 | }
130 | },
131 | this.message
132 | )
133 | ]
134 | )
135 | }
136 | })
137 |
138 | })();
--------------------------------------------------------------------------------
/stage-3/vue-0.3.js:
--------------------------------------------------------------------------------
1 | /**
2 | * make some change to the data and rerender the virtual dom
3 | * then diff the previous vnode tree and the current vnode tree
4 | * finally patch those diffs to the real dom
5 | */
6 |
7 | ;(function () {
8 |
9 | function vnode (tag, data, children, text, elm) {
10 | this.tag = tag;
11 | this.data = data;
12 | this.children = children;
13 | this.text = text;
14 | this.elm = elm;
15 | }
16 |
17 | function normalizeChildren (children) {
18 | if (typeof children === 'string') {
19 | return [createTextVNode(children)]
20 | }
21 | return children
22 | }
23 |
24 | function createTextVNode (val) {
25 | return new vnode(undefined, undefined, undefined, String(val))
26 | }
27 |
28 | function createElement (tag, data, children) {
29 | return new vnode(tag, data, normalizeChildren(children), undefined, undefined);
30 | }
31 |
32 | function createElm (vnode) {
33 | var tag = vnode.tag;
34 | var data = vnode.data;
35 | var children = vnode.children;
36 |
37 | if (tag !== undefined) {
38 | vnode.elm = document.createElement(tag);
39 |
40 | if (data.attrs !== undefined) {
41 | var attrs = data.attrs;
42 | for (var key in attrs) {
43 | vnode.elm.setAttribute(key, attrs[key])
44 | }
45 | }
46 |
47 | if (children) {
48 | createChildren(vnode, children)
49 | }
50 | } else {
51 | vnode.elm = document.createTextNode(vnode.text);
52 | }
53 |
54 | return vnode.elm;
55 | }
56 |
57 | function createChildren (vnode, children) {
58 | for (var i = 0; i < children.length; ++i) {
59 | vnode.elm.appendChild(createElm(children[i]));
60 | }
61 | }
62 |
63 | function sameVnode (vnode1, vnode2) {
64 | return vnode1.tag === vnode2.tag
65 | }
66 |
67 | function emptyNodeAt (elm) {
68 | return new vnode(elm.tagName.toLowerCase(), {}, [], undefined, elm)
69 | }
70 |
71 | function patchVnode (oldVnode, vnode) {
72 | var elm = vnode.elm = oldVnode.elm;
73 | var oldCh = oldVnode.children;
74 | var ch = vnode.children;
75 |
76 | if (!vnode.text) {
77 | if (oldCh && ch) {
78 | updateChildren(oldCh, ch);
79 | }
80 | } else if (oldVnode.text !== vnode.text) {
81 | elm.textContent = vnode.text;
82 | }
83 | }
84 |
85 | function updateChildren (oldCh, newCh) {
86 | // assume that every element node has only one child to simplify our diff algorithm
87 | if (sameVnode(oldCh[0], newCh[0])) {
88 | patchVnode(oldCh[0], newCh[0])
89 | } else {
90 | patch(oldCh[0], newCh[0])
91 | }
92 | }
93 |
94 | function patch (oldVnode, vnode) {
95 | var isRealElement = oldVnode.nodeType !== undefined; // virtual node has no `nodeType` property
96 | if (!isRealElement && sameVnode(oldVnode, vnode)) {
97 | patchVnode(oldVnode, vnode);
98 | } else {
99 | if (isRealElement) {
100 | oldVnode = emptyNodeAt(oldVnode);
101 | }
102 | var elm = oldVnode.elm;
103 | var parent = elm.parentNode;
104 |
105 | createElm(vnode);
106 |
107 | parent.insertBefore(vnode.elm, elm);
108 | parent.removeChild(elm);
109 | }
110 |
111 | return vnode.elm
112 | }
113 |
114 | function initData (vm) {
115 | var data = vm.$data = vm.$options.data;
116 | var keys = Object.keys(data);
117 | var i = keys.length
118 | // proxy data so you can use `this.key` directly other than `this.$data.key`
119 | while (i--) {
120 | proxy(vm, keys[i])
121 | }
122 | }
123 |
124 | function proxy (vm, key) {
125 | Object.defineProperty(vm, key, {
126 | configurable: true,
127 | enumerable: true,
128 | get: function () {
129 | return vm.$data[key]
130 | },
131 | set: function (val) {
132 | vm.$data[key] = val
133 | }
134 | })
135 | }
136 |
137 | function Vue (options) {
138 | var vm = this;
139 | vm.$options = options;
140 |
141 | initData(vm);
142 | vm.mount(document.querySelector(options.el))
143 | }
144 |
145 | Vue.prototype.mount = function (el) {
146 | var vm = this;
147 | vm.$el = el;
148 | vm.update(vm.render())
149 | }
150 |
151 | Vue.prototype.update = function (vnode) {
152 | var vm = this;
153 | var prevVnode = vm._vnode;
154 | vm._vnode = vnode;
155 | if (!prevVnode) {
156 | vm.$el = vm.patch(vm.$el, vnode);
157 | } else {
158 | vm.$el = vm.patch(prevVnode, vnode);
159 | }
160 | }
161 |
162 | Vue.prototype.patch = patch;
163 |
164 | Vue.prototype.render = function () {
165 | var vm = this;
166 | return vm.$options.render.call(vm)
167 | }
168 |
169 | var vm = new Vue({
170 | el: '#app',
171 | data: {
172 | message: 'Hello world',
173 | isShow: true
174 | },
175 | render () {
176 | return createElement(
177 | 'div',
178 | {
179 | attrs: {
180 | 'class': 'wrapper'
181 | }
182 | },
183 | [
184 | this.isShow
185 | ? createElement(
186 | 'p',
187 | {
188 | attrs: {
189 | 'class': 'inner'
190 | }
191 | },
192 | this.message
193 | )
194 | : createElement(
195 | 'h1',
196 | {
197 | attrs: {
198 | 'class': 'inner'
199 | }
200 | },
201 | 'Hello world'
202 | )
203 | ]
204 | )
205 | }
206 | })
207 |
208 | // test
209 | setTimeout(function () {
210 | vm.message = 'Hello';
211 | vm.update(vm.render())
212 | }, 1000)
213 |
214 | setTimeout(function () {
215 | vm.isShow = false;
216 | vm.update(vm.render())
217 | }, 2000)
218 | })();
--------------------------------------------------------------------------------
/stage-4/vue-0.4.js:
--------------------------------------------------------------------------------
1 | /**
2 | * make data reactive
3 | * when data changes, the `update` method will be invoked automatically and thus updates the DOM
4 | * @Tips
5 | * some code may be useless but helpful for understanding
6 | * some code is incompleted and heavily simplified but also for better understanding
7 | */
8 |
9 | ;(function () {
10 |
11 | function defineReactive (obj, key, val) {
12 | var dep = new Dep();
13 | Object.defineProperty(obj, key, {
14 | get: function () {
15 | if (Dep.target) {
16 | Dep.target.addDep(dep);
17 | }
18 | return val
19 | },
20 | set: function (newVal) {
21 | if (newVal === val) return;
22 | val = newVal;
23 | dep.notify();
24 | }
25 | })
26 | }
27 |
28 | function observe (obj) {
29 | for (var key in obj) {
30 | defineReactive(obj, key, obj[key])
31 | }
32 | }
33 |
34 | var uid$1 = 0;
35 |
36 | function Dep () {
37 | this.subs = [];
38 | this.id = uid$1++;
39 | }
40 |
41 | Dep.target = null;
42 |
43 | Dep.prototype.addSub = function (sub) {
44 | this.subs.push(sub)
45 | }
46 |
47 | Dep.prototype.notify = function () {
48 | var subs = this.subs;
49 | for (var i = 0, l = subs.length; i < l; i++) {
50 | subs[i].update()
51 | }
52 | }
53 |
54 | function Watcher (vm, expOrFn, cb) {
55 | this.vm = vm;
56 | this.getter = expOrFn;
57 | this.cb = cb;
58 | this.depIds = [];
59 | this.value = this.get();
60 | }
61 |
62 | Watcher.prototype.get = function () {
63 | Dep.target = this; /* ! */
64 | var value = this.getter.call(this.vm);
65 | Dep.target = null;
66 | return value
67 | }
68 |
69 | Watcher.prototype.update = function () {
70 | var value = this.get();
71 | if (this.value !== value) {
72 | var oldValue = this.value;
73 | this.value = value;
74 | this.cb.call(this.vm, value, oldValue);
75 | }
76 | }
77 |
78 | Watcher.prototype.addDep = function (dep) {
79 | var id = dep.id;
80 | // to avoid depending the watcher to the same dep more than once
81 | if (this.depIds.indexOf(id) === -1) {
82 | this.depIds.push(id);
83 | dep.addSub(this);
84 | }
85 | }
86 |
87 | function vnode (tag, data, children, text, elm) {
88 | this.tag = tag;
89 | this.data = data;
90 | this.children = children;
91 | this.text = text;
92 | this.elm = elm;
93 | }
94 |
95 | function normalizeChildren (children) {
96 | if (typeof children === 'string') {
97 | return [createTextVNode(children)]
98 | }
99 | return children
100 | }
101 |
102 | function createTextVNode (val) {
103 | return new vnode(undefined, undefined, undefined, String(val))
104 | }
105 |
106 | function createElement (tag, data, children) {
107 | return new vnode(tag, data, normalizeChildren(children), undefined, undefined);
108 | }
109 |
110 | function createElm (vnode) {
111 | var tag = vnode.tag;
112 | var data = vnode.data;
113 | var children = vnode.children;
114 |
115 | if (tag !== undefined) {
116 | vnode.elm = document.createElement(tag);
117 |
118 | if (data.attrs !== undefined) {
119 | var attrs = data.attrs;
120 | for (var key in attrs) {
121 | vnode.elm.setAttribute(key, attrs[key])
122 | }
123 | }
124 |
125 | if (children) {
126 | createChildren(vnode, children)
127 | }
128 | } else {
129 | vnode.elm = document.createTextNode(vnode.text);
130 | }
131 |
132 | return vnode.elm;
133 | }
134 |
135 | function createChildren (vnode, children) {
136 | for (var i = 0; i < children.length; ++i) {
137 | vnode.elm.appendChild(createElm(children[i]));
138 | }
139 | }
140 |
141 | function sameVnode (vnode1, vnode2) {
142 | return vnode1.tag === vnode2.tag
143 | }
144 |
145 | function emptyNodeAt (elm) {
146 | return new vnode(elm.tagName.toLowerCase(), {}, [], undefined, elm)
147 | }
148 |
149 | function patchVnode (oldVnode, vnode) {
150 | var elm = vnode.elm = oldVnode.elm;
151 | var oldCh = oldVnode.children;
152 | var ch = vnode.children;
153 |
154 | if (!vnode.text) {
155 | if (oldCh && ch) {
156 | updateChildren(oldCh, ch);
157 | }
158 | } else if (oldVnode.text !== vnode.text) {
159 | elm.textContent = vnode.text;
160 | }
161 | }
162 |
163 | function updateChildren (oldCh, newCh) {
164 | // assume that every element node has only one child to simplify our diff algorithm
165 | if (sameVnode(oldCh[0], newCh[0])) {
166 | patchVnode(oldCh[0], newCh[0])
167 | } else {
168 | patch(oldCh[0], newCh[0])
169 | }
170 | }
171 |
172 | function patch (oldVnode, vnode) {
173 | var isRealElement = oldVnode.nodeType !== undefined; // virtual node has no `nodeType` property
174 | if (!isRealElement && sameVnode(oldVnode, vnode)) {
175 | patchVnode(oldVnode, vnode);
176 | } else {
177 | if (isRealElement) {
178 | oldVnode = emptyNodeAt(oldVnode);
179 | }
180 | var elm = oldVnode.elm;
181 | var parent = elm.parentNode;
182 |
183 | createElm(vnode);
184 |
185 | parent.insertBefore(vnode.elm, elm);
186 | parent.removeChild(elm);
187 | }
188 |
189 | return vnode.elm
190 | }
191 |
192 | function initData (vm) {
193 | var data = vm.$data = vm.$options.data;
194 | var keys = Object.keys(data);
195 | var i = keys.length
196 | // proxy data so you can use `this.key` directly other than `this.$data.key`
197 | while (i--) {
198 | proxy(vm, keys[i])
199 | }
200 |
201 | observe(data)
202 | }
203 |
204 | function proxy (vm, key) {
205 | Object.defineProperty(vm, key, {
206 | configurable: true,
207 | enumerable: true,
208 | get: function () {
209 | return vm.$data[key]
210 | },
211 | set: function (val) {
212 | vm.$data[key] = val
213 | }
214 | })
215 | }
216 |
217 | function Vue (options) {
218 | var vm = this;
219 | vm.$options = options;
220 |
221 | initData(vm);
222 | vm.mount(document.querySelector(options.el))
223 | }
224 |
225 | Vue.prototype.mount = function (el) {
226 | var vm = this;
227 | vm.$el = el;
228 | new Watcher(vm, function () {
229 | vm.update(vm.render());
230 | });
231 | }
232 |
233 | Vue.prototype.update = function (vnode) {
234 | var vm = this;
235 | var prevVnode = vm._vnode;
236 | vm._vnode = vnode;
237 | if (!prevVnode) {
238 | vm.$el = vm.patch(vm.$el, vnode);
239 | } else {
240 | vm.$el = vm.patch(prevVnode, vnode);
241 | }
242 | }
243 |
244 | Vue.prototype.patch = patch;
245 |
246 | Vue.prototype.render = function () {
247 | var vm = this;
248 | return vm.$options.render.call(vm)
249 | }
250 |
251 | var vm = new Vue({
252 | el: '#app',
253 | data: {
254 | message: 'Hello world',
255 | isShow: true
256 | },
257 | render () {
258 | return createElement(
259 | 'div',
260 | {
261 | attrs: {
262 | 'class': 'wrapper'
263 | }
264 | },
265 | [
266 | this.isShow
267 | ? createElement(
268 | 'p',
269 | {
270 | attrs: {
271 | 'class': 'inner'
272 | }
273 | },
274 | this.message
275 | )
276 | : createElement(
277 | 'h1',
278 | {
279 | attrs: {
280 | 'class': 'inner'
281 | }
282 | },
283 | 'Hello world'
284 | )
285 | ]
286 | )
287 | }
288 | })
289 |
290 | // test
291 | setTimeout(function () {
292 | vm.message = 'Hello';
293 | }, 1000)
294 |
295 | setTimeout(function () {
296 | vm.isShow = false;
297 | }, 2000)
298 |
299 | })();
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-for-learning
2 |
3 | Learning vue.js source code progressively and effectively
4 |
5 | ### what you will get
6 |
7 | Totally understand how does vue actually work with minimum cost
8 |
9 | ### stage-1
10 |
11 | - use virtual dom to represent real dom
12 | - mount virtual dom to real dom
13 |
14 | > learn `vnode`, `createElm`
15 |
16 | ### stage-2
17 |
18 | - modify the previous code to make it looks more like Vue
19 | - and prepare for next stage
20 |
21 | > learn `proxy`
22 |
23 | ### stage-3
24 |
25 | - make some change to the data and generate new vnode tree
26 | - then diff the previous vnode tree and the current vnode tree
27 | - finally patch those diffs to the real dom
28 |
29 | > learn `diff`, `patch`
30 |
31 | ### stage-4
32 |
33 | - make the data reactive so that rerender will excute automatically
34 |
35 | > learn `Dep`, `Watcher`, `Getter`, `Setter`, `Subscribe/Publish`
36 |
37 |
38 | ### preview
39 |
40 | The final code is less than 300 rows
41 |
42 | ```js
43 | ;(function () {
44 |
45 | function defineReactive (obj, key, val) {
46 | var dep = new Dep();
47 | Object.defineProperty(obj, key, {
48 | get: function () {
49 | if (Dep.target) {
50 | Dep.target.addDep(dep);
51 | }
52 | return val
53 | },
54 | set: function (newVal) {
55 | if (newVal === val) return;
56 | val = newVal;
57 | dep.notify();
58 | }
59 | })
60 | }
61 |
62 | function observe (obj) {
63 | for (var key in obj) {
64 | defineReactive(obj, key, obj[key])
65 | }
66 | }
67 |
68 | var uid$1 = 0;
69 |
70 | function Dep () {
71 | this.subs = [];
72 | this.id = uid$1++;
73 | }
74 |
75 | Dep.target = null;
76 |
77 | Dep.prototype.addSub = function (sub) {
78 | this.subs.push(sub)
79 | }
80 |
81 | Dep.prototype.notify = function () {
82 | var subs = this.subs;
83 | for (var i = 0, l = subs.length; i < l; i++) {
84 | subs[i].update()
85 | }
86 | }
87 |
88 | function Watcher (vm, expOrFn, cb) {
89 | this.vm = vm;
90 | this.getter = expOrFn;
91 | this.cb = cb;
92 | this.depIds = [];
93 | this.value = this.get();
94 | }
95 |
96 | Watcher.prototype.get = function () {
97 | Dep.target = this; /* ! */
98 | var value = this.getter.call(this.vm);
99 | Dep.target = null;
100 | return value
101 | }
102 |
103 | Watcher.prototype.update = function () {
104 | var value = this.get();
105 | if (this.value !== value) {
106 | var oldValue = this.value;
107 | this.value = value;
108 | this.cb.call(this.vm, value, oldValue);
109 | }
110 | }
111 |
112 | Watcher.prototype.addDep = function (dep) {
113 | var id = dep.id;
114 | // to avoid depending the watcher to the same dep more than once
115 | if (this.depIds.indexOf(id) === -1) {
116 | this.depIds.push(id);
117 | dep.addSub(this);
118 | }
119 | }
120 |
121 | function vnode (tag, data, children, text, elm) {
122 | this.tag = tag;
123 | this.data = data;
124 | this.children = children;
125 | this.text = text;
126 | this.elm = elm;
127 | }
128 |
129 | function normalizeChildren (children) {
130 | if (typeof children === 'string') {
131 | return [createTextVNode(children)]
132 | }
133 | return children
134 | }
135 |
136 | function createTextVNode (val) {
137 | return new vnode(undefined, undefined, undefined, String(val))
138 | }
139 |
140 | function createElement (tag, data, children) {
141 | return new vnode(tag, data, normalizeChildren(children), undefined, undefined);
142 | }
143 |
144 | function createElm (vnode) {
145 | var tag = vnode.tag;
146 | var data = vnode.data;
147 | var children = vnode.children;
148 |
149 | if (tag !== undefined) {
150 | vnode.elm = document.createElement(tag);
151 |
152 | if (data.attrs !== undefined) {
153 | var attrs = data.attrs;
154 | for (var key in attrs) {
155 | vnode.elm.setAttribute(key, attrs[key])
156 | }
157 | }
158 |
159 | if (children) {
160 | createChildren(vnode, children)
161 | }
162 | } else {
163 | vnode.elm = document.createTextNode(vnode.text);
164 | }
165 |
166 | return vnode.elm;
167 | }
168 |
169 | function createChildren (vnode, children) {
170 | for (var i = 0; i < children.length; ++i) {
171 | vnode.elm.appendChild(createElm(children[i]));
172 | }
173 | }
174 |
175 | function sameVnode (vnode1, vnode2) {
176 | return vnode1.tag === vnode2.tag
177 | }
178 |
179 | function emptyNodeAt (elm) {
180 | return new vnode(elm.tagName.toLowerCase(), {}, [], undefined, elm)
181 | }
182 |
183 | function patchVnode (oldVnode, vnode) {
184 | var elm = vnode.elm = oldVnode.elm;
185 | var oldCh = oldVnode.children;
186 | var ch = vnode.children;
187 |
188 | if (!vnode.text) {
189 | if (oldCh && ch) {
190 | updateChildren(oldCh, ch);
191 | }
192 | } else if (oldVnode.text !== vnode.text) {
193 | elm.textContent = vnode.text;
194 | }
195 | }
196 |
197 | function updateChildren (oldCh, newCh) {
198 | // assume that every element node has only one child to simplify our diff algorithm
199 | if (sameVnode(oldCh[0], newCh[0])) {
200 | patchVnode(oldCh[0], newCh[0])
201 | } else {
202 | patch(oldCh[0], newCh[0])
203 | }
204 | }
205 |
206 | function patch (oldVnode, vnode) {
207 | var isRealElement = oldVnode.nodeType !== undefined; // virtual node has no `nodeType` property
208 | if (!isRealElement && sameVnode(oldVnode, vnode)) {
209 | patchVnode(oldVnode, vnode);
210 | } else {
211 | if (isRealElement) {
212 | oldVnode = emptyNodeAt(oldVnode);
213 | }
214 | var elm = oldVnode.elm;
215 | var parent = elm.parentNode;
216 |
217 | createElm(vnode);
218 |
219 | parent.insertBefore(vnode.elm, elm);
220 | parent.removeChild(elm);
221 | }
222 |
223 | return vnode.elm
224 | }
225 |
226 | function initData (vm) {
227 | var data = vm.$data = vm.$options.data;
228 | var keys = Object.keys(data);
229 | var i = keys.length
230 | // proxy data so you can use `this.key` directly other than `this.$data.key`
231 | while (i--) {
232 | proxy(vm, keys[i])
233 | }
234 |
235 | observe(data)
236 | }
237 |
238 | function proxy (vm, key) {
239 | Object.defineProperty(vm, key, {
240 | configurable: true,
241 | enumerable: true,
242 | get: function () {
243 | return vm.$data[key]
244 | },
245 | set: function (val) {
246 | vm.$data[key] = val
247 | }
248 | })
249 | }
250 |
251 | function Vue (options) {
252 | var vm = this;
253 | vm.$options = options;
254 |
255 | initData(vm);
256 | vm.mount(document.querySelector(options.el))
257 | }
258 |
259 | Vue.prototype.mount = function (el) {
260 | var vm = this;
261 | vm.$el = el;
262 | new Watcher(vm, function () {
263 | vm.update(vm.render());
264 | });
265 | }
266 |
267 | Vue.prototype.update = function (vnode) {
268 | var vm = this;
269 | var prevVnode = vm._vnode;
270 | vm._vnode = vnode;
271 | if (!prevVnode) {
272 | vm.$el = vm.patch(vm.$el, vnode);
273 | } else {
274 | vm.$el = vm.patch(prevVnode, vnode);
275 | }
276 | }
277 |
278 | Vue.prototype.patch = patch;
279 |
280 | Vue.prototype.render = function () {
281 | var vm = this;
282 | return vm.$options.render.call(vm)
283 | }
284 |
285 | var vm = new Vue({
286 | el: '#app',
287 | data: {
288 | message: 'Hello world',
289 | isShow: true
290 | },
291 | render () {
292 | return createElement(
293 | 'div',
294 | {
295 | attrs: {
296 | 'class': 'wrapper'
297 | }
298 | },
299 | [
300 | this.isShow
301 | ? createElement(
302 | 'p',
303 | {
304 | attrs: {
305 | 'class': 'inner'
306 | }
307 | },
308 | this.message
309 | )
310 | : createElement(
311 | 'h1',
312 | {
313 | attrs: {
314 | 'class': 'inner'
315 | }
316 | },
317 | 'Hello world'
318 | )
319 | ]
320 | )
321 | }
322 | })
323 |
324 | // test
325 | setTimeout(function () {
326 | vm.message = 'Hello';
327 | }, 1000)
328 |
329 | setTimeout(function () {
330 | vm.isShow = false;
331 | }, 2000)
332 | })();
333 | ```
--------------------------------------------------------------------------------