Created
December 12, 2018 12:57
-
-
Save cj1128/deb59b6758809d07e95a5970eea4b538 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Mue(opts) { | |
opts.data = opts.data || {} | |
opts.computed = opts.computed || {} | |
opts.watch = opts.watch || {} | |
const self = this | |
const watchers = {} | |
function observe(key, fn) { | |
if(watchers[key] == null) { | |
watchers[key] = [] | |
} | |
watchers[key].push(fn) | |
} | |
function notify(key) { | |
if(watchers[key]) { | |
watchers[key].forEach(fn => fn.call(self)) | |
} | |
} | |
const defaultWatcher = key => () => console.log("run default watcher for ", key) | |
function makeReactive(obj, key, value) { | |
observe(key, defaultWatcher(key)) | |
Object.defineProperty(obj, key, { | |
get() { | |
if(Dep.target) { | |
// 当前 target 依赖 key | |
Dep.depend(key) | |
} | |
return value | |
}, | |
set(newValue) { | |
if(value === newValue) return | |
value = newValue | |
notify(key) | |
Dep.updateDependents(key) | |
Dep.notifyDependents(key) | |
}, | |
}) | |
} | |
function makeComputed(obj, key, fn) { | |
const noValue = {} | |
let cache = noValue | |
observe(key, () => { | |
cache = noValue | |
Dep.updateDependents(key) | |
Dep.notifyDependents(key) | |
}) | |
Object.defineProperty(obj, key, { | |
get() { | |
if(Dep.target) { | |
Dep.depend(key) | |
} | |
if(cache !== noValue) return cache | |
console.log("calc computed property: ", key) | |
Dep.pushTarget(key) | |
Dep.depending[key] = [] | |
cache = fn.call(self) | |
Dep.popTarget() | |
return cache | |
}, | |
}) | |
} | |
function pushIfNotExists(arr, item) { | |
if(!arr.includes(item)) { | |
arr.push(item) | |
} | |
} | |
const Dep = global.Dep = { | |
_targets: [], | |
// 值为一个数组,表示依赖键的对象 | |
// key: v1, v2, v3, 表示 v1,v2,v3 依赖 key | |
dependents: {}, | |
// 键为计算属性,值为依赖的属性 | |
// key: v1, v2, v3, 表示 key 依赖 v1, v2, v3 | |
depending: {}, | |
depend(key) { | |
if(Dep.dependents[key] == null) { | |
Dep.dependents[key] = [] | |
} | |
if(Dep.depending[Dep.target] == null) { | |
Dep.depending[Dep.target] = [] | |
} | |
pushIfNotExists(Dep.dependents[key], Dep.target) | |
pushIfNotExists(Dep.depending[Dep.target], key) | |
}, | |
pushTarget(key) { | |
Dep._targets.unshift(key) | |
}, | |
popTarget() { | |
Dep._targets.shift() | |
}, | |
get target() { | |
return Dep._targets[0] | |
}, | |
notifyDependents(key) { | |
if(Dep.dependents[key]) { | |
Dep.dependents[key].forEach(notify) | |
} | |
}, | |
updateDependents(key) { | |
if(Dep.dependents[key]) { | |
Dep.dependents[key] = Dep.dependents[key].filter(k => Dep.depending[k].includes(key)) | |
} | |
}, | |
} | |
self.$data = {} | |
// make reactive | |
Object.keys(opts.data).forEach(key => makeReactive(self.$data, key, opts.data[key])) | |
// proxy | |
Object.keys(opts.data).forEach(key => { | |
Object.defineProperty(self, key, { | |
get() { | |
return self.$data[key] | |
}, | |
set(newValue) { | |
self.$data[key] = newValue | |
}, | |
}) | |
}) | |
// custom watcher | |
Object.keys(opts.watch).forEach(key => { | |
observe(key, opts.watch[key]) | |
}) | |
// computed | |
Object.keys(opts.computed).forEach(key => makeComputed(self, key, opts.computed[key])) | |
} | |
const vm = new Mue({ | |
data: { | |
firstName: "first", | |
lastName: "last", | |
age: 20, | |
}, | |
computed: { | |
fullName() { | |
return this.firstName + " " + this.lastName | |
}, | |
upperFullName() { | |
return this.fullName.toUpperCase() + this.age | |
}, | |
x() { | |
if(this.age === 20) { | |
return "first name: " + this.firstName | |
} else { | |
return "last name: " + this.lastName | |
} | |
}, | |
}, | |
watch: { | |
age: function() { | |
console.log("age changed:", this.age) | |
}, | |
}, | |
}) | |
// 数据属性 | |
// - change vm.firstName, should run default watcher | |
// - change vm.lastName, should run default watcher | |
// - change vm.age, should run default watcher and custom watcher | |
// 计算属性 | |
// - change vm.firstName, vm.fullName should update | |
// - change vm.lastName, vm.fullName should update | |
// 依赖计算属性的计算属性 | |
// - change vm.firstName, vm.upperFullName should update | |
// - change vm.age, vm.upperFullName should update | |
// 计算属性依赖的数据属性在变化 | |
// - change vm.firstName, vm.x should update | |
// - change vm.age to 30, vm.x should update | |
// - change vm.firstName, vm.x should NOT update | |
// - change vm.lastName, vm.x should update |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment