Update: Thank you for your support. Recently, a blog official website came out to facilitate your system reading. There will be more content and more optimization in the future

—— The following is the text ——

Vue advanced series summary below, welcome to read.

Vue advanced series (1) response formula principle and implementation

Vue advanced series (2) plug-in principle and implementation

Vue advanced series (3) Render function principle and implementation

What is reactive Reactivity

Reactivity represents how a state change dynamically changes the entire system, in a real project application scenario, that is, how data dynamically changes the Dom.

demand

Now I have a requirement, I have two variables a and B, and I want B to always be 10 times as much as A. How do I do that?

Easy Try 1:

let a = 3;
let b = a * 10;
console.log(b); / / 30
Copy the code

At first glance, it looks like it does, but b is fixed, and no matter how you change a, B doesn’t change with it. In other words, B does not keep data synchronization with A. B changes only if you redefine the value of B after a changes.

a = 4;
console.log(a); / / 4
console.log(b); / / 30
b = a * 10;
console.log(b); / / 40
Copy the code

Easy Try 2:

Define the relationship between a and b in a function, then execute the function after changing a, and the value of B will change. The pseudocode is shown below.

onAChanged((a)= > {
    b = a * 10;
})
Copy the code

So now the question becomes how to implement the onAChanged function, which automatically executes onAChanged when a changes, see the follow-up.

Combined with the view layer

Now combine a and B with the View page, where A corresponds to the data and B corresponds to the page. The business scenario is simple: change data A and then change page B.

<span class="cell b"></span>

document
    .querySelector('.cell.b')
    .textContent = state.a * 10
Copy the code

Now establish the relationship between data A and page B, wrapped in a function to create the following relationship.

<span class="cell b"></span>

onStateChanged((a)= > {
    document.QuerySelector ('.cell.b ').textContent = state.a *10
})
Copy the code

Again abstracted, it looks like this.

<span class="cell b">
    {{ state.a * 10 }}
</span>

onStateChanged(() => {
    view = render(state)
})
Copy the code

View = render(state) is a high-level abstraction for all page rendering. The implementation of View = render(state) is not considered here, as it involves a number of technical details such as DOM structure and implementation. What we need here is an implementation of onStateChanged.

implementation

This is done through getter and setter methods in Object.defineProperty. Please refer to the following link for specific usage.

The Object of MDN. DefineProperty

Note that the get and set functions are access descriptors, and the value and writable functions are data descriptors. The descriptor must be one of these two forms, but they cannot coexist, or an exception will occur.

Example 1: Implementationconvert()function

Requirements are as follows:

  • 1. Pass objectsobjAs a parameter
  • 2, use,Object.definePropertyConvert all properties of an object
  • 3. The converted object retains its original behavior but outputs logs in get or SET operations

Example:

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


obj.foo // Get key "foo": 123
obj.foo = 234 Setting key "foo" to 234
obj.foo // Output getting key "foo": 234
Copy the code

Once you know how to use getters and setters in Object.defineProperty, you can implement onAChanged and onStateChanged by modifying the GET and set functions.

Implementation:

function convert (obj) {

  // Iterate over all attributes of the object
  // And convert to getter/setters with object.defineProperty ()
  Object.keys(obj).forEach(key= > {
  
    // Save the original value
    let internalValue = obj[key]
    
    Object.defineProperty(obj, key, {
      get () {
        console.log(`getting key "${key}": ${internalValue}`)
        return internalValue
      },
      set (newValue) {
        console.log(`setting key "${key}" to: ${newValue}`)
        internalValue = newValue
      }
    })
  })
}
Copy the code

Example 2: ImplementationDepclass

Requirements are as follows:

  • 1. Create oneDepClass containing two methods:dependandnotify
  • 2. Create oneautorunFunction, pass in aupdateFunction as argument
  • 3, inupdateCall from a functiondep.depend(), explicitly depends onDepThe instance
  • 4, calldep.notify()The triggerupdateFunction rerun

Example:

const dep = new Dep()

