Vue AST源码解析第一篇


Posted in Javascript onJuly 19, 2017

讲完了数据劫持原理和一堆初始化,现在是DOM相关的代码了。

上一节是从这个函数开始的:

// Line-3924
 Vue.prototype._init = function(options) {
  // 大量初始化
  // ...
  // Go!
  if (vm.$options.el) {
   vm.$mount(vm.$options.el);
  }
 };

弄完data属性的数据绑定后,开始处理el属性,也就是挂载的DOM节点,这里的vm.$options.el也就是传进去的'#app'字符串。

有一个值得注意的点是,源码中有2个$mount函数都是Vue$3的原型函数,其中一个标记了注释public mount method,在7531行,另外一个在9553行。打断点进入的是后面,因为定义的晚,覆盖了前面的函数。

// Line-7531
 // public mount method
 Vue$3.prototype.$mount = function(el,hydrating) {
  el = el && inBrowser ? query(el) : undefined;
  return mountComponent(this, el, hydrating)
 };

 // Line-9552
 var mount = Vue$3.prototype.$mount;
 Vue$3.prototype.$mount = function(
  el,
  hydrating
 ) {
  // ...很多代码
  return mount.call(this, el, hydrating)
 };

现在进入后面的$mount函数看看内部结构:

// Line-9552
 var mount = Vue$3.prototype.$mount;
 Vue$3.prototype.$mount = function(el,hydrating) {
  // 将el格式化为DOM节点
  el = el && query(el);
  // 判断是否挂载到body或者html标签上
  if (el === document.body || el === document.documentElement) {
   "development" !== 'production' && warn(
    "Do not mount Vue to <html> or <body> - mount to normal elements instead."
   );
   return this
  }

  var options = this.$options;
  // 处理template/el 转换为渲染函数
  if (!options.render) {
   // ...非常多代码
  }
  return mount.call(this, el, hydrating)
 };

代码前半段首先将el转换为DOM节点,并判断是否挂载到body或者html标签,看看简单的query函数:

// Line-4583
 function query(el) {
  // 如果是字符串就调用querySelector
  if (typeof el === 'string') {
   var selected = document.querySelector(el);
   if (!selected) {
    "development" !== 'production' && warn(
     'Cannot find element: ' + el
    );
    // 找不到就返回一个div
    return document.createElement('div')
   }
   return selected
  }
  // 不是字符串就默认传进来的是DOM节点 
  else {
   return el
  }
 }

函数比较简单,值得注意的几个点是,由于调用的是querySelector方法,所以可以传标签名、类名、C3新选择器等,都会返回查询到的第一个。当然,总是传一个ID或者确定的DOM节点才是正确用法。

下面看接下来的代码:

// Line-9552
 var mount = Vue$3.prototype.$mount;
 Vue$3.prototype.$mount = function(el,hydrating) {
  // ...el转换为DOM节点
  // ...
  // 没有render属性 进入代码段
  if (!options.render) {
   var template = options.template;
   // 没有template 跳
   if (template) {
    if (typeof template === 'string') {
     if (template.charAt(0) === '#') {
      template = idToTemplate(template);
      /* istanbul ignore if */
      if ("development" !== 'production' && !template) {
       warn(
        ("Template element not found or is empty: " + (options.template)),
        this
       );
      }
     }
    } else if (template.nodeType) {
     template = template.innerHTML;
    } else {
     {
      warn('invalid template option:' + template, this);
     }
     return this
    }
   }
   // 有el 获取字符串化的DOM树
   else if (el) {
    template = getOuterHTML(el);
   }
   if (template) {
    // ...小段代码
   }
  }
  return mount.call(this, el, hydrating)
 };

由于没有template属性,会直接进入第二个判断条件,调用getOuterHTML来初始化template变量,函数比较简单, 来看看:

// Line-9623
 function getOuterHTML(el) {
  if (el.outerHTML) {
   return el.outerHTML
  }
  // 兼容IE中的SVG
  else {
   var container = document.createElement('div');
   container.appendChild(el.cloneNode(true));
   return container.innerHTML
  }
 }

简单来讲,就是调用outerHTML返回DOM树的字符串形式,看图就明白了:

Vue AST源码解析第一篇

下面看最后一段代码:

// Line-9552
 var mount = Vue$3.prototype.$mount;
 Vue$3.prototype.$mount = function(el,hydrating) {
  // ...el转换为DOM节点
  // ...
  // 没有render属性 进入代码段
  if (!options.render) {
   // ...处理template
   // ...
   if (template) {
    // 编译开始
    if ("development" !== 'production' && config.performance && mark) {
     mark('compile');
    }

    // 将DOM树字符串编译为函数
    var ref = compileToFunctions(template, {
     shouldDecodeNewlines: shouldDecodeNewlines,
     delimiters: options.delimiters
    }, this);
    // options添加属性
    var render = ref.render;
    var staticRenderFns = ref.staticRenderFns;
    options.render = render;
    options.staticRenderFns = staticRenderFns;

    // 编译结束
    if ("development" !== 'production' && config.performance && mark) {
     mark('compile end');
     measure(((this._name) + " compile"), 'compile', 'compile end');
    }
   }
  }
  return mount.call(this, el, hydrating)
 };

忽略2段dev模式下的提示代码,剩下的代码做了3件事,调用compileToFunctions函数肢解DOM树字符串,将返回的对象属性添加到options上,再次调用mount函数。

