Vue源码解析之指令和生命周期

86 阅读3分钟

指令实现过程

class Compile {
      constructor(el, vue) {
        this.$vue = vue //vue实例
        //挂载点
        this.$el = document.querySelector(el)
        if(this.$el) {
          // 让节点变为Fragment
          let $fragment = this.node2Fragment(this.$el)
          // 编译
          this.compile($fragment)
          // 显示
          this.$el.appendChild($fragment)
        }
      }
      node2Fragment(el) {
        console.log('node2Fragment',el)
        var fragment = document.createDocumentFragment();
        var child;
        while(child = el.firstChild) {
          fragment.appendChild(child)
        }
        return fragment
      }
      compile(el) {
        console.log('compile',el)
        var childNodes = el.childNodes;
        var self = this
        var reg = /\{\{(.*)\}\}/;
        childNodes.forEach(node=>{
          var text = node.textContent
          if(node.nodeType == 1) {
            self.compileElement(node)
          }if(node.nodeType == 3&&reg.test(text)) {
            let name = text.match(reg)[1]
            self.compileText(node, name)
            // console.log(text)
            // self.compileElement(node)
          }
          
        })
      }
      compileElement(node) {
        var nodeAttrs = node.attributes;
        console.log('nodeAttrs',nodeAttrs);
        // 类数组变成数组
        // Array.prototype
        [].slice.call(nodeAttrs).forEach(attr=>{
          var attrName = attr.name
          var value = attr.value
          //指令v-
          var dir = attrName.substring(2)
          if(attrName.indexOf('v-')== 0) {
            console.log('dir',dir,value)
            if(dir == 'model') {
              // TODO
              new Watcher(self.$vue,value,value=>{
                node.value = value
              })
              var v = self.getVueVal(self.$vue,value)
              node.value = v
              node.addEventListener('input',e=>{
                var newVal = e.target.value
                setVueVal(self.value,value,newVal)
                v = newVal
              })
            } else if(dir == 'if') {
              
            } else if(dir == 'for') {
              
            }
          }
        })
      }
      compileText(node, name) {
        node.textContent = this.getVueVal(this.$vue, name)
        new Watcher(this.$vue,name,value=>{
          node.textContent = value
        })
      }
      getVueVal(vue,exp) {
        var val = vue
        exp = exp.split('.')
        exp.forEach(k=>{
          val = val[k]
        })
        return val
      }

      setVueVal(vue,exp,value) {
        var val = vue
        exp = exp.split('.')
        exp.forEach((k,i)=>{
          if(i<exp.length-1){
            val = val[k]
          } else {
            val[k] = value
          }
        })
      }
    }


    class Vue {
      constructor(options) {
        this.$options = options || {}
        this._data = options.data || undefined
        
        // TODO 响应式数据代码
        // observe(this._data)
        // 默认数据变为响应式,这里就是生命周期
        this._initData()
        this._initWatch()
        // this._initComputed()
        new Compile(options.el, this)
      }

      _initData() {
        var self = this;
        Object.keys(this._data).forEach(key=>{
          Object.defineProperty(self, key, {
            get(){
              return self._data[key]
            },
            set(newVal) {
              self._data[key] = newVal
            }
          })
        })
      }
      _initWatch() {
        var self = this
        var watch = this.$options.watch
        Object.keys(watch).forEach(key=>{
          // TODO
          new Watcher(self, key, watch[key])
        })
      }
    }



    new Vue({
      el: '#app',
      data: {
        a: 10
      },
      watch:{

      }
    })

   

生命周期

image.png

image.png

Vue2.0的生命周期钩子一共有10个

生命周期钩子说明
beforCreate实例初始化完成。最初调用触发,data和events都不能用。可以在这里处理整个系统加载的Loading;
created实例化已经完成之后被调用,数据观测,属性和方法的运算,watch/event事件回调已完成,挂载还没还没开始 $el属性上还没有。
beforeMount开始挂载之前调用,相关的render函数首次被调用 此时的DOM是数据挂载前的DOM,数据还未挂载
mounted数据已挂载后调用 初始化的el被vm.$el替换,并挂载到实例上去之后的钩子
beforeUpdate数据更新时调用,发生在虚拟DOM重新渲染和打补丁之前
updated在数据驱动下导致的虚拟DOM重新渲染和打补丁之后调用,此时,组件DOM已经更新
activatedkeep-alive 组件激活时调用 如果你使用keep-alive进行缓存, 又希望每次切换组件的时候更新数据,那么更新数据的请求方式必须写在该钩子函数里
deactivatedkeep-alive 组件停用时调用
beforeDestroy实例销毁之前调用,此时实例还可用
destroyed实例销毁后调用,实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁

beforeCreate -> created

  • 初始化vue实例,进行数据观测

created

  • 完成数据观测,属性与方法的运算,watchevent事件回调的配置
  • 可调用methods中的方法,访问和修改data数据触发响应式渲染dom,可通过computedwatch完成数据计算
  • 此时vm.$el 并没有被创建

created -> beforeMount

  • 判断是否存在el选项,若不存在则停止编译,直到调用vm.$mount(el)才会继续编译
  • 优先级:render > template > outerHTML
  • vm.el获取到的是挂载DOM

beforeMount

  • 在此阶段可获取到vm.el
  • 此阶段vm.el虽已完成DOM初始化,但并未挂载在el选项上

beforeMount -> mounted

  • 此阶段vm.el完成挂载,vm.$el生成的DOM替换了el选项所对应的DOM

mounted

  • vm.el已完成DOM的挂载与渲染,此刻打印vm.$el,发现之前的挂载点及内容已被替换成新的DOM

beforeUpdate

  • 更新的数据必须是被渲染在模板上的(eltemplaterender之一)
  • 此时view层还未更新
  • 若在beforeUpdate中再次修改数据,不会再次触发更新方法

updated

  • 完成view层的更新
  • 若在updated中再次修改数据,会再次触发更新方法(beforeUpdateupdated

beforeDestroy

  • 实例被销毁前调用,此时实例属性与方法仍可访问

destroyed

  • 完全销毁一个实例。可清理它与其它实例的连接,解绑它的全部指令及事件监听器
  • 并不能清除DOM,仅仅销毁实例

Vue的挂载过程中发生了什么

  • new Vue的时候调用会调用_init方法

    • 定义 $set $get 、$delete$watch 等方法
    • 定义 $on$off$emit$off 等事件
    • 定义 _update$forceUpdate$destroy生命周期
  • 调用$mount进行页面的挂载

  • 挂载的时候主要是通过mountComponent方法

  • 定义updateComponent更新函数

  • 执行render生成虚拟DOM

  • _update将虚拟DOM生成真实DOM结构,并且渲染到页面中