#013#vue3的响应式原理
创始人
2024-06-03 01:41:57
0

前言

日志,各位看官就当乐子看吧。

正经人谁写日记啊?!! ——鹅城县长

当提及Vue时,会说起什么呢?

  1. 基础:指令的区别或者原理,如v-showv-if
  2. 解析过程,即生命周期;
  3. 它的特性的原理:如何实现响应式,这其中就包括refreactive
  4. 组件
  5. vue3的区别

今天看看如何实现响应式的,还有vue2是如何实现的。

vue3和vue2的区别

Proxy和defineProperty

原文:https://vue3js.cn/interview/vue3/proxy.html

defineProperty

vue2 使用defineProperty生成响应式对象。下面通过代码看看如何实现的:

function update(value){app.innerText=obj.foo
}
function defineReactive(obj, key, val){Object.defineProperty(obj, key, {get() {console.log(`get ${key}: ${val}`)return val;},set(newVal){if(newVal !== val){val=newValupdate()}}})
}

调用defineProperty实现obj.foo的数据响应式,

const obj={}
defineProperty(obj, 'foo', '')
setTimeout(()=>{obj.foo = new Date().toLocaleTimeString()
},1000)

1秒后,数据发生变化,update方法会被触发。当存在多个key时,需要利用遍历,给obj中的每个key设置getset方法

function observe(obj) {if (typeof obj !== 'object' || obj==null){return;}Object.keys(obj).forEach(k => {defineReactive(obj, k, obj[k])})
}

如果value也是个对象,那么还需要在defineProperty中利用递归,

// defineReactive 函数
observe(val)    // 递归设置val
Object.defineProperty(obj, key, {get() {console.log(`get ${key}: ${val}`)return val;},set(newVal){if(newVal !== val){val=newValupdate()}}
})

当赋的是一个对象时,set同样需要递归

set(newVal) {if(newVal !== val){observe(newVal)notifyUpdate()}
}

基本的对象响应式完整代码:

function update(value){app.innerText=obj.foo
}
function defineReactive(obj, key, val){observe(val)Object.defineProperty(obj, key, {get() {console.log(`get ${key}: ${val}`)return val;},set(newVal) {if(newVal !== val){observe(newVal)notifyUpdate()}}})
}
function observe(obj) {if (typeof obj !== 'object' || obj==null){return;}Object.keys(obj).forEach(k => {defineReactive(obj, k, obj[k])})
}// 使用
const obj={foo: 'foo',bar: 'bar'
}
observe(obj)

但是,其中还存在较多问题:

  1. 无法劫持删除添加操作
  2. 数组的监听,并不好用,对数组的api(push,pop等)无法劫持
  3. 可以看到当数据是嵌套对象时,需要递归,会对性能产生影响(这个问题 vue3 似乎也存在)

对上述1,2两个问题,vue2的解决方案:

// 数组重写
const originalProto=Array.prototype
const arrayProto=Object.create(originalProto)
['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'].forEach(method => {arrayProto[method] = function(){originalProto(method).apply(this.arguments);dep.notice();}
})

Proxy

Proxy可以代理对象的默认行为,我们可以改变默认行为,实现自定义。能代理的行为有:

  • get(target,propKey,receiver):拦截对象属性的读取
  • set(target,propKey,value,receiver):拦截对象属性的设置
  • has(target,propKey):拦截propKey in proxy的操作,返回一个布尔值
  • deleteProperty(target,propKey):拦截delete proxy[propKey]的操作,返回一个布尔值
  • ownKeys(target):拦截Object.keys(proxy)、for…in等循环,返回一个数组
  • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象
  • defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc),返回一个布尔值
  • 其他:preventExtensions(target)getPrototypeOf(target)isExtensible(target)setPrototypeOf(target, proto)apply(target, object, args)construct(target, args)

Reflect对象,用于调用对象原先的默认行为,上面提到的所有内容。

补充
通过Proxy.revocable(target, handler);可以创建一个可撤销的代理对象。调用revoke()方法即可撤销。

使用Proxy实现一个响应式方法reactive

function reactive(obj) {if (typeof obj !== 'object' && obj != null) {return obj}// Proxy相当于在对象外层加拦截const observed = new Proxy(obj, {get(target, key, receiver) {const res = Reflect.get(target, key, receiver)console.log(`获取${key}:${res}`)// 如果是嵌套对象,使用递归return isObject(res) ? reactive(res) : res},set(target, key, value, receiver) {const res = Reflect.set(target, key, value, receiver)console.log(`设置${key}:${value}`)return res},deleteProperty(target, key) {const res = Reflect.deleteProperty(target, key)console.log(`删除${key}:${res}`)return res}})return observed
}

defineProperty不同,Proxy直接劫持对象,所以不需要通过遍历劫持对象属性了。

挂载时发生了什么

// App.vue
import { createApp } from 'vue'
// 从一个单文件组件中导入根组件
import App from './App.vue'const app = createApp(App)
app.mount('#app')

1. createApp

export const createApp = ((...args) => {const app = ensureRenderer().createApp(...args)...
});

2. ensureRenderer

