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去掉空格的方法集合
Dec 28 Javascript
javascript 随机展示头像实现代码
Dec 06 Javascript
表单元素的submit()方法和onsubmit事件应用概述
Feb 01 Javascript
基于javascript的COOkie的操作实现只能点一次
Dec 26 Javascript
JavaScript将一个数组插入到另一个数组的方法
Mar 19 Javascript
JavaScript中操作字符串小结
May 04 Javascript
使用bootstrap-paginator.js 分页来进行ajax 异步分页请求示例
Mar 09 Javascript
javascript数据结构之串的概念与用法分析
Apr 12 Javascript
详解webpack分包及异步加载套路
Jun 29 Javascript
使用gulp构建前端自动化的方法示例
Dec 25 Javascript
解决vue单页面修改样式无法覆盖问题
Aug 05 Javascript
Vue项目开发常见问题和解决方案总结
Sep 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
PHP YII框架开发小技巧之模型(models)中rules自定义验证规则
2015/11/16 PHP
php下载文件超时时间的设置方法
2016/10/06 PHP
PHP实现的各类hash算法长度及性能测试实例
2017/08/27 PHP
javascript offsetX与layerX区别
2010/03/12 Javascript
推荐10个超棒的jQuery工具提示插件
2011/10/11 Javascript
JavaScript中判断页面关闭、页面刷新的实现代码
2014/08/27 Javascript
jquery实现可拖拽弹出层特效
2015/01/04 Javascript
Vue表单实例代码
2016/09/05 Javascript
vuejs动态组件给子组件传递数据的方法详解
2016/09/09 Javascript
javascript中异常处理案例(推荐)
2016/10/03 Javascript
Vue.js实现一个todo-list的上移下移删除功能
2017/06/26 Javascript
防止页面url缓存中ajax中post请求的处理方法
2017/10/10 Javascript
vue.js中created方法作用
2018/03/30 Javascript
微信小程序iBeacon测距及稳定程序的实现解析
2019/07/31 Javascript
Vue前端判断数据对象是否为空的实例
2020/09/02 Javascript
Vue实现返回顶部按钮实例代码
2020/10/21 Javascript
Vue自定义表单内容检查rules实例
2020/10/30 Javascript
可拖拽组件slider.js使用方法详解
2020/12/04 Javascript
多种类型jQuery网页验证码插件代码实例
2021/01/09 jQuery
介绍Python中的__future__模块
2015/04/27 Python
Python中的上下文管理器和with语句的使用
2018/04/17 Python
python3 json数据格式的转换(dumps/loads的使用、dict to str/str to dict、json字符串/字典的相互转换)
2019/04/01 Python
django中账号密码验证登陆功能的实现方法
2019/07/15 Python
Python 之 Json序列化嵌套类方式
2020/02/27 Python
Pyspark读取parquet数据过程解析
2020/03/27 Python
python获取linux系统信息的三种方法
2020/10/14 Python
python3中for循环踩过的坑记录
2020/12/14 Python
设计专业自荐信
2014/06/19 职场文书
火锅店的活动方案
2014/08/15 职场文书
违反交通法规检讨书
2014/09/10 职场文书
综合素质自我评价怎么写
2014/09/14 职场文书
小学教师师德培训心得体会
2016/01/09 职场文书
2016年八一建军节活动总结
2016/04/05 职场文书
导游词书写之黄山
2019/08/06 职场文书
golang协程池模拟实现群发邮件功能
2021/05/02 Golang
PostgreSQL之连接失败的问题及解决
2023/05/08 PostgreSQL