1.vue响应式原理 和proxy的对比
参考: 1. vue2 里的响应式其实有点像是一个半完全体,对于对象上新增的属性无能为力,对于数组则需要拦截他的原型方法来实现响应式. 2. 这个时候Vue提供了一个api :this.$set
来实现新增的属性也拥有响应式的效果.
问题:但是很多时候需要小心翼翼的去判断到底什么情况下需要用 $set,什么时候可以直接触发响应式。
在 Vue3 中,这些都将成为过去。proxy 到底会给 Vue3 带来怎么样的便利。
例子1.
let vm = new Vue({
data() {
return {
a: 1
}}})
// ❌ oops,没反应!
vm.b = 2
let vm = new Vue({
data() {
return {
a: 1
}
},
watch: {
b() {
console.log('change !!')
}}})
// ❌ oops,没反应!
vm.b = 2
1.3 vue2 响应式原理
例1.利用input实现数据双向绑定
理解:
发布者
订阅者
观察者
<!-- 发布者 -->
<input type="text">
<!-- 订阅者 -->
<div class="demo"></div>
<!-- 观察者 Object.defineProperty(目标对象,目标对象中的属性,{
6个属性:set,get,value,枚举,可选值,是否可写}) -->
<script>
/* 观察者 */
// let target = {} //目标对象
// Object.defineProperty(target, 'msg', {
// get () { },
// set (value) {
// console.log(value, 5);
// demo.innerHTML = value
// ipt.value = value
// }
// })
// let ipt = document.querySelector('input')
// let demo = document.querySelector('.demo')
// ipt.addEventListener('input', (e) => {
// console.log(e.target.value, 99);
// target.msg = e.target.value
// })
双向绑定
1.什么是双向绑定
单向绑定: javascript代码更新model时,view就会自动跟新双向绑定
在单向绑定的基础上,用户更新了View,Model的数据也自动被更新了
2.双向绑定的原理
Vue 是数据双向绑定的框架,双向绑定由三个重要部分构成
a.双向绑定由三个重要部分构成
数据层(Model):应用的数据及业务逻辑
视图层(View):应用的展示效果,各类UI组件
业务逻辑层(ViewModel):框架封装的核心,它负责将数据与视图关联起来
所以MVVM这里的控制层的核心功能便是 “数据双向绑定”
b.理解:业务逻辑层(ViewModel)
数据变化后更新视图
视图更新后更新数据
主要组成部分:
监听器(Observer):对所有数据的属性进行监听
解析器:(Compiler):对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数
实现原理:
对象类型:通过Objeact.defineProperty()对属性的 setter getter在数据变动时放送给订阅者,触发相应的监听回调
读取、修改进行拦截(数据劫持)
数组类型:通过重写更新数组的一系类方法拦截(对数组的变更方法进行包裹)
存在问题:
a.当对象中属性过多时Objeact.dfineProperty()需求对每一个属性进行遍历实现响应式,效率不高.
b.新增属性,删除属性,界面不会刷新;
c.只有configurable为true时候,该属性才能从对应的对象上被删除,单元数据不会响应式删除.
d.直接通过下标修改数组,界面不会自动更新.
**实现步骤:
a.第一步需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter 这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
b.第二步 compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
c.第三步 Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()方法
3、待属性变动dep.notice()通知时,能调用自身的 update() 方法,并触发Compile中绑定的回调,则功成身退。
d.第四步:MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
**
例1:
在源数据上 添加数值 属性
<script>
// 源数据
let person = {
name: '张三',
age: 18
}
Proxy和Reflect是window上内置的函数
let p = new Proxy(person, {
get(target, propName) {
console.log(`有人读取了p身上的${
propName}属性`)
return Reflect.get(target, propName)
},
set(target, propName, value) {
console.log(`有人新增/修改了p身上的${
propName}属性,我要去更新界面了!`)
Reflect.set(target, propName, value)
},
deleteProperty(target, propName) {
console.log(`有人删除了p身上的${
propName}属性,我要去更新界面了!`)
return Reflect.deleteProperty(target, prop)
}
})
</script>
1.2 vue3 proxy响应式原理
实现原理:
通过Proxy(代理):拦截对象中任意属性变化,包括:属性值的读写,属性的添加,属性的删除等.
通过Reflect(反射):对源对象的属性进行操作.
MDN文档中描述的Proxy与Reflect:
Proxy Reflect
例2:
/* Proxy */
let proxy = new Proxy({
}, {
// set (目标对象,key,value) {
set (target, key, value) {
console.log(target, key, value, 888);
demo.innerHTML = value
ipt.value = value
}, // setter
get () {
} // getter
})
let ipt = document.querySelector('input')
let demo = document.querySelector('.demo')
ipt.addEventListener('input', (e) => {
console.log(e.target.value, 99);
proxy.msg = e.target.value
})
Proxy:
//语法:let p = new Proxy(target, handler)
target:要使用 Proxy 包装的目标对象,此处为person源数据;
handler:一个对象。可以只传一个空对象,也能实现增删改查操作let p = new Proxy(person, {
});可以向上述代码一样传入含有的get set deleteproperty函数来拦截对象中任意属性的变化;
set函数 get函数 deleteProperty函数 用法
//get函数:
get(target, propName) {
// target:目标对象,也就是person源数据;
// propName:被获取的属性名;
console.log(`target, propName`, target, propName)
return target[propName]
},
//set函数:
// set函数对新增或修改一个属性都可以拦截到
set(target, propName, value) {
// value:新属性值
console.log(`target, propName,value`, target, propName,value)
target[propName] = value
}
//deleteProperty函数:
deleteProperty(target, propName) {
console.log(`有人删除了p身上的${
propName}属性,我要去更新界面了!`)
return delete target[propName]
}
Reflect:
Reflect函数身上的一些方法与Object相同,比如:
Reflect.get(target,propName)相当于get函数中直接target[propName];
Reflect.set(target,propName,value)相当于set函数中target[propName] = value;
Reflect.deleteProperty(target,propName)相当于deleteProperty函数中的delete target[propName]
当然,Reflect函数身上还有Reflect.defineProperty方法,与Object.defineProperty有所不同:
Object.defineProperty对同一个对象同一个属性重复操作时,系统会报错代码运行不下去,但Reflect.defineProperty不会报错只会运行第一条结果并继续执行后边代码;
Object.defineProperty:
let obj = {
a: 1, b: 2 }
Object.defineProperty(obj, 'c', {
get() {
return 3
}
})
Object.defineProperty(obj, 'c', {
get() {
return 4
}
})
Reflect.defineProperty:
let obj = {
a: 1, b: 2 }
const x1 = Reflect.defineProperty(obj, 'c', {
get() {
return 3
}
})
console.log('x1',x1)
const x2 = Reflect.defineProperty(obj, 'c', {
get() {
return 4
}
})
console.log('obj')
注:一般组件封装用Reflect函数比较多,宽容度好,组件更健壮