JavaScript发布订阅模式与defineProperty模拟实现Vue数据双向绑定

什么是发布-订阅模式?

发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在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){...})事件
JavaScript发布订阅模式与defineProperty模拟实现Vue数据双向绑定_第1张图片
图片资源来自[3]

HTML

观察者对象

   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] -进击的前端

你可能感兴趣的:(JavaScript发布订阅模式与defineProperty模拟实现Vue数据双向绑定)