<!DOCTYPE html>
<html>
<head>
<title>Reactivity System Demo</title>
<script src="dep.js"></script>
<script src="watcher.js"></script>
<script src="observer.js"></script>
</head>
<body>
<input type="text" placeholder="First name" onchange="window.data.firstName = this.value" />
<input type="text" placeholder="Last name" onchange="window.data.lastName = this.value" />
<ul id="console">
</ul>
<script>
window.data = {
firstName: 'hello',
lastName: 'world',
}
walk(window.data)
const watcher = new Watcher(() => `Hi! My name is "${window.data.firstName} ${window.data.lastName}"`,
(val) => consoleLog(val)
)
function consoleLog(val) {
const consoleEl = document.querySelector('#console')
const newItem = document.createElement('li')
newItem.appendChild(document.createTextNode(val))
consoleEl.appendChild(newItem)
}
</script>
</body>
</html>
// See https://github.com/vuejs/vue/blob/be9ac624c81bb698ed75628fe0cbeaba4a2fc991/src/core/observer/dep.js
// for full implementation
class Dep {
constructor() {
this.subs = new Set()
}
addSub(sub) {
this.subs.add(sub)
}
depend() {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify() {
this.subs.forEach(sub => sub.update())
}
}
// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
const targetStack = []
function pushTarget(_target) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
function popTarget() {
Dep.target = targetStack.pop()
}
// See https://github.com/vuejs/vue/blob/61187596b9af48f1cb7b1848ad3eccc02ac2509d/src/core/observer/index.js
// for full implementation
/* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
function walk(obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
function defineReactive(obj, key, val) {
if (val !== null && typeof val === 'object') {
walk(val) // Add reactivity to all children of val
}
const dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
dep.depend()
return val
},
set: function reactiveSetter(newVal) {
val = newVal
dep.notify()
}
})
}
// See https://github.com/vuejs/vue/blob/be9ac624c81bb698ed75628fe0cbeaba4a2fc991/src/core/observer/watcher.js
// for full implementation
class Watcher {
constructor(getter, cb) {
this.getter = getter // function that returns a value based on reactive properties
this.cb = cb // function that is run on value updates, and is passed value and old value
this.value = this.get()
this.cb(this.value, null)
}
get() {
pushTarget(this) // from dep.js
const value = this.getter()
popTarget() // from dep.js
return value
}
addDep(dep) {
dep.addSub(this)
}
update() {
const value = this.get()
const oldValue = this.value
this.value = value
this.cb(value, oldValue)
}
}