首先看一下compileToFunctions函数,该函数接受3个参数,分别为字符串、配置对象、当前vue实例。

由于函数比较长,而且部分是错误判断,简化后如下:

// Line-9326
 function compileToFunctions(template,options,vm) {
  // 获取配置参数
  options = options || {};

  // ...

  var key = options.delimiters ?
   String(options.delimiters) + template :
   template;
  // 检测缓存
  if (functionCompileCache[key]) {
   return functionCompileCache[key]
  }

  // 1
  var compiled = compile(template, options);

  // ...

  // 2
  var res = {};
  var fnGenErrors = [];
  res.render = makeFunction(compiled.render, fnGenErrors);
  var l = compiled.staticRenderFns.length;
  res.staticRenderFns = new Array(l);
  for (var i = 0; i < l; i++) {
   res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i], fnGenErrors);
  }

  // ...

  // 3
  return (functionCompileCache[key] = res)
 }

可以看到,这个函数流程可以分为4步,获取参数 => 调用compile函数进行编译 => 将得到的compiled转换为函数 => 返回并缓存。

 第一节现在这样吧。一张图总结下:

Vue AST源码解析第一篇

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript设置FieldSet展开与收缩
May 15 Javascript
WEB前端设计师常用工具集锦
Dec 09 Javascript
node.js开机自启动脚本文件
Dec 24 Javascript
jQuery中[attribute^=value]选择器用法实例
Dec 31 Javascript
浅谈javascript中this在事件中的应用
Feb 15 Javascript
JS实现带提示的星级评分效果完整实例
Oct 30 Javascript
js实现接收表单的值并将值拼在表单action后面的方法
Nov 23 Javascript
jQuery+ajax的资源回收处理机制分析
Jan 07 Javascript
Node.js实现文件上传的示例
Jun 28 Javascript
对vue下点击事件传参和不传参的区别详解
Sep 15 Javascript
JavaScript中callee和caller的区别与用法实例分析
Jun 28 Javascript
小程序采集录音并上传到后台
Nov 22 Javascript
Vue之Watcher源码解析(1)
Jul 19 #Javascript
angular.js + require.js构建模块化单页面应用的方法步骤
Jul 19 #Javascript
Vue学习笔记进阶篇之多元素及多组件过渡
Jul 19 #Javascript
vue中的非父子间的通讯问题简单的实例代码
Jul 19 #Javascript
Vue之Watcher源码解析(2)
Jul 19 #Javascript
Angular.js项目中使用gulp实现自动化构建以及压缩打包详解
Jul 19 #Javascript
JS+canvas实现的五子棋游戏【人机大战版】
Jul 19 #Javascript
You might like
php桌面中心(四) 数据显示
2007/03/11 PHP
一个多次搜索+多次传值的解决方案
2007/01/20 Javascript
javascript 嵌套的函数(作用域链)
2010/03/15 Javascript
js 实现在离开页面时提醒未保存的信息(减少用户重复操作)
2013/01/16 Javascript
Jquery 复选框取值兼容FF和IE8(测试有效)
2013/10/29 Javascript
js检测输入内容全为空格的方法
2014/05/03 Javascript
JS模拟键盘打字效果的方法
2015/08/05 Javascript
jQuery+php实时获取及响应文本框输入内容的方法
2016/05/24 Javascript
JavaScript判断数组重复内容的两种方法(推荐)
2016/06/06 Javascript
ES6新特征数字、数组、字符串
2016/10/01 Javascript
微信小程序进行微信支付的步骤昂述
2016/12/01 Javascript
Vue.js实现一个SPA登录页面的过程【推荐】
2017/04/29 Javascript
详解angular ui-grid之过滤器设置
2017/06/07 Javascript
JS实现求数组起始项到终止项之和的方法【基于数组扩展函数】
2017/06/13 Javascript
AngularJs ng-change事件/指令的用法小结
2017/11/01 Javascript
angular2 组件之间通过service互相传递的实例
2018/09/30 Javascript
微信小程序时间控件picker view使用详解
2018/12/28 Javascript
原生JS封装拖动验证滑块的实现代码示例
2020/06/01 Javascript
详解ES6中class的实现原理
2020/10/03 Javascript
SublimeText 2编译python出错的解决方法(The system cannot find the file specified)
2013/11/27 Python
Python 正则表达式入门(初级篇)
2016/12/07 Python
Python+tkinter模拟“记住我”自动登录实例代码
2018/01/16 Python
一百多行python代码实现抢票助手
2018/09/25 Python
python计算二维矩形IOU实例
2020/01/18 Python
Python ADF 单位根检验 如何查看结果的实现
2020/06/03 Python
Python3以GitHub为例来实现模拟登录和爬取的实例讲解
2020/07/30 Python
Python实现数字的格式化输出
2020/08/01 Python
css3隔行变换色实现示例
2014/02/19 HTML / CSS
一款简洁的纯css3代码实现的动画导航
2014/10/31 HTML / CSS
用CSS3实现无限循环的无缝滚动的实例代码
2017/07/04 HTML / CSS
人事文员岗位职责
2014/02/16 职场文书
2014年度安全生产目标管理责任书
2014/07/25 职场文书
2014年校务公开工作总结
2014/12/18 职场文书
傲慢与偏见读书笔记
2015/06/29 职场文书
thinkphp 获取控制器及控制器方法
2021/04/16 PHP
电脑开机弹出documents文件夹怎么回事?弹出documents文件夹解决方法
2022/04/08 数码科技