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

453 阅读4分钟

之前看了大佬的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) {
          // 拿到元素的属性,这里有啥用呢?请看下图第一张 拿到的是这个节点的属性
          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) {
  
          }
      },

1.1

image.png

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

image.png