什么是发布-订阅模式?
发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在JavaScript开发中,我们一般用事件模型来替代传统的发布-订阅模式。[1]
DOM节点绑定事件函数是最常用的发布-订阅模式
//订阅点击事件
document.body.addEventListener( 'click', function(){
alert(2)
}, false )
document.body.click(); //模拟用户点击
//取消订阅点击事件
document.body.removeEventListener( 'click' )
Object.defineProperty
我们平时能轻松创建、修改对象,都是基于以下特性↓
JavaScript的对象有两种内部属性类型:数据属性和访问器属性。
数据属性承载(payload)对象指定属性的值([[Value]]
)以及控制其是否可被修改([[Writable]]
)或者是否可被删除([[Configurable]]
)和是否可被for-in循环返回到[[enumerable]]
,是对内的属性。
访问器属性,对外的属性。在读取属性时,会调用getter
函数,这个函数负责返回有效的值;在修改属性值时,会调用setter
函数承载传入的新值并决定如何处理数据。这两个函数都是可以被重写的。
而以上这些功能必须使用Object.defineProperty()
方法实现
Tips: vue.js就是利用Object.defineProperty()的访问器思想来更新视图的。
IE8是第一个实现Object.defineProperty()的浏览器版本。然而这个版本实现存在诸多限制:只能在DOM对象上使用这个方法,而且只能创建访问器属性[2]。由于实现不彻底被不建议开发者使用,但在IE9得到改善,所以vue.js只能兼容到IE9及以上版本。
get and set在ES6的表现
ES6中的新语法class类也很好地保留了 类似访问器属性
class Person {
constructor(name, age){
this.name = name;
this.age = age;
this.innerTitle = "";
}
get title(){
return this.innerTitle;
}
set title(value){
this.innerTitle = value;
}
sayName(){
alert(this.name);
}
getOlder(years){
this.age += years;
}
}
var p = new Person('Niko',24)
p.title = 'asd'
console.log(p.title) //asd
基于设计模式和Object.defineProperty简单实现vue双向绑定模型
基本思路:
- 利用Object.defineProperty监听对象赋值动作,
- 遍历所有节点,
- 使用观察者模式对拥有‘v-model’属性的DOM节点订阅上述事件
- 对拥有‘v-bind’属性的DOM节点进行发布事件
- 针对表单标签仅监听addEventListener('input',function(e){...})事件
HTML
<div id="app">
<textarea cols="30" rows="10" v-model="asd"></textarea>
<h1 v-bind="asd"></h1>
<h1 v-bind="qwe"></h1>
</div>
观察者对象
const _observer = {
//存储消息列表
clientList:{},
//监听消息
listen:function(key,fn){
if(!this.clientList[key]){
this.clientList[key] = []
}
this.clientList[key].push(fn)
},
//发布消息
trigger:function(key,data){
let fns = this.clientList[key];
if(!fns || fns.length === 0) {
return false;
}
for(let i=0,fn;fn = fns[i++];){
fn.call(this,data);
}
}
};
具体实现
// 遍历app所有节点
let nodes = document.querySelector('#app').children;
// v-model节点对象
let vModelList = Object.create(null);
for(let i=0,node;node=nodes[i++];) {
//存储v-model nodes
if (node.hasAttribute('v-model')) {
let key = node.getAttribute('v-model');
//添加表单监听事件
node.addEventListener('input', function (e) {
vModelList[key] = e.target.value
});
/**
* 核心 Object.defineProperty
* **/
//监听vModelList对象指定key值变化
Object.defineProperty(vModelList, key, {
enumerable: true,
configurable: true,
set: function (newVal) {
// 发布
_observer.trigger(key, newVal)
}
});
}
//存储v-bind nodes
if (node.hasAttribute('v-bind')) {
let key = node.getAttribute('v-bind');
// 订阅
_observer.listen(key, function (newVal) {
node.innerHTML = newVal //这里只用innerHTML简单实现,未使用模版引擎
})
}
}
- [1] JavaScript设计模式与开发实践[M] p.110
- [2] JavaScript高级程序设计(第3版)[M] p.141
- [3] vue.js关于Object.defineProperty的利用原理[N]
简书-进击的前端