上一节到了parseHTML函数,该函数接受一个字符串与一个对象,字符串即对应的DOM,对象包含几个字符串匹配集及3个长函数。

  简略梳理部分函数代码如下:

    // Line-7672
function parseHTML(html, options) {
var stack = [];
var expectHTML = options.expectHTML;
var isUnaryTag$$1 = options.isUnaryTag || no;
var canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no;
var index = 0;
var last, lastTag;
while (html) {
last = html;
// 排除script,style,textarea三个标签
if (!lastTag || !isPlainTextElement(lastTag)) {
var textEnd = html.indexOf('<');
if (textEnd === 0) {
// 截取注释
if (comment.test(html)) {
var commentEnd = html.indexOf('-->'); if (commentEnd >= 0) {
advance(commentEnd + 3);
continue
}
}
// 处理向下兼容的注释 比如说<!--[if lt IE 9]>
if (conditionalComment.test(html)) {
var conditionalEnd = html.indexOf(']>'); if (conditionalEnd >= 0) {
advance(conditionalEnd + 2);
continue
}
}
// Doctype:
var doctypeMatch = html.match(doctype);
if (doctypeMatch) {
advance(doctypeMatch[0].length);
continue
}
// End tag:
var endTagMatch = html.match(endTag);
if (endTagMatch) {
var curIndex = index;
advance(endTagMatch[0].length);
parseEndTag(endTagMatch[1], curIndex, index);
continue
}
// Start tag:
// 匹配起始标签
var startTagMatch = parseStartTag();
if (startTagMatch) {
handleStartTag(startTagMatch);
continue
}
}
// 初始化为undefined 这样安全且字符数少一点
var text = (void 0),
rest$1 = (void 0),
next = (void 0);
if (textEnd >= 0) {
rest$1 = html.slice(textEnd);
while (!endTag.test(rest$1) &&
!startTagOpen.test(rest$1) &&
!comment.test(rest$1) &&
!conditionalComment.test(rest$1)
) {
// 处理文本中的<字符
next = rest$1.indexOf('<', 1);
if (next < 0) {
break
}
textEnd += next;
rest$1 = html.slice(textEnd);
}
text = html.substring(0, textEnd);
advance(textEnd);
} if (textEnd < 0) {
text = html;
html = '';
} if (options.chars && text) {
options.chars(text);
}
} else {
/* code... */
} if (html === last) {
/* code... */
}
}
// Clean up any remaining tags
parseEndTag(); function advance(n) {
/* code... */
} function parseStartTag() {
/* code... */
} function handleStartTag(match) {
/* code... */
} function parseEndTag(tagName, start, end) {
/* code... */
}
}

  函数比较长,除去开头的参数获取,后面直接用while循环开始对字符串进行切割。

  在判断标签不是script,style,textarea三个特殊标签后,当字符串以<开头时,以注释、向下兼容注释、Doctype、结束标签、起始标签的顺序依次切割。

  由于案例中字符串是以<div开头,所以直接跳到起始标签的匹配:

    // Line-7722
var startTagMatch = parseStartTag();
if (startTagMatch) {
handleStartTag(startTagMatch);
continue
} // Line-7795
function parseStartTag() {
// 正则匹配
var start = html.match(startTagOpen);
if (start) {
var match = {
// 标签名(div)
tagName: start[1],
// 属性
attrs: [],
// 游标索引(初始为0)
start: index
};
advance(start[0].length);
var end, attr;
// 进行属性的正则匹配
// startTagClose匹配/>或>
// attribute匹配属性 正则太长 没法讲
// 本例中attr匹配后 => ['id=app','id','=','app']
while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
advance(attr[0].length);
// 属性加入
match.attrs.push(attr);
}
// 在第二次while循环后 end匹配到结束标签 => ['>','']
if (end) {
match.unarySlash = end[1];
advance(end[0].length);
// 标记结束位置
match.end = index;
// 返回匹配对象
return match
}
}
} // Line-7790
// 该函数将函数局部变量index往前推 并切割字符串
function advance(n) {
index += n;
html = html.substring(n);
}

  可以看到,通过起始标签的匹配,字符串的<div id='app'>已经被切割出来,保存在一个对象中返回:

  接下来,会调用handleStartTag方法再次处理返回的对象,看一下这个方法:

    // Line-7818
