• vue响应式原理


    vue作为一个MVVM框架,是如果对数据属性实现响应式的呢?通过深入研究,发现它是通过Object.defineProperty(只支持纯对象)绑定get,set来实现的,下面就来探究一下其中的原理。

    Object.definePropety()

    /**
     * @param {[Object]} obj 目标对象
     * @param {[String]} prop 目标对象的属性
     * @param {[String]} descriptor [属性描述符:数据描述符和存取描述符两种形式]
     */
    var obj = {};
    var descriptor = Object.create(null) // 没有继承的属性
    // 默认没有enumerable,没有configurabel,没有writeable
    descriptor.value  = 'static';
    object.definePorperty(obj, 'key', descriptor);
    
    // 属性描述符
    object.definePorperty(obj, 'key', {
      enumerable: false,  // 定义对象的属性是否是可枚举
      writeable: faslse,  // 定义对象的属性是否可修改
      configurable: fase, // 定义对象属性是否可删除
      value: "static"
    })
    
    // 存取描述符
    var bValue
    object.defineProperty(obj, 'key', {
      get: function() {
        return bValue;
      },
      set: function(newValue) {
        bValue = newValue
      },
      eumerable: true,
      configurable: true
    })
    
    

    vue在denfineProperty的方法上进行进一步的封装

    function defineReactive(data,key,value) {
      Object.defineProerty(data,key,{
        get: function() {
          return value
        },
        set: function(newValue) {
          if(newValue === value) return;
          value = newValue;
        },
        eumerable: true,
        writeable: true
      })
    }
    

    怎么观察Observer

    我们之所以要观察Observer,是为了当数据属性发生变化的时候,通知那些使用到该属性的地方。
    这要我们就可以先把所有使用到该属性依赖全部收集起来,当属性改变的时候,循环通知所有的属性依赖进行更新

    依赖搜集

    在defineReative的基础上进一步实现

    /**
     * [defineReactive description]
     * @param  {[Object]} data  [对象obj]
     * @param  {[String]} key   [对象属性]
     * @param  {[String]} value [对象属性的值]
     * @return {[type]}       [description]
     */
    function defineReactive(data,key,value) {
      let dep = [];
      Object.defineProperty(data,key,{
        eumerable: true,
        configurable: true,
        get: function() {
          dep.push(watcher);
        },
        set: function(newValue) {
          if(value === newValue) return;
          for(let i = 0; i < dep.length; i++ ) {
            watcher.update(newValue, value);
          }
          value = newValue
        }
      })
    }
    
    
    // 进一步把收集依赖的部分封装起来
    class Dep {
      static target: ?Watcher;
      id: Number;
      subs: String<watcher>;
    
      construstor() {
        this.id = uid++;
        this.subs = [];
      }
    
      addSub(sub: Watcher) {
        this.subs.push(sub)
      }
    
      removeSub(sub: Watcher) {
        remove(this.subs, sub)
      }
    
      depend() {
        if(Dep.target == Watcher) {
          this.addSub(Dep.target)
        }
      }
    
      notify() {
        const subs = this.subs.splice();
        for(let i = 0; i < subs.length; i++) {
          subs[i].update();
        }
      }
    }
    
    // 修改后的代码
    function defineReactive(data,key,value) {
      let dep = New Dep();
      Object.defineProperty(data,key, {
        eumerable: true,
        configaruable: true,
        get: function() {
          dep.depend();
          return value;
        },
        set: function(NewValue) {
          if(value === NewValue) return;
          dep.notify();
          value = NewValue;
        }
      })
    }
    
    

    总结: 要观察就要收集依赖到Dep用于专门存储依赖, 上面我们将所有存储到dep数组的叫做watcher。致这些watcher依赖是在vue中的templete,watch,computed中都要是收集的,当数据改变的时候就去通知到这些地方,这样就需要抽象出一个能够处理不同情况的类,然后我们在依赖收集的时候只需要收集这些封装好的类进来,通知也是通知到它一个,然后它负责通知其他地方使用到该属性的地方,这个类我们叫它watcher

    watcher

    class Watcher {
      constructor(expOrFn, callback) {
        this.getter = parsePath(expOrFn);
        this.callback = callback;
        this.value = this.get();
      }
    
      get() {   // 执行get, 将自己注入到dep.target
        Dep.target = this;
        this.getter.call(vm);   // 触发getter,getter里面触发depend, depend执行addSub,将watcher放到dep依赖中
        Dep.tabget = undefined;
      }
    
      update() {
        const oldValue = this.value;
        this.value = this.get();
        this.callback(this.value, oldValue)
      }
    }
    

    上面的代码就是一个完整的一个watcher,所做的事情就是获取和更新要用到的属性

    至此,单个属性监测响应的整个过程就实现了,对于多个属性的监测代码实现如下,遍历对象的所有属性

    function observe(obj) {
      if(obj.constructor !== Object) return;
      const keys = obj.keys();
      for(let i = 0; i < keys.length; i++) {
        defineReactive(obj, keys[i], obj[keys[i]])
      }
    }
    
    function defineReactive(data,key,value) {
      observe(val);
      let dep = new Dep();
      Object.defineProperty(data,key, {
        eumerable: true,
        configurable: true,
        get: function() {
          dep.depend();
          return value;
        },
        set: function(newValue) {
          if(value === newValue) return;
          dep.notify();
          value = newValue;
        }
      })
    }
    

    说明

    • 属性的可枚举性: 可以通过for-in循环来进行遍历
    • Object.keys() : 该方法会返回一个由一个给定对象的自身可枚举属性组成的数组
  • 相关阅读:
    get the default proxy by Powershell
    import-module in $profile
    Basic Queries (LINQ to XML)
    xpath中双斜杠的作用// double slash
    Powershell 常见问题
    touch all contents in a folder recursively
    What's the difference between HEAD, working tree and index, in Git?
    beyond compare 比较文本 standard alignment VS unaligned
    bat文件中调用传参的问题
    bacth参数说明 cmd parameter
  • 原文地址:https://www.cnblogs.com/Imflyer/p/8080728.html
Copyright © 2020-2023  润新知