今天发生一个很有意思的事情,我的好朋友(贴贴)今天问了我一个问题,当时我在忙,就简单回复了一下,下午我们发起了学习的热潮!!!
那我接下来就好好唠唠响应式和双向绑定吧
一、响应式原理
思考💡:vue最大的特点呢就是数据驱动视图,什么是数据驱动视图呢?
数据 state、视图UI
render(state) = UI
数据变化,视图就会发生变化,数据是单向的,vue就扮演了这个render的角色
思考💡:data变化,为什么视图就会变化?
数据驱动视图只干2件事
1、 监听数据变化
用Object.defineProperty监听数据变化
说明:vue2 用的Object.defineProperty实现数据劫持,vue3用的Proxy实现数据代理(这个有时间我再写)
2、更新视图
用发布订阅者模式更新视图
1、vue开始展示
<body>
<div id="app">
<h1>{{message}}</h1>
</div>
<script src="./vue.js"></script>
<script>
var app = new Vue({
el: '#app',
data: {
message: '哈哈哈!',
}
})
</script>
</body>
思考💡:通过app.message修改数据,vue内部是如何监听数据的变化的?
通过Object.defineProperty()
思考💡:vue监听到数据的变化,是如何通知页面更新的?
通过发布订阅者模式
一道面试题拿下!!
2、看看Object.defineProperty()
<script>
const obj = {
message: '哈哈哈',
name: 'why'
}
//遍历对象的所有key值
Object.keys(obj).forEach(key => {
//获取值
let value = obj[key];
// 通过 Object.defineProperty完成监听
Object.defineProperty(obj, key, {
set(newValue) {
console.log('监听' + key + '的改变');
value = newValue;
},
get() {
console.log('获取' + key + '的值');
return value;
}
})
})
</script>
obj定义的数据就相当于实例化Vue中data定义的数据,遍历对象的所有key值,通过Object.defineProperty监听对象的所有属性
数据变化触发set方法,访问数据触发get方法
思考💡:set中干点什么?get中干点什么?
既然要说到发布订阅模式了,就先理理思路,首先要通知视图更新,我得知道谁用我了,那访问数据即调用get方法的时候,我就收集起来,收集起来的我们优雅的称为订阅者,收集的过程我们成为依赖收集,其次就是视图更新了,数据变化的时候会触发set方法,所以在set中去通知更新是最合适的了。
以上的分析就概括为一句话在set中去通知更新,在get中去添加订阅
3、学学发布订阅者模式
1、前言
在set中去通知更新,也就是会调用notify()
在get中去添加订阅,因为只有调用get方法,才知道谁调用了,谁调用了就通知谁
2、代码实现发布订阅
//发布者
class Dep {
constructor() {
//存储订阅者
this.subs = [];
}
//添加订阅者
addSub(watcher) {
this.subs.push(watcher);
}
//通知更新
notify() {
this.subs.forEach((item) => {
item.update();
})
}
}
//订阅者
class Watcher {
constructor(name) {
this.name = name;
}
update() {
console.log(this.name + '发生了更新');
}
}
//new的时候会实现constructor
let dep = new Dep();
// 定义订阅者,并且添加订阅者
let w1 = new Watcher('张三');
dep.addSub(w1);
let w2 = new Watcher('李四');
dep.addSub(w2);
let w3 = new Watcher('王五');
dep.addSub(w3);
dep.notify();
发布者能够
用this.subs = [] 实现存储订阅者
用addSub()实现新增订阅者
用notify()实现通知更新
订阅者能够
自己的属性,通过constructor实现
通过update实现能够更新视图
3、发布订阅结合Object.defineProperty
defineReactive(data, key, val) {
const dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
if (Dep.target) {
// get收集订阅
dep.addSub(Dep.target);
}
return val;
},
set(nv) {
if (nv === val) {
return;
}
console.log(nv, val);
val = nv;
// set通知更新
dep.notify();
},
});
}
}
监听之前,定义一个发布者
在get方法中添加订阅者 dep.addSub(Dep.target)
在set中通知更新: dep.notify();
二、响应式原理(图文并茂版)
1.new一个vue的过程
new一个vue的时候创建了一个vue实例,主要有2部分,el和data
2.Observer的工作
Observer通过Object.defineProperty将data对象的属性都监听起来,每个属性都对应一个Dep对象(依赖),Dep中定义了subs(依赖收集器),subs存储的是所有watcher(订阅者)
3.Compile的工作
Compile解析html的代码,在解析时,会触发get方法,也就会触发添加订阅者的方法,将watcher添加到subs中
4.数据更新
数据更新时,会触发set方法,set会调用notify(),notify会通知所有订阅者,调用自身的update()更新视图
三、数据劫持存在的问题
vue是通过数据劫持实现响应式的,那么也存在一些问题
1.问题
无法监听数组长度的变化
通过下标操作数组的方式无法监听到
给对象新增属性、删除属性无法监听到
2.解决方案
vue.set()
vue3的proxy
四、双向绑定
双向绑定数据是双向的
数据改变视图改变,也就是响应式
视图改变,数据改变,这是原生的方式👇
从视图到数据,这个本来就很好理解,你想,你的input是不是一个框,input天生就有value属性,也就是说你输入一个数,input.value就拿到那个数了,然后你保存下来,发给后台,存到数据库,一条线,完成
也就是可以总结一下,响应式是双向绑定中的一环
1.vue方式实现
<input type="text" v-model="message">
- 原生实现
<input type="text" :value="message" @input="message = $event.target.value">
3.分析
在value中 数据的流向 message=>value ,也就是说data中定义的属性通过value绑定给input
在@input中 数据流向 value =>message ,也就是说通过监听input事件,值发生改变时,改变data中的属性的值
思考💡:oninput和@input和onchange的区别?
oninput事件是输入框在输入时就会触发
onchange是输入框输入内容完毕,失去焦点的时候触发
@input是vue中的输入框事件,输入值的发生变化会触发