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数组处理多个字符串的连接问题
Aug 20 Javascript
下载文件个别浏览器文件名乱码解决办法
Mar 19 Javascript
在JavaScript中处理字符串之fontcolor()方法的使用
Jun 08 Javascript
jquery实现实时改变网页字体大小、字体背景色和颜色的方法
Aug 05 Javascript
JS+CSS实现电子商务网站导航模板效果代码
Sep 10 Javascript
jQuery新窗口打开外链接
Jul 21 Javascript
BootStrap iCheck插件全选与获取value值的解决方法
Aug 24 Javascript
JavaScript职责链模式概述
Sep 17 Javascript
vue使用高德地图根据坐标定位点的实现代码
Aug 22 Javascript
javascript/jquery实现点击触发事件的方法分析
Nov 11 jQuery
基于javascript实现放大镜特效
Dec 03 Javascript
使用javascript解析二维码的三种方式
Nov 11 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
网站当前的在线人数
2006/10/09 PHP
PHP版QQ互联OAuth示例代码分享
2015/07/05 PHP
php数组分页实现方法
2016/04/30 PHP
PHP处理CSV表格文件的常用操作方法总结
2016/07/01 PHP
ThinkPHP中session函数详解
2016/09/14 PHP
实例分析PHP将字符串转换成数字的方法
2019/01/27 PHP
Laravel 微信小程序后端实现用户登录的示例代码
2019/11/26 PHP
picChange 图片切换特效的函数代码
2010/05/06 Javascript
过虑特殊字符输入的js代码
2010/08/05 Javascript
JS中不为人知的五种声明Number的方式简要概述
2013/02/22 Javascript
JavaScript中继承用法实例分析
2015/05/16 Javascript
JavaScript基于自定义函数判断变量类型的实现方法
2016/11/23 Javascript
Angular使用动态加载组件方法实现Dialog的示例
2018/05/11 Javascript
微信小程序form表单组件示例代码
2018/07/15 Javascript
浅谈Vue.js 关于页面加载完成后执行一个方法的问题
2019/04/01 Javascript
js实现贪吃蛇小游戏
2019/10/29 Javascript
vue实现简单图片上传
2020/06/30 Javascript
Vue中父子组件的值传递与方法传递
2020/09/28 Javascript
vue 如何使用递归组件
2020/10/23 Javascript
Python多进程编程技术实例分析
2014/09/16 Python
django 自定义用户user模型的三种方法
2014/11/18 Python
微信跳一跳python辅助脚本(总结)
2018/01/11 Python
Centos7 Python3下安装scrapy的详细步骤
2018/03/15 Python
使用Python实现微信提醒备忘录功能
2018/12/04 Python
python仿抖音表白神器
2019/04/08 Python
在linux下实现 python 监控usb设备信号
2019/07/03 Python
文件上传服务器-jupyter 中python解压及压缩方式
2020/04/22 Python
详解pytorch tensor和ndarray转换相关总结
2020/09/03 Python
python从Oracle读取数据生成图表
2020/10/14 Python
突袭HTML5之Javascript API扩展5—其他扩展(应用缓存/服务端消息/桌面通知)
2013/01/31 HTML / CSS
柒牌官方商城:中国男装优秀品牌
2017/06/30 全球购物
Vans(范斯)新西兰官方网站:美国原创极限运动品牌
2020/09/19 全球购物
婚纱店策划方案
2014/05/22 职场文书
单位单身证明样本
2014/10/11 职场文书
2015年社区中秋节活动总结
2015/03/23 职场文书
MySQL存储过程及语法详解
2022/08/05 MySQL