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