美文网首页
Vue 进阶【1】— Reactivity(响应式)

Vue 进阶【1】— Reactivity(响应式)

作者: 弱冠而不立 | 来源:发表于2020-10-15 11:50 被阅读0次

1.1 Getters and Setters

目标

实现一个有如下功能的 convert 函数:

  • takes an Object as the argument

以对象作为参数

使用 Object.defineProperty 方法将对象的属性转换为 getter/setters

  • The converted object should retain original behavior, but at the same time
    log all the get/set operations.

转换后的对象应该保留原来的行为,但与此同时记录所有get/set操作。

预期效果:

const obj = { foo: 123 }
convert(obj)

obj.foo // should log: 'getting key "foo": 123'
obj.foo = 234 // should log: 'setting key "foo" to: 234'
obj.foo // should log: 'getting key "foo": 234'

具体实现:

function isObject (obj) {
  return typeof obj === 'object'
    && !Array.isArray(obj)
    && obj !== null
    && obj !== undefined
}

function convert(obj) {
  if(!isObject(obj)) {
    throw new TypeError()
  }

  for(let key in obj) {
    let currentVal = obj[key];
    Object.defineProperty(obj, key, 
      {
        get() {
          console.log(`getting key "${key}": ${currentVal}`);
          return currentVal;
        },

        set(newValue) {
          console.log(`setting key "${key}" to: ${newValue}`);
          currentVal = newValue;
        }
      }
    )
  }
}

1.2 Dependency Tracking (依赖跟踪)

目标

  • Create a Dep class with two methods: depend and notify.

创建一个 Dep 依赖关系类,这个类有两个方法 dependnotifydepend表示当前执行的代码正在收集依赖项;notify表示依赖发生改变,之前任何被定义为依赖的如表达式,函数等都会被通知

  • Create an autorun function that takes an updater function.

创建一个 autorun 函数,去接收一个 updater 函数

  • Inside the updater function, you can explicitly depend on an instance of Dep by calling dep.depend()

在updater函数内部,通过调用dep.depend()显式地依赖于' Dep '的一个实例,目的就是为了收集依赖或者说订阅这个 updater 函数

  • Later, you can trigger the updater function to run again by calling dep.notify().

稍后,通过调用' dep.notify() '再次触发updater函数运行。

预期效果:

// 构建一个 依赖类,然后在 updater 函数中调用依赖对象dep的depend()方法去订阅这个函数,然后能够通过dep对象的 notify() 再次调用这个updater函数
const dep = new Dep()

autorun(() => {
  dep.depend()
  console.log('updated')
})
// should log: "updated"

dep.notify()
// should log: "updated"

具体实现:

// a class representing a dependency
// exposing it on window is necessary for testing
window.Dep = class Dep {
   constructor() {
    this.subscribers = new Set()
   }

   depend() {
    if(activeUpdate) {
      // 注册这个 activeUpdate 作为订阅者
      this.subscribers.add(activeUpdate)
    }
   }

   notify() {
      // 通知所有订阅者
      this.subscribers.forEach(sub => sub())
   }
}

let activeUpdate;

function autorun (update) {
  function wrappedUpdate() {
    activeUpdate = wrappedUpdate
    update()
    activeUpdate = null
  }
  wrappedUpdate()
}

体验一下这个函数的效果

    const dep = new window.Dep()
    function say() {
        dep.depend();
        console.log("say");
    }
    function laugh() {
        dep.depend();
        console.log("laugh");
    }
    autorun(say);  //===> say
    autorun(laugh);  //===> laugh

    dep.notify();
    //===> say
    //===> laugh

1.3 Mini Observer(迷你观察者)

目标

结合前面两个函数,将 convert() 重命名为 observe() 并保留 autorun():

  • observe() converts the properties in the received object and make them
    reactive. For each converted property, it gets assigned a Dep instance which keeps track of a list of subscribing update functions, and triggers them to re-run when its setter is invoked.

observe()接收对象中的属性进行转换,让它们具有响应式的效果。对于每个转换后的属性,它会被分配一个' Dep '实例,该实例跟踪订阅更新函数的列表,并在其setter被调用时触发它们重新运行。

  • autorun() takes an update function and re-runs it when properties that the
    update function subscribes to have been mutated. An update function is said
    to be "subscribing" to a property if it relies on that property during its
    evaluation.

autorun() 接收一个更新函数并且当这个更新函数订阅的属性改变时重新运行它。如果更新函数在求值期间依赖于某个属性,则称为“订阅”该属性。

预期效果:

const state = {
  count: 0
}

observe(state)

autorun(() => {
  console.log(state.count)
})
// should immediately log "count is: 0"

state.count++
// should log "count is: 1"

具体实现:

  function isObject(obj) {
    return typeof obj === 'object'
      && !Array.isArray(obj)
      && obj !== null
      && obj !== undefined
  }


  window.Dep = class Dep {
    constructor() {
      this.subscribers = new Set()
    }

    depend() {
      if (activeUpdate) {
        // 注册这个 activeUpdate 作为订阅者
        this.subscribers.add(activeUpdate)
      }
    }

    notify() {
      // 通知所有订阅者
      this.subscribers.forEach(sub => sub())
    }
  }

  const dep = new window.Dep();

  function observe(obj) {
    if (!isObject(obj)) {
      throw new TypeError()
    }

    for (let key in obj) {
      let currentVal = obj[key];
      Object.defineProperty(obj, key,
        {
          get() {
            dep.depend()
            return currentVal
          },

          set(newValue) {
            if (newValue != currentVal) {
              currentVal = newValue
              dep.notify()
            }
          }
        }
      )
    }
  }

  let activeUpdate;

  function autorun(update) {
    function wrappedUpdate() {
      activeUpdate = wrappedUpdate
      update()
      activeUpdate = null
    }
    wrappedUpdate()
  }

体验一下效果

    let state = {
        count: 0,
        message: "stateMesage"
    }

    observe(state);

    autorun(()=> {
        console.log("state.count:", state.count);
        console.log("state.message:", state.message);
    })

    state.count++;
    state.message = "changeMessage";
每次状态改变,都会触发autorun里的update函数

总结

  1. 构建一个依赖跟踪类 Dep, 类里有一个叫depend方法,该方法用于收集依赖项;另外还有一个notify方法,该方法用于触发依赖项的执行,也就是说只要在之前使用dep方法收集的依赖项,当调用notfiy方法时会被触发执行。
  2. ES5的Object.defineProperty提供监听属性变更的功能,封装成一个 obseve() 方法。在对象属性的 get 方法下通过 dep.depend() 去收集需要监听更改的属性,然后在 set 方法下调用 dep.notify() 去触发set新数据后执行需要的方法。
  3. 通过封装好的 obseve(),去观察一个对象。然后在 autorun 函数内写需要的更新操作,可以是简单的打印一句话,也可以是渲染DOM。然后在更改被观察对象的属性时,就会自动执行 autorun 内的更新操作

相关文章

网友评论

      本文标题:Vue 进阶【1】— Reactivity(响应式)

      本文链接:https://www.haomeiwen.com/subject/zeekuktx.html