- 上一节说到,require(‘vue’) 最终返回的是require('vue/src/main.js')里面的module.exports,而在require('vue/src/main.js')内部有这样一句,可以得知,最后返回的是viewModel

-
ViewModel = require('./viewmodel') 如何转为ViewModel =require(‘vue/src/viewmodel.js’)
-
源码没有‘./viewmodel’路径的注册模块,而是通过require.relative来处理
-
由于是在‘vue/src/viewmodel.js’模块中,所以parent路径为‘vue/src/viewmodel.js’,得到的上级路径就是‘vue/src’从而寻找到了‘vue/src/viewmodel.js’ 模块
-
至此 new Vue相当于 new ViewModel
Paste_Image.png
-
ViewModel构造函数,实际上又调用了另一个构造函数 Compiler

- Compiler位于注册模块‘vue/src/compiler.js‘, 这部分代码比较好理解,传ViewModel的实例(this)设置一些状态属性,把options传到Compiler等,但是要注意 utils.processOptions(options)这一句

-
理解utils.processOptions(options)的处理
-
可以看到processOptions对每一个组件、partials(vue2.0已移除)、过滤器、模板进行相应处理
processOptions
-
-
todo 这里可以先记录utils.toContructor()、parseTemplateOption()、checkFilter()方法,以后有时间再深入研究做了什么处理
-
接下来的三句代码中由于现在传给vue的options只有el和data所以并没有什么意义

- 接下来就是初始化元素
// initialize element
var el = compiler.el = compiler.setupElement(options)
// setupElement方法
CompilerProto.setupElement = function (options) {
// create the node first 判断el是不是字符串,如果是则用querySelector去获取,如果不是,则判断是否存在,如果否根据tagName创建一个标签
var el = typeof options.el === 'string'
? document.querySelector(options.el)
: options.el || document.createElement(options.tagName || 'div')
var template = options.template,
child, replacer, i, attr, attrs
// 如果template选项存在则,遍历循环el元素的子元素,并添加到rawContent中,其中由于使用了appendChild方法,每次添加后el.firstchild都会变成下一个子元素
if (template) {
// collect anything already in there
if (el.hasChildNodes()) { // 这里处理时已经将el的子元素append到了this.rawContent上
this.rawContent = document.createElement('div')
/* jshint boss: true */
while (child = el.firstChild) {
this.rawContent.appendChild(child)
}
}
// replace option: use the first node in
// the template directly 这里是判断是否传入了replace属性, 如果为false将模板插入(注意原来el里面的内容不保留),如果为true,默认为true,template内容直接替换
if (options.replace && template.firstChild === template.lastChild) { 处理template只有一个元素并且repalce为true时的情况
replacer = template.firstChild.cloneNode(true)
if (el.parentNode) {
el.parentNode.insertBefore(replacer, el)
el.parentNode.removeChild(el)
}
// copy over attributes
if (el.hasAttributes()) {
i = el.attributes.length
while (i--) {
attr = el.attributes[i]
replacer.setAttribute(attr.name, attr.value)
}
}
// replace
el = replacer
} else {
el.appendChild(template.cloneNode(true))
}
}
// apply element options
if (options.id) el.id = options.id
if (options.className) el.className = options.className
attrs = options.attributes
if (attrs) {
for (attr in attrs) {
el.setAttribute(attr, attrs[attr])
}
}
return el
}


-
经过下列代码的设置,得到了compiler 和 vm上面的属性,其中Compiler实例.vm指向vm, ViewModel的实例.$compiler指向Compiler
Paste_Image.png


- 这里主要获取并设置vm的$root, => todo options.parent在哪里出现


- compiler.setupObserver()

CompilerProto.setupObserver = function () {
var compiler = this,
bindings = compiler.bindings,
options = compiler.options,
observer = compiler.observer = new Emitter(compiler.vm)
// a hash to hold event proxies for each root level key
// so they can be referenced and removed later
//用于保存每个根级别密钥的事件代理的哈希以便以后可以引用和删除它们
observer.proxies = {}
// add own listeners which trigger binding updates
// 为compiler的binding绑定事件
// compiler.bindings = utils.hash() 追踪utils..hash() 可以找到
/**
* Create a prototype-less object
* which is a better hash/map
* 通过Object.create(null)创建出来的对象是一个无属性对象,可以更好的做一个hash映射
* hash: function () {
* return Object.create(null)
},
*/
observer
.on('get', onGet)
.on('set', onSet)
.on('mutate', onSet)
//注册钩子函数,钩子数组见上图
// register hooks
var i = hooks.length, j, hook, fns
while (i--) {
hook = hooks[i]
fns = options[hook]
if (Array.isArray(fns)) { // 从源码看每个钩子可以挂一个函数也可以挂一个数组
j = fns.length
// since hooks were merged with child at head,
// we loop reversely.
while (j--) {
registerHook(hook, fns[j])
}
} else if (fns) {
registerHook(hook, fns)
}
}
// broadcast attached/detached hooks
observer
.on('hook:attached', function () {
broadcast(1)
})
.on('hook:detached', function () {
broadcast(0)
})
// DepsParser 是通过一个记录触发getterd时自动提取一个计算属性依赖关系的模块
function onGet (key) {
check(key)
DepsParser.catcher.emit('get', bindings[key])
}
function onSet (key, val, mutation) {
observer.emit('change:' + key, val, mutation)
check(key)
bindings[key].update(val)
}
function registerHook (hook, fn) {
observer.on('hook:' + hook, function () {
fn.call(compiler.vm)
})
}
function broadcast (event) {
var children = compiler.children
if (children) {
var child, i = children.length
while (i--) {
child = children[i]
if (child.el.parentNode) {
event = 'hook:' + (event ? 'attached' : 'detached')
child.observer.emit(event)
child.emitter.emit(event)
}
}
}
}
function check (key) {
if (!bindings[key]) {
compiler.createBinding(key) // 下面分析createBinding ,这里先理解为创建一个对象
}
}
}

