双向绑定的方法
- KnockoutJS 基于观察者模式(
发布-订阅
)的双向绑定 - Ember 基于
数据模型
的双向绑定 - Angular 基于
脏检查
的双向绑定 - 基于数据劫持的双向绑定 (重点讲解)
Object.defineProperty
Proxy
数据劫持
数据劫持的优点
-
不需要显示的调用
比如:Vue 利用数据劫持+发布订阅,可以直接通知变化并且驱动视图
-
可以精确得知变化数据
我们劫持了属性
setter
,当属性值改变我们可以精确的获取变化的内容newVal
,不需要额外的diff
操作
数据劫持双向绑定的实现思路
- 利用
Proxy
或Object.defineProperty
生成的Observer
针对对象/对象的属性进行劫持,在属性发生变化时通知订阅者 - 解析器
Compile
会解析模板中的指令,收集指令依赖的数据和方法,等待数据变化,然后进行渲染 Watcher
接收Observer
产生的数据变化,根据Compile
提供的指令进行视图渲染,使得数据变化促使视图更新
基于 Object.defineProperty 双向绑定的特点
利用 Object.defineProperty
方法劫持对象的访问器,在 属性值
发生变化的时候获取到变化,并且可以进一步操作。
// 这是将要被劫持的对象
const data = {
name: ""
};
function say(name) {
switch (name) {
case "a":
console.log("my name is a");
break;
case "b":
console.log("my name is b");
break;
case "c":
console.log("my name is c");
break;
default:
console.log("my name is none");
break;
}
}
// 遍历对象,对其属性值进行劫持
Object.keys(data).forEach(function(key) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
console.log("get");
},
set: function(newVal) {
// 当属性值发生变化时我们可以进行额外操作
console.log(`大家好,我系${newVal}`);
say(newVal);
}
});
});
data.name = "a";
//大家好,我系a
//my name is a
案例地址:https://codepen.io/xiaomuzhu/pen/jxBRgj/?editors=1010
基于 Proxy 实现的双向绑定的特点
const input = document.getElementById("input");
const p = document.getElementById("span");
const obj = {};
const newObj = new Proxy(obj, {
get: function(target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
console.log(target, key, value, receiver);
// 第一次打印:{} "text" "f" Proxy {}
// 第二次打印:{text: "f"} "text" "ff" Proxy {text: "f"}
if (key === "text") {
input.value = value;
p.innerHTML = value;
}
return Reflect.set(target, key, value, receiver);
}
});
input.addEventListener("keyup", function(e) {
newObj.text = e.target.value;
});
案例地址:https://codepen.io/xiaomuzhu/pen/zjwGoN/
对比两种双向绑定
Object.defineProperty 的缺陷
-
无法监听数组变化
Vue 的文档提到了 Vue 是可以检测到数组变化的,但是只有以下八种方法:
push pop shift unshift splice sort reverse
,vm.items[indexOfItem] = newValue 这种是无法检测的。 -
只能劫持对象的属性
只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历,如果属性值也是对象那么需要深度遍历,显然能劫持一个完整的对象是更好的选择
-
需要 hack
Proxy 的优点
- Proxy 可以直接监听数组的变化
- Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而非属性
- Proxy 支持的拦截操作一览,一共 13 种,比如:has、apply、construct、deleteProperty…
- 兼容性问题
了解Proxy更多详情:https://blog.csdn.net/m_review/article/details/90443242