function handleStartTag(match) {
var tagName = match.tagName;
var unarySlash = match.unarySlash; if (expectHTML) {
// PhrasingTag(段落元素)涉及到标签元素类型 具体可见http://www.5icool.org/a/201308/a2081.html
if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
parseEndTag(lastTag);
}
// 可以省略闭合标签
if (canBeLeftOpenTag$$1(tagName) && lastTag === tagName) {
parseEndTag(tagName);
}
} // 自闭合标签
var unary = isUnaryTag$$1(tagName) || tagName === 'html' && lastTag === 'head' || !!unarySlash; // 记录属性个数 目前只有一个id属性
var l = match.attrs.length;
var attrs = new Array(l);
for (var i = 0; i < l; i++) {
var args = match.attrs[i];
// 一个bug 在对(.)?匹配时会出现
if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) {
if (args[3] === '') {
delete args[3];
}
if (args[4] === '') {
delete args[4];
}
if (args[5] === '') {
delete args[5];
}
}
// 匹配属性名app
var value = args[3] || args[4] || args[5] || '';
attrs[i] = {
name: args[1],
// 处理转义字符
value: decodeAttr(
value,
options.shouldDecodeNewlines
)
};
}
// 将切割出来的字符串转换为AST
if (!unary) {
stack.push({
tag: tagName,
lowerCasedTag: tagName.toLowerCase(),
attrs: attrs
});
// 标记结束标签
lastTag = tagName;
} // 这是参数中第一个函数
if (options.start) {
options.start(tagName, attrs, unary, match.start, match.end);
}
} // Line-7667
function decodeAttr(value, shouldDecodeNewlines) {
// lg,gt等字符的正则
var re = shouldDecodeNewlines ? encodedAttrWithNewLines : encodedAttr;
return value.replace(re, function(match) {
return decodingMap[match];
})
}

  在该函数中,对之前的对象进行了二次处理,根据标签名、属性生成一个新对象,push到最开始的stack数组中,结果如图所示:

  由于匹配的是起始标签,所以也会以这个标签名结束,因此被标记为最后的结束标签,即前面一直是undefined的lastTag。

  最后,调用了一个start函数,蒙了半天没找到,后来才发现是最开始传进来的参数中有3个函数:start、end、chars,现在可以看一下这个方法干啥用的了。

  start方法只接受3个参数,这里传了5个,后面2个被忽略,参数情况是这样的:

  这个方法比较长:

    // Line-8026
