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 相关文章推荐
国外Lightbox v2.03.3 最新版 下载
Oct 17 Javascript
Mootools 1.2教程 同时进行多个形变动画
Sep 15 Javascript
跨域传值即主页面与iframe之间互相传值
Dec 09 Javascript
jQuery 2.0.3 源码分析之core(一)整体架构
May 27 Javascript
jQuery中trigger()与bind()用法分析
Dec 18 Javascript
学习Bootstrap滚动监听 附调用方法
Jul 02 Javascript
关于javascript原型的修改与重写(覆盖)差别详解
Aug 31 Javascript
ReactNative之键盘Keyboard的弹出与消失示例
Jul 11 Javascript
Angular中自定义Debounce Click指令防止重复点击
Jul 26 Javascript
node作为中间服务层如何发送请求(发送请求的实现方法详解)
Jan 02 Javascript
在vue中给列表中的奇数行添加class的实现方法
Sep 05 Javascript
javascript canvas API内容整理
Feb 16 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 操作excel文件的方法小结
2009/12/31 PHP
elgg 获取文件图标地址的方法
2010/03/20 PHP
CI框架在CLI下执行占用内存过大问题的解决方法
2014/06/17 PHP
PHP读取大文件末尾N行的高效方法推荐
2016/06/03 PHP
PHP使用反向Ajax技术实现在线客服系统详解
2019/07/01 PHP
jquery焦点图片切换(数字标注/手动/自动播放/横向滚动)
2013/01/24 Javascript
jQuery对象和Javascript对象之间转换的实例代码
2013/03/20 Javascript
jquery 层次选择器siblings与nextAll的区别介绍
2013/08/02 Javascript
自定义函数实现IE7与IE8不兼容js中trim函数的问题
2015/02/03 Javascript
JS和css实现检测移动设备方向的变化并判断横竖屏幕
2015/05/25 Javascript
javascript生成随机数方法汇总
2015/11/12 Javascript
谈谈js中的prototype及prototype属性解释和常用方法
2015/11/25 Javascript
JS数组去掉重复数据只保留一条的实现代码
2016/08/11 Javascript
jQuery Easyui 下拉树组件combotree
2016/12/16 Javascript
Javascript 制作图形验证码实例详解
2016/12/22 Javascript
详解Windows下安装Nodejs步骤
2017/05/18 NodeJs
bootstrap+jQuery 实现下拉菜单中复选框全选和全不选效果
2017/06/12 jQuery
nodejs中sleep功能实现暂停几秒的方法
2017/07/12 NodeJs
ReactNative中使用Redux架构总结
2017/12/15 Javascript
基于vue展开收起动画的示例代码
2018/07/05 Javascript
React 使用Hooks简化受控组件的状态绑定
2019/03/18 Javascript
JS实现获取当前所在周的周六、周日示例分析
2019/05/11 Javascript
微信小程序实现单个卡片左滑显示按钮并防止上下滑动干扰功能
2019/12/06 Javascript
Vue项目开发常见问题和解决方案总结
2020/09/11 Javascript
使用Python实现BT种子和磁力链接的相互转换
2015/11/09 Python
使用python 和 lint 删除项目无用资源的方法
2017/12/20 Python
使用Html5中的cavas画一面国旗
2019/09/25 HTML / CSS
Craghoppers德国官网:户外和旅行服装
2020/02/14 全球购物
生物科学系大学生的自我评价
2013/12/20 职场文书
实习生自我评价
2014/01/18 职场文书
晚会邀请函范文
2014/01/24 职场文书
班子个人四风问题整改措施
2014/10/04 职场文书
2014年青年志愿者工作总结
2014/12/09 职场文书
2015年绩效考核工作总结
2015/05/23 职场文书
六一活动主持词
2015/06/30 职场文书
Java图书管理系统,课程设计必用(源码+文档)
2021/06/30 Java/Android