Vue设计原理2

MVVM框架的原理及代码实现

  • MVVM框架
    • 数据响应式
      • 涉及到的类的介绍
        • KVue :框架构造函数
        • Observer:执行数据响应(分辨数据是对象还是数组)
        • Complie:模板编译,初始化视图,收集依赖(更新函数,watcher创建)
      • 依赖收集
        • Watcher:执行更新函数
        • Dep:管理多个watcher,批量更新
        • 依赖收集,创建Dep实例
    • 写在最后

MVVM框架

数据响应式

首先原理的分析:
1.new Vue()首先执行初始化,对data执行响应化处理,这个过程发生在Observer中
2.同时对模板进行编译,找到其中动态绑定的数据,从data中获取并初始化视图,这个发生在Compile中
3.同时定义一个更新函数和watcher,将来会对应数据变化时wather会调用更新函数。
4.由于data中的某个key在一个视图中可能出现多次,所以每个key都需要一个管家Dep来管理多个watcher
5.将来data中的数据发生变化时,首先会找到对应的Dep,通知所有的watcher执行更新函数。
Vue设计原理2_第1张图片

涉及到的类的介绍

KVue :框架构造函数

框架构造函数:执行初始化
执行初始化,对data执行响应化处理,Kvue.js

// KVue:解析选项,响应式、编译等等
class LVue{
    constructor(options){  //此处获取到的是LVue对象,包括数据和函数
     this.$options = options
     console.log(options)
     this.$data = options.data
     console.log(this.data)
     console.log(this)  //此处的this是KVue的实例

     // 对data选项做响应式处理
     observe(this.$data)
     //代理
     proxy(this)

     new Compile(options.el,this)
     
    }
}

function observe(obj){
   if(typeof obj !=='object'||obj===null){
     return 
   }
   new Observer(obj)
}

class Observer{  //创建响应类,判断时村对象还是数组进而进行不同的更新
   constructor(value){
     this.value = value
     if(Array.isArray(value)){ //如果是数组执行数组的响应式
   
     }else{
         this.walk(value)
     }
   }

   walk (obj) {  //对象响应式,将对象的每一个属性进行响应式处理
    Object.keys(obj).forEach(key=>defineReactive(obj,key,obj[key]))
   }
}

通过proxy为data做代理

// 代理函数:可以将$data代理到KVue的实例
// vm是KVue实例
function proxy(vm){
   Object.keys(vm.$data).forEach(key=>{
       //此处为当前的实例做代理,建立data与key对应的关系
    Object.defineProperty(vm,key,{
        get(){
            return vm.$data[key]
        },
        set(newVal){
          if(newVal){
            vm.$data[key] = newVal
          }
        }
    })
   })
}

关于proxy代理的问题我之前也一直不理解具体是干嘛的,查了一些资料,发现有一篇文章介绍的很白话,通俗易懂,附上地址链接: Proxy 对象是做什么用的?.

Observer:执行数据响应(分辨数据是对象还是数组)

// Observer: 辨别当前对象类型是纯对象还是数组,从而做不同响应式操作
class Observer {
  constructor(value) {
    this.value = value
    // 辨别类型
    if (Array.isArray(value)) {  //数组
      // todo
    } else {  //对象
      this.walk(value)
    }
  }
  walk(obj) {
    // 对象响应式
    Object.keys(obj).forEach(key => defineReactive(obj, key, obj[key]))
  }
}

Complie:模板编译,初始化视图,收集依赖(更新函数,watcher创建)

编译模板中vue模板的特殊语法,初始化视图,更新视图。
获取DOM->遍历子元素->编译节点/编译文本->对于节点遍历属性->分辨是事件还是元素

// Compile: 遍历视图模板,解析其中的特殊模板语法为更新函数
// new Compile(el, vm)
class Compile {
  constructor(el, vm) {
    // el:宿主元素选择器
    // vm:KVue的实例
    this.$vm = vm;
    this.$el = document.querySelector(el)

    // 执行编译
    this.compile(this.$el)
  }

  compile(el) {
    // 遍历子元素,判断他们类型并做响应处理
    el.childNodes.forEach(node => {
      // 判断类型
      if (node.nodeType === 1) {
        // 元素节点
        // console.log('编译元素', node.nodeName);
        this.compileElement(node)
      } else if (this.isInter(node)) {
        // 文本节点
        // console.log('文本节点', node.textContent);
        // double kill
        this.compileText(node)
      }
      // 递归子节点
      if (node.childNodes) {
        this.compile(node)
      }
    })
  }

