浅聊一下
前两天写了一个“丐中丐”版的Vue3的呼应式实现原理,现在回想起来感到十分的羞愧,reactive,是我对不住你,再给我一次机会,这次我必定好好来写你…
假如您也和我相同,在预备春招。欢迎加我微信shunwuyu,这儿有几十位专心去大厂的友友能够彼此鼓励,共享信息,模仿面试,共读源码,齐刷算法,手撕面经。来吧,友友们!
开始
在从头手写reactive之前,假如还有不理解Vue3呼应式的掘友们能够先去看我的一篇文章,看完再来阅览本文(到底该用ref仍是reactive???)
首要仍是请上永久18岁的坤坤
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="module">
import { reactive } from './reactive.js'
const state = reactive({
name:'坤坤',
age:18
})
setInterval(()=>{
state.age++
},1000)
</script>
</body>
</html>
依照reactive的用法,将state变成呼应式方针,设置一个定时器,每一秒改动一次坤坤的年龄,不过我这儿引证的reactive是自己写的,接下来,咱们就来实现这个reactive
实现
其实实现reactive分三步走
- 用Proxy署理方针
- 在署理函数 get 中 对使用了的特点做副作用函数搜集
- 在署理函数 set 中 对修改了的特点做副作用函数触发
接下来咱们就来一步步完成
用Proxy署理方针
本着一个函数完成一个功能的原则,将reactive拆分
export function reactive(target){//将target变成呼应式
return createReactiveObject(target,mutableHandlers)
}
export function createReactiveObject(targe,proxyHandlers){//创立呼应式函数
//判别target是否是引证类型
if(typeof target !== 'object' || target === null){//不是方针就不给操作
return target
}
}
在下面完成校验你传进来的是不是一个方针,假如不是一个方针,那我就直接回来…为什么必定要是方针类型呢?
Proxy 能够理解成,在方针方针之前架起一层“拦截”,外界对该方针的访问,都必须先通过这层拦截,因而提供了一种机制,能够对外界的访问进行过滤和改写。Proxy 这个词的本意是署理,用在这儿表示由它来“署理”某些操作,能够译为“署理器”。
这就能解释理解了,由于Proxy只能处理方针类型的数据…
言归正传,接下来当咱们传入的是一个方针今后,该进行什么操作?判别一下,这个传进来target方针是否现已是一个署理方针,假如我传进来的方针现已是一个署理方针,还要对他进行署理,那不是画蛇添足嘛…
export const reactiveMap = new WeakMap();//new Weakmap 对内存的收回愈加友好
export function reactive(target){//将target变成呼应式
return createReactiveObject(target,reactiveMap,mutableHandlers)
}
export function createReactiveObject(target,proxyMap,proxyHandlers){//创立呼应式函数
//判别target是否是引证类型
if(typeof target !== 'object' || target === null){//不是方针就不给操作
return target
}
//该方针是否现已被署理过(现已是呼应式方针)
const existingProxy = proxyMap.get(target);
if(existingProxy){
return existingProxy
}
}
界说一个Weakmap方针,用来保存现已署理过的方针,首要判别Weakmap方针中是否含有target,假如含有,就直接回来target的署理方针existingProxy,假如没有呢,再进行下一步操作
export const reactiveMap = new WeakMap();//new Weakmap 对内存的收回愈加友好
export function reactive(target){//将target变成呼应式
return createReactiveObject(target,reactiveMap,mutableHandlers)
}
export function createReactiveObject(target,proxyMap,proxyHandlers){//创立呼应式函数
//判别target是否是引证类型
if(typeof target !== 'object' || target === null){//不是方针就不给操作
return target
}
//该方针是否现已被署理过(现已是呼应式方针)
const existingProxy = proxyMap.get(target);
if(existingProxy){
return existingProxy
}
//履行署理操作(将target处理成呼应式)
const proxy = new Proxy(target,proxyHandlers)
//往proxyMap 增加 proxy,把现已署理过的方针缓存起来
proxyMap.set(target,proxy)
return proxy
}
各种判别完成今后,咱们就给传进来的target创立一个Proxy署理方针啦!,并且在创立完成今后,要记得把target和他的署理方针存入咱们的reactiveMap中…
在创立Proxy实例方针的时分,不要忘记咱们还有一个参数proxyHandlers
,咱们对target进行操作时,会被署理方针拦截,而咱们要进行的操作,就在proxyHandlers
里边
const get = createGetter()
const set = createSetter()
function createGetter(){
return function get(target, key,receiver){
return Reflect.get(target, key,receiver);
}
}
function createSetter(){
return function set(target, key, value,receiver) {
return Reflect.set(target, key, value,receiver);
}
}
export const mutableHandlers = {
get,
set,
}
这儿创立了一个 get 和 set 函数,并且写进 mutableHandlers
里
在署理函数 get 中 对使用了的特点做副作用函数搜集
什么是副作用函数?
副作用函数是指在履行进程中会发生额定的影响,而不仅仅是回来一个值的函数。也便是说,或许改动外部特点的函数就叫副作用函数,例如computed
和watch
便是副作用函数,不知道computed
和watch
的掘友也能够去看看我的文章(都要春招了,还不知道computed和watch有什么区别??)
咱们要搜集这些副作用函数,由于当咱们的呼应式方针进行更新的时分,咱们要通知这些副作用函数调用一次,才干实现对外部数据的呼应式更新…话不多说,来看看怎么搜集副作用函数
const targetMap = new WeakMap();
let activeEffect = null;//一个副作用函数
export function track(target,key){
// targetMap = {
// target:{
// key:[Effect1,Effect2]
// }
// }
let depsMap = targetMap.get(target);
if (!depsMap){//初度读取到值 搜集effect
targetMap.set(target,depsMap = new Map())
}
let deps = depsMap.get(key);
if (!deps){//该特点还未添加过副作用
depsMap.set(key,deps = new Set());
}
if(!deps.has(activeEffect) && activeEffect){
//存入一个effect函数
deps.add(activeEffect);
}
depsMap.set(key,deps);
}
界说了一个activeEffect副作用函数,让他暂时为null。咱们将搜集来的副作用函数以如下结构贮存
targetMap = {
target:{
key:[Effect1,Effect2]
}
}
解释一下,代码中的targetMap
便是最外层的结构,用来贮存target和他的key
targetMap = {
target: key
}
depsMap
便是里边的那层结构,用来贮存key和他具有的副作用函数
target:{
key:[Effect1,Effect2]
}
deps
便是key具有的副作用函数的数组
-
let depsMap = targetMap.get(target);
:从targetMap
中获取方针方针target
对应的依靠映射。 -
if (!depsMap)
:查看是否存在depsMap
,假如不存在,则阐明之前没有盯梢过这个方针方针的依靠联系。 -
targetMap.set(target, depsMap = new Map())
:假如不存在depsMap
,则创立一个新的Map
方针,并将它与方针方针target
相关起来,作为该方针的依靠映射。 -
let deps = depsMap.get(key);
:从依靠映射中获取方针特点key
对应的依靠调集。 -
if (!deps)
:查看是否存在依靠调集deps
,假如不存在,则阐明之前没有盯梢过这个方针特点的依靠联系。 -
depsMap.set(key, deps = new Set());
:假如不存在依靠调集deps
,则创立一个新的Set
方针,并将它与方针特点key
相关起来,作为该特点的依靠调集。 -
if (!deps.has(activeEffect) && activeEffect)
:查看当时活动的副作用函数activeEffect
是否现已存在于依靠调集中,假如不存在且当时有活动的副作用函数,就将其添加到依靠调集中。 -
deps.add(activeEffect);
:将当时活动的副作用函数activeEffect
添加到依靠调集中,树立依靠联系。 -
depsMap.set(key, deps);
:将更新后的依靠调集从头相关到方针特点key
上,保证依靠调集的最新状态被保存在依靠映射中。
咱们在什么时分调用这个函数呢?get中,当咱们要操作这个target的时分,第一步肯定是会触发get函数的,所以咱们要在get中搜集副作用函数
function createGetter(){
return function get(target, key,receiver){
//这个特点究竟还有哪些地方用到了(副作用函数的搜集,computed,watch等等)
track(target,key)
return Reflect.get(target, key,receiver);
}
}
已然搜集完了副作用函数,下一步便是在改动target的时分触发他们了
在署理函数 set 中 对修改了的特点做副作用函数触发
export function trigger(target,key){
const depsMap = targetMap.get(target);
if(!depsMap){//当时方针中所有的key都没有副作用函数(从来没有使用过)
return
}
const deps = depsMap.get(key);
if(!deps){
return
}
deps.forEach(effectFn => {
effectFn()
});
}
这儿就很简略了,便是看这个key有没有副作用函数,有就拿出来全部调用一遍
那么咱们这儿还有一个问题,咱们的副作用函数上面界说的不是一个null吗,这怎么搞
export function effect(fn,options = {}){ //watch 和 computed 的核心逻辑
const effectFn = ()=>{
try{
activeEffect = effectFn
return fn()
}finally{
activeEffect = null
}
}
if(!options.lazy){
effectFn();
}
return effectFn;
}
-
export function effect(fn, options = {})
:这是一个导出的函数effect
,它承受两个参数:fn
(要履行的函数)和options
(选项方针,包含一些额定的配置参数)。 -
const effectFn = () => { ... }
:界说了一个内部函数effectFn
,它是实践履行副作用的函数体。这个函数体内部有一个try...finally
块,用来保证在履行完fn
后,将activeEffect
从头置为null
,以避免副作用函数被过错地嵌套调用。 -
try { activeEffect = effectFn; return fn(); } finally { activeEffect = null; }
:在try
块中,将activeEffect
设置为当时的effectFn
,然后调用传入的函数fn
,并回来其履行成果。在finally
块中,将activeEffect
从头置为null
,保证不会影响其他副作用函数。 -
if (!options.lazy) { effectFn(); }
:假如在options
中没有设置lazy
特点或者lazy
特点为假值,则当即履行effectFn
,否则将推迟履行。 -
return effectFn;
:回来创立的副作用函数effectFn
。
副作用函数触发写完了,最终添加到set函数中
function createSetter(){
return function set(target, key, value,receiver) {
//需求记录下来此刻是哪一个key的值改变了,再去通知其他依靠该值的函数收效,更新浏览器的视图(呼应式)
//触发被修改的特点身上的副作用函数 依靠搜集(被修改的key在哪些地方被用到了) 发布订阅
trigger(target,key)
return Reflect.set(target, key, value,receiver);
}
}
看看效果
来到最开始的页面上
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="module">
import { reactive } from './reactive.js'
import { effect } from './effect.js'
const state = reactive({
name:'坤坤',
age:18
})
console.log(state.age)
effect(
()=>console.log(`${state.name}本年${state.age}岁了`),
{lazy:false}
)
setInterval(()=>{
state.age++
},1000)
</script>
</body>
</html>
使用咱们创立的副作用函数,来打印一下坤坤的年龄
结束
原Vue3源码呢写的是Typescript版别的,为了简化一点点,就写了js版别的reactive…首要我写的上一版太丑恶了,从头加工…
假如您也和我相同,在预备春招。欢迎加我微信shunwuyu,这儿有几十位专心去大厂的友友能够彼此鼓励,共享信息,模仿面试,共读源码,齐刷算法,手撕面经。来吧,友友们!