阅读 611

text-parser | 每天读一点Vue源码

前言

面试的时候经常被问一些Vue源码相关的问题,通常情况下, 我会在面试前恶补掘金上的面筋来对付面试,什么双向绑定的原理呀,什么虚拟dom树呀,实际上我压根儿就没仔细研究过,其一是自己真的比较菜,其二工作上也用不上,别自己给自己添堵。但后面想一下,很多事情,为之则易,不为则难,给自己设立困难(负重)才能进步,决定每天多一点Vue的源码,在Vue的源码选择上,我选择了最老的版本(0.1)😬(真的怕自己看起来吃力), 阅读的模式为通读,从易到难一个文件一个文件的看,看完一个文件后再看它的单元测试,等完全吃透后复制粘贴代码到本地运行测试用例为代码块写一些中文注释,打上tag推到自己的仓库,开始梳理写文章总结(之前有犹豫过是否应该在掘金上写文章,因为这类Vue源码解析的文章已经很多了,而且还写的很好,我再写一遍是否还存在意义,后面想还是写吧,流水总结也不错💧)。

正文

这是我发的第三篇关于Vue源码的文章, 本文介绍text-parser(文本解析), text-parser在Vue里用于html标签中属性的解析和标签里innerText的解析,比如:

<span class="primary {{test}}">message: {{hello}} {{world | toCapital}}</span>
复制代码

text-parser解析文本函数 TextParser.parse("message: {{hello}} {{world | toCapital}}"),解析属性函数TextParser.parseAttr("primary {{test}}")

细节过程

text-parser实现思路大致分这几步:

  1. 通过正则表达式匹配Mustache(双大括号)
// mustache正则, 匹配 {{xxx}} 或 {{{xxxxx}}}
function buildInterpolationRegex() {
    var open = escapeRegex(openChar),
        end = escapeRegex(endChar)
    // 正则为: /{{{?(.+?)}?}}/
    return new RegExp(open + open + open + '?(.+?)' + end + '?' + end + end)
}
复制代码
  1. 通过parser函数,利用上面大正则循环匹配,分割文本,返回数组。比如 输入: "abc xb {{a | filterFn}} xbc {{{b}}}" => 输出: ['abc xb ', {key: 'a | filterFn'}, ' xbc ', {key: 'b', html: true}]
function parse(text) {
    if (!exports.Regex.test(text)) return null
    var m, i, token, match, tokens = []
    while (m = text.match(exports.Regex)) {
        i = m.index // 当前匹配到的位置
        if (i > 0) tokens.push(text.slice(0, i)) // 这是第一类 plain string
        token = { key: m[1].trim() } // 第二类 捕获组里面的, 即$1
        match = m[0] // 这个匹配的字符, 即$&
        token.html = match.charAt(2) === openChar &&
            match.charAt(match.length - 3) === endChar// 第三类 匹配 {{{x}}}
        tokens.push(token)
        text = text.slice(i + match.length) // 匹配下一段文本,类似于移动光标    
    }
    if (text.length) tokens.push(text) // 文本结尾
    return tokens
}
复制代码
  1. 再通过inlineFilters把上面返回的数组中的{key: 'a | filterFn'}进一步解析成this.$compiler.getOption("filters", "filterFn").call(this,a), inlineFilters里面调用Directive.parse, Directive.parse里面调用了Directive.inlineFilters,在Directive.inlineFilters里面拼接好this.$compiler.getOption("filters", "filterFn").call(this,a), Directive.parse的代码太长了,就不贴到文章了,可以点击链接查看,下面贴Directive.inlineFilters的实现:
Directive.inlineFilters = function (key, filters) {
    var args, filter
    for (var i = 0, l = filters.length; i < l; i++) {
        filter = filters[i]
        args = filter.args
            ? ',"' + filter.args.map(escapeQuote).join('","') + '"'
            : ''
        key = 'this.$compiler.getOption("filters", "' +
            filter.name +
            '").call(this,' +
            key + args +
            ')'
    }
    return key
}

复制代码

整体过程

以 {{a | filterFn}}为例,说下上面调用整体过程:

// 伪代码
TextParser.parseAttr = function(str){
    TextParser.parse(str)
    .map(
        (item) => Directive.parse(item)
            .map(
                (item) => Directive.inlineFilters(item.key, item.filters)
            )
    )
}
复制代码
  1. 通过TextParser.parse 输入:{{a | filterFn}} 输出: [{key: 'a | filterFn'}]

  2. 调用Directive.parse 输入:a | filterFn, 输出: [{key: a, filters: [{name: 'filterFn', args:[]}]}]

  3. 调用Directive.inlineFilters 输入: a, [{name: 'filterFn', args:[]}]}], 输出: this.$compiler.getOption("filters", "filterFn").call(this,a)

最后

text-parser.js

text-parser.test.js

持续更新...❤️