文章目录
- 1、极简双向绑定示例
- 1.1 Object.defineProperty()
- 1.2 实现过程
- 1.2.1 单个DOM与单个Obj
- 1.2.2 两个DOM与单个Obj
- 2、思考
- 3.参考资料
1、极简双向绑定示例
前端有关VUE的笔试和面试中,观察订阅者模式和VUE中双向绑定的原理这两道题几乎是必考的了。
Vue双向绑定vm视图模型简单来说就是利用了Object.defineProperty(),通过劫持setter,实现model到view,view到model则是一堆事件监听 输入框的input,选择组件的 change等等。具体复杂些的实现就要看观察订阅设计模式了。
核心是通过Object.defineProperty() + addEventListener,对data的每个属性进行了get、set的拦截。其实只用Object.defineProperty()已经可以实现双向绑定,只是这样做效率很低。
1.1 Object.defineProperty()
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。该方法有三个参数,(属性所在的对象,你要操作的属性,被操作的属性的特性),示例如下。
js">// 示例
let _xxObj = {}
Object.defineProperty(_xxObj, 'xx_val', {
get: function (_n) {
console.log('读取时get被触发')
// 可以在这里操作
return _n;
}
})
1.2 实现过程
1.2.1 单个DOM与单个Obj
这里定义了一个input框,一个用来显示值的span块,并定义了一个名为_xxObj的Object,现在利用Object.defineProperty() + addEventListener 来实现input中的value与该xxObj中的一个名为xx_val的属性的绑定。原理大致如下图。
代码如下。
<body>
<h1>极简双向绑定</h1>
<input id="id_input_01" type="text"/>
<h3>当前xx_value的值:<span id="id_text_01"></span></h3>
<script>
// -------------------获取元素--------------------------
let eInput01 = document.getElementById("id_input_01");
let eText01 = document.getElementById("id_text_01");
// 定义一个obj
let _xxObj = {
xx_val: null, // 这里的定义xx_val将会被之后的defineProperty()修改
};
// ---------------往Obj里写修改Dom的操作-------------------
/*
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,
或者修改一个对象的现有属性,并返回此对象。
*/
Object.defineProperty(_xxObj, "xx_val", {
get: function () {
console.log('读取时,get被触发')
},
set:function(_n){
console.log("写入时,set被触发")
// 直接在这里操作DOM 相当于在这里单向绑定了一次 Obj->DOM
// _xxObj -> eInput01 eText01
eInput01.value = _n;
// 这里是单向绑定,用于查看当前value的值
eText01.innerHTML = _n;
}
});
// -----------------给DOM绑定监听事件----------------------------
eInput01.addEventListener("keyup", function(e){
console.log(e.target.value);
// 在这里操作Obj,相当于反向绑定了一次 eInput01->_xxObj
_xxObj.xx_val = e.target.value;
})
</script>
</body>
效果图如下。
1.2.2 两个DOM与单个Obj
现在新增加一个id为"id_input_02"的input框,并与_xxObj.xx_val双向绑定,示意图如下。
代码如下。
<body>
<h1>极简双向绑定</h1>
<input id="id_input_01" type="text"/>
<h3>当前xx_value的值:<span id="id_text_01"></span></h3>
<input id="id_input_02" type="text"/>
<script>
// -------------------获取元素--------------------------
let eInput01 = document.getElementById("id_input_01");
let eInput02 = document.getElementById("id_input_02");
let eText01 = document.getElementById("id_text_01");
// 定义一个obj
let _xxObj = {
xx_val: null, // 这里的定义xx_val将会被之后的defineProperty()修改
};
// ---------------往Obj里写修改Dom的操作-------------------
/*
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,
或者修改一个对象的现有属性,并返回此对象。
*/
Object.defineProperty(_xxObj, "xx_val", {
get: function (_n) {
console.log('读取时get被触发')
return _n;
},
set:function(_n){
console.log("写入时set被触发")
// 直接在这里操作DOM 相当于在这里单向绑定了一次 Obj->DOM
// _xxObj -> eInput01 eInput02 eText01
eInput01.value = _n;
eInput02.value = _n;
// 这里是单向绑定,用于查看当前value的值
eText01.innerHTML = _n;
}
});
// -----------------给DOM绑定监听事件----------------------------
eInput01.addEventListener("keyup", function(e){
console.log(e.target.value);
// 在这里操作Obj,相当于反向绑定了一次 eInput01->_xxObj
_xxObj.xx_val = e.target.value;
})
eInput02.addEventListener("keyup", function(e){
console.log(e.target.value);
// 在这里操作Obj,相当于反向绑定了一次 eInput02->_xxObj
_xxObj.xx_val = e.target.value;
})
</script>
</body>
实现效果如下,这两个输入框都和_xxObj.xx_value完成了双向绑定。
2、思考
当有多个DOM和多个Obj需要双向绑定的时候呢?
上面这种模式有什么问题?
答案显而易见,每当要新增加一个Obj和DOM的双向绑定时,都要到对应的Obj定义和DOM定义的地方新加代码。涉及到的DOM和Obj多了,维护起来就变得非常麻烦,而且代码也变得很不易读,是不符合“高内聚,低耦合”的原则的。于是,需要引入观察-订阅者模式来简化代码。
阅者模式在双向绑定中扮演了什么角色呢?它让双向绑定更有效率!
秉持着文章“简洁”的原则,这篇文章我就只介绍到这里,之后我会专门写一篇有关观察订阅者模式和一篇详解vue双向绑定原理的文章…敬请期待。
另外,值得注意的是,观察者模式和订阅发布模式是有一些差别的…
规律大致为:Publishers + Subscribers = Observer Pattern
3.参考资料
剖析Vue原理&实现双向绑定MVVM