前言
日志,各位看官就当乐子看吧。
正经人谁写日记啊?!! ——鹅城县长
当提及Vue
时,会说起什么呢?
- 基础:指令的区别或者原理,如
v-show
、v-if
- 解析过程,即生命周期;
- 它的特性的原理:如何实现响应式,这其中就包括
ref
、reactive
; - 组件
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=newVal
update()
}
}
})
}
调用defineProperty
实现obj.foo
的数据响应式,
const obj={}
defineProperty(obj, 'foo', '')
setTimeout(()=>{
obj.foo = new Date().toLocaleTimeString()
},1000)
1秒后,数据发生变化,update
方法会被触发。当存在多个key
时,需要利用遍历,给obj
中的每个key
设置get
和set
方法
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=newVal
update()
}
}
})
当赋的是一个对象时,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)
但是,其中还存在较多问题:
- 无法劫持删除和添加操作
- 对数组的监听,并不好用,对数组的api(push,pop等)无法劫持
- 可以看到当数据是嵌套对象时,需要递归,会对性能产生影响(这个问题 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
<div id="app"></div>
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<
HostNode = RendererNode,
HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer<HostNode, HostElement>(options)
}
4. baseCreateRenderer
这个函数中,实现了vnode
、diff
、patch
,很大很重要,目前只关注其返回值。
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<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
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的,之后再看吧!
彩蛋
今天去了我这的市图书馆,环境还不错哈。