使用Object.observe 实现数据绑定

Object.observe API概述

        JavaScript的MVC框架在Web开发届非常流行。在实现MVC框架的时候,一个非常重要的技术就是数据绑定技术。如果要实现模型与视图的分离,就必须要使用数据绑定技术。但是,MVC框架的原作者对于数据绑定处理实现得并不如人意,因此,Google公司在ECMAScript中封装了一个Object.observe API,专用于实现数据绑定处理。

        Object.observe API可以被称为一种“可以对任何对象的属性值修改进行监视的事件处理函数”。

        在Firefox浏览器中,实现了与之相类似的可以对DOM对象进行观察的Mutation观察器。

        本文介绍Object.observe API中的基本功能及一些代码示例。

        目前为止,Object.observe API中包括如下所示的四个方法:

  • Object.observe:为对象指定监视时调用的回调函数
  • Object.unobserve:移除监视时调用的回调函数
  • Object.deliverChangeRecords:通过回调函数对对象值进行修改
  • Object.getNotifier:获取Notifier对象

        可以观察到的属性操作包括以下几种:

  • new:添加属性
  • updated:修改属性值
  • reconfigured:修改属性设定
  • deleted:删除属性

        接下来介绍如何使用Object.observe方法。

  简单代码示例

      Object.observe方法用于为对象指定监视到属性修改时调用的回调函数,使用方法如下所示。

Object.observe(obj, callback);
        Object.observe方法中使用两个参数,其中第一个参数值为需要被监视的对象,第二个参数值为监视到属性修改时调用的回调函数名。可以将各对象的属性操作时生成的ChangeRecord对象数组设置为回调函数的参数。

        ChangeRecord对象拥有type、name、oldValue、object四个属性,各属性含义如下所示。


function callback(changes) {
    changes.forEach(function(change) {
        console.log(change.type);     //对属性进行了什么操作 new/updated/reconfigured/delted
        console.log(change.name);     //属性名
        console.log(change.oldValue); //修改之前的属性值
        console.log(change.object);   //被监视的对象
    });
}

        使用如下所示的代码,可以在任何时刻对于对象属性的上述四种操作(new/updated/reconfigured/delted)进行监视:


var obj = {a: 1};
    Object.observe(obj, output); //为对象指定监视时调用的回调函数
 
    obj.b = 2; //添加属性
    obj.a = 2; //修改属性值
    Object.defineProperties(obj, {a: { enumerable: false}}); //修改属性设定
    delete obj.b; //删除属性  
    function output(change) {
        //回调函数,可以在此处书写在页面上的输出。
    }

      测试代码 :

<!DOCTYPE html>
<head>
<meta charset="UTF-8" />
<title>Object.observer API代码示例页面</title>
<style>
table, td, th {
    border: 2px #000000 solid;
}
</style>
<script>
window.addEventListener('DOMContentLoaded',function() {
    if (!Object.observe) {
        alert('您的浏览器不支持Object.observe API。请使用Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的浏览器,并启用“启用实验性 JavaScript”选项。');
        return;
    }
    var obj = {a: 1};  
    Object.observe(obj, output);  
    obj.b = 2; //添加属性
    obj.a = 2; //修改属性值
    Object.defineProperties(obj, {a: { enumerable: false}}); //修改属性设定
    delete obj.b; //删除属性
  
    function output(changes) {
        var results = document.getElementById('results');
        var table = document.createElement('table');
        results.appendChild(table);
        var caption = document.createElement('caption');
        caption.innerText = '监视到的事件列表';
        table.appendChild(caption);
        var thead = document.createElement('thead');
        thead.innerHTML = '<tr><th>序号</th><th>操作种类</th><th>属性名</th><th>修改前的属性值</th><th>修改后的属性值</th></tr>';
        table.appendChild(thead);
        changes.forEach(function(change, i) {
            var tr = document.createElement('tr');
            tr.innerHTML = '<td>' + i + '</td><td>' + change.type + '</td><td>' + change.name + '</td><td>' + change.oldValue + '</td><td>' + change.object[change.name] + '</td>';
            table.appendChild(tr);
        });
    }
});
</script>
</head>
<body>
<div id="event"><div>示例代码</div>
<pre>
obj.b = 2; //添加属性
obj.a = 2; //修改属性值
Object.defineProperties(obj, {a: { enumerable: false}});  //修改属性设定
delete obj.b; //删除属性
</pre>
</div>
<div id="results"></div>
</body>
</html>

        页面运行结果如下图所示:

