Vuejs 核心代码解析之编译阶段

发布于 2021-11-05 12:50:48 字数 8288 浏览 1276 评论 0

接着第一节,这节我们来看下,编译阶段的 dom,文本节点处理,我归为编译的第一部分,现在看下面完整例子:

html

<div id="output">
        <input  type="text" v-model="show">
        {{show}}
</div>

javascript

var vm = new Vue({
            data:{
                show:'init1212'
            }
         });

vm.$mount('#output');

上节说了,new Vue 的初始化,这节来说下,$mount 方法的第一部分,为方便阅读,代码已经进行过删减。

现在来看下 $mount 方法,它为 api 中一个方法较复杂。

当然可以在 new 时直接el指定 controller,但没有 $mount 直观。

下面为 $mount:删减后

exports.$mount = function (el) {

  el = document.createElement('div')

  this._compile(el)

  this._isCompiled = true

  return this

}

这里没是好解释的,源码加入很多生命周期回调。

这里直接来说 _compile:


exports._compile = function (el) {

  var original = el

    this._initElement(el)

    this._unlinkFn = compile(el, options)(this, el)
    
    return el

}

这里主要的为基本所有编译都在 compile(el, options)(this, el) 这个高阶函数中。

_initElement 这个主要就是把el 赋给vm,类,

 vm.$el = el

这节我们来主要说的就是 compile() 第一个fn执行阶段。

首先进入 compile:


function compile (el, options, partial, transcluded) {

  //对当前dom进行编译
  var nodeLinkFn = compileNode(el, options)

  //是否存在子节点,对子节点进行编译
  var childLinkFn =
    !nodeLinkFn  &&
    el.tagName !== 'SCRIPT' &&
    el.hasChildNodes()
      ? compileNodeList(el.childNodes, options)
      : null


//下一节要讲的执行阶段 
  function compositeLinkFn (vm, el) {
    // save original directive count before linking
    // so we can capture the directives created during a
    // partial compilation.
    var originalDirCount = vm._directives.length

    // cache childNodes before linking parent, fix #657
    var childNodes = _.toArray(el.childNodes)
    // if this is a transcluded compile, linkers need to be
    // called in source scope, and the host needs to be
    // passed down.
    var source = vm
    var host = transcluded ? vm : undefined
    // link
    if (nodeLinkFn) nodeLinkFn(source, el, host)
    if (childLinkFn) childLinkFn(source, childNodes, host)

    var selfDirs = vm._directives.slice(originalDirCount)


    /**
     * The linker function returns an unlink function that
     * tearsdown all directives instances generated during
     * the process.
     *
     * @param {Boolean} destroying
     */
    return function unlink (destroying) {
      teardownDirs(vm, selfDirs, destroying)
    }
  }

  return compositeLinkFn

}

刚开始读的感觉越陷越深,当返回时已经不记得之前的了。(哈哈,我脑内存太低)

后来感觉还是从整体上来分析叫好,首先我们来看下,涉及到几个重要Fn,

1.compileNode

根据节点 type,执行 3 - compileElement 或 4 - compileTextNode

function compileNode (node) {
  var type = node.nodeType
  if (type === 1 && node.tagName !== 'SCRIPT') {
    return compileElement(node, options)
  } else if (type === 3 && node.data.trim()) {
    return compileTextNode(node, options)
  } else {
    return null
  }
}

2.compileNodeList

遍历子节点,递归节点,进行编译


function compileNodeList (nodeList, options) {
  var linkFns = []
  var nodeLinkFn, childLinkFn, node
  for (var i = 0, l = nodeList.length; i < l; i++) {
    node = nodeList[i]
    nodeLinkFn = compileNode(node, options)
    childLinkFn =
      !(nodeLinkFn && nodeLinkFn.terminal) &&
      node.tagName !== 'SCRIPT' &&
      node.hasChildNodes()
        ? compileNodeList(node.childNodes, options)
        : null
    linkFns.push(nodeLinkFn, childLinkFn)
  }
  return linkFns.length
    ? makeChildLinkFn(linkFns)
    : null
}

3.compileElement

目前这个例子只针对普通指令不包括 v-if v-repeat component,直接进行编译 5 - compileDirectives

删减后:

function compileElement (el, options) {
  var linkFn
  var hasAttrs = el.hasAttributes()

  // normal directives
  if (!linkFn && hasAttrs) {
    linkFn = compileDirectives(el, options)
  }

  return linkFn
}