function start(tag, attrs, unary) {
// 检查命名空间是否是svg或者math
var ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag); // handle IE svg bug
if (isIE && ns === 'svg') {
attrs = guardIESVGBug(attrs);
} var element = {
type: 1,
tag: tag,
// {name:'id',value:'app'}
attrsList: attrs,
// {id:'app'}
attrsMap: makeAttrsMap(attrs),
parent: currentParent,
children: []
};
if (ns) {
element.ns = ns;
}
// 检查tag属性是否是style、script
if (isForbiddenTag(element) && !isServerRendering()) {
element.forbidden = true;
/* warning */
} // apply pre-transforms 本例中没有
// Line-7990:preTransforms = pluckModuleFunction(options.modules, 'preTransformNode');
for (var i = 0; i < preTransforms.length; i++) {
preTransforms[i](element, options);
} if (!inVPre) {
// 判断是否有v-pre属性
processPre(element);
if (element.pre) {
inVPre = true;
}
}
// 判断tag是不是pre
if (platformIsPreTag(element.tag)) {
inPre = true;
}
// 分支跳转到else
if (inVPre) {
processRawAttrs(element);
} else {
// 处理v-for
processFor(element);
// 处理v-if,v-else,v-else-if
processIf(element);
// 处理v-once
processOnce(element);
// 处理:
processKey(element); // 检测是否是空属性节点
element.plain = !element.key && !attrs.length; // 处理:ref或v-bind:ref属性
processRef(element);
// 当tag为slot时
processSlot(element);
// 处理:is或v-bind:is属性
processComponent(element);
// Line-7991:transforms = pluckModuleFunction(options.modules, 'transformNode');
// 处理class与style属性 包括原始的和通过:动态绑定
for (var i$1 = 0; i$1 < transforms.length; i$1++) {
transforms[i$1](element, options);
}
// 处理属性
processAttrs(element);
} // 根元素不允许为slot或template 且不能有v-for属性
// 总之必须为单一不可变的节点
function checkRootConstraints(el) {
{
if (el.tag === 'slot' || el.tag === 'template') {
warnOnce(
"Cannot use <" + (el.tag) + "> as component root element because it may " +
'contain multiple nodes.'
);
}
if (el.attrsMap.hasOwnProperty('v-for')) {
warnOnce(
'Cannot use v-for on stateful component root element because ' +
'it renders multiple elements.'
);
}
}
} // tree management
// 这个root是在parse函数开始的时候定义的
if (!root) {
root = element;
checkRootConstraints(root);
} else if (!stack.length) {
// allow root elements with v-if, v-else-if and v-else
if (root.if && (element.elseif || element.else)) {
checkRootConstraints(element);
addIfCondition(root, {
exp: element.elseif,
block: element
});
} else {
warnOnce(
"Component template should contain exactly one root element. " +
"If you are using v-if on multiple elements, " +
"use v-else-if to chain them instead."
);
}
}
// 没有父元素 跳过
if (currentParent && !element.forbidden) {
if (element.elseif || element.else) {
processIfConditions(element, currentParent);
} else if (element.slotScope) { // scoped slot
currentParent.plain = false;
var name = element.slotTarget || '"default"';
(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element;
} else {
currentParent.children.push(element);
element.parent = currentParent;
}
}
if (!unary) {
currentParent = element;
stack.push(element);
} else {
endPre(element);
}
// 没有这个 跳过
// Line-7992:postTransforms = pluckModuleFunction(options.modules, 'postTransformNode');
for (var i$2 = 0; i$2 < postTransforms.length; i$2++) {
postTransforms[i$2](element, options);
}
} // Line-8470
// 函数作用:attrs={name:'id',value:'app'} => map = {id : app}
function makeAttrsMap(attrs) {
var map = {};
for (var i = 0, l = attrs.length; i < l; i++) {
// 检测重复属性名
if (
"development" !== 'production' &&
map[attrs[i].name] && !isIE && !isEdge
) {
warn$2('duplicate attribute: ' + attrs[i].name);
}
map[attrs[i].name] = attrs[i].value;
}
return map
}

  这个方法首先对标签名进行校验,然后再对属性进行更细致的处理,比如说v-pre,v-for,v-if等等,案例里没有,所以暂时不分析如何处理,下次再搞。

  值得注意的是transforms这个数组,包含两个函数:transformNode与transformNode$1,其实只是对静态或动态绑定的class与style进行处理,代码如下:

    // Line-9416
