Vue responsive implementation principle
Object.defineProperty
grammar
Object.defineProperty(obj, prop, descriptor)
Copy the code
parameter
obj
Copy the code
The object for which attributes are to be defined.
prop
Copy the code
The name or Symbol of the property to be defined or modified.
descriptor
Copy the code
The property descriptor to define or modify.
Description of descriptor parameters
-
configurable
The descriptor of this property can be changed and deleted from the corresponding object only when the property’s key is set to true. The default is false.
-
enumerable
The property appears in the object’s enumerable property if and only if its Enumerable key value is true. The default is false.
-
value
The value corresponding to this property. It can be any valid JavaScript value (numeric value, object, function, etc.). The default is false.
-
writable
The property’s value, the value above, can be changed by the assignment operator if and only if the property’s writable key is true. The default is false.
-
get
Property, or undefined if there is no getter. This function is called when the property is accessed. No arguments are passed, but this object is passed (because of inheritance, this is not necessarily the object that defines the property). The return value of this function is used as the value of the property. The default is undefined.
-
set
Property, or undefined if there is no setter. This function is called when the property value is modified. This method takes an argument (that is, the new value being assigned) and passes in the this object at the time of assignment. The default is undefined.
Getter and setter
ES5’s Object.defineProperty provides the ability to listen for property changes, and I’ll show you how covert functions can print logs when modifying Object properties by modifying getters and setters of incoming objects.
const obj = { foo: 123 }
convert(obj)
obj.foo // Need to print: 'Getting key "foo": 123'
obj.foo = 234 Setting key "foo" to 234'
obj.foo // Need to print: 'Getting key "foo": 234'
Copy the code
The Covert function is implemented as follows:
function convert (obj) {
// object. keys Retrieves all the keys of the Object and modifies each property through forEach
Object.keys(obj).forEach(key= > {
// Save the initial value of the property
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
Dependency tracking (subscription publishing mode)
We need to implement a dependency tracking class Dep with a method called Depend that collects dependencies. There is also a notify method that triggers the execution of dependencies, that is, as long as the dependencies that were previously collected using the DEP method will be triggered when the Notfiy method is called.
Here is what the Dep class is expected to do. The dep. Depend method is called to collect dependencies. When dep.notify is called, the console prints its updated statement again
const dep = new Dep()
autorun(() = > {
dep.depend()
console.log('updated')})// Print: "updated"
dep.notify()
// Print: "updated"
Copy the code
The Autorun function receives a function that helps us create a response area in which dependencies can be registered via dep.depend
The final Dep class code is as follows:
window.Dep = class Dep {
constructor () {
// Subscribes to a queue of tasks in the same way as the Set data structure
this.subscribers = new Set()}// Used to register dependencies
depend () {
if (activeUpdate) {
this.subscribers.add(activeUpdate)
}
}
// Used to publish messages that trigger dependency re-execution
notify () {
this.subscribers.forEach(sub= > sub())
}
}
let activeUpdate = null
function autorun (update) {
// Assign wrappedUpdate to activeUpdate, which causes the update function to be reexecuted when the dependency changes
WrappedUpdate is actually called, and the dependency tracker will continue to collect dependencies if any changes are made later
// Because the update function may contain conditions, collect this dependency if a variable is true and others if it is false
// Therefore, the dependency collection system needs to update these dependencies dynamically to keep them up to date
const wrappedUpdate = () = > {
activeUpdate = wrappedUpdate
update()
activeUpdate = null
}
wrappedUpdate()
}
Copy the code
Implementing mini observer
We put the above two exercises together to implement a small observer that automatically updates data by calling the Depend and Notfiy methods in getter and setter, which is the core principle behind Vue’s automatic updates.
Expected call effect:
const state = {
count: 0
}
/ / to monitor the state
observe(state)
// dependency injection
autorun(() = > {
console.log(state.count)
})
// Print "count is: 0"
// Execute notfiy on each reassignment to iterate over all dependent functions
state.count++
// Print "count is: 1"
Copy the code
The final integration code is as follows:
class Dep {
constructor () {
this.subscribers = new Set()
}
depend () {
if (activeUpdate) {
this.subscribers.add(activeUpdate)
}
}
notify () {
this.subscribers.forEach(sub= > sub())
}
}
function observe (obj) {
Object.keys(obj).forEach(key= > {
let internalValue = obj[key]
const dep = new Dep()
Object.defineProperty(obj, key, {
// Collect dependencies in the getter and re-run when notify is triggered
get () {
dep.depend()
return internalValue
},
// setters are used to call notify
set (newVal) {
constchanged = internalValue ! == newVal internalValue = newValif (changed) {
dep.notify()
}
}
})
})
return obj
}
let activeUpdate = null
function autorun (update) {
const wrappedUpdate = () = > {
activeUpdate = wrappedUpdate
update()
activeUpdate = null
}
wrappedUpdate()
}
Copy the code