Vue AST源码解析第一篇
讲完了数据劫持原理和一堆初始化,现在是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树的字符串形式,看图就明白了:
下面看最后一段代码:
// 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转换为函数 => 返回并缓存。
第一节现在这样吧。一张图总结下:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
下一篇: 浅谈JS封闭函数、闭包、内置对象