├── 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 | ``` --------------------------------------------------------------------------------