Vue 对数据的控制,本质是 [对对象数据的劫持] 和 [对dom的事件绑定]
1. 【对象的数据劫持】Object.defineProperty做什么的? 对对象的某个参数值进行拦截
2. 【对象的数据劫持】Proxy 做什么的? 对整个对象变化进行拦截
3. 【代码实现】Vue 双向绑定是如何实现的?
4. 【Vue响应式原理】https://cn.vuejs.org/v2/guide/reactivity.html
本质是对对象的数据变化的劫持。
通过Object.defineProperty实现。
对象数据某参数值变化,通过set拦截。触发watcher方法, 去重新渲染。
1 - Object.defineProperty
在对象上可以 定义新的属性 或 变更现有属性值的 方法 。
<script>
const targetObj = {
id: 1
}
// 暂时储存 targetObj.a 的值
let temp_id = 1
Object.defineProperty(targetObj, 'id', {
configurable: false, // 是否可以删除目标属性或是否可以再次修改属性的特性
enumerable: true, // 是否可以被枚举
set: function (newVal) {
// 对象属性变化的拦截器
console.log('set', newVal)
temp_id += newVal
},
get: function () {
return temp_id
}
}
)
console.log(targetObj.id)
targetObj.id = 100
console.log(targetObj.id)
</script>
代码实现可参考: vue 的双向绑定原理及实现
下面是个简单的概念说明。
2 - 双向绑定实现 I
从数据变化监听 到 View变化, 核心是Object.defineProperty来拦截 数据set方法
<html>
<head></head>
<body>
<!-- {{ }} 空白区域填入某值 -->
<h1 id="app">{{ appName }}</h1>
<script>
function Vue (data, el, key) {
this.data = data
// 监听数据变化的注册
Observer(data, el)
el.innerHTML = this.data[key] // 将值填入到模板上
return this
}
// 观察者
function Observer (data, el) {
// 遍历每个key值
Object.keys(data).forEach(key => {
// 当value有变化的拦截器
let oldVal = data[key]
Object.defineProperty(
data,
key,
{
enumerable: true,
configurable: true,
set: function (newVal) {
if (oldVal === newVal) {
return
}
console.log('oldVal newVal', oldVal, newVal)
// 赋值
oldVal = newVal
// 通知数据变化, 可以再去render了
Render(el, key, newVal)
},
get: function () {
return oldVal
}
}
)
})
}
// 数据变化 触发 Render
function Render (el, key, newVal) {
console.log(key, newVal)
el.innerHTML = newVal
}
const el = document.getElementById('app')
const data = { appName: 'default' }
const vm = new Vue(data, el, 'appName')
data.appName = '我的值变了'
console.log(data.appName)
</script>
</body>
</html>
3 - 双向绑定实现 II
概念是: 初始化获取在模板上的数据,该数据对应data里的值,有不同,则触发从View到Model侧的同步。
// Compile View => Model
function Compile (that) {
that.el.addEventListener('input', (e) => {
// that.data[prop] = e.target.value
console.log('这里绑定了', e.target.value)
})
}
4 - Recap
本质: 对数据变化进行劫持, 对视图上数据变化进行事件监听
- 从Model => View, 当对象里的某参数变化时候(Observer),通过Object.defineProperty对参数值进行set 方法的拦截。从而通知 (Watcher) 去 render el.innerHTML 数据。
- 从View => Model, (Compile)方法,View通过得知该节点的数据是有变化,从而触发Model数据的变化。
- Watcher是连接Observer和Compile的桥梁
5 - Proxy与Object.defineProperty的优劣对比?
- Object.defineProperty 是对对象中某个属性 为单位,进行数据拦截。
- Proxy 监听的是整个对象。但是兼容性不是很好。另外返回的是一个新的对象。
<script>
const obj = {
a: 1,
b: 2
}
// 返回个新的对象
const proxy = new Proxy(obj, {
get (target, key, value) {
console.log(key, value)
return target[key]
},
set (target, key, value) {
console.log(key, value)
target[key] = value
}
})
proxy.a = '测试'
console.log(proxy.a)
</script>