创建自定义Notify对象

        可以监视到的事件并不局限于以上所述的几种,可以自定义监视事件。

        可以使用Notifier对象来自定义针对对象的可访问属性(可使用getter方法或setter方法读取或设置的属性)被修改时所触发的事件。这时,我们需要Object.getNotifier()方法获取被监视对象的Notifier对象,并使用notify方法进行属性被修改的通知。

        在以下这个示例代码中,为对象自定义time_updated事件及time_read事件并利用这两个事件监视对象的私有属性_time的读取及修改。

var obj2 = {_time: new Date(0)};
var notifier = Object.getNotifier(obj2); //获取Notifier对象
Object.defineProperties(obj2, { //设置对象的可访问属性 
    _time: {
        enumerable: false,
        configrable: false
    },
    seen: {
        set: function(val) {
            var notifier = Object.getNotifier(this);
            notifier.notify({
                type: 'time_updated', //定义time_updated事件
                name: 'seen',
                oldValue: this._time
            });
            this._time = val;
        },
        get: function() {
            var notifier = Object.getNotifier(this);
            notifier.notify({
                type: 'time_read', //定义time_read事件
                name: 'seen',
                oldValue: this._time
            });
            return this._time;
        }
    }
});
Object.observe(obj2, output); //为对象指定监视时调用的回调函数
//执行属性操作
var first_time = obj2.seen; //触发time_read事件
obj2.seen = new Date();      //触发time_updated事件
var second_time = obj2.seen; //触发time_read事件

 测试代码:

<!DOCTYPE html>
<head>
<meta charset="UTF-8" />
<title>Object.observer API代码示例页面</title>
<style>
table, td, th {
    border: 2px #000000 solid;
}
</style>
<script>
window.addEventListener('DOMContentLoaded',function() {
    if (!Object.observe) {
        alert('您的浏览器不支持Object.observe API。请使用Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的浏览器,并启用“启用实验性 JavaScript”选项。');
        return;
    }
    var obj = {a: 1};  
    Object.observe(obj, output);  
    obj.b = 2; //添加属性
    obj.a = 2; //修改属性值
    Object.defineProperties(obj, {a: { enumerable: false}}); //修改属性设定
    delete obj.b; //删除属性
  
    function output(changes) {
        var results = document.getElementById('results');
        var table = document.createElement('table');
        results.appendChild(table);
        var caption = document.createElement('caption');
        caption.innerText = '监视到的事件列表';
        table.appendChild(caption);
        var thead = document.createElement('thead');
        thead.innerHTML = '<tr><th>序号</th><th>操作种类</th><th>属性名</th><th>修改前的属性值</th><th>修改后的属性值</th></tr>';
        table.appendChild(thead);
        changes.forEach(function(change, i) {
            var tr = document.createElement('tr');
            tr.innerHTML = '<td>' + i + '</td><td>' + change.type + '</td><td>' + change.name + '</td><td>' + change.oldValue + '</td><td>' + change.object[change.name] + '</td>';
            table.appendChild(tr);
        });
    }
});
</script>
</head>
<body>
<div id="event"><div>示例代码</div>
<pre>
obj.b = 2; //添加属性
obj.a = 2; //修改属性值
Object.defineProperties(obj, {a: { enumerable: false}});  //修改属性设定
delete obj.b; //删除属性
</pre>
</div>
<div id="results"></div>
</body>
</html>

        页面运行结果如下图所示

创建自定义Notify对象

        可以监视到的事件并不局限于以上所述的几种,可以自定义监视事件。

        可以使用Notifier对象来自定义针对对象的可访问属性(可使用getter方法或setter方法读取或设置的属性)被修改时所触发的事件。这时,我们需要Object.getNotifier()方法获取被监视对象的Notifier对象,并使用notify方法进行属性被修改的通知。

        在以下这个示例代码中,为对象自定义time_updated事件及time_read事件并利用这两个事件监视对象的私有属性_time的读取及修改。


