本篇文章会一直更新,直到把vue
源码看完,有啥问题就可以在评论中写上一起研究。
小黄鸭调试法
最近发现了一个及其有用的终极调试方法,就是小黄鸭调试法。 小黄鸭调试法: 传说中的大师都是随身携带小黄鸭的,遇到bug就向小黄鸭解释每一行代码的用处,可能讲到一半就灵光乍现,bug灰飞烟灭。
本来只看vue
源码的时候感觉遇到了很多问题,把这些问题记录下来,然后带着问题学习源码就感觉更加清晰。
差异
在vue2
中使用了Object.defineProperty
进行数据劫持,对于数组进行单独处理,修改数组原型上的方法
if (Array.isArray) {
this.observeArray(value)
} else {
this.walk(value)
}
在Vue3
使用了Proxy
进行数据劫持,优异于defineProperty
,Proxy
能够对数组修改进行监听。在Vue2
中如果使用list[index]=5
这种方式是无法监听到的,Proxy
解决了这种问题,不仅如此proxy
除了提供set
、get
还提供了has
、deleteProperty
等等。
在Vue2
中,对于我们在data
中所定义的数据,在Vue
初始化的时候,会遍历data
中所有的数据进行响应式。
JS中文网 – 前端进阶资源教程 www.javascriptC.com
一个致力于帮助开发者用代码改变世界为使命的平台,每天都可以在这里找到技术世界的头条内容
function defineReactive(obj, key, val) {
/* 对象的子对象递归进行observe并返回子节点的Observer对象 */
let childOb = observe(val)
}
当data
中绑定的数据足够大的时候,有些数据可能没有用到,就能造成不必要的性能损失。在Vue3
中,就避免了这种问题,在Vue
初始化的时候只会对data
这一层加上proxy
,当获取其中数据的时候,才会为对应的数据添加响应式
instance.data = reactive(data);
function reactive(target, handler) {
observed = new Proxy(target, handler)
}
function createGetter() {
return function get(target, key) {
let res = Reflect.get(target, key)
return isObject(res)
? reactive(res)
: res;
}
}
例如
data: {
status: {
change: true,
}
}
只有当使用status.change
的时候,才会创建对应的proxy
。
proxy
的一些问题
多次触发set
arr = []
arrProxy = new Proxy(arr, {
get: (target, prop) => {
console.log(prop, 'get');
return Reflect.get(target, prop)
},
set: (target, prop, value) => {
console.log(prop, 'set')
Reflect.set(target, prop, value)
return true
}
})
arrProxy.push(1)
把这段代码在chrome
中执行,发现他会触发两次set
和get
操作。把set
中的console
看成render
函数,那么这个操作就会触发多次render
,这显然是不合理的。在vue
中主要是用下面方式进行解决:
function set(target, key) {
const hadKey = hasOwn(target, key);
const oldValue = target[key];
if (!hadKey) {
console.log('trigger')
} else if (val !== oldValue) {
console.log('trigger')
}
}
可以看到,现在只会执行一次trigger
,因为设置length
是后执行的,但是这时候数组的长度已经更改了,所以后面的程序不会执行了。
只能监听到第一层
obj = {
first: {
inner: 3
}
}
proxyObj = new Proxy(obj, {
get: (target, prop) => {
console.log(prop, 'get');
return Reflect.get(target, prop)
},
set: (target, prop, value) => {
console.log(prop, 'set')
Reflect.set(target, prop, value)
return true
}
})
proxyObj.first.inner
// first get
proxObj.first.inner = 4
// first get
当获取inner
属性值或者设置的时候,只会触发到第一层,所以子对象需要我们自己去实现
function get(target, key) {
const res = Reflect.get(target, key, receiver);
return isObject(res) ? reactive(res) : res;
}
WeakMap
WeakMap
是es6
中新加的一种数据类型,它和Map
有何区别
const m = new Map();
m.set('key', 'value')
const n = new WeakMap();
let obj = {};
n.set(obj, 'value')
obj = null;
n.has(obj);
Map
能够使用字符串来作为key
,但是WeakMap
只能让对象来作为key
。WeakMap
最大的优势是能够避免内存泄漏,例如在上面例子中是了,使用了obj
作为WeakMap
的key
,当把obj
置为null
的时候,这时候WeakMap
是无法获取到对应的value
值,意思是这个数据已经被垃圾回收器回收掉。
effect
的作用
在effect.spec.ts
中
let dummy
const counter = reactive({ num: 0 })
effect(() => (dummy = counter.num))
expect(dummy).toBe(0)
counter.num = 7;
expect(dummy).toBe(7)
从上面可以看出effect
收到一个回调函数,并且立即执行。当触发counter
的set
操作的时候,在执行一遍effect
中的函数,下面是effect
的实现:
function effect(fn, options) {
// ...
const effect = createReactiveEffect(fn, options);
if (!options.effect) {
effect();
}
return effect;
}
effect
就是对回调函数进行一次包装,并在包装的过程中进行一些操作,实际就是依赖收集,最后调用run
方法
function run(effect, fn, args) {
// ...
try {
effectStack.push(effect);
return fn(...args)
} finally {
effectStack.pop();
}
}
这里就像vue2
中的watcher
实现,effectStack
是一个全局变量,当发布者data
中的数据触发get
操作的时候进行依赖收集,当触发set
操作的时候,通知所有的订阅者更新。
Log
12-4
算是大概看完了响应式的部分
> 作者:B_Cornelius
> 链接:https://juejin.im/post/6844904013368934413
看完两件小事
如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:
- 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
- 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程
本文著作权归作者所有,如若转载,请注明出处
转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com