所谓双向数据绑定,无非就是视图层和数据层的数据同步,在写入数据时视图层实时更新:

主要采用 发布者-订阅者模式,通过Object.defineProperty()来劫持各个setter/getter,在数据变动时发布消息给订阅者,触发相应的监听回调

  1. 数据监听器 Observer —— 对数据对象所有属性进行监听,有变化时可拿到最新值并通知订阅者;
  2. 指令解析器 Compiler —— 对元素节点的指令进行扫描解析,根据指令模板替换数据,绑定更新函数;
  3. 订阅者 Watcher —— 作为连接Observer 和 Compiler的桥梁,订阅并且接受每个属性变动的通知,执行回调,更新视图

订阅者-发布者模式:个人理解就是getter函数执行的任务就是watcher订阅者,setter函数执行的任务就是发布者

ECMAScript中有两种属性: 数据属性和访问器属性, 数据属性一般用于存储数据数值, 访问器属性对应的是set/get操作, 不能直接存储数据值, 每种属性下面又都含有四个特性:

  • Configurable 表示能否通过delete将属性删除,能否把属性修改为数据属性, 默认为false
  • Enumerable 表示属性可否被枚举(即是否可以通过for in循环返回),默认false
  • ** Writable(数据属性)** 表示属性是否可写(即是否可以修改属性的值),默认false
  • ** Value(数据属性)** 该属性的数据值, 默认是false
  • ** Get(访问器属性)** 读取属性时调用的函数, 默认是undefined
  • ** Set(访问器属性)** 写入属性时调用的函数, 默认是undefined
    在修改属性的特性或者定义访问器属性的时候, 需要借助ECMAScript 5中的一个方法: Object.defineProperty(), 这个方法接收三个参数: 属性所在对象, 属性的名字, 描述符对象; 为对象定义多个属性的话, 就用函数的复数写法:Object.defineProperties();
<input type="text" id="inp" />
<div id="box"></div>
<script>
let obj = {};
let oInp = document.getElementById('inp');
let oBox = document.getElementById('box');
Object.defineProperty(obj, 'name', {
    configurable: true,
    enumerable: true,
    get: function() {
        console.log(111)
        return val;
    },
    set: function(newVal) {
        oInp.value = newVal;
        oBox.innerHTML = newVal;
    }
});
oInp.addEventListener('input', function(e) {
    obj.name = e.target.value;
});
obj.name = '苏日俪格';

那么实现数据双向绑定的核心就是利用为每一个属性都创建了订阅者的实例对象, 以便观察, getter函数里面返回一个value值,在setter函数中写入修改后的值并调用update方法更新视图的数据值, Configurable和Enumerable这两个特性描述符默认是true, 因此不用写

function defineReactive (obj, key, val) {
    var dep = new Dep();  //这是一个构造函数  其原型是为属性添加订阅者
    Object.defineProperty(obj, key, {
       get: function() {
            if(Dep.target) {
                dep.addSub(Dep.target);  //添加订阅者到Dep实例对象
            }
            return val;  // 返回监听到的value值
        },
        set: function (newVal) {
            if(newVal === val) return;
            val = newVal;  // 写入新的value值
            dep.notify();  // 作为发布者发出通知 然后dep会迭代调用各自的update方法来更新视图
        }
    });
}
function observe(obj, vm) {
     Object.keys(obj).forEach(function(key) {
           defineReactive(vm, key, obj[key]);
     });
}