autorun((a)= > {
  dep.depend()
  console.log('updated')})// To register subscribers, print updated

dep.notify()
// Notifies the change
Copy the code

You first need to define the autorun function that takes the update function as an argument. Because the subscriber is registered in the Dep when autorun is called and the update function is re-executed when dep.notify() is called, the UPDATE reference must be held in the Dep, where the variable activeUpdate is used to represent the function that wraps the update.

The implementation code is as follows.

let activeUpdate = null 

function autorun (update) {
  const wrappedUpdate = (a)= > {
    activeUpdate = wrappedUpdate    // Reference assignment to activeUpdate
    update()                        // Call update, i.e. call internal DEP.depend
    activeUpdate = null             // Clear references after successful binding
  }
  wrappedUpdate()                   / / call
}
Copy the code

WrappedUpdate is essentially a closure. The activeUpdate variable can be retrieved from the update function, and the activeUpdate variable can be retrieved from the dep.depend() function, so the implementation of deP is simple.

The implementation code is as follows.

class Dep {

  / / initialization
  constructor () {          
    this.subscribers = new Set()}// Subscribe to the list of update functions
  depend () {
    if (activeUpdate) {     
      this.subscribers.add(activeUpdate)
    }
  }

  // Rerun all update functions
  notify () {              
    this.subscribers.forEach(sub= > sub())
  }
}
Copy the code

Combining the above two parts is the complete implementation.

Example 3: Implementing responsive systems

Requirements are as follows:

  • 1. Combining the above two examples,convert()Rename it observerobserve()
  • 2,observe()Transform the attributes of the object to make them responsive. For each converted attribute, it is assigned oneDepInstance, which tracks subscriptionsupdateFunction list and in callsetterTriggers them to run again
  • 3,autorun()receiveupdateFunction as arguments, and inupdateThe function is re-run when the properties to which it subscribed change.

Example:

const state = {
  count: 0
}

observe(state)

autorun((a)= > {
  console.log(state.count)
})
// Print count is: 0

state.count++
// Print count is: 1
Copy the code

This can be achieved by combining instance 1 and instance 2, with the obj property modified in Observe and the INSTANCE of Dep allocated, subscribers registered in GET, and changes notified in SET. The Autorun function is saved unchanged. The implementation is as follows:

class Dep {

  / / initialization
  constructor () {          
    this.subscribers = new Set()}// Subscribe to the list of update functions
  depend () {
    if (activeUpdate) {     
      this.subscribers.add(activeUpdate)
    }
  }

  // Rerun all update functions
  notify () {              
    this.subscribers.forEach(sub= > sub())
  }
}

function observe (obj) {

  // Iterate over all attributes of the object
  // And convert to getter/setters with object.defineProperty ()
  Object.keys(obj).forEach(key= > {
    let internalValue = obj[key]

    // Each attribute is assigned a Dep instance
    const dep = new Dep()

    Object.defineProperty(obj, key, {
    
      // Getter is responsible for registering subscribers
      get () {
        dep.depend()
        return internalValue
      },

      // Setters are responsible for notifying changes
      set (newVal) {
        constchanged = internalValue ! == newVal internalValue = newVal// Recalculate after triggering
        if (changed) {
          dep.notify()
        }
      }
    })
  })
  return obj
}

let activeUpdate = null

function autorun (update) {

  // Wrap the update function in the "wrappedUpdate" function,
  // The "wrappedUpdate" function registers and unregisters itself
  const wrappedUpdate = (a)= > {
    activeUpdate = wrappedUpdate
    update()
    activeUpdate = null
  }
  wrappedUpdate()
}
Copy the code

This is even clearer when combined with the flow chart in the Vue documentation.

Job Done!!

This article is based on a paid video by VUE author Iu

communication

My Github link is as follows. Welcome to Star

Github.com/yygmind/blo…

My name is Mu Yiyang, a senior front-end engineer of netease. I will focus on one front-end interview every week. Next, let me take you into the world of advanced front-end, on the way to progress, mutual encouragement!