zoukankan      html  css  js  c++  java
  • 响应式原理(对象)

    说起vue的响应式原理,大家也许只知道它的底层是对js的原生方法defineProperty的封装,而具体的实现却是一头雾水。下面我们就一探究竟。

    1、封装defineProperty

    主要目的:就是存取拦截;在get中收集依赖,在set中触发依赖;

    /**
     * 存取描述符
     */
    function defineReactive(data, key, val) {
      // 新增 递归子属性;
      if (typeof val === 'object') {
        new Observer(val)
      }
      let dep = new Dep()
      Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        get: function () {
          // 收集依赖
          dep.depend()
          return val
        },
        set: function (newval) {
          if (val === newval) {
            return
          }
          val = newval
          // 通知触发收集的依赖项
          dep.notify()
        }
      })
    }
    

    2、封装 Dep的类;

    主要目的:用于处理依赖。

    class Dep {
      constructor() {
        // 收集依赖的集合
        this.subs = []
      }
    
      // 添加依赖;
      addSub(sub) {
        this.subs.push(sub)
      }
    
      // 删除依赖;
      removeSub(sub) {
        remove(this.subs, sub)
      }
    
      // 收集依赖
      depend() {
        // 假设我们把依赖存在 `window.target`上;
        if (window.target) {
          this.addSub(window.target)
        }
      }
    
      // 通知
      notify() {
        const subs = this.subs.slice()
        for (let i = 0; i < subs.length; i++) {
          subs[i].update()
        }
      }
    }
    
    function remove(arr, item) {
      if (arr.length) {
        const index = arr.indexOf(item)
        if (index > -1) {
          return arr.splice(index, 1)
        }
      }
    }
    

    3、观察者;

    作用:将一个数据内的所有的属性都转换成getter/setter的形式。然后去追踪他们的变化;

    class Observer {
        constructor(value) {
          this.value = value
          if (!Array.isArray(value)) {
            this.walk(value)
          }
        }
    
        // 此方法只在数据类型为对象时,调用;
        walk(obj) {
          const keys = Object.keys(obj)
          for (let i = 0; i < keys.length; i++) {
            defineReactive(obj, keys[i], obj[keys[i]])
          }
        }
      }
    

    4、Watcher;

    一个 Watcher 中介的角色,收集的依赖就是它。

    class Watcher {
        constructor(vm, expOrFn, cb) {
          this.vm = vm
          // 执行this.getter(),就可以读取data.a.b.c的内容
          this.getter = parsePath(expOrFn)
          this.cb = cb
          this.value = this.get()
        }
    
        get() {
          window.target = this
          let value = this.getter.call(this.vm, this.vm)
          window.target = undefined
          return value
        }
    
        update() {
          const oldValue = this.value
          this.value = this.get()
          this.cb.call(this.vm, this.value, oldValue)
        }
      }
    
    // 用于根据路径来读取属性的值;
      const bailRE = /[^w.$]/
    
      function parsePath(path) {
        if (bailRE.test(path)) {
          return
        }
        const segments = path.split('.')
        return function (obj) {
          for (let i = 0; i < segments.length; i++) {
            if (!obj) return
            obj = obj[segments[i]]
          }
          return obj
        }
      }
    

    5、使用;

    实现:vm.$watch()

    // 定义一个对象;
    var data = {
        name: '小红',
        age: 18,
        skill: {
          dance: '芭蕾舞',
          exercise: '跑步'
        }
      }
      // 观测所有的属性的变化
      new Observer(data)
    
      function Vm() {}
    
      Vm.prototype.$watch = function (obj, path, cb) {
        new Watcher(obj, path, cb)
      }
      var vm = new Vm()
    
      vm.$watch(data, 'skill.dance', (newVal, oldVal) => {
        console.log('我执行了', newVal, oldVal)
      })
    

    总结;

    vue通过对js的原生方法的封装;来达到对数据的访问和赋值的拦截,然后做执行自己的逻辑代码;

    要想达到对数据的响应,就要对数据进行处理,就是把用到这个数据的地方收集起来,然后当这个值发生变化时,通过便会通过触发get函数,来通知用到这个数据的地方,则其修改;

  • 相关阅读:
    mybatis中一直获取xml配置文件输入流值为空的类似解决方法
    switch中能有的值都有哪些
    length,length(),size()
    Spring中IOC的基本原理
    ajax中的一些小问题
    Servlet简单业务流程
    推荐用字节流处理文件拷贝
    更有效率的数据交换
    1.7版本处理io流自动关闭流的写法
    集合中的简单知识
  • 原文地址:https://www.cnblogs.com/ywnh/p/12862547.html
Copyright © 2011-2022 走看看