  // 是否插值绑定
  isInter(node) {
    return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
  }

  // 绑定表达式解析
  compileText(node) {
    // 获取匹配表达式 RegExp.$1,比如counter, vm['counter']
    // node.textContent = this.$vm[RegExp.$1]
    this.update(node, RegExp.$1, 'text')
  }

  // 编译元素节点:判断指令和事件
  compileElement(node) {
    let _this = this
    // 获取属性
    const attrs = node.attributes

    Array.from(attrs).forEach(attr => {
      // k-text="counter"
      // attr是一个对象{name:'k-text', value: 'counter'}
      const { name, value } = attr
      // 判断是否是指令
      if (name.indexOf('k-') === 0) {
        // 截取指令
        const dir = name.substring(2)
        // 指令指令
        this[dir] && this[dir](node, value)
      }
      // 判断是否是事件 @
      else if (name.indexOf('@') === 0) {
        node.onclick = (function(){
          const handle = name.substring(1)
          //此处不知道如何获取绑定的函数名
           _this.$vm.$options.methods.handleclick()
          
        })()        
       console.log(node)
        // var attrVal = node.getAttribute('@')
        // console.log(attrVal)
      }
    })
  }

依赖收集

视图中会用到data中某key,这成为依赖。同一个key可能出现多次,每次都需要一个watcher来维护他们,这就是依赖收集。
每一个key对应一个Dep,但是会出现一个key的数据有多个用到的地方,所以就会有多个watcher需要一个Dep来管理,需要更新时由Dep同意通知。
根据案例,整理watcher与Dep直接按的关系:

new Vue({
    template:
    `  

{{name1}}

{{name2}}

{{name3}}

`
, data:{ name1:'aaaa', name2:'bbbbb' } })

Vue设计原理2_第2张图片
具体实现思路:
1.defineReactive中为每一个key值创建一个Dep实例
2.初始化视图的时候,读取某个key值,如name1,创建一个Watcher1
3.由于触发name1的getter方法,便将Watcher1添加到name1对应的Dep中
4.当name1更新,setter触发时,便可通过对应Dep通知其管理所有的Watcher更新

Watcher:执行更新函数

// Watcher: 管理依赖,执行更新
// const watchers = []
class Watcher {
  // vm是KVue实例
  // key是data中对应的key名称
  // fn是更新函数,他知道怎么更新dom
  constructor(vm, key, fn) {
    this.vm = vm
    this.key = key
    this.fn = fn

    // watchers.push(this)

    // 建立dep和watcher之间的关系
    Dep.target = this
    this.vm[this.key] // 读一下key的值触发其getter
    Dep.target = null
  }

Dep:管理多个watcher,批量更新

// Dep: 管理多个watcher实例,当对应key发生变化时,通知他们更新
class Dep {
  constructor() {
    this.deps = []
  }
  addDep(dep) {
    // 添加订阅者,dep就是watcher实例
    this.deps.push(dep)
  }
  // 通知更新
  notify() {
    this.deps.forEach(w => w.update())
  }
}

依赖收集,创建Dep实例

function defineReactive(obj, key, val) {

  // val可能还是对象,此时我们需要递归
  observe(val)
  // 创建Dep实例,他和key一对一对应关系
  const dep = new Dep()
  // 参数3是描述对象
  Object.defineProperty(obj, key, {
    get() {
      // console.log('get', key);
      // 依赖收集:Dep.target就是当前新创建Watcher实例
      Dep.target && dep.addDep(Dep.target)
      return val
    },
    set(newVal) {
      if (newVal !== val) {
        console.log('set', key);
        // 防止newVal是对象,提前做一次observe
        observe(newVal)
        val = newVal
        // 通知更新
        dep.notify()
      }
    }
  })
}

写在最后

这里的watcher与Dep的关系,是vue1中用到的,就是每一个变量都会对应一个Watcher,而Dep与Watcher的对应关系也是一对多。但在vue2进行了改进,Watcher对应的是一个组件,而不是一个变量,跟Dep之间的关系也是多个Dep对应多个Watcher,后面整理好了再分享。

你可能感兴趣的:(vue原理)