之前看了大佬的Vue源码的0.1版本的解读,前文分析 这里回来继续进行分析,今天主要分析的是Compile、事件怎么进行处理,怎么进行更新?
-
Compile,上文提到了解析页面上一些数据和指令,今天我们来细细的看一下怎么实现?
其实里面涉及的东西还很多的,我们需要了解其中一些啥?可能涉及DOM的一些知识。我们一步步配合着断点来进行解析,部分解析内容直接写在下面的代码里面了。
总结几点:指令解析(正则)、文本解析(nodeType)、元素解析(nodeType)
知识点:nodeType、childNodes
image-20210509215905529 image-20210509221504589// 指令解析器 // 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) { // 拿到元素的属性,这里有啥用呢?请看下图1.1 拿到的是这个节点的属性 const attrs = el.attributes let name // 属性进行遍历 然后做了三个判断,分别对监听、绑定、model进行解析,然后分别放入addDir里面, // 里面的逻辑基本就是很简单了,我们通过断点进行看一下,下图1.2 当然整个过程是递归的过程,一直往下查找自己的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) { } },
-
是不是好奇事件怎么进行处理?
上面代码里面已经解析了部分,其实一开始我也不知道,寻思着全局搜一下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(){ }
-
如何进行数据更新
上文中提到了如何让数据进行了搜集,然后现在看一下怎么进行依赖的更新
image-202105101050320081. 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 },
网友评论