mv背景html代码,深入浅出MV*框架源码(三):Moon的html->code实现

前言

MV*框架中模板转化成代码的的过程涉及到编译原理,把这个html->code和第二篇文章的code->html连接起来就是完整的一个流程。

词法分析和语法分析

html其实就是一系列的字符串,这个将字符串转化成可执行代码的过程一般就叫做编译或者转译,如果会转成机器代码就是编译,否则就是转译。

很显然,MV*框架做的事情也是转译。而这个转译又主要分为词法分析和语法分析两个过程。

词法分析

词法分析就是将一个完整的句子分割成各个独立的单元的过程,这些单元被称为tokens。

例如一个表达式是“3+2”,那么tokens就是3、+、2。

同理一段html代码

Some HTML

的tokens就是

、Some HTML、

语法分析

语法分析是在词法分析之后的,它的目的是将这个tokens组织起来变成可执行的结构。

在MV*框架里通常就是将tokens转换成一颗AST(抽象语法树)。

compile的使用

Moon的官网提及了Moon.compile的使用:

Moon.compile("

Some HTML

");

它将会被转成如下code:

function anonymous(m) {

var instance = this;

return m("p", {attrs: {}}, {"shouldRender": false}, [m("#text", {"shouldRender": false}, "Some HTML")]);

}

也就是我们第二篇文章中render函数用到的m函数!之后大家可以自行推测出后续过程了吧~

compile的实现

我们重新new一个Moon实例,打一个断点跟到compile:

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

Moon-compile.jpg

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

compile.jpg

实现很简洁,有没有大吃一惊?

其实这就是一个template通过词法分析器lex生成tokens,tokens又通过语法分析器生成ast,最后通过ast生成node的过程。

lex

lex的实现:

var lex = function(input) {

var state = {

input: input,

current: 0,

tokens: []

}

lexState(state);

return state.tokens;

}

它构造了一个state对象交给lexState分析,分析完后直接拿结果返回。

lexState

它其实也就是对input进行分流处理,有三条分支:不以

也就是说它把tokens分成三种类型:文本、注释、标签。

var lexState = function(state) {

var input = state.input;

var len = input.length;

while (state.current < len) {

// Check if it is text

if (input.charAt(state.current) !== "

lexText(state);

continue;

}

// Check if it is a comment

if (input.substr(state.current, 4) === "就行了。

结果

最初的html:

{{msg}}

{{computeData}}

最后得到这个结果:

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

lex-result.jpg

parse的实现

parse函数的实现同样很简洁:

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

parse.jpg

它建立了一个root对象来存储ast,每个子元素通过parseWalk得出。

parseWalk

parseWalk是用来遍历tokens的:

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

parseWalk-1.jpg

它会构造token、previousToken、nextToken三个指针,然后声明了一个move函数用来移动这三个指针,有点类似链表的操作。

在当前token的类型是文本或者注释的时候,会执行move,只不过一个返回previousToken.value一个返回null,因为文本的前面肯定是标签,而注释就没有处理的意义了。下面进入正题:type是tag的时候:

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

parseWalk-2.jpg

它会先获取tagType、closeStart、closeEnd,还会判断这个标签是不是SVG或者空元素,之后通过createParseNode创建一个parseNode(也就是ast树的一个node),接着又兵分三路,只重点处理非空非svg无closeStart的标签。

这里有个难琢磨的地方就是递归parseWalk生成子节点,这里需要自己多跟一下代码。

createParseNode

其实就是创建一个ast的node对象:

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

createParseNode.jpg

parse结果0.

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

parse-result.jpg

generate的实现

generate一改之前lexer和parse的简洁,变成很难读的样子了:

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

generate.jpg

不过还好,增加点耐心就没事。

可以看出ast这颗树实际上是挂载在root的children上,这里声明了一个state对象,里面包括了attr、directive、dep,接着通过generateNode生成若干个m函数,用dependencies把dep拿出来生成dependenciesCode最终拼接成最终的执行代码,然后返回根据这段代码生成的render函数。

generateNode

这个函数负责生成vnode

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

generateNode1.jpg

可以看出来这里还是兵分三路:

node是string类型

node是slot类型

其他情况

它们分别做了什么呢?第一种情况直接编译模板和meta,第二种情况是对slot进行处理,最后是一般情况对meta、prop、directive、children等进行处理,最终返回生成好的调用代码。

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

generateNode2.jpg

defaultMetadata

它负责生成默认渲染配置的对象:

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

defaultMetadata.jpg

generateProps

generateProps是个大函数,涉及到很多小函数

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

generateProps.jpg

它先获取props,然后把这个props丢进vnode的attrs,接着处理指令。接着开始生成props代码,和指令匹配,如果匹配到就进入beforeGenerate、afterGenerate、duringGenerate里。

beforeGenerate

这个函数是存在于各个内置指令对象里的,除了它之外,还有duringPropGenerate和afterGenerate方法。

这里以m-on为例,它会获取事件名、事件回调,然后编译模板表达式,生成修饰符,最后加上事件监听代码:

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

m-on-beforeGenerate.jpg

compileTemplateExpression

它负责编译模板里的依赖表达式:

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

compileTemplateExpression.jpg

它会获取模板里的dependencies,或者说{{}}里面的表达式。

addEventListenerCodeToVNode

它负责给vnode添加事件监听:

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

addEventListenerCodeToVNode.jpg

它会从vnode.meta获取eventListeners,最终取出eventHandlers给它加入handler来给vnode添加上事件监听。

compileTemplate->compileTemplateState

compileTemplate负责编译一个模板,具体工作交给compileTemplateState做:

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

compileTemplate.jpg

compileTemplateState对模板做了什么呢?它会通过escapeString先进行一些转义工作:

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

escapeString.jpg

如果是{{}}表达式,会通过scanTemplateStateUntil扫描依赖和scanTemplateStateForWhitespace去除空格:

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

compileTemplateState.jpg

这样就得到了{{}}里面的依赖,接着调用compileTemplateExpression编译这个表达式,迭代进行下去这个过程把dependencies收集过来。

scanTemplateStateUntil

这其实就是个扫描器,返回{{}}里面真正的表达式:

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

scanTemplateStateUntil.jpg

scanTemplateStateForWhitespace

它同样是个扫描器,不过它的作用是扫描空格然后略过去:

02f9d058ebd6?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

scanTemplateStateForWhitespace.jpg

generateMeta和generateEventlisteners

它们分别生成meta执行代码和事件执行代码,在generateMeta中对meta里的eventListeners进行特殊处理进到generateEventlisteners里去。

var generateMeta = function(meta) {

var metaCode = "{";

for (var key in meta) {

if (key === "eventListeners") {

metaCode += generateEventlisteners(meta[key])

} else {

metaCode += "\"" + key + "\": " + (meta[key]) + ", ";

}

}

metaCode = metaCode.substring(0, metaCode.length - 2) + "}, ";

return metaCode;

}

var generateEventlisteners = function(eventListeners) {

var eventListenersCode = "\"eventListeners\": {";

for (var type in eventListeners) {

var handlers = eventListeners[type];

eventListenersCode += "\"" + type + "\": [";

for (var i = 0; i < handlers.length; i++) {

eventListenersCode += (handlers[i]) + ", ";

}

eventListenersCode = eventListenersCode.substring(0, eventListenersCode.length - 2) + "], ";

}

eventListenersCode = eventListenersCode.substring(0, eventListenersCode.length - 2) + "}, ";

return eventListenersCode;

}

总结

html->code总体经历了三个阶段:lex语法分析、compile词法分析、generate生成可执行代码,实际过程就是html->tokens->ast->code。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值