Open jiefancis opened 3 years ago
const vm = { data(){ return { a: { b: 1 }, c: { d: { e: 2 } } } }, props: {}, computed:{ d(vData){ // console.log('计算属性',vData) return this.a.b + this.c.d.e } }, watch: { 'a.b'(){ console.log('a.b触发watcher') }, 'c.d'() { console.log('c.d触发watcher') } }, methods: { twoSum() { return this?.a?.b + this?.c?.d?.e } } } function noop(){} let vData = vm.data(); let id = 0; const sharedPropertyDefine = { enumerable: true, configurable: true, get(){}, set(){} } // proxy data on instance function proxy(target, source, key) { sharedPropertyDefine.get = function(){ return source[key] } sharedPropertyDefine.set = function(newVal){ source[key] = newVal } Object.defineProperty(vm, key, sharedPropertyDefine) } /* * observer */ function initData(data) { let keys = Object.keys(data), length = keys.length, i = 0; while(i < length) { key = keys[i] console.log(data, 'initData-proxy on instance', key) proxy(vm, data, key) ++i } observe(data) } function observe(data) { if(data && typeof data === 'object') { Object.keys(data).forEach(key => { defineReactive(key, data[key], data) }) } } function defineReactive(key, value, obj) { const dep = new Dep() const descriptor = Object.getOwnPropertyDescriptor(obj, key) // 避免直接对obj属性进行get和set操作造成死循环。 const getter = descriptor && descriptor.get const setter = descriptor && descriptor.set if(value && typeof value === 'object') { observe(value) } Object.defineProperty(obj, key, { configurable: true, // writable: true enumerable: true, get(){ const result = getter && getter.call(obj,key) || value if(Dep.target) { dep.addSub(Dep.target) } return result; }, set(newVal){ if(value === newVal) return; if(setter) { setter.call(obj, newVal) } else { value = newVal } if(newVal && typeof newVal === 'object') { observe(newVal) } dep.notify() } }) } /** * Dep */ class Dep{ constructor(){ this.subs = [] } addSub(watcher) { //避免重复添加wathcer if(!this.subs.some(watch => watch.id === watcher.id)){ this.subs.push(watcher) } } notify(){ this.subs && this.subs.forEach(watcher => watcher.update()) } } Dep.target = null const targetStack = [] function pushTarget(target) { targetStack.push(target) Dep.target = target } function popTarget(){ targetStack.pop() Dep.target = targetStack[targetStack.length - 1] } /** * computed */ function initComputed(vm, computed){ const watchers = vm._computedWatchers = Object.create(null); Object.keys(computed).forEach(prop => { const watcher = watchers[prop] = new Watcher(vm, computed[prop], noop) if (vData[prop]) { console.error(`computed props ${prop} has allready defined in data`, vData) } else { defineComputed(vm,prop) } }) } function defineComputed(vm, prop){ Object.defineProperty(vm, prop, { configurable: true, enumerable: true, get() { const watcher = vm._computedWatchers[prop] const value = watcher.get() return value } }) } /** * watcher */ class Watcher{ constructor( vm, expOrFn, cb, options ){ this.cb = cb if(typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) } this.id = ++id; this.value = this.get() } get(){ pushTarget(this) const value = this.getter.call(vData, vData) return value } update(){ this.value = this.get() this.cb.call(vData, vData) } } /** * utils */ function parsePath(expOrFn){ let segments = expOrFn.split('.') return function(obj){ for(let i = 0; i < segments.length; i++) { // console.log('obj', obj) obj = obj[segments[i]] } return obj } } function initWatcher(watch){ Object.keys(watch).forEach(prop => { new Watcher(vData,prop, watch[prop]) }) } /* * proxy method on instance */ function initMethods(methods) { let fn = noop Object.keys(methods).forEach(prop => { if(vm.props[prop]) { console.error(`methods ${prop} has allready on props`) return } if(vm.computed[prop]) { console.error(`methods ${prop} has allready on computed`) return } fn = methods[prop] vm[prop] = typeof fn !== 'function' ? noop : fn.bind(vm) }) } function Vue({data, computed, watch,methods}){ initData(vData) initComputed(vm,computed) initWatcher(watch) initMethods(methods) } new Vue({ data: vData, computed: vm.computed, watch: vm.watch, methods: vm.methods })
如何computed的依赖收集
如何实现user-watcher的依赖收集
proxy methods on vm
下面是简化版实现。