We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mobx 最关键的函数在于 autoRun,举个例子,它可以达到这样的效果:
autoRun
const obj = observable({ a: 1, b: 2 }) autoRun(() => { console.log(obj.a) }) obj.b = 3 // 什么都没有发生 obj.a = 2 // observe 函数的回调触发了,控制台输出:2
我们发现这个函数非常智能,用到了什么属性,就会和这个属性挂上钩,从此一旦这个属性发生了改变,就会触发回调,通知你可以拿到新值了。没有用到的属性,无论你怎么修改,它都不会触发回调,这就是神奇的地方。
使用 autoRun 实现 mobx-react 非常简单,核心思想是将组件外面包上 autoRun,这样代码中用到的所有属性都会像上面 Demo 一样,与当前组件绑定,一旦任何值发生了修改,就直接 forceUpdate,而且精确命中,效率最高。
mobx-react
forceUpdate
autoRun 的专业名词叫做依赖收集,也就是通过自然的使用,来收集依赖,当变量改变时,根据收集的依赖来判断是否需要更新。
为了兼容,Mobx 使用了 Object.defineProperty 拦截 getter 和 setter,但是无法拦截未定义的变量,为了方便,我们使用 proxy 来讲解,而且可以监听未定义的变量哦。
Object.defineProperty
getter
setter
proxy
众所周知,事件监听是需要预先存储的,autoRun 也一样,为了知道当变量修改后,哪些方法应该被触发,我们需要一个存储结构。
首先,我们需要存储所有的代理对象,让我们无论拿到原始对象,还是代理对象,都能快速的找出是否有对应的代理对象存在,这个功能用在判断代理是否存在,是否合法,以及同一个对象不会生成两个代理。
代码如下:
const proxies = new WeakMap() function isObservable<T extends object>(obj: T) { return (proxies.get(obj) === obj) }
重点来了,第二个要存储的是最重要的部分,也就是所有监听!当任何对象被改变的时候,我们需要知道它每一个 key 对应着哪些监听(这些监听由 autoRun 注册),也就是,最终会存在多个对象,每个对象的每个 key 都可能与多个 autoRun 绑定,这样在更新某个 key 时,直接触发与其绑定的所有 autoRun 即可。
key
const observers = new WeakMap<object, Map<PropertyKey, Set<Observer>>>()
第三个存储结构就是待观察队列,为了使同一个调用栈多次赋值仅执行一次 autoRun,所有待执行的都会放在这个队列中,在下一时刻统一执行队列并清空,执行的时候,当前所有 autoRun 都是在同一时刻触发的,所以让相同的 autoRun 不用触发多次即可实现性能优化。
const queuedObservers = new Set()
我们还要再存储两个全局变量,分别是是否在队列执行中,以及当前执行到的 autoRun。
let queued = false let currentObserver: Observer = null
这一步讲解的是 observable 做了哪些事,首先第一件就是,如果已经存在代理对象了,就直接返回。
observable
function observable<T extends object>(obj: T = {} as T): T { return proxies.get(obj) || toObservable(obj) }
我们继续看 toObservable 函数,它做的事情是,实例化代理,并拦截 get set 等方法。
toObservable
get
set
我们先看拦截 get 的作用:先拿到当前要获取的值 result,如果这个值在代理中存在,优先返回代理对象,否则返回 result 本身(没有引用关系的基本类型)。
result
上面的逻辑只是简单返回取值,并没有注册这一步,我们在 currentObserver 存在时才会给对象当前 key 注册 autoRun,并且如果结果是对象,又不存在已有的代理,就调用自身 toObservable 再递归一遍,所以返回的对象一定是代理。
currentObserver
registerObserver 函数的作用是将 targetObj -> key -> autoRun 这个链路关系存到 observers 对象中,当对象修改的时候,可以直接找到对应 key 的 autoRun。
registerObserver
targetObj -> key -> autoRun
observers
那么 currentObserver 是什么时候赋值的呢?首先,并不是访问到 get 就要注册 registerObserver,必须在 autoRun 里面的才符合要求,所以执行 autoRun 的时候就会将当前回调函数赋值给 currentObserver,保证了在 autoRun 函数内部所有监听对象的 get 拦截器都能访问到 currentObserver。以此类推,其他 autoRun 函数回调函数内部变量 get 拦截器中,currentObserver 也是对应的回调函数。
const dynamicObject = new Proxy(obj, { // ... get(target, key, receiver) { const result = Reflect.get(target, key, receiver) // 如果取的值是对象,优先取代理对象 const resultIsObject = typeof result === 'object' && result const existProxy = resultIsObject && proxies.get(result) // 将监听添加到这个 key 上 if (currentObserver) { registerObserver(target, key) if (resultIsObject) { return existProxy || toObservable(result) } } return existProxy || result }), // ... })
setter 过程中,如果对象产生了变动,就会触发 queueObservers 函数执行回调函数,这些回调都在 getter 中定义好了,只需要把当前对象,以及修改的 key 传过去,直接触发对应对象,当前 key 所注册的 autoRun 即可。
queueObservers
const dynamicObject = new Proxy(obj, { // ... set(target, key, value, receiver) { // 如果改动了 length 属性,或者新值与旧值不同,触发可观察队列任务 if (key === 'length' || value !== Reflect.get(target, key, receiver)) { queueObservers<T>(target, key) } // 如果新值是对象,优先取原始对象 if (typeof value === 'object' && value) { value = value.$raw || value } return Reflect.set(target, key, value, receiver) }, // ... })
没错,主要逻辑已经全部说完了,新对象之所以可以检测到,是因为 proxy 的 get 会触发,这要多谢 proxy 的强大。
可能有人问 Object.defineProperty 为什么不行,原因很简单,因为这个函数只能设置某个 key 的 getter setter~。
symbol proxy reflect 这三剑客能做的事还有很多很多,这仅仅是实现 Object.observe 而已,还有更强大的功能可以挖掘。
symbol
reflect
Object.observe
es6 真的非常强大,呼吁大家抛弃 ie11,拥抱美好的未来!
The text was updated successfully, but these errors were encountered:
赞!
Sorry, something went wrong.
不错,要是能贴一些源码讲解一下就更好了
No branches or pull requests
Mobx 思想的实现原理
Mobx 最关键的函数在于
autoRun
,举个例子,它可以达到这样的效果:我们发现这个函数非常智能,用到了什么属性,就会和这个属性挂上钩,从此一旦这个属性发生了改变,就会触发回调,通知你可以拿到新值了。没有用到的属性,无论你怎么修改,它都不会触发回调,这就是神奇的地方。
autoRun 的用途
使用
autoRun
实现mobx-react
非常简单,核心思想是将组件外面包上autoRun
,这样代码中用到的所有属性都会像上面 Demo 一样,与当前组件绑定,一旦任何值发生了修改,就直接forceUpdate
,而且精确命中,效率最高。依赖收集
autoRun
的专业名词叫做依赖收集,也就是通过自然的使用,来收集依赖,当变量改变时,根据收集的依赖来判断是否需要更新。实现步骤拆解
为了兼容,Mobx 使用了
Object.defineProperty
拦截getter
和setter
,但是无法拦截未定义的变量,为了方便,我们使用proxy
来讲解,而且可以监听未定义的变量哦。步骤一 存储结构
众所周知,事件监听是需要预先存储的,
autoRun
也一样,为了知道当变量修改后,哪些方法应该被触发,我们需要一个存储结构。首先,我们需要存储所有的代理对象,让我们无论拿到原始对象,还是代理对象,都能快速的找出是否有对应的代理对象存在,这个功能用在判断代理是否存在,是否合法,以及同一个对象不会生成两个代理。
代码如下:
重点来了,第二个要存储的是最重要的部分,也就是所有监听!当任何对象被改变的时候,我们需要知道它每一个
key
对应着哪些监听(这些监听由autoRun
注册),也就是,最终会存在多个对象,每个对象的每个key
都可能与多个autoRun
绑定,这样在更新某个key
时,直接触发与其绑定的所有autoRun
即可。代码如下:
第三个存储结构就是待观察队列,为了使同一个调用栈多次赋值仅执行一次
autoRun
,所有待执行的都会放在这个队列中,在下一时刻统一执行队列并清空,执行的时候,当前所有autoRun
都是在同一时刻触发的,所以让相同的autoRun
不用触发多次即可实现性能优化。代码如下:
我们还要再存储两个全局变量,分别是是否在队列执行中,以及当前执行到的
autoRun
。代码如下:
步骤二 将对象加工可观察
这一步讲解的是
observable
做了哪些事,首先第一件就是,如果已经存在代理对象了,就直接返回。代码如下:
我们继续看
toObservable
函数,它做的事情是,实例化代理,并拦截get
set
等方法。我们先看拦截
get
的作用:先拿到当前要获取的值result
,如果这个值在代理中存在,优先返回代理对象,否则返回result
本身(没有引用关系的基本类型)。上面的逻辑只是简单返回取值,并没有注册这一步,我们在
currentObserver
存在时才会给对象当前key
注册autoRun
,并且如果结果是对象,又不存在已有的代理,就调用自身toObservable
再递归一遍,所以返回的对象一定是代理。registerObserver
函数的作用是将targetObj -> key -> autoRun
这个链路关系存到observers
对象中,当对象修改的时候,可以直接找到对应key
的autoRun
。那么
currentObserver
是什么时候赋值的呢?首先,并不是访问到get
就要注册registerObserver
,必须在autoRun
里面的才符合要求,所以执行autoRun
的时候就会将当前回调函数赋值给currentObserver
,保证了在autoRun
函数内部所有监听对象的get
拦截器都能访问到currentObserver
。以此类推,其他autoRun
函数回调函数内部变量get
拦截器中,currentObserver
也是对应的回调函数。代码如下:
setter
过程中,如果对象产生了变动,就会触发queueObservers
函数执行回调函数,这些回调都在getter
中定义好了,只需要把当前对象,以及修改的key
传过去,直接触发对应对象,当前key
所注册的autoRun
即可。代码如下:
没错,主要逻辑已经全部说完了,新对象之所以可以检测到,是因为
proxy
的get
会触发,这要多谢proxy
的强大。可能有人问
Object.defineProperty
为什么不行,原因很简单,因为这个函数只能设置某个key
的getter
setter
~。symbol
proxy
reflect
这三剑客能做的事还有很多很多,这仅仅是实现Object.observe
而已,还有更强大的功能可以挖掘。总结
es6 真的非常强大,呼吁大家抛弃 ie11,拥抱美好的未来!
The text was updated successfully, but these errors were encountered: