FED

©FrontEndDev.org
2015 - 2024
web@2.22.0 api@2.20.0

Vuejs核心代码解析之编译阶段1(二)

接着第一节,这节我们来看下,编译阶段的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源码,方便阅读。thanks