1.API中的数据(data、prop、propData、method、watch)五个初始化入口是在initState模块中:
export function initState (vm: Component) { 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) } }
接着对props属性中的每个key,进行处理:
for (const key in propsOptions) { keys.push(key) const value = validateProp(key, propsOptions, propsData, vm) .... }
validateProp处理一些其他情况之后,开始构造Observe数据:
value = getPropDefaultValue(vm, prop, key) //对象 // since the default value is a fresh copy, // make sure to observe it. const prevShouldObserve = shouldObserve toggleObserving(true) observe(value) toggleObserving(prevShouldObserve)
observer是一个比较大的递归,设计到Observer构造、walk函数、defineReactive三个函数和Dep类及其对象。
export function observe (value: any, asRootData: ?boolean): Observer | void { if (!isObject(value) || value instanceof VNode) { return //不是对象或者是虚拟节点递归结束 } let ob: Observer | void if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__ //该对象已经被定义了Observe } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value) //通过对象构造ob } if (asRootData && ob) { ob.vmCount++ } return ob }
new一个VUE对象主要做了两件事:第一个是监听数据:observe(data),第二个是编译 HTML:nodeToFragement(id)。他们之间的桥梁是通过虚拟节点来完成的,
虚拟节点是dom节点对象的一个子级,基本原理如下:
;(function () { function vnode (tag, data, children, text, elm) { this.tag = tag; this.data = data; this.children = children; this.text = text; this.elm = elm; } function normalizeChildren (children) { if (typeof children === 'string') { return [createTextVNode(children)] } return children } function createTextVNode (val) { return new vnode(undefined, undefined, undefined, String(val)) } function createElement (tag, data, children) { return new vnode(tag, data, normalizeChildren(children), undefined, undefined); } function createElm (vnode) { var tag = vnode.tag; var data = vnode.data; var children = vnode.children; if (tag !== undefined) { vnode.elm = document.createElement(tag); if (data.attrs !== undefined) { var attrs = data.attrs; for (var key in attrs) { vnode.elm.setAttribute(key, attrs[key]) } } if (children) { createChildren(vnode, children) } } else { vnode.elm = document.createTextNode(vnode.text); } return vnode.elm; } function createChildren (vnode, children) { for (var i = 0; i < children.length; ++i) { vnode.elm.appendChild(createElm(children[i])); } } function sameVnode (vnode1, vnode2) { return vnode1.tag === vnode2.tag } function emptyNodeAt (elm) { return new vnode(elm.tagName.toLowerCase(), {}, [], undefined, elm) } function patchVnode (oldVnode, vnode) { var elm = vnode.elm = oldVnode.elm; var oldCh = oldVnode.children; var ch = vnode.children; if (!vnode.text) { if (oldCh && ch) { updateChildren(oldCh, ch); } } else if (oldVnode.text !== vnode.text) { elm.textContent = vnode.text; } } function updateChildren (oldCh, newCh) { // assume that every element node has only one child to simplify our diff algorithm if (sameVnode(oldCh[0], newCh[0])) { patchVnode(oldCh[0], newCh[0]) } else { patch(oldCh[0], newCh[0]) } } function patch (oldVnode, vnode) { var isRealElement = oldVnode.nodeType !== undefined; // virtual node has no `nodeType` property if (!isRealElement && sameVnode(oldVnode, vnode)) { patchVnode(oldVnode, vnode); } else { if (isRealElement) { oldVnode = emptyNodeAt(oldVnode); } var elm = oldVnode.elm; var parent = elm.parentNode; createElm(vnode); parent.insertBefore(vnode.elm, elm); parent.removeChild(elm); } return vnode.elm } function initData (vm) { var data = vm.$data = vm.$options.data; var keys = Object.keys(data); var i = keys.length // proxy data so you can use `this.key` directly other than `this.$data.key` while (i--) { proxy(vm, keys[i]) } } function proxy (vm, key) { Object.defineProperty(vm, key, { configurable: true, enumerable: true, get: function () { return vm.$data[key] }, set: function (val) { vm.$data[key] = val } }) } function Vue (options) { var vm = this; vm.$options = options; initData(vm); vm.mount(document.querySelector(options.el)) } Vue.prototype.mount = function (el) { var vm = this; vm.$el = el; vm.update(vm.render()) } Vue.prototype.update = function (vnode) { var vm = this; var prevVnode = vm._vnode; vm._vnode = vnode; if (!prevVnode) { vm.$el = vm.patch(vm.$el, vnode); } else { vm.$el = vm.patch(prevVnode, vnode); } } Vue.prototype.patch = patch; Vue.prototype.render = function () { var vm = this; return vm.$options.render.call(vm) } var vm = new Vue({ el: '#app', data: { message: 'Hello world', isShow: true }, render () { return createElement( 'div', { attrs: { 'class': 'wrapper' } }, [ this.isShow ? createElement( 'p', { attrs: { 'class': 'inner' } }, this.message ) : createElement( 'h1', { attrs: { 'class': 'inner' } }, 'Hello worldx' ) ] ) } }) // test setTimeout(function () { vm.message = 'Hello'; vm.update(vm.render()) }, 1000) setTimeout(function () { vm.isShow = false; vm.update(vm.render()) }, 2000) })();