function transformNode(el, options) {
var warn = options.warn || baseWarn;
// 获取原始class属性
var staticClass = getAndRemoveAttr(el, 'class');
if ("development" !== 'production' && staticClass) {
var expression = parseText(staticClass, options.delimiters);
if (expression) {
/*<div class="{{ val }}"> => <div :class="val"> */
}
}
// 将原始class属性保存为属性
if (staticClass) {
el.staticClass = JSON.stringify(staticClass);
}
// 获取:class
var classBinding = getBindingAttr(el, 'class', false /* getStatic */ );
if (classBinding) {
el.classBinding = classBinding;
}
} // Line-9458
function transformNode$1(el, options) {
var warn = options.warn || baseWarn;
var staticStyle = getAndRemoveAttr(el, 'style');
if (staticStyle) {
/* istanbul ignore if */
{
var expression = parseText(staticStyle, options.delimiters);
if (expression) {
/*<div style="{{ val }}"> => <div :style="val"> */
}
}
el.staticStyle = JSON.stringify(parseStyleText(staticStyle));
} var styleBinding = getBindingAttr(el, 'style', false /* getStatic */ );
if (styleBinding) {
el.styleBinding = styleBinding;
}
}

  在最后,调用processAttrs对动态绑定的属性(v-,@,:)进行处理,代码如下:

    // Line-8376
function processAttrs(el) {
// {name:'id',value:'app'}
var list = el.attrsList;
var i, l, name, rawName, value, modifiers, isProp;
for (i = 0, l = list.length; i < l; i++) {
// id
name = rawName = list[i].name;
// app
value = list[i].value;
// dirRE => 以v-、@、:开头
if (dirRE.test(name)) {
// 标记为拥有动态绑定属性 本例中没有 跳过……
el.hasBindings = true;
// modifiers
modifiers = parseModifiers(name);
if (modifiers) {
name = name.replace(modifierRE, '');
}
if (bindRE.test(name)) { // v-bind
name = name.replace(bindRE, '');
value = parseFilters(value);
isProp = false;
if (modifiers) {
if (modifiers.prop) {
isProp = true;
name = camelize(name);
if (name === 'innerHtml') {
name = 'innerHTML';
}
}
if (modifiers.camel) {
name = camelize(name);
}
if (modifiers.sync) {
addHandler(
el,
("update:" + (camelize(name))),
genAssignmentCode(value, "$event")
);
}
}
if (isProp || platformMustUseProp(el.tag, el.attrsMap.type, name)) {
addProp(el, name, value);
} else {
addAttr(el, name, value);
}
} else if (onRE.test(name)) { // v-on
name = name.replace(onRE, '');
addHandler(el, name, value, modifiers, false, warn$2);
} else { // normal directives
name = name.replace(dirRE, '');
// parse arg
var argMatch = name.match(argRE);
var arg = argMatch && argMatch[1];
if (arg) {
name = name.slice(0, -(arg.length + 1));
}
addDirective(el, name, rawName, value, arg, modifiers);
if ("development" !== 'production' && name === 'model') {
checkForAliasModel(el, value);
}
}
} else {
{
var expression = parseText(value, delimiters);
if (expression) {
/* warn */
}
}
// 添加了个attrs属性
addAttr(el, name, JSON.stringify(value));
}
}
}

  到此,element的属性如图所示:,attrs和attrsList是一样的,都是一个数组,包含一个对象,值为{name:id,value:app}。

  这函数有点长。

  

