之前看了大佬的Vue源码的0.1版本的解读,前文分析 这里回来继续进行分析,今天主要分析的是Compile、事件怎么进行处理,怎么进行更新?
1. Compile,上文提到了解析页面上一些数据和指令,今天我们来细细的看一下怎么实现?
其实里面涉及的东西还很多的,我们需要了解其中一些啥?可能涉及DOM的一些知识。我们一步步配合着断点来进行解析,部分解析内容直接写在下面的代码里面了。
总结几点:指令解析(正则)、文本解析(nodeType)、元素解析(nodeType)
知识点:nodeType、childNodes
// 指令解析器
// Compile构造函数
export default function Compile(vm) {
this.el = vm.$el
this.vm = vm
// 正则 对下面元素的name进行匹配
this.onRe = /^(v-on:|@)/
this.modelRe = /^v-model/
this.bindRe = /^(v-bind:|:)/
this.braceRe1 = /{{\w+}}/g
this.braceRe2 = /[{}]/g
// 用于存放指令的一些方法
this.dirs = []
// 指令的处理
this.handles = handles
// 初始化
this.init()
}
Compile.prototype = {
init() {
// 解析元素
this.parse(this.el)
//进行渲染
this.render()
},
// 我们一起来看一下parse做了啥?
// 一步步解析,看代码主要打断点。
//首先默认parse传入了el,默认肯定就是挂载的根节点。然后继续往下看
parse(el) {
// 拿到元素的属性,这里有啥用呢?请看下图第一张 拿到的是这个节点的属性
const attrs = el.attributes
let name
// 属性进行遍历 然后做了三个判断,
//分别对监听、绑定、model进行解析,然后分别放入addDir里面,
// 里面的逻辑基本就是很简单了,我们通过断点进行看一下,
//下图第二章 当然整个过程是递归的过程,一直往下查找自己的childNodes,然后进行。
[...attrs].forEach(e => {
if (this.onRe.test(e.name)) {
name = e.name.replace(this.onRe, '')
// 比如我在页面上年龄加了一个click=“”
//然后最终name就是click ,处理函数就是handle.on监听
//e.value就是属性的值 el就是当前元素 ,没事后面我会对这个进行解读
this.addDir(this.handles.on, name, e.name, e.value, el)
} else if (this.bindRe.test(e.name)) {
// 类似:bind="name" 解析完后将原本的值删掉
el.removeAttribute(e.name.split('=')[0])
name = e.name.replace(this.bindRe, '')
this.addDir(this.handles.bind, name, e.name, e.value, el)
} else if (this.modelRe.test(e.name)) {
name = e.name.replace(this.modelRe, '')
this.addDir(this.handles.model, name, e.name, e.value, el)
}
})
// 遍历子节点
const children = el.childNodes
if (children.length > 0) {
// children 主要就分成两块了这里分别处理的是两种类型的节点
// 通过元素的nodeType,具体的链接上面已经给出。
// 是元素节点的继续解析,文本节点的话,则进行判断值,
// 看看其有没有引入变量,有的话就添加到顶层元素的_textNodes
children.forEach(ele => {
switch (ele.nodeType) {
// 元素节点
case 1:
this.parse(ele)
break
// 文本节点
case 3:
if (this.braceRe1.test(ele.nodeValue)) {
this.vm._textNodes.push(ele)
}
break
}
})
}
},
addDir(handle, dirName, name, value, el) {
this.dirs.push({
vm: this.vm,
dirName,
handle,
rawName: name,
expOrFn: value,
el
})
},
// 这里我们继续来看render,这个就是比较简单的了
//主要对handle进行具体实现。然后有一个handle.js我们具体看看。
render() {
const vm = this.vm
const that = this
this.dirs.forEach(e => {
const handle = e.handle
if (handle.implement) {
handle.implement(e.vm, e.el, e.dirName, e.expOrFn)
}
const update = function (newVal, oldVal) {
handle.update(e.vm, e.el, e.expOrFn, newVal, oldVal)
}
// 在这里开始创建观察者实例 将监听的值变化时 触发update回调函数
new Watcher(this.vm, e.expOrFn, update)
})
const handles = this.handles.textNode
vm._textNodes.forEach(e => {
let arry = e.nodeValue.match(this.braceRe1)
let rawValue = e.nodeValue
arry.forEach(str => {
let variable = str.replace(this.braceRe2, '')
handles.implement(vm, e, variable)
const update = function (newVal, oldVal) {
handles.update(vm, newVal, oldVal, e, variable, rawValue, that.braceRe1, that.braceRe2)
}
// 监听文本节点 在这里开始创建观察者实例
//将监听的值变化时 触发update回调函数
new Watcher(vm, variable, update)
})
})
}
}
// 举例来说 参数传入的是name el vm expOrFn 比如页面里面用到了click 就是事件监听嘛,
// 然后这里就是做了就是元素的事件绑定 怎么绑定的呢。简单expOrFn是解析出属性对应的值,
// 然后vm上挂在了方法 然后指向
on: {
implement(vm, el, name, expOrFn) {
el['on' + name] = vm[expOrFn].bind(vm)
},
update(vm, el, expOrFn, newVal, oldVal) {
}
},
2. 是不是好奇事件怎么进行处理?
上面代码里面已经解析了部分,其实一开始我也不知道,寻思着全局搜一下addEventListener,结果没搜到很失望。然后就细细看,这里模拟了一下,这里主要就是用了下面这样的方式。具体的上面已经说了。
on: {
implement(vm, el, name, expOrFn) {
el['on' + name] = vm[expOrFn].bind(vm)
},
update(vm, el, expOrFn, newVal, oldVal) {
}
},
document.querySelector('.test')['onclick'] = function(){
}
document.querySelector('.test').onclick = = function(){
}
3. 如何进行数据更新?
上文中提到了如何让数据进行了搜集,然后现在看一下怎么进行依赖的更新
1. observer 里面在set里面作了一层拦截,数据发生改变的时候,
做出了触发更新 dep.notify()的操作
set(newVal) {
if (val === newVal) {
return
}
val = newVal
// 如果新值是对象 递归监听
if (typeof val === 'object') {
new Observer(val)
}
// 触发更新
dep.notify()
}
2. dep 里面的更新操作主要就是有一个收集依赖的数组,然后对其进行遍历,
里面其实都是观察者的实例, 触发更新函数,下面每一个回调的e即是观察者实例。
执行了update,其实就是执行了下面的run函数。
notify() {
this.subs.forEach(e => {
e.update()
})
}
update() {
this.run()
},
run() {
// 触发更新后执行回调函数
const value = this.get()
const oldValue = this.value
if (value !== oldValue) {
this.cb.call(this.vm, value, oldValue)
}
this.value = value
},