双向绑定包括: 从视图到的数据更新(通过监听实现),从数据到视图的更新。
Vue响应式原理:
MVVM架构模式
Model 层代表数据模型,定义数据修改和操作的业务逻辑
View 代表UI 组件,它负责将数据模型转化成UI 展现出来
ViewModel 是一个同步View 和 Model的对象
在MVVM架构下,View 和 Model 之间并没有直接的联系,ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
Vue2实现过程
1、把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项
简单双向绑定
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>标题</title>
</head>
<body>
<input type="text" id="demo" />
<div id="xxx">{{name}}</div>
<script type="text/javascript">
const obj = {};
Object.defineProperty(obj, 'name', {
set: function(value) { //从数据到视图
document.getElementById('xxx').innerHTML = value;
document.getElementById('demo').value = value;
}
});
document.querySelector('#demo').oninput = function(e) {//从视图到数据
obj.name = e.target.value;
}
obj.name = '';
</script>
</body>
</html>
2、Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。
当数据变化时,就会触发其set函数,把需要更新的方法放这里面就实现了从数据到视图的更新
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。
3、每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染
意思是说,每个组件都把自己需要用到的data作为依赖项,只要有依赖项进行更新操作(监听),就会触发依赖项自己的set函数,通知订阅此data的watcher。watcher监控到它的变化,再告诉组件进行更新操作。
vue2缺陷及解决办法:
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。
a、Vue 无法检测 property 的添加或移除——通过Vue.set(vm.someObject, ‘b’, 2),或者 vm.$set
b、无法检测数组更改例如 a[1]=0;——同上方法Vue.set(vm.items, indexOfItem, newValue)
c、不能在data里面增加项——初始时写入data,值为空
- 从数据到视图的更新,是需要对数据进行监听劫持,这里我们设置一个监听器Observer来实现对所有数据的监听;
- 设置一个订阅者Watcher,收到属性的变化通知并执行相应的函数,从而更新视图;
- 设置一个解析器Compiler,解析视图DOM中所有节点的指令,并将模板中的数据进行初始化,然后初始化对应的订阅器。
Vue3实现过程:
1、从一个组件的 data 函数中返回一个普通的 JavaScript 对象
2、Vue 会将该对象包裹在一个带有 get 和 set 处理程序的 Proxy 中。
Proxy 和 Reflect是在 ES6 中引入的,它使 Vue 3 避免了 Vue 早期版本中存在的一些响应性问题。
Proxy,代理,是一个对象,它包装了另一个对象(类似浅拷贝),并允许你拦截对该对象的任何交互。
用法:
let exam = {
name: "Tom",
age: 24
}
let handler = {
get: function(target, key){
console.log("getting "+key);
return Reflect.get(target,key);
},
set: function(target, key, value){
console.log("setting "+key+" to "+value)
Reflect.set(target, key, value);
}
}
let proxy = new Proxy(exam, handler)
proxy.name = "Jerry"
proxy.name
// setting name to Jerry
// getting name
// "Jerry"
get是读控制,set是写控制
3、包装好proxy后,需要跟踪一个 property(属性) 何时被读取
const dinner = {
meal: 'tacos'
}
const handler = {
get(target, property, receiver) {//
track(target, property)
return Reflect.get(...arguments)
}
}
const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)
// tacos
track()函数它将检查当前运行的是哪个effect(文档中翻译为副作用函数),并将其与 target 和 property 记录在一起,这使得vue知道该属性是当前effect的依赖——订阅。
4、在 property 值更改时重新运行这个副作用,为此,需要在代理上使用一个 set 函数
const dinner = {
meal: 'tacos'
}
const handler = {
get(target, property, receiver) {
track(target, property)
return Reflect.get(...arguments)
},
set(target, property, value, receiver) {
trigger(target, property)
return Reflect.set(...arguments)
}
}
const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)
// tacos
通过trigger()函数,去触发以target的property为依赖的effect——发布