- 以method:{testFn: function(){}}为例分析compiler.createBinding()

- new Binding(compiler, key, isExp, isFn)


-
compiler.defineVmProp(key, binding, methods[key])
-
里面的def 是Object.defineProperty缩写 Object.defineProperty(obj, prop, descriptor)
Paste_Image.png
-
-
将testFn挂在到vm实例上,此时的实例对象如图,这也就不难解释我们为什么能在实例中直接用this.testFn()调用

-
挂在数据到vm上和$data中
Paste_Image.png
-
开始执行created钩子compiler.execHook('created')
CompilerProto.execHook = function (event) {
event = 'hook:' + event
this.observer.emit(event)
this.emitter.emit(event)
}
- 经过了created的钩子函数用户可能已经更新了数据,所以有下面一句
data = compiler.data = vm.$data
- 用户还可以在vm中设置一些属性,在这种情况下,我们应该将其复制回$data,这里的data与上面的compiler.data = vm.$data均为引用同一个对象的关系,所以改变data,compiler.data,vm.$data也相应更新了
var vmProp
for (key in vm) {
vmProp = vm[key]
if (
key.charAt(0) !== '$' && /
data[key] !== vmProp &&
typeof vmProp !== 'function'
) {
data[key] = vmProp
}
}
-
compiler.observeData(data) 由于涉及到事件问题,这里暂不深入理解
/**
现在我们可以观察数据。
这将将数据属性转换为getter / setter
并发出第一批设置事件
反过来又创建相应的绑定。
**/
- 解析template模板
```js
CompilerProto.resolveContent = function () {
var outlets = slice.call(this.el.getElementsByTagName('content')), // 这里抽离出template中的content,可以将原来el中的内容提出,
// 例如: template: "<div><content></content></div>"
raw = this.rawContent, // 在初始化元素中设置的rawContent,里面包含el原有的元素
outlet, select, i, j, main
i = outlets.length
if (i) {
// first pass, collect corresponding content
// for each outlet.
while (i--) {
outlet = outlets[i]
if (raw) { // 如果el原来存在有子元素
select = outlet.getAttribute('select')
if (select) { // select content
outlet.content =
slice.call(raw.querySelectorAll(select))
} else { // default content
main = outlet
}
} else { // fallback content
outlet.content =
slice.call(outlet.childNodes)
}
}
// second pass, actually insert the contents
for (i = 0, j = outlets.length; i < j; i++) {
outlet = outlets[i]
if (outlet === main) continue
insert(outlet, outlet.content)
}
// finally insert the main content
if (raw && main) {
insert(main, slice.call(raw.childNodes)) //将原来el的子元素插入content的父元素中
}
}
function insert (outlet, contents) {
var parent = outlet.parentNode,
i = 0, j = contents.length
for (; i < j; i++) {
parent.insertBefore(contents[i], outlet)
}
parent.removeChild(outlet)
}
this.rawContent = null
}
-
compiler.compile(el, true) 1096行
- 解析el为元素且不为script的情况
if (nodeType === 1 && node.tagName !== 'SCRIPT') { // a normal node
this.compileElement(node, root)
}
- **this.compileElement(node, root)** // node为el,root = true
```js
// 以<div id="demo">
// <b>{{test}}</b>
// </div> 分析vue是如何编译
CompilerProto.compileElement = function (node, root) {
//textarea很烦人
//因为它的值创建childNodes
//我们不想编译。
if (node.tagName === 'TEXTAREA' && node.value) {
node.value = this.eval(node.value)
}
//只有当这个元素有属性时才编译
//或者其tagName包含一个连字符号(这意味着它可以
//可能是一个自定义元素)
if (node.hasAttributes() || node.tagName.indexOf('-') > -1) {
// skip anything with v-pre
if (utils.attr(node, 'pre') !== null) { // utils将处理v-pre,获取此属性值
return
}
var i, l, j, k
//优先级指令列表
//需要按照特定的顺序进行检查
priorityDirectives = [
'if',
'repeat',
'view',
'component'
]
//检查优先级指令。
//如果其中任何一个存在,它将接管带有一个childVM的节点
//所以我们可以跳过其他
for (i = 0, l = priorityDirectives.length; i < l; i++) {
if (this.checkPriorityDir(priorityDirectives[i], node, root)) {
return
}
}
// 检查是否有transition和animation属性
node.vue_trans = utils.attr(node, 'transition')
node.vue_anim = utils.attr(node, 'animation')
node.vue_effect = this.eval(utils.attr(node, 'effect'))
var prefix = config.prefix + '-',
params = this.options.paramAttributes,
attr, attrname, isDirective, exp, directives, directive, dirname
// v-with 在其他指令中有特别优先权
//在计算的属性被评估之前它需要从父项中提取值,因为在这个阶段
//计算的属性尚未设置它们的依赖关系。
if (root) {
var withExp = utils.attr(node, 'with')
if (withExp) {
directives = this.parseDirective('with', withExp, node, true)
for (j = 0, k = directives.length; j < k; j++) {
this.bindDirective(directives[j], this.parent)
}
}
}
var attrs = slice.call(node.attributes)
for (i = 0, l = attrs.length; i < l; i++) {
attr = attrs[i]
attrname = attr.name
isDirective = false
if (attrname.indexOf(prefix) === 0) { //判断是不是v-的指令
// a directive - split, parse and bind it.
isDirective = true
dirname = attrname.slice(prefix.length)
// build with multiple: true
directives = this.parseDirective(dirname, attr.value, node, true) // 如果是则走指令的解释通道
// loop through clauses (separated by ",")
// inside each attribute
for (j = 0, k = directives.length; j < k; j++) {
this.bindDirective(directives[j])
}
} else if (config.interpolate) {
// 非指令属性,检查插值标签
//TextParser.parseAttr使用可能的插补标签解析属性值
//返回一个指令友好的表达式 e.g. a {{b}} c => "a " + b + " c"
exp = TextParser.parseAttr(attr.value) // attr.value为demo
if (exp) {
directive = this.parseDirective('attr', exp, node)
directive.arg = attrname
if (params && params.indexOf(attrname) > -1) {
// a param attribute... we should use the parent binding
// to avoid circular updates like size={{size}}
this.bindDirective(directive, this.parent)
} else {
this.bindDirective(directive)
}
}
}
if (isDirective && dirname !== 'cloak') {
node.removeAttribute(attrname)
}
}
}
// 递归子元素
if (node.hasChildNodes()) {
slice.call(node.childNodes).forEach(this.compile, this)
}
}
2.解析文本元元素的情况
else if (nodeType === 3 && config.interpolate) {
this.compileTextNode(node)
}
- compileTextNode()
// 以<b>{{test}}</b>中的{{test}}的文本节点为例
CompilerProto.compileTextNode = function (node) {
var tokens = TextParser.parse(node.nodeValue) // node.nodeValue= {{test}},解析后的tokens=[{html:false,key:"test"}]
if (!tokens) return
var el, token, directive
for (var i = 0, l = tokens.length; i < l; i++) {
token = tokens[i] // tokens={html:false,key:"test"}
directive = null
if (token.key) { // a binding
if (token.key.charAt(0) === '>') { // a partial
el = document.createComment('ref')
directive = this.parseDirective('partial', token.key.slice(1), el)
} else {
if (!token.html) { // text binding
el = document.createTextNode('')
directive = this.parseDirective('text', token.key, el) // 返回一个包含bind和update函数的对象
} else { // html binding
el = document.createComment(config.prefix + '-html')
directive = this.parseDirective('html', token.key, el)
}
}
} else { // a plain string
el = document.createTextNode(token)
}
// insert node
node.parentNode.insertBefore(el, node) // 将文本元素插入
// bind directive
this.bindDirective(directive) // 绑定指令。解析如下
}
node.parentNode.removeChild(node)// 删除原来的{{test}}
}
this.bindDirective(directive)
CompilerProto.bindDirective = function (directive, bindingOwner) {
if (!directive) return
// keep track of it so we can unbind() later
this.dirs.push(directive)
// 对于空或者文字指令我们仅仅调用bind方法
if (directive.isEmpty || directive.isLiteral) {
if (directive.bind) directive.bind()
return
}
// otherwise, we got more work to do...
var binding,
compiler = bindingOwner || this,
key = directive.key
if (directive.isExp) {
// expression bindings are always created on current compiler
binding = compiler.createBinding(key, directive)
} else {
//递归地定位哪个编译器拥有绑定
while (compiler) {
if (compiler.hasKey(key)) {
break
} else {
compiler = compiler.parent
}
}
compiler = compiler || this
binding = compiler.bindings[key] || compiler.createBinding(key)
}
binding.dirs.push(directive)
directive.binding = binding
var value = binding.val()
// invoke bind hook if exists
if (directive.bind) {
directive.bind(value)
}
//初始化value值
directive.$update(value, true) // 这里实际上是调用了directives.text的update方法
}
directives.text
directives.text = {
bind: function () {
this.attr = this.el.nodeType === 3
? 'nodeValue'
: 'textContent'
},
update: function (value) {
this.el[this.attr] = utils.guard(value) // 设置nodeValue值可以将文本元素直接设为value值
}
}
网友评论