Reactivity Mechanism
Vue recursively traverses the object returned by data() and overrides its properties using Object.defineProperty() from ES5 to intercept read and write operations on these properties. For instance, if there's a property called username in data:
const originalValue = data.username;
Object.defineProperty(data, 'username', {
get() {
return originalValue;
},
set(newValue) {
originalValue = newValue;
}
});
After setting up interception for data, Vue employs the Observer Pattern to manage data change initiators and their dependencies. Note that Vue uses the Observer Pattern rather than Publish/Subscribe, which involves an additional "broker" component.
Simplified Vue Implementation
Observer/Dependency
class Dep {
static target = null;
subs = [];
depend() {
if (Dep.target) {
this.subs.push(Dep.target);
}
}
notify() {
this.subs.forEach(sub => sub.update());
}
}
Watcher
class Watcher {
data;
key;
oldVal;
callback;
constructor(data, key, callback) {
this.data = data;
this.key = key;
this.callback = callback;
Dep.target = this;
this.oldVal = this.data[this.key];
Dep.target = null;
}
update() {
const newVal = this.data[this.key];
if (newVal !== this.oldVal) {
this.callback(newVal, this.oldVal);
this.oldVal = newVal;
}
}
}
Vue Class
class MyVue {
constructor(data, inputs, outputs) {
this.data = data;
Object.keys(data).forEach(key => {
const dep = new Dep();
let val = data[key];
Object.defineProperty(data, key, {
get() {
dep.depend();
return val;
},
set(newVal) {
val = newVal;
dep.notify();
}
});
});
inputs.forEach(input => {
input.ele.addEventListener(input.event, e => {
this.data[input.key] = e.target.value;
});
});
outputs.forEach(output => {
output.ele[output.attr] = this.data[output.key];
new Watcher(this.data, output.key, value => {
output.ele[output.attr] = value;
});
});
}
}
Test Example
window.onload = function() {
const data = { name: 'initial value' };
const inputs = [
{
key: 'name',
ele: document.querySelector('#input'),
event: 'input'
}
];
const outputs = [
{
key: 'name',
ele: document.querySelector('#output'),
attr: 'innerHTML'
}
];
const myVue = new MyVue(data, inputs, outputs);
};
<div id="output"></div>
<input id="input" />