.7-Vue源码之AST(3)的更多相关文章

  1. 大白话Vue源码系列(03):生成AST

    阅读目录 AST 节点定义 标签的正则匹配 解析用到的工具方法 解析开始标签 解析结束标签 解析文本 解析整块 HTML 模板 未提及的细节 本篇探讨 Vue 根据 html 模板片段构建出 AST ...

  2. [Vue源码]一起来学Vue模板编译原理(一)-Template生成AST

    本文我们一起通过学习Vue模板编译原理(一)-Template生成AST来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫持和发布订阅 一起来学Vu ...

  3. [Vue源码]一起来学Vue模板编译原理(二)-AST生成Render字符串

    本文我们一起通过学习Vue模板编译原理(二)-AST生成Render字符串来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫持和发布订阅 一起来学V ...

  4. Vue源码后记-其余内置指令(3)

    其实吧,写这些后记我才真正了解到vue源码的精髓,之前的跑源码跟闹着玩一样. go! 之前将AST转换成了render函数,跳出来后,由于仍是字符串,所以调用了makeFunction将其转换成了真正 ...

  5. 大白话Vue源码系列(02):编译器初探

    阅读目录 编译器代码藏在哪 Vue.prototype.$mount 构建 AST 的一般过程 Vue 构建的 AST 题接上文,上回书说到,Vue 的编译器模块相对独立且简单,那咱们就从这块入手,先 ...

  6. 大白话Vue源码系列(03):生成render函数

    阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...

  7. 大白话Vue源码系列(04):生成render函数

    阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...

  8. 大白话Vue源码系列(05):运行时鸟瞰图

    阅读目录 Vue 实例的生命周期 实例创建 响应的数据绑定 挂载到 DOM 节点 结论 研究 runtime 一边 Vue 一边源码 初看 Vue 是 Vue 源码是源码 再看 Vue 不是 Vue ...

  9. 入口文件开始,分析Vue源码实现

    Why? 网上现有的Vue源码解析文章一搜一大批,但是为什么我还要去做这样的事情呢?因为觉得纸上得来终觉浅,绝知此事要躬行. 然后平时的项目也主要是Vue,在使用Vue的过程中,也对其一些约定产生了一 ...

  10. 入口开始,解读Vue源码(一)-- 造物创世

    Why? 网上现有的Vue源码解析文章一搜一大批,但是为什么我还要去做这样的事情呢?因为觉得纸上得来终觉浅,绝知此事要躬行. 然后平时的项目也主要是Vue,在使用Vue的过程中,也对其一些约定产生了一 ...

随机推荐

  1. 【机器学习实战】Machine Learning in Action 代码 视频 项目案例

    MachineLearning 欢迎任何人参与和完善:一个人可以走的很快,但是一群人却可以走的更远 Machine Learning in Action (机器学习实战) | ApacheCN(apa ...

  2. 记一次 node.js 的populate用法

    最近在学习node.js做一个简单的博客的网站,用的express框架和mongodb的数据库.之前没有接触过这个数据库,所有在一开始写的时候遇到了一些问题,如何初始化model类型,又如何实现简单的 ...

  3. merge 语法解析

    merge into 支持sqlserver 2008 和以上的版本 无论是INSERT还是UPDATE,从执行之间上看,MERGE INTO(MERGE)都要比直接INSERT/UPDATE的效率高 ...

  4. global,local,static的区别

    1.在函数内部使用global关键字定义的变量可以成为全局变量,如果该变量已经被定义了,那么他的值就是原来的值,否则就是一个新的全局变量(一句话:已存在就不再创建): <?php $a=1; f ...

  5. 01.python基础知识_01

    一.编译型语言和解释型语言的区别是什么? 1.编译型语言将源程序全部编译成机器码,并把结果保存为二进制文件.运行时,直接使用编译好的文件即可 2.解释型语言只在执行程序时,才一条一条的解释成机器语言给 ...

  6. Oracle第一波

    html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...

  7. 126邮箱发送邮件python实现

    126邮箱发送邮件python实现 from email.mime.text import MIMEText from email.utils import formataddr import smt ...

  8. C#钩子类 几乎捕获键盘鼠标所有事件

    using System; using System.Text; using System.Runtime.InteropServices; using System.Reflection; usin ...

  9. django celery的分布式异步之路(二) 高并发

    当你跑通了前面一个demo,博客地址:http://www.cnblogs.com/kangoroo/p/7299920.html,那么你的分布式异步之旅已经起步了. 性能和稳定性是web服务的核心评 ...

  10. Microsoft Offce 使用纪事:oneNote笔记本分区删除

    OneNote 笔记本和分区删除 OneNote 目前无法在客户端和本地删除已有的笔记本和分区,只能通过OneDrive才能够从云端删除: step1 step2 step3 后记 由于需要登录One ...