Skip to content

Instantly share code, notes, and snippets.

@cj1128
Created December 12, 2018 12:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cj1128/deb59b6758809d07e95a5970eea4b538 to your computer and use it in GitHub Desktop.
Save cj1128/deb59b6758809d07e95a5970eea4b538 to your computer and use it in GitHub Desktop.
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