Vue0.1版本源码解读一下下(2)

之前看了大佬的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) {
            // 拿到元素的属性,这里有啥用呢?请看下图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) {
    
            }
        },
    
    image-20210509215905529
    image-20210509221504589
  1. 是不是好奇事件怎么进行处理?

    上面代码里面已经解析了部分,其实一开始我也不知道,寻思着全局搜一下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(){
    
    }
    
  1. 如何进行数据更新

    上文中提到了如何让数据进行了搜集,然后现在看一下怎么进行依赖的更新

    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
        },
    
    image-20210510105032008

你可能感兴趣的:(Vue0.1版本源码解读一下下(2))