吴忠市住房和城乡建设局网站,怎样做商城手机网站,网站开发专业就业好不好,自己做的网站怎么放到外网上一、副作用函数。 副作用函数指的是会产生副作用的函数。例如#xff1a;effect函数会直接或间接影响其他函数的执行#xff0c;这时我们便说effect函数产生了副作用。
function effect(){document.body.innerText hello vue3
} 再例如#xff1a;
//全局变量let val 2f…一、副作用函数。 副作用函数指的是会产生副作用的函数。例如effect函数会直接或间接影响其他函数的执行这时我们便说effect函数产生了副作用。
function effect(){document.body.innerText hello vue3
} 再例如
//全局变量let val 2function effect() {val 2 //修改全局变量产生副作用
}
二、响应式数据。 上代码
const obj { text: hello world }function effect() {// effect 函数的执行会读取 obj.textdocument.body.innerText obj.text
} 当前修改obj.text的值的时候除了本身的发生变化之外不会有任何其他反应。 若修改obj.text的值的时候effect函数副作用函数自动重新执行如果能实现这个目标那么obj对象就是响应式数据。很显然目前还不能实现接下来我们将数据变成响应式数据。
三、响应式数据的基本实现。 接上思考如何将数据变为响应式数据呢 通过上面我们可以发现有两点 1.当effect副作用函数执行时触发obj.text的读取操作。 2.当修改obj.text的值的时候出发obj.text的设置操作。 问题的关键我们如何才能拦截一个对象属性的读取和设置操作。在ES2015之前只能通过Object.defineProperty函数实现这也是Vue.js 2所采用的方式。在ES2015中我们可以使用Proxy代理对象来实现这也是Vue.js 3所采用的方式。 采用Proxy来实现
/*** 实现一个响应式 * param { Object } bucket* param { Object } data* param { Function } effect* param { Object } obj */
// 存储副作用函数的桶
const bucket new Set()// 副作用函数
function effect() {console.log(obj.text)
}//原始数据
const data { text: hello world }
//对数据的代理
const obj new Proxy(data, {//拦截读取操作get(target, key) {//将副作用函数加入到桶里bucket.add(effect)//返回属性值return target[key]},//拦截设置操作set(target, key, newVal) {//设置属性值target[key] newVal//把副作用函数从桶里取出来并执行bucket.forEach(fun fun())//返回 true 代表设置操作成功return true}
})
effect()setTimeout(() {console.log(一秒后触发设置)obj.text hello vue3
},1000) 目前还存在许多缺陷我们需要去掉通过名字来获取副作用函数的硬编码机制 。
四、实现一个完善的响应式系统。 1.解决副作用函数收集到桶里的硬编码机制——我们需要注册一个副作用函数的机制
/*** 注册副作用函数机制* param {any} activeEffect* param {Function} effect* param {Object} obj1* param {Object} data 用的是上面的*/
//用全局变量存储被注册的副作用函数
let activeEffect
// effect 函数用于注册副作用函数
function effect(fn) {// 当调用effect注册副作用函数时将副作用函数赋值给activeEffectactiveEffect fn// 执行副作用函数fn()
}
const obj1 new Proxy(data, {//拦截读取操作get(target, key) {//--将副作用函数加入到桶里//--bucket.add(effect)//将activeEffect中存储的副作用函数收集到“桶”中if (activeEffect) {bucket.add(activeEffect)}//返回属性值return target[key]},//拦截设置操作set(target, key, newVal) {//设置属性值target[key] newVal//把副作用函数从桶里取出来并执行bucket.forEach(fun fun())//返回 true 代表设置操作成功return true}
})
effect(() {console.log(run, obj1.text)}
)
setTimeout(() {console.log(一秒后触发设置)obj1.notExist hello vue3
},1000) 存在问题 没有在副作用函数与被操作的目标字段之间建立明确的联系。无论读取的是哪一个属性都会把副作用函数收集到“桶”里。
2.解决上述问题。 首先分析一下注册副作用函数触发都存在哪些角色 ①obj1对象 ②text字段 ③使用副作用函数注册的函数 我们来为这三个角色建立一个树形关系。target表示obj1——代理对象所代理的原始对象用key来表示text字段——被操作的字段名effectFn表示被注册的副作用函数。这个联系建立起来后就可以解决前文提到的问题了。 我们需要重新设计“桶”的数据结构不能简单的去使用Set类型的数据作为“桶”我们需要将Set桶改为WeakMap桶。 为啥要用到WeakMap而不是Map 因为WeakMap对key是弱引用只有被引用的有价值的信息可以访问没有被引用的信息就会被垃圾回收器回收。如果是Map即使信息没有引用垃圾回收器也不会去回收它那么就会有很大机率导致内存溢出。 代码如下
const bucketMap new WeakMap()
const obj2 new Proxy(data, {get(target, key) {// 没有activeEffect直接返回if(!activeEffect) return target[key]// 取出WeakMap桶里的值 target keylet depsMap bucketMap.get(target)// 如果不存在depsMap那就新建Map与target建立联系if(!depsMap) {bucketMap.set(target, (depsMap new Map()))}// key effectFnlet deps depsMap.get(key)if(!deps) {depsMap.set(key, deps new Set())}// 注册副作用函数deps.add(activeEffect)return target[key]},set(target, key, newVal) {target[key] newVal// 取targetconst depsMap bucketMap.get(target)if(!depsMap) return// 根据key取副作用函数const effects depsMap.get(key)// 执行副作用函数effect effect.forEach(fn fn())return true}
}) 我们可以将activeEffect注册副作用函数机制单独封装到一个函数track中表达追踪的含义。将触发副作用函数单独封装到trigger函数中。代码更改如下
/*** 建立联系* param { Object } bucketMap* param { Object } obj2* param { Function } track 追踪* param { Function } trigger 触发*/
// WeakMap桶
const bucketMap new WeakMap()
function track(target, key) {// 没有activeEffect直接返回if (!activeEffect) return target[key]// 取出WeakMap桶里的值 target keylet depsMap bucketMap.get(target)// 如果不存在depsMap那就新建Map与target建立联系if (!depsMap) {bucketMap.set(target, (depsMap new Map()))}// key effectFnlet deps depsMap.get(key)if (!deps) {depsMap.set(key, deps new Set())}// 注册副作用函数deps.add(activeEffect)
}
function trigger(target, key) {// 取targetconst depsMap bucketMap.get(target)if (!depsMap) return// 根据key取副作用函数const effects depsMap.get(key)// 执行副作用函数effect effect.forEach(fn fn())
}
const obj2 new Proxy(data, {get(target, key) {// 注册副作用函数track(target, key)return target[key]},set(target, key, newVal) {target[key] newVal// 触发副作用函数trigger(target, key)return true}
})
五、分支切换与cleanup 我们用以下代码说明分支切换。如下
const data { ok: true, text: hello world}
const obj new Proxy(/*....*/)
effect(function effectFn{document.body.innerText obj.ok ? obj.text : not
}) 在effectFn函数内部的三元表达式根据ok字段值的不同会执行不同的代码分支。ok的值发生变化时代码执行的分支会根治变化这就是所谓的分支切换。 分支切换可能会产生一流的副作用函数。根据上面的代码案例来说effectFn与响应式数据建立的关系如下 副作用函数与响应式数据之间的联系 当修改ok字段值改为false的时候text的不会被读取所以指挥触发ok字段的读取而不会触发text读取所以理想状态下effectFn不应该被字段text所对应的依赖集合收集。 显然我们目前还不能做到这一点。 理想状态 遗留的副作用函数会导致不必要的更新。解决问题的思路就是每次副作用函数执行时我们可以先把它从所有与之关联的依赖几何中删除。当副作用函数执行完毕后会重新建立联系新的联系里不会包含遗留的副作用函数。 重新设计effectFn函数
function effect(fn) {const effectFn () {activeEffect effectFnfn()}// deps用来存储所有与这副作用函数相关联的依赖集合effectFn.deps []effectFn()
} 修改 track 追踪函数
function track(target, key) {if (!activeEffect) return target[key]let depsMap bucketMap.get(target)if (!depsMap) {bucketMap.set(target, (depsMap new Map()))}let deps depsMap.get(key)if (!deps) {depsMap.set(key, deps new Set())}deps.add(activeEffect)// 主要就是增加关联数组中 activeEffect.deps.push(deps)
} 对依赖集合收集 有了这个联系后我们就可以在每次副作用函数执行时根据deps获取所有相关联的依赖集合进而将副作用函数从依赖集合中移除。
function effect(fn) {const effectFn () {cleanup(effectFn)activeEffect effectFnfn()}// deps用来存储所有与这副作用函数相关联的依赖集合effectFn.deps []effectFn()
}
function cleanup(effectFn) {//遍历effectFn的deps数组for(let i 0; i effectFn.deps.length; i) {let deps effectFn.deps[i]deps.delete(effectFn)}// 最后需要重置effectFn.deps数组effectFn.deps.length 0
} 现在我们来执行一下这完整代码看会有啥效果
const data1 {ok: true,text: hello world
}
const obj2 new Proxy(data1, {get(target, key) {// 注册副作用函数track(target, key)return target[key]},set(target, key, newVal) {target[key] newVal// 触发副作用函数trigger(target, key)return true}
})function trigger(target, key) {// 取targetconst depsMap bucketMap.get(target)if (!depsMap) return// 根据key取副作用函数const effects depsMap.get(key)// 执行副作用函数effects effects.forEach(fn fn())
}/*** 重新设计effectFn* param { Function } effect* param { Function } cleanup* param { Function } track
*/
function effect(fn) {const effectFn () {cleanup(effectFn)activeEffect effectFnfn()}// deps用来存储所有与这副作用函数相关联的依赖集合effectFn.deps []effectFn()
}
function cleanup(effectFn) {//遍历effectFn的deps数组for(let i 0; i effectFn.deps.length; i) {let deps effectFn.deps[i]deps.delete(effectFn)}// 最后需要重置effectFn.deps数组effectFn.deps.length 0
}function track(target, key) {// 没有activeEffect直接返回if (!activeEffect) return target[key]// 取出WeakMap桶里的值 target keylet depsMap bucketMap.get(target)// 如果不存在depsMap那就新建Map与target建立联系if (!depsMap) {bucketMap.set(target, (depsMap new Map()))}// key effectFnlet deps depsMap.get(key)if (!deps) {depsMap.set(key, deps new Set())}// 注册副作用函数deps.add(activeEffect)// 主要就是增加关联数组中 activeEffect.deps.push(deps)
}// 测试
effect(() {let n obj2.ok ? obj2.text : not console.log(run, n)}
)
setTimeout(() {console.log(一秒后触发设置)obj2.ok false
},1000) 可以看到目前会无限不断去执行 问题出现在哪里呀问题便出现在trigger函数下面这句中。
effects effects.forEach(fn fn()) Why有啥问题来看下面代码
const set new Set([1])set.forEach(item {set.delete(1)set.add(1)console.log(遍历中)
})
· 由于不断执行我截不下全图。不断执行的原因语言规范中说过在调用forEach遍历Set集合时一个值被访问过了但被删除后又被重新添加到集合如果此时forEach遍历没有结束那么该值会重新被访问。所以上面代码会不断去执行 。 同理trigger函数里面的effects也是一样当副作用函数执行的时候cleanup会进行清除但是副作用函数的执行会导致其被重新收集到集合中而此时遍历仍然在进行所以我们实现的响应式才会不断的去执行。 如何更改无限循环呢 我们可以构造另一个Set集合并遍历它。我们去修改一下trigger触发函数
function trigger(target, key) {// 取targetconst depsMap bucketMap.get(target)if (!depsMap) return// 根据key取副作用函数const effects depsMap.get(key)// 执行副作用函数const effectToRun new Set(effects) //新增effectToRun effectToRun.forEach(fn fn()) //新增// effects effects.forEach(fn fn()) //剔除
} 如上图所示无限循环问题得以解决。
Vue响应式系统(二)