Write in the front: This article is a summary of personal daily work and study, which is convenient for later checking and filling in the missing, non authoritative information. Please bring your own thinking.
Previous link: Learn about Vue - [how Vue implements responsive (I)].
Learn about Vue - [how Vue implements responsive (II)].
I've got some understanding of the response. Here, I'll try to write it by myself (copy it).
Set the object data as the response object to be defined and the key as the attribute in data.
- Define getter/setter for each property key in data through defineProperty, so that the corresponding operation will be triggered when data[key] is referenced and assigned, which is the basis of responsive implementation;
- dep object, as a dependent entity, contains methods such as dependency addition, watcher addition, and change notification, which are used for dependency collection and change distribution, and contains a list of observers.
- watcher object, observer object, including dependency collection and update methods; used for dependency collection and update, including dependency list;
Dep constructor
let uid = 0; class Dep { constructor() { this.id = ++uid; this.subs = []; } addSub(target) { // Add target to the observer list this.subs.push(target); } depend() { // if (Dep.target) { Dep.target.addDep(this); } } removeSub(target) { this.subs.splice(this.subs.findIndex(_ => _.id == target.id), 1); } notify() { const subs = this.subs.slice(); for (let i = 0; i < subs.length; i++) { subs[i].update(); } } } Dep.target = null; // Currently active watcher const targetStack = []; // The stack where the watcher is stored const pushTarget = target => { // Update the currently active Dep.target if (Dep.target) { targetStack.push(target); // Push target (watcher) into the stack } Dep.target = target; }; const popTarget = () => { // Pop the top watcher from the watcher stack Dep.target = targetStack.pop(); };
watcher constructor
class Watcher { constructor(getter, options = {}) { this.deps = []; // Dependency list this.newDeps = []; // Last added dependency list this.depIds = new Set(); // List of dependent ids this.newDepIds = new Set(); // List of last added dependent ids this.getter = getter; // this.lazy = !!options.lazy; // Lazy dependency, do not execute getter when instantiating for the first time this.dirty = this.lazy; // Dirty value identification, mainly used in computed calculation; this.lazy ? undefined : this.get(); } get() { pushTarget(this); // Take the current watcher as the active watcher object and push it into the targetStack stack const value = this.getter(); popTarget(); // Set the current watcher as the previous one on the stack this.cleanupDeps(); // Dependency collation, mainly used to collate this.deps, this.depIds return value; } addDep(dep) { if (!this.newDepIds.has(dep.id)) { this.newDepIds.add(dep.id); this.newDeps.push(dep); if (!this.depIds.has(dep.id)) { dep.addSub(this); } } } cleanupDeps() { let i = this.deps.length; while (i--) { const dep = this.deps[i]; if (!this.newDepIds.has(dep.id)) { // If the new dependency list no longer contains the previous dependency, the dep.removeSub method is called to remove the current watcher from the dep.subs list. dep.removeSub(this); } } [this.deps, this.newDeps] = [this.newDeps, this.deps]; this.newDeps.length = 0; [this.depIds, this.newDepIds] = [this.newDepIds, this.depIds]; this.newDepIds.clear(); } evaluate() { this.value = this.get(); this.dirty = false; } update() { if (this.lazy) { this.dirty = true; } else { new Promise((resolve) => { resolve(); }).then(() => { this.get(); }); } } depend() { // Add the dependency of the current watcher to the dependency list of the current Dep.target const deps = this.deps; for (let i = 0; i < deps.length; i++) { deps[i].depend(); } } }
defineReactive method
Used to define getter/setter for data[key]
const defineReactive = (target, key, val) => { const dep = new Dep(); // The instantiated dep object is used to access the object for dependency collection when the getter/setter is triggered. In essence, the currently instantiated dep corresponds to the current data[key] one by one. Object.defineProperty(target, key, { enumerable: true, configurable: true, get() { if (Dep.target) { dep.depend(); // Dependency adding } return val; }, set(newVal) { if (val === newVal) return; val = newVal; dep.notify(); }, }); };
data, computed initialization
const initData = data => { const keys = Object.keys(data); for (let i = 0; i < keys.length; i++) { defineReactive(vm, keys[i], data[keys[i]]); } }; const initComputed = computed => { const keys = Object.keys(computed); for (let i = 0; i < keys.length; i++) { const userDef = computed[keys[i]].bind(vm); const watcher = new Watcher(userDef, { lazy: true }); Object.defineProperty(vm, keys[i], { enumerable: true, configurable: true, get() { if (watcher) { if (watcher.dirty) { watcher.evaluate(); } if (Dep.target) { watcher.depend(); } return watcher.value; } }, }); } };
Simulation page rendering:
<!-- html --> <body> <div id="app"></div> <button id="btn">To update</button> </body>
const data = initData(vm.data); const computed = initComputed(vm.computed); const updateComponent = () => { const app = document.querySelector('#app'); var a = vm.current; // Reference vm.current var b = vm.computedCurrent; // Apply computed app.innerHTML = `data: <i>${a}</i> computed: <strong>${b}</strong>`; }; defineReactive(vm, 'current', vm.current); // Define getter/setter for vm.current const watcher = new Watcher(updateComponent);
You can see that click the button to update the current attribute in vm, and the page has been updated successfully...
Process analysis
Initialize the data property getter / setter - > instantiate the watcher, with the update method, addDep method - > watcher.get method to perform dependency collection (the referenced data property in this method is regarded as the dependency of the current watcher) - > during the process of watcher dependency collection, the dependent data property will also be collected by the watcher -- > data property update -- > notify the watcher, and the update method will adjust. Use a new round of dependency collection, comparison of old and new dependencies, and delete the new dependency from the dependency list if the old dependency is missing, and add the observer watcher to the dependent subs watcher list at the same time to execute the business code (view update).
For computed, it is not only an observer, but also a dependency.
For a watch, it is an observer. It depends on the data or calculated to be watched. When it depends on an update, it will notify it and execute the corresponding method.
In principle, there are many details.
THE END