var obj2 = {_time: new Date(0)};
var notifier = Object.getNotifier(obj2); //获取Notifier对象
Object.defineProperties(obj2, { //设置对象的可访问属性 
    _time: {
        enumerable: false,
        configrable: false
    },
    seen: {
        set: function(val) {
            var notifier = Object.getNotifier(this);
            notifier.notify({
                type: 'time_updated', //定义time_updated事件
                name: 'seen',
                oldValue: this._time
            });
            this._time = val;
        },
        get: function() {
            var notifier = Object.getNotifier(this);
            notifier.notify({
                type: 'time_read', //定义time_read事件
                name: 'seen',
                oldValue: this._time
            });
            return this._time;
        }
    }
});
Object.observe(obj2, output); //为对象指定监视时调用的回调函数
//执行属性操作
var first_time = obj2.seen; //触发time_read事件
obj2.seen = new Date();      //触发time_updated事件
var second_time = obj2.seen; //触发time_read事件

测试代码:

<!DOCTYPE html>
<head>
<title>Object.observer API代码示例页面</title>
<style>
table, td, th {
    border: 2px #000000 solid;
}
</style>
<script>
window.addEventListener('DOMContentLoaded',function() {
    if (!Object.observe) {
        alert('您的浏览器不支持Object.observe API。请使用Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的浏览器,并启用“启用实验性 JavaScript”选项。');
        return;
    }
    var obj2 = {_time: new Date(0)};
    var notifier = Object.getNotifier(obj2);
    Object.defineProperties(obj2, {
        _time: {
            enumerable: false,
            configrable: false
        },
        seen: {
            set: function(val) {
                var notifier = Object.getNotifier(this);
                notifier.notify({
                    type: 'time_updated', //定义time_updated事件
                    name: 'seen',
                    oldValue: this._time
                });
                this._time = val;
            },
            get: function() {
                var notifier = Object.getNotifier(this);
                notifier.notify({
                    type: 'time_read', //定义time_read事件
                    name: 'seen',
                    oldValue: this._time
                });
                return this._time;
            }
        }
    });

    Object.observe(obj2, output);
  
    var seen = document.getElementById('seen');
  
    var first_seen = document.createElement('div');
    var first_time = obj2.seen; //触发time_read事件
    first_seen.innerText = 'first_seen:' + first_time;
    seen.appendChild(first_seen);
  
    obj2.seen = new Date(); //触发time_updated事件
  
    var second_seen = document.createElement('div');
    var second_time = obj2.seen; //触发time_read事件
    second_seen.innerText = 'second_seen:' + second_time;
    seen.appendChild(second_seen);


    function output(changes) {
        var results = document.getElementById('results');
        var table = document.createElement('table');
        results.appendChild(table);
        var caption = document.createElement('caption');
        caption.innerText = '监视到的事件列表';
        table.appendChild(caption);
        var thead = document.createElement('thead');
        thead.innerHTML = '<tr><th>序号</th><th>操作种类</th><th>属性名</th><th>修改前的属性值</th></tr>';
        table.appendChild(thead);
    
      changes.forEach(function(change, i) {
          var tr = document.createElement('tr');
          tr.innerHTML = '<td>' + i + '</td><td>' + change.type + '</td><td>' + change.name + '</td><td>' + change.oldValue + '</td>';
          table.appendChild(tr);
      });
    }
});
</script>
</head>
<body>
<div id="event"><div>示例代码</div>
<pre>
var first_time = obj2.seen; //触发time_read事件
obj2.seen = new Date();      //触发time_updated事件
var second_time = obj2.seen; //触发time_read事件
</pre>
</div>
<div id="seen"></div>
<div id="results"></div>
</body>
</html>

控制回调函数的执行时间

        在默认情况下,使用Object.observe API指定的回调函数将在JavaScript脚本代码执行结束时被调用。因此如果对同一对象的同一属性执行了多次操作,回调函数中获取到的各属性值为最后一个操作结束后的值。将前面这个示例中的代码稍作修改,对使用Object.observe API进行监视的对象的属性值连续修改七次(为了避免回调函数的循环调用删除对time_read事件的监视)。


