转载:https://blog.csdn.net/qq_41996454/article/details/107988996
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() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。该方法有三个参数,(属性所在的对象,你要操作的属性,被操作的属性的特性),示例如下。
// 示例 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

参考资料:剖析Vue原理&实现双向绑定MVVM