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

之前看了大佬的Vue源码的0.1版本的解读,前文分析 这里回来继续进行分析,今天主要分析的是Compile、事件怎么进行处理,怎么进行更新?

  1. Compile,上文提到了解析页面上一些数据和指令,今天我们来细细的看一下怎么实现?

    其实里面涉及的东西还很多的,我们需要了解其中一些啥?可能涉及DOM的一些知识。我们一步步配合着断点来进行解析,部分解析内容直接写在下面的代码里面了。

    总结几点:指令解析(正则)、文本解析(nodeType)、元素解析(nodeType)

    知识点:nodeTypechildNodes

    // 指令解析器
    // 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355