obj3.seen = new Date(2013, 0, 1, 0, 0, 0); //触发time_updated事件
  obj3.seen = new Date(2024, 0, 2, 0, 0, 0); //触发time_updated事件
  obj3.seen = new Date(2024, 0, 3, 0, 0, 0); //触发time_updated事件
  obj3.seen = new Date(2024, 0, 4, 0, 0, 0); //触发time_updated事件
  obj3.seen = new Date(2024, 0, 5, 0, 0, 0); //触发time_updated事件
  obj3.seen = new Date(2024, 0, 6, 0, 0, 0); //触发time_updated事件
  obj3.seen = new Date(2024, 0, 7, 0, 0, 0); //触发time_updated事件

测试代码:

<!DOCTYPE html>
<head>
<title>Object.observer API代码示例页面</title>
<style>
table, td, th {
    border: 2px #000000 solid;
}
</style>
<script>
window.addEventListener('DOMContentLoaded',function() {
    if (!Object.observe) {
        alert('您的浏览器不支持Object.observe API。请使用Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的浏览器,并启用“启用实验性 JavaScript”选项。');
        return;
    }
  
    var obj3 = {_time: new Date(0)};
    var notifier = Object.getNotifier(obj3);
    Object.defineProperties(obj3, {
        _time: {
            enumerable: false,
            configrable: false
        },
        seen: {
            set: function(val) {
                var notifier = Object.getNotifier(this);
                notifier.notify({
                    type: 'time_updated', // 時間更新イベントの定義
                    name: 'seen',
                    oldValue: this._time
                });
                this._time = val;
            },
            get: function() {
                return this._time;
            }
        }
    });

    Object.observe(obj3, output);
  
    obj3.seen = new Date(2024, 0, 1, 0, 0, 0); //触发time_updated事件
    obj3.seen = new Date(2024, 0, 2, 0, 0, 0); //触发time_updated事件
    obj3.seen = new Date(2024, 0, 3, 0, 0, 0); //触发time_updated事件
    obj3.seen = new Date(2024, 0, 4, 0, 0, 0); //触发time_updated事件
    obj3.seen = new Date(2024, 0, 5, 0, 0, 0); //触发time_updated事件
    obj3.seen = new Date(2024, 0, 6, 0, 0, 0); //触发time_updated事件
    obj3.seen = new Date(2024, 0, 7, 0, 0, 0); //触发time_updated事件
  
    function output (changes) {
        var results = document.getElementById('results');
        var table = document.createElement('table');
        results.appendChild(table);
        var caption = document.createElement('caption');
        caption.innerText = '监视到的事件列表';
        table.appendChild(caption);
        var thead = document.createElement('thead');
        thead.innerHTML = '<tr><th>序号</th><th>操作种类</th><th>属性名</th><th>修改前的属性值</th><th>修改后的属性值</th></tr>';
        table.appendChild(thead);
    
        changes.forEach(function(change, i) {
            var tr = document.createElement('tr');
            tr.innerHTML = '<td>' + i + '</td><td>' + change.type + '</td><td>' + change.name + '</td><td>' + change.oldValue + '</td><td>' + change.object[change.name] + '</td>';
            table.appendChild(tr);
        });
    }
});
</script>
</head>
<body>
<div id="event"><div>示例代码</div>
<pre>
obj3.seen = new Date(2024, 0, 1, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2024, 0, 2, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2024, 0, 3, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2024, 0, 4, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2024, 0, 5, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2024, 0, 6, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2024, 0, 7, 0, 0, 0); //触发time_updated事件
</pre>
</div>
<div id="seen"></div>
<div id="results"></div>
</body>
</html>

        为了强制获取事件触发后立即设置的属性值,我们需要使用Object.deliverChangeRecords方法。

        在如下所示的代码中,每次修改了属性值后,即调用Object.deliverChangeRecords方法立即调用回调函数。


obj4.seen = new Date(2024, 0, 1, 0, 0, 0); //触发time_updated事件
  Object.deliverChangeRecords(output);       //调用回调函数
  obj4.seen = new Date(2024, 0, 2, 0, 0, 0); //触发time_updated事件
  Object.deliverChangeRecords(output);       //调用回调函数
  obj4.seen = new Date(2024, 0, 3, 0, 0, 0); //触发time_updated事件
  Object.deliverChangeRecords(output);       //调用回调函数
  obj4.seen = new Date(2024, 0, 4, 0, 0, 0); //触发time_updated事件
  Object.deliverChangeRecords(output);       //调用回调函数
  obj4.seen = new Date(2024, 0, 5, 0, 0, 0); //触发time_updated事件
  Object.deliverChangeRecords(output);       //调用回调函数
  obj4.seen = new Date(2024, 0, 6, 0, 0, 0); //触发time_updated事件
  Object.deliverChangeRecords(output);       //调用回调函数
  obj4.seen = new Date(2024, 0, 7, 0, 0, 0); //触发time_updated事件

