先给出vue简单的使用demo,经过建立一个Vue的实例,<div id="app"></div>将被替换成template模版中的内容,a,b的值也会被换成data属性的值vue
<div id="app"></div> var vm = new Vue({ el: '#app', template: `<div> <p>{{a}}</p> <p>{{b}}</p> <div :class="a">btn1</div> <div @click="plus()">btn2</div> <div> <div> <div>str1</div> <div>str2</div> </div> <div>str3</div> </div> </div>`, data(){ return { a: 1, b: 2 } } })
如下的分析代码都通过做者简化,只为简单清楚的解析vue的实现逻辑
首先根据参数template属性生成render函数node
function Vue (options) { this._init(options); } Vue.prototype._init = function (options) { var vm = this; //参数el属性存在,就调用mount方法;初始化时也能够不传el属性,后续调用mount方法 if (vm.$options.el) { vm.$mount(vm.$options.el); } } Vue.prototype.$mount = function () { var ref = compileToFunctions(template, { shouldDecodeNewlines: shouldDecodeNewlines, delimiters: options.delimiters, comments: options.comments }, this); //由template参数获得render方法 var render = ref.render; //由template参数获得最大静态渲染树 var staticRenderFns = ref.staticRenderFns; };
下面看下compileToFunctions生成render方法的具体实现正则表达式
var ast = parse(template.trim(), options); optimize(ast, options); var code = generate(ast, options);
首先根据template字符串生成ast对象,parse函数主要是经过正则表达式将str转换成
树结构的对象,ast对象基本结构以下:浏览器
而后对ast对象进行优化,找出ast对象中全部的最大静态子树(能够简单理解为不包含参数data属性的dom节点,每次data数据改变致使页面从新渲染的时候,最大静态子树不须要从新计算生成),基本实现逻辑以下:app
function optimize (root, options) { //将ast对象的全部节点标记为是否静态 markStatic(root); markStaticRoots(root, false); } function markStatic (node) { //当前节点是否静态 node.static = isStatic(node); //递归标记node的子节点是否静态 for (var i = 0, l = node.children.length; i < l; i++) { var child = node.children[i]; markStatic(child); //只要有一个子节点非静态,父节点也非静态 if (!child.static) { node.static = false; } } } function markStaticRoots (node, isInFor) { //将包含至少一个非文本子节点(node.type === 3表明文本节点)的节点标记为最大静态树的根节点 if (node.static && node.children.length && !( node.children.length === 1 && node.children[0].type === 3 )) { node.staticRoot = true; return } else { node.staticRoot = false; } //当前node节点不是静态根节点,递归判断子节点 if (node.children) { for (var i = 0, l = node.children.length; i < l; i++) { markStaticRoots(node.children[i], isInFor || !!node.for); } } }
本例中的最大静态树:dom
最终生成的渲染函数code以下函数
其中render是整个模版的渲染函数,staticrenderfns是静态树的渲染函数,staticrenderfns中的函数只会初始化一次,后续不须要再计算
render函数中用到的一些方法以下oop
function installRenderHelpers (target) { target._o = markOnce; target._n = toNumber; //转换为string对象 target._s = toString; target._l = renderList; target._t = renderSlot; target._q = looseEqual; target._i = looseIndexOf; target._m = renderStatic; target._f = resolveFilter; target._k = checkKeyCodes; target._b = bindObjectProps; //生成虚拟文本节点 target._v = createTextVNode; target._e = createEmptyVNode; target._u = resolveScopedSlots; target._g = bindObjectListeners; } //生成虚拟dom节点 vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
获得render函数后会继续调用下面的方法优化
function mountComponent ( updateComponent = function () { vm._update(vm._render(), hydrating); }; //对vue实例新建一个Watcher监听对象,每当vm.data数据有变化,Watcher监听到后负责调用updateComponent进行dom更新 vm._watcher = new Watcher(vm, updateComponent, noop); )
render函数调用后(vm._render())会生成vnode对象,也就是你们熟知的虚拟dom树,调用update方法就能根据vonde更新真实的浏览器dom。
接下来咱们分析下updatecomponents是如何调用的,这就涉及到了vue经典的watch机制(此处先简单介绍,下一篇会有较详细的分析)。this
//new Watcher时会先调用一次updateComponent,后续会监听vm.data的变化 var Watcher = function Watcher (vm,expOrFn){ if (typeof expOrFn === 'function') { this.getter = expOrFn; } this.get(); } Watcher.prototype.get = function get () { value = this.getter.call(vm, vm); }
最后再讲一下 vm._update方法的实现
Vue.prototype._update = function (vnode, hydrating) { var prevVnode = vm._vnode; vm._vnode = vnode; //判断vnode是否初始化过 if (!prevVnode) { // initial render vm.$el = vm.__patch__( // vm.$el是vm对象挂载的节点,本例是<div id="app"></div> // vm.$options._parentElm是组件挂载的节点(父节点),后面介绍组件时分析 vm.$el, vnode, hydrating, false /* removeOnly */, vm.$options._parentElm, vm.$options._refElm ); } else { // updates vm.$el = vm.__patch__(prevVnode, vnode); } } //vm.__pathch__方法 function function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm){ //根据vnode生成element并插入parentElm createElm(vnode, insertedVnodeQueue, parentElm, refElm); } //下面主要介绍初始化dom的实现 function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) { //根据vnode节点生成浏览器Element对象 vnode.elm = nodeOps.createElement(tag, vnode); var children = vnode.children; //递归将vnode子节点生成Element对象 createChildren(vnode, children, insertedVnodeQueue); //将生成的vnode.elm插入到浏览器的父节点当中 insert(parentElm, vnode.elm, refElm); } function createChildren (vnode, children, insertedVnodeQueue) { if (Array.isArray(children)) { for (var i = 0; i < children.length; ++i) { createElm(children[i], insertedVnodeQueue, vnode.elm, null, true); } //当vnode是文本节点时中止递归 } else if (isPrimitive(vnode.text)) { nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(vnode.text)); } }