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中的类继承
Nov 25 Javascript
网页打开自动最大化的js代码
Aug 22 Javascript
jQuery标签替换函数replaceWith()的使用例子
Aug 28 Javascript
jQuery图片轮播滚动切换代码分享
Apr 20 Javascript
jquery实现全选、不选、反选的两种方法
Sep 06 Javascript
jQuery实现导航滚动到指定内容效果完整实例【附demo源码下载】
Sep 20 Javascript
JavaScript 身份证号有效验证详解及实例代码
Oct 20 Javascript
Javascript三种字符串连接方式及性能比较
May 28 Javascript
vue使用自定义指令实现拖拽
Jan 29 Javascript
vue 实现cli3.0中使用proxy进行代理转发
Oct 30 Javascript
微信小程序实现列表左右滑动
Nov 19 Javascript
javascript实现左右缓动动画函数
Nov 25 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分页集合包括使用方法
2013/10/21 PHP
PHP中使用php://input处理相同name值的表单数据
2015/02/03 PHP
PHP获取文件扩展名的方法实例总结
2017/06/10 PHP
Laravel等框架模型关联的可用性浅析
2019/12/15 PHP
js正确获取元素样式详解
2009/08/07 Javascript
ASP.NET jQuery 实例1(在TextBox里面创建一个默认提示)
2012/01/13 Javascript
js创建对象几种方式的优缺点对比
2016/09/28 Javascript
JS实现最简单的冒泡排序算法
2017/02/15 Javascript
JS实现双击内容变为可编辑状态
2017/03/03 Javascript
JavaScript实现三级联动菜单实例代码
2017/06/26 Javascript
jQuery实现选中行变色效果(实例讲解)
2017/07/06 jQuery
jQuery EasyUI Layout实现tabs标签的实例
2017/09/26 jQuery
vue+iview+less 实现换肤功能
2018/08/17 Javascript
react native基于FlatList下拉刷新上拉加载实现代码示例
2018/09/30 Javascript
vuex的module模块用法示例
2018/11/12 Javascript
HTML+JavaScript实现扫雷小游戏
2019/09/30 Javascript
vue v-for出来的列表,点击某个li使得当前被点击的li字体变红操作
2020/07/17 Javascript
浅析JavaScript预编译和暗示全局变量
2020/09/03 Javascript
Vue双向数据绑定(MVVM)的原理
2020/10/03 Javascript
antd design table更改某行数据的样式操作
2020/10/31 Javascript
Python使用matplotlib模块绘制图像并设置标题与坐标轴等信息示例
2018/05/04 Python
python 输出所有大小写字母的方法
2019/01/02 Python
Python图像处理之图像的缩放、旋转与翻转实现方法示例
2019/01/04 Python
解决Django部署设置Debug=False时xadmin后台管理系统样式丢失
2020/04/07 Python
基于python连接oracle导并出数据文件
2020/04/28 Python
详解在Python中使用Torchmoji将文本转换为表情符号
2020/07/27 Python
Java提供了哪些企业应用编程接口
2015/02/13 面试题
介绍一下你对SOA的认识
2016/04/24 面试题
法学研究生自我鉴定范文
2013/12/04 职场文书
毕业设计计划书
2014/01/09 职场文书
开展批评与自我批评发言材料
2014/05/15 职场文书
2014最新版群众路线四风整改措施
2014/09/24 职场文书
教师个人发展总结
2015/02/11 职场文书
2015年办公室工作总结范文
2015/03/31 职场文书
OpenCV实现反阈值二值化
2021/11/17 Java/Android
uniapp开发打包多端应用完整方法指南
2022/12/24 Javascript