4.compileTextNode

编译文本节点,返回nodeLinkFn


function compileTextNode (node, options) {

  var tokens = textParser.parse(node.data)
  if (!tokens) {
    return null
  }
  var frag = document.createDocumentFragment()
  var el, token
  for (var i = 0, l = tokens.length; i < l; i++) {
    token = tokens[i]
    el = token.tag
      ? processTextToken(token, options)
      : document.createTextNode(token.value)
    frag.appendChild(el)
  }
  return makeTextNodeLinkFn(tokens, frag, options)
}

上面代码中:

1- tokens 根据 prefix 返回一个 array,最上面文本处理后:

{
  html: false
  oneTime: false
  tag: true
  value: "show"
}

2- processTextToken 单个文本标记过程,token 添加指令依赖,以及表达式


5.compileDirectives

function compileDirectives (elOrAttrs, options) {
  var attrs = elOrAttrs.attributes
  var i = attrs.length
  var dirs = []
  var attr, name, value, dirName, dirDef
  while (i--) {
    attr = attrs[i]
    name = attr.name
    value = attr.value
    if (value === null) continue
    if (name.indexOf(config.prefix) === 0) {
      dirName = name.slice(config.prefix.length)
      dirDef = options.directives[dirName]
      _.assertAsset(dirDef, 'directive', dirName)
      if (dirDef) {
        dirs.push({
          name: dirName,
          descriptors: dirParser.parse(value),
          def: dirDef
        })
      }
    }
  }
  // sort by priority, LOW to HIGH
  if (dirs.length) {
    dirs.sort(directiveComparator)
    return makeNodeLinkFn(dirs)
  }
}

很明显这里是编译指令操作,主要是生成类似:

{
  //指令名: modal
  name: dirName,
     
  //相关表达式
  descriptors: [{
    expression: "show"
    raw: "show"
  }],
     
  //指令依赖的属性,和方法进行绑定
  def:{
    bind: () {}
    handlers:[]
    priority: 800
    twoWay: true
  }
}

这里基本所有执行都是返回Fn.

Fn 插入数组,等待执行阶段执行,遍历绑定事件,或触发访问器。

上面介绍几个函数,有部分解析过程叫复杂可以自己看一下,类似正则解析 {{ }} {} 等。

当然,我们上面的例子没有表达式,以后会探讨。

现在,根据上面的例子走下流程。

html

<div id="output">
        <input  type="text" v-model="show">
        {{show}}
</div>

首先,最外层也就是 id 为 output 的。这里没有指令所以:

后头看 comple fn:

//这句返回的为undefined,这句暂时不用管
var nodeLinkFn = compileNode(el, options)

直接:

//是否存在子节点,对子节点进行编译
  var childLinkFn =
    !nodeLinkFn  &&
    el.tagName !== 'SCRIPT' &&
    el.hasChildNodes()
      ? compileNodeList(el.childNodes, options)
      : null

很明显为 true,执行 compileNodeList 可以结合上面介绍的看下大概意思:

遍历所有节点,寻找文本或元素,在进行 compileTextNode || compileElement

最后返回: makeChildLinkFn()

function makeChildLinkFn (linkFns) {
  return function childLinkFn (vm, nodes, host) {
    var node, nodeLinkFn, childrenLinkFn
    for (var i = 0, n = 0, l = linkFns.length; i < l; n++) {
      node = nodes[n]
      nodeLinkFn = linkFns[i++]
      childrenLinkFn = linkFns[i++]
      // cache childNodes before linking parent, fix #657
      var childNodes = _.toArray(node.childNodes)
      if (nodeLinkFn) {
        nodeLinkFn(vm, node, host)
      }
      if (childrenLinkFn) {
        childrenLinkFn(vm, childNodes, host)
      }
    }
  }
}

这个和执行阶段关联,

返回的 childLinkFn 就是 compile 的 childLinkFn。

当执行 compile 返回fn时,就是进行编译绑定等的操作。(下节讲)

有叙述错误的地方,请指点。

基本概念讲完,放出精简后vue源码,方便阅读。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

青萝楚歌

暂无简介

0 文章
0 评论
502 人气
更多

推荐作者

醉城メ夜风

文章 0 评论 0

远昼

文章 0 评论 0

平生欢

文章 0 评论 0

微凉

文章 0 评论 0

Honwey

文章 0 评论 0

qq_ikhFfg

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文