php网站欣赏,网站的链接要怎么做,代引流推广公司,加强部门网站建设vue3响应式原理初探 为什么要使用proxy取代defineProperty使用proxy如何完成依赖收集呢#xff1f; 为什么要使用proxy取代defineProperty
原因1#xff1a;defineproperty无法检测到原本不存在的属性。打个#x1f330; new Vue({data(){return {name:wxs,age:25}}})在vue… vue3响应式原理初探 为什么要使用proxy取代defineProperty使用proxy如何完成依赖收集呢 为什么要使用proxy取代defineProperty
原因1defineproperty无法检测到原本不存在的属性。打个 new Vue({data(){return {name:wxs,age:25}}})在vue2中底层会通过definproperty来响应式data返回的对象也就是{name:wxs,age:25},具体的响应式监听如下。 const obj {name:wxs,age:25}Object.entries(obj).forEach(([key,value]) {Object.defineProperty(obj,key,{get(){return value},set(newValue){console.log(监听到属性${key}改变)value newValue}})})obj.name 11obj.age 22obj.ak47 ak47看看控制台输出 可以看出由于初始化中并没有初始化ak47所以在vue2中对未初始化的对象key值的修改是无法监听到的。 再来看看es6新特性proxy的优越性 const obj {name:wxs,age:25}const prxoyTarget new Proxy(obj,{get(target,key){return target.key},set(target,key,value){console.log(监听到${key}需要改成${value})target[key] value}})prxoyTarget.name 11prxoyTarget.age 22prxoyTarget.ak47 ak47所以回答这个问题的思路就很清晰了 1、proxy可以完成对未初始化对象key的代理监听而definproperty不可以相比较之下proxy更加优越。 2、由vue2的响应式原理可以看出vue底层需要对vue实例的返回的每一个key进行get和set操作无论这个值有没有被用到。所以在vue中定义的data属性越多那么初始化开销就会越大。而proxy是一个惰性的操作它只会在用到这个key的时候才会执行get改值的时候才会执行set。所以在vue3中实现响应式的性能实际上要比vue2实现响应式性能要好。
使用proxy如何完成依赖收集呢
首先看到vue3官网中给出的初始化一个vue实例的基础用法。 因为我们需要响应式一个对象所以我们使用reactive api。
script srchttps://unpkg.com/vue3/dist/vue.global.js/scriptdiv idapp{{ message }}/divscriptconst { createApp, reactive, toRefs } VuecreateApp({setup() {const data reactive({name: wxs})return data}}).mount(#app)
/script由这个简单的实例可得知vue对象内部首先暴露了一个createApp函数用于新建vue实例那么大致写出Vue内部的执行逻辑注意看以下代码重点部分都有注释讲解
const Vue {// 从官网示例可得从vue中解构出了createApp函数那么其内部肯定有createApp的定义createApp(options){// createApp实现细节后文补充return {// 在mount中执行初次挂载mount(domSelector){// 在vue3的源码中也会在mount中执行挂载任务但是由于此文主要讲解的是vue3的响应式原理那么AST解析和diff比较就略过直接显示setup的返回值即可也就是模拟vue的模版内容为 div{{ name }}/div。 document.querySelector(domSelector).innerHTML 页面渲染}}},// 响应式代理在前一小节有介绍此节不做赘述。reactive(target){return new Proxy(target,{get(target,key){// 代理对象的get方法return target[key]},set(target,key,newValue){// 代理对象的set方法target[key] newValue}})}}将上述代码执行之后浏览器应该会出现如下页面 但是很明显我们想要将响应式数据呈现在页面上。比如代码中定义到的name。那么我们可以将createApp做如下改写。具体思路是拿出setUp的返回值。然后将返回值渲染到页面。 createApp(options) {let dataSource nullif (options.setup) {dataSource options.setup()}return {// 在mount中执行初次挂载mount(domSelector) {document.querySelector(domSelector).innerHTML dataSource.name}}}, 这样就将响应式数据和template模版之间构建了联系。但是千万不要被这个简化的代码所迷惑实际上vue底层会执行diff算法来比较最小化更新之后在渲染页面。因为此文是介绍vue3的响应式原理所以此过程略过 完成createApp的补充之后实际上我们已经完成了vue的初次渲染工作。那么剩下的就是需要完成vue组件的更新工作了其实也就是说在响应式数据更新的时候重新执行一下mount里的代码完成页面刷新 理清思路之后稍微重构一下mount函数
mount(domSelector) {const update () document.querySelector(domSelector).innerHTML dataSource.nameeffect(update)}实际上vue3是通过effect函数来将更新函数和响应式数据来建立链接。所谓的建立链接也就是通过effect执行的函数中如果包含了响应式对象如果响应式对象发生改变函数就会重新执行。 来看看effect函数的实现细节。 let currentCallback nullconst effect (callback) {currentCallback callbackcallback()currentCallback null}有同学可能会问了先存一下callback然后执行一下更新函数再置空那么这个赋值不就是脱了裤子放屁-----多此一举吗那么看看这两行中间夹缝生存的的这一行神奇的代码callback()。执行一下传入的callback那么也就是update函数。如果执行update函数必然会触发dataSource.name的get方法。那么我们就可以在这里完成依赖收集。将代理对象和key和更新函数之间建立如下的链接关系。 // 在get里面触发依赖收集get(target, key) {track(target,key)// 代理对象的get方法return target[key]},// 从上图出发定义的track函数const track (target,key) {let depMap reactiveMap.get(target)if(!depMap){depMap new Map()reactiveMap.set(target,depMap)}let watcherSet depMap.get(key)if(!watcherSet){watcherMap new Set();watcherSet.add(currentCallback)depMap.set(key,watcherSet)}}// 依赖收集完成定义触发函数 触发函数其实就是track函数的逆运算把track存起来的东西全部取出来const trigger (target, key) {reactiveMap.get(target).get(key).forEach(cb cb())}最后将trigger写到set里面。 set(target, key, newValue) {// 代理对象的set方法target[key] newValuetrigger(target, key)}大功告成可以直接改变对象值看看效果。 createApp({setup() {const data reactive({name: wxs})setTimeout(() {data.name 456}, 1000)return data}}).mount(#app)老规矩 贴上全部代码
!DOCTYPE html
htmlheadmeta charsetUTF-8!-- import CSS --link relstylesheet hrefhttps://unpkg.com/element-ui/lib/theme-chalk/index.cssstyle.pagination-container {position: sticky;bottom: 0;z-index: 100;}#box {width: 100%;height: 50%;background: black;}/style
/headbodydiv idapp{{name}}/div
/body!-- script srchttps://unpkg.com/vue3/dist/vue.global.js/script --scriptlet currentCallback nullconst effect (callback) {currentCallback callbackcallback()currentCallback null}let reactiveMap new Map();const track (target, key) {let depMap reactiveMap.get(target)if (!depMap) {depMap new Map()reactiveMap.set(target, depMap)}let watcherSet depMap.get(key)if (!watcherSet) {watcherSet [];watcherSet.push(currentCallback)depMap.set(key, watcherSet)}}const trigger (target, key) {reactiveMap.get(target).get(key).forEach(cb cb())}const Vue {createApp(options) {let dataSource nullif (options.setup) {dataSource options.setup()}return {// 在mount中执行初次挂载mount(domSelector) {const update () document.querySelector(domSelector).innerHTML dataSource.nameeffect(update)}}},reactive(target) {return new Proxy(target, {get(target, key) {track(target, key)// 代理对象的get方法return target[key]},set(target, key, newValue) {// 代理对象的set方法target[key] newValuetrigger(target, key)}})}}const { createApp, reactive } VuecreateApp({setup() {const data reactive({name: wxs})setTimeout(() {data.name 456}, 1000)return data}}).mount(#app)/script
/html各位看官稍安勿躁全部代码算上css才100行当然vue内部实现肯定比我这个要复杂比如它内部存更新函数是用的set等等但是对于响应式原理而言我们只需要拿出最精华的部分即可。第一遍看不懂没关系我看视频也是看了七八遍才看懂各位coder们一定要有耐心拿下vue3响应式