前言
之前一直有读Vue的源码,不过总是一个点,一个点的去看,导致只能在短时间记住,时间一久还是不明所以,想着写一篇由一个点一个点串起来的,争取做到浅显易懂。
例子
<template>
<div id="root">
<div>{{obj}}</div>
<button @click="addObj()">点击</button>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
obj: {
name: "waiterLin",
age: 22,
},
};
},
methods: {
addObj(){
this.obj.sex = "男";
console.log('obj',this.obj)
}
},
};
</script>
这在新手开发中是很常见的一个问题,为什么数据更新了,但视图不发生改变?就产生了下列的问题
1.如何去解决数据变了,视图不变的问题?
2.为什么这个方法能够解决这个问题?
3.导致这个问题产生的根本原因是什么?
4.Vue3.x是如何解决这个问题的?
如何去解决数据变了,视图不变的问题(this.$set)?
之所以这个放在第一条,是因为在实际开发中,我和代码,有一项能跑就行,既然我不能跑,那就先让代码跑起来
聪明的小伙子,早就不到一分钟就知道该怎么做了,心想:就这?还非得水一篇文章?
addObj(){
this.$set(this.obj,'sex','男');
}
为什么这个方法能够解决这个问题?
我们反手直接打开源码
//接受3个参数
//数组或者对象,key值,val值
export function set (target: Array<any> | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
//判断是数组并且符合有效key值
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
//利用数组的变异方法splice,触发响应式
target.splice(key, 1, val)
return val
}
//key值是否存在
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
//对象是否为响应数据,如果不是直接赋值
if (!ob) {
target[key] = val
return val
}
//如果是调用defineReactive进行处理
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
从源码中可以知道,存在两种情况
1.第一个参数为数组时,会调用 splice方法
触发更新
2.第一个参数为对象时,会判断是否为响应数据,如果不是直接复制,是的话触发依赖收集(defineReactive)
导致这个问题产生的根本原因是什么?
这就要说到,人们津津乐道的Object.defineProperty,Vue的双向数据绑定的核心就是Object.defineProperty
的get和set方法,简而言之,对数据对象进行数据劫持
并返回更改后的对象.
由Object.defineProperty实现双向数据绑定的简易代码
function observe(obj, callback) {
let newObj = {};
Object.keys(obj).forEach((key) => {
console.log(key)
Object.defineProperty(newObj,key,{
enumerable:true,
configurable:true,
get(){
return obj[key]
},
set(newVal){
obj[key] = newVal
callback(key,obj[key])
}
})
})
return newObj
}
//进行测试
const person = observe({
name: 'waiterLin',
sex: '男'
},
(key, value) => {
console.log(`${key}的值被修改为${value}`)
}
)
person.name = 'watermelon'
person.sex = '女'
//name的值被修改为watermelon
//sex的值被修改为女
但我们发现Object.defineProperty
无法对新增的对象属性进行监听
,并且实际上是对Object的key值进行监听,所以需要循环遍历,性能上有所偏颇.
Vue3.x是如何解决这个问题的?
vue3.x采用了Proxy进行重写
关于Proxy
1.Proxy监听的是对象
,而不是对象的属性
,所以即使对象属性新增了也能监听的到,也就不需要this.$set
这种操作
2.Proxy的hander的对象方法有13
种,这是Object.defineProperty所不具备的
3.Vue3.x中直接返回的是一个Proxy对象
,所以可以不用遍历也能监听到对象的改变
由Proxy实现双向数据绑定的简易代码
function proxyObj(obj,fn){
return new Proxy(obj,{
get(target,key){
return target[key]
},
set(target,key,val){
target[key] = val;
fn(key,val)
}
})
}
//进行测试
const person = proxyObj({
name: 'waiterLin',
sex: '男'
},
(key, value) => {
console.log(`${key}的值被修改为${value}`)
}
)
person.name = 'watermelon'
person.sex = '女'
//name的值被修改为watermelon
//sex的值被修改为女
两者进行比较,明显看出由Proxy重写的方法简易了不少。
最后
希望大家看完之后,有一定的收获,写的不好地方也欢迎指出,共同进步.打工人,奥利给。