测试代码:

<!DOCTYPE html>
<head>
<title>Object.observer API代码示例页面</title>
<style>
table, td, th {
    border: 2px #000000 solid;
}
</style>
<script>
window.addEventListener('DOMContentLoaded',function() {
    if (!Object.observe) {
        alert('您的浏览器不支持Object.observe API。请使用Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的浏览器,并启用“启用实验性 JavaScript”选项。');
        return;
    }
  
    var obj4 = {_time: new Date(0)};
    var notifier = Object.getNotifier(obj4);
    Object.defineProperties(obj4, {
        _time: {
            enumerable: false,
            configrable: false
        },
        seen: {
            set: function(val) {
                var notifier = Object.getNotifier(this);
                notifier.notify({
                    type: 'time_updated', // 時間更新イベントの定義
                    name: 'seen',
                    oldValue: this._time
                });
                this._time = val;
            },
            get: function() {
                return this._time;
            }
        }
    });

    Object.observe(obj4, output);
  
    obj4.seen = new Date(2013, 0, 1, 0, 0, 0); //触发time_updated事件
    Object.deliverChangeRecords(output);       //调用回调函数
    obj4.seen = new Date(2024, 0, 2, 0, 0, 0); //触发time_updated事件
    Object.deliverChangeRecords(output);       //调用回调函数
    obj4.seen = new Date(2024, 0, 3, 0, 0, 0); //触发time_updated事件
    Object.deliverChangeRecords(output);       //调用回调函数
    obj4.seen = new Date(2024, 0, 4, 0, 0, 0); //触发time_updated事件
    Object.deliverChangeRecords(output);       //调用回调函数
    obj4.seen = new Date(2024, 0, 5, 0, 0, 0); //触发time_updated事件
    Object.deliverChangeRecords(output);       //调用回调函数
    obj4.seen = new Date(2024, 0, 6, 0, 0, 0); //触发time_updated事件
    Object.deliverChangeRecords(output);       //调用回调函数
    obj4.seen = new Date(2024, 0, 7, 0, 0, 0); //触发time_updated事件
  
    function output (changes) {
        var results = document.getElementById('results');
        var table = document.createElement('table');
        results.appendChild(table);
        var caption = document.createElement('caption');
        caption.innerText = '监视到的事件列表';
        table.appendChild(caption);
        var thead = document.createElement('thead');
        thead.innerHTML = '<tr><th>序号</th><th>操作种类</th><th>属性名</th><th>修改前的属性值</th><th>修改后的属性值</th></tr>';
        table.appendChild(thead);
    
        changes.forEach(function(change, i) {
            var tr = document.createElement('tr');
            tr.innerHTML = '<td>' + i + '</td><td>' + change.type + '</td><td>' + change.name + '</td><td>' + change.oldValue + '</td><td>' + change.object[change.name] + '</td>';
            table.appendChild(tr);
        });
    }
});
</script>
</head>
<body>
<div id="event"><div>示例代码</div>
<pre>
obj4.seen = new Date(2024, 0, 1, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output);       //调用回调函数
obj4.seen = new Date(2024, 0, 2, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output);       //调用回调函数
obj4.seen = new Date(2024, 0, 3, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output);       //调用回调函数
obj4.seen = new Date(2024, 0, 4, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output);       //调用回调函数
obj4.seen = new Date(2024, 0, 5, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output);       //调用回调函数
obj4.seen = new Date(2024, 0, 6, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output);       //调用回调函数
obj4.seen = new Date(2024, 0, 7, 0, 0, 0); //触发time_updated事件
</pre>
</div>
<div id="seen"></div>
<div id="results"></div>
</body>
</html>

        从结果中我们可以看出,每次执行Object.deliverChangeRecords方法时都将调用回调函数在页面中输出修改后的属性值。

  • 24
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mango's Louvre

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值