资讯专栏INFORMATION COLUMN

Vue源码解析(一)-模版渲染

KitorinZero / 2950人阅读

摘要:接下来我们分析下是如何调用的,这就涉及到了经典的机制此处先简单介绍,下一篇会有较详细的分析。

Vue demo

先给出vue简单的使用demo,通过创建一个Vue的实例,

将被替换成template模版中的内容,a,b的值也会被换成data属性的值

var vm = new Vue({ el: "#app", template: `

{{a}}

{{b}}

btn1
btn2
str1
str2
str3
`, data(){ return { a: 1, b: 2 } } })
模版渲染

以下的分析代码都经过作者简化,只为简单清楚的解析vue的实现逻辑
首先根据参数template属性生成render函数

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数据改变导致页面重新渲染的时候,最大静态子树不需要重新计算生成),基本实现逻辑如下:

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);
      }
    }
}

本例中的最大静态树:

最终生成的渲染函数code如下

其中render是整个模版的渲染函数,staticrenderfns是静态树的渲染函数,staticrenderfns中的函数只会初始化一次,后续不需要再计算
render函数中用到的一些方法如下

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机制(此处先简单介绍,下一篇会有较详细的分析)。

//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对象挂载的节点,本例是
// 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)); } }

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/89430.html

相关文章

  • vue源码阅读之数据渲染过程

    摘要:图在中应用三数据渲染过程数据绑定实现逻辑本节正式分析从到数据渲染到页面的过程,在中定义了一个的构造函数。一、概述 vue已是目前国内前端web端三分天下之一,也是工作中主要技术栈之一。在日常使用中知其然也好奇着所以然,因此尝试阅读vue源码并进行总结。本文旨在梳理初始化页面时data中的数据是如何渲染到页面上的。本文将带着这个疑问一点点追究vue的思路。总体来说vue模版渲染大致流程如图1所...

    AlphaGooo 评论0 收藏0
  • Vue源码解析(1)-模版渲染

    摘要:首先在里调用函数把获取相应传递过去里在原型上定义方法对浏览器的怪癖做兼容布尔值。对浏览器的怪癖做兼容布尔值。 首先在init.js里调用$mount函数把el #app获取相应dom传递过去 Vue.prototype._init = function (options) { ... if (vm.$options.el) { vm.$mount(vm.$opt...

    k00baa 评论0 收藏0
  • Vue源码解析模版字符串转AST语法树

    摘要:通过对源码阅读,想写一写自己的理解,能力有限故从尤大佬第一次提交开始读,准备陆续写模版字符串转语法树语法树转函数双向绑定原理虚拟比较原理其中包含自己的理解和源码的分析,尽量通俗易懂由于是的最早提交,所以和最新版本有很多差异,后续将陆续补充, 通过对 Vue2.0 源码阅读,想写一写自己的理解,能力有限故从尤大佬2016.4.11第一次提交开始读,准备陆续写: 模版字符串转AST语法...

    chengjianhua 评论0 收藏0
  • Vue源码解析模版字符串转AST语法树

    摘要:通过对源码阅读,想写一写自己的理解,能力有限故从尤大佬第一次提交开始读,准备陆续写模版字符串转语法树语法树转函数双向绑定原理虚拟比较原理其中包含自己的理解和源码的分析,尽量通俗易懂由于是的最早提交,所以和最新版本有很多差异,后续将陆续补充, 通过对 Vue2.0 源码阅读,想写一写自己的理解,能力有限故从尤大佬2016.4.11第一次提交开始读,准备陆续写: 模版字符串转AST语法...

    王伟廷 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<