function ensureRenderer() {return renderer || (renderer = createRenderer(rendererOptions))
}

3. createRenderer

export function createRenderer(options: RendererOptions) {return baseCreateRenderer(options)
}

4. baseCreateRenderer

这个函数中,实现了vnodediffpatch,很大很重要,目前只关注其返回值。

function baseCreateRenderer(options: RendererOptions,createHydrationFns?: typeof createHydrationFunctions
): any {const {insert: hostInsert,remove: hostRemove,patchProp: hostPatchProp,createElement: hostCreateElement,createText: hostCreateText,createComment: hostCreateComment,setText: hostSetText,setElementText: hostSetElementText,parentNode: hostParentNode,nextSibling: hostNextSibling,setScopeId: hostSetScopeId = NOOP,cloneNode: hostCloneNode,insertStaticContent: hostInsertStaticContent} = options// ....此处省略两千行,我们先不管return {render,hydrate,createApp: createAppAPI(render, hydrate)}
}

5. createAppAPI

export function createAppAPI(render: RootRenderFunction,hydrate?: RootHydrateFunction
): CreateAppFunction {return function createApp(rootComponent, rootProps = null) {if (rootProps != null && !isObject(rootProps)) {__DEV__ && warn(`root props passed to app.mount() must be an object.`)rootProps = null}// 创建默认APP配置const context = createAppContext()...const app: App = {...// 都是一些眼熟的方法use() {},mixin() {},component() {},directive() {},// mount 我们拎出来讲mount() {},unmount() {},// ...}return app}
}

6. createAppContext

export function createAppContext(): AppContext {return {config: {isNativeTag: NO,devtools: true,performance: false,globalProperties: {},optionMergeStrategies: {},isCustomElement: NO,errorHandler: undefined,warnHandler: undefined},mixins: [],components: {},directives: {},provides: Object.create(null)}
}

OK,结束了,虽然有很多东西没看到:如何实现响应式的、如何构建VNode的,之后再看吧!

彩蛋

今天去了我这的市图书馆,环境还不错哈。

相关内容

热门资讯

求经典台词和经典旁白 求经典台词和经典旁白谁有霹雳布袋戏里的经典对白和经典旁白啊?朋友,你尝过失去的滋味吗? 很多人在即将...
小王子第二章主要内容概括 小王子第二章主要内容概括小王子第二章主要内容概括小王子第二章主要内容概括
爱情睡醒了第15集里刘小贝和项... 爱情睡醒了第15集里刘小贝和项天骐跳舞时唱的那首歌是什么谢谢开始找舞伴的时候是林俊杰的《背对背拥抱》...
世界是什么?世界是什么概念?可... 世界是什么?世界是什么概念?可以干什么?物质的和意识的 除了我们生活的地方 比方说山 河 公路 ...
全职猎人中小杰和奇牙拿一集被抓 全职猎人中小杰和奇牙拿一集被抓动画片是第五十九集,五十八集被发现,五十九被带回基地,六十逃走
“不周山”意思是什么 “不周山”意思是什么快快快快......一座山,神话里被共工撞倒了。
《揭秘》一元一分15张跑得快群... 一元一分麻将群加群主微【ab120590】【tj525555】 【mj120590】等风也等你。喜欢...
玩家必看手机正规红中麻将群@2... 好运连连,全网推荐:(ab120590)(mj120590)【tj525555】-Q号:(QQ443...
始作俑者15张跑的快群@24小... 微信一元麻将群群主微【ab120590】 【tj525555】【mj120590】一元一分群内结算,...
《重大通知》24小时一元红中麻... 加V【ab120590】【tj525555】【mj120590】红中癞子、跑得快,等等,加不上微信就...
盘点一下正规一块红中麻将群@2... 一元一分麻将群加群主微:微【ab120590】 【mj120590】【tj525555】喜欢手机上打...
(免押金)上下分一元一分麻将群... 微【ab120590】 【mj120590】【tj525555】专业麻将群三年房费全网最低,APP苹...
[解读]正规红中麻将跑的快@群... 微信一元麻将群群主微【ab120590】 【tj525555】【mj120590】一元一分群内结算,...
《普及一下》全天24小时红中... 微【ab120590】 【mj120590】【tj525555】专业麻将群三年房费全网最低,APP苹...
优酷视频一元一分正规红中麻将... 好运连连,全网推荐:(ab120590)(mj120590)【tj525555】-Q号:(QQ443...
《火爆》加入附近红中麻将群@(... 群主微【ab120590】 【mj120590】【tj525555】免带押进群,群内跑包包赔支持验证...
《字节跳动》哪里有一元一分红中... 1.进群方式-[ab120590]或者《mj120590》【tj525555】--QQ(QQ4434...
全网普及红中癞子麻将群@202... 好运连连,全网推荐:(ab120590)(mj120590)【tj525555】-Q号:(QQ443...
「独家解读」一元一分麻将群哪里... 1.进群方式《ab120590》或者《mj120590》《tj525555》--QQ(4434063...
通知24小时不熄火跑的快群@2... 1.进群方式《ab120590》或者《mj120590》《tj525555》--QQ(4434063...