Vue応答式の原理の浅い分析

28123 ワード

Vue応答式の原理の浅い分析
次の状況を考慮します.
const state = {
    a: 1
    b: 10
}

どのようにaを変化させる時、bはずっとaの10倍ですか?つまり次のようになります.
state.a = 2 //   "state.b is 20"
state.a = 3 //   "state.b is 30"

これはvueの応答式の原理であり、vueはObjectに依存する.definePropertyのgetとset関数はこの機能を完了します.
1.1 Getters and Setters
Goal
Implement a convert function that:
  • takes an Object as the argument
  • converts the Object’s properties in-place into getter/setters using Object.defineProperty (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)
  • The converted object should retain original behavior, but at the same time log all the get/set operations.

  • expected usage:
    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'
    

    solution
    <script>
    function convert (obj) {
      // Implement this!
      Object.keys(obj).forEach(key => {
        let internalValue = obj[key]
        Object.defineProperty(obj, key, {
          get () {
            console.log(`getting key "${key}": ${internalValue}`)
            return internalValue
          },
          set (newValue) {
            internalValue = newValue
            console.log(`setting key "${key}" to: ${newValue}`)
          }
        })
      })
    }
    </script>
    

    1.2 Dependency Tracking(依存トレース)
    1.watchとcomputedはVueの依存追跡メカニズムに基づいており、あるデータ(依存データと呼ばれる)が変化すると、このデータに依存するすべての「関連」データが「自動」に変化し、関連する関数を自動的に呼び出してデータの変動を実現することを試みている.
    2.methods:methodsでは関数を定義するために使用されます.実行するには手動で呼び出す必要があることは明らかです.watchやcomputedのように「自動実行」で定義された関数ではありません
    Goal
    出力が期待通りになるように、次のコードを完了します.
  • Create a Dep class with two methods: depend and notify .(クラスDepを作成するには、dependとnotifyの2つの方法があります)
  • Depクラス代表依存
  • dependの役割は登録依存
  • である.
  • notifyの役割は、依存を実行するサブスクライバです.現在の依存が変更され、以前のすべての依存Depというクラスの変数(関数、式など)が再実行される必要があります.つまり、Depが変更されたことを通知する(依存するサブスクライバ)
  • です.
  • Create an autorun function that takes an updater function.(パラメータとして更新関数を受信するautorun関数を作成)
  • Inside the updater function,you can explicitly depend on an instance of Dep by calling dep.depend()(更新関数の内部に呼び出しdep.depend()が表示される)
  • Later, you can trigger the updater function to run again by calling dep.notify() .(その後、呼び出しdep.notify()が更新関数をトリガすることを示すことができる)
  • .
    class Dep {
      // Implement this!
    }
    
    function autorun (update) {
      // Implement this!
    }
    const dep = new Dep()
    const update1 = () => {
        dep.depend()
        console.log('111')
    }
    const update2 = () => {
        dep.depend()
        console.log('222')
    }
    autorun(update1)
    autorun(update2)
    dep.notify()
    //     :
    // 111
    // 222
    // 111
    // 222
    

    まずグローバル変数activeUpdateが宣言され、JSは単一スレッド言語であり、いつでも1つの関数だけが実行されます.mark自体が実行できる関数を作成すると、この関数が実行されているかどうか、つまりこの関数の内部にあるかどうかをいつでも簡単に知ることができます.
    次にautorunの内部に関数wrappedUpateを作成し、wrappedUpateでupdate関数を実行します.このupdate関数の内部にあるかどうかを知りたいので、次のようにします.
    class Dep {
      // Implement this!
    }
    
    let activeUpdate
    
    function autorun (update) {
      // Implement this!
        function wrappedUpate () {
            activeUpdate = wrappedUpate
            unpdate()
            activeUpdate = null
        }
        wrappedUpate()
    }
    
    const dep = new Dep()
    const update1 = () => {
        dep.depend()
        console.log('111')
    }
    const update2 = () => {
        dep.depend()
        console.log('222')
    }
    autorun(update1)
    autorun(update2)
    dep.notify()
    //     :
    // 111
    // 222
    // 111
    // 222
    

    wrappedUpdate関数を実行すると、update関数が実行され、update関数の内部でdep.depend()が呼び出されます.このdep.depend()はグローバル変数activeUpdateにアクセスするので、dep.depend()がactiveUpdateにアクセスする前にwrappedUpdateに値を割り当てます.
    これにより、activeUpdateが空でなければ、activeUpdateが指す変数をdepの依存購読者として登録依存を完了する.notifyメソッドの役割は、すべての購読者を実行することです.したがって、Depはすべてのサブスクライバを格納する属性を必要とし、Setを使用して格納します.
    class Dep {
      // Implement this!
        constructor () {
            this.subscribers = new Set()
        }
        depend () {
            if (activeUpdate) {
                //    active update        
                this.subscribers.add(activeUpdate)
            }
        }
        notify () {
            //         
            this.subscribers.forEach(sub => sub())
        }
    }
    
    let activeUpdate
    
    function autorun (update) {
      // Implement this!
        function wrappedUpate () {
            activeUpdate = wrappedUpate
            update()
            activeUpdate = null
        }
        wrappedUpate()
    }
    
    const dep = new Dep()
    const update1 = () => {
        dep.depend()
        console.log('111')
    }
    const update2 = () => {
        dep.depend()
        console.log('222')
    }
    autorun(update1)
    autorun(update2)
    dep.notify
    

    1.3 Mini Observer
    1.1と1.2を組み合わせて、最初に提起された問題を解決します.
    Goal
    Combine the previous two functions, renaming convert() to observe() and keeping 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.
  • 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.

  • They should support the following usage:
    const state = {
        a: 1
    }
    
    observe(state)
    computed(() => {
        state.b = state.a * 10
        console.log(`state.b is: ${state.b}`)
    })
    
    state.a = 2
    state.a = 3
    

    Solutions
    class Dep {
      constructor () {
        this.subscribers = new Set()
      }
      depend () {
        if (activeUpate) {
          this.subscribers.add(activeUpate)
        }
      }
      notify () {
        this.subscribers.forEach(sub => sub())
      }
    }
    
    let activeUpate
    
    function observe (obj) {
      // Implement this!
      let dep = new Dep()
      Object.keys(obj).forEach(key => {
        let internalValue = obj[key]
        Object.defineProperty(obj, key, {
          get () {
            dep.depend()
            return internalValue
          },
          set (newValue) {
            let changed = internalValue !== newValue
            internalValue = newValue
            if (changed) {
              dep.notify()
            }
          }
        })
      })
    }
    
    function computed (update) {
      // Implement this!
      function wrapperUpdate () {
        activeUpate = wrapperUpdate
        update()
        activeUpate = null
      }
      wrapperUpdate()
    }
    

    1.1のconvert関数をobserver,autorunをcomputedと改名し,getとset関数でdep.depend()とdep.notify()を呼び出しただけで,残りは前述したコードであり,言うまでもない.
    (finished)