Object.observe API概述

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

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

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

目前为止,Object.observe API已经被strawman proposal所承认,被正式使用在V8中。自11月末开始,已经可以在Chrome Canary与开发者通道中对其进行启用。

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

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

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

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

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

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

目前(2012年12月6日)为止,如果要使用Object.observe API,需要使用Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的浏览器,同时在chrome://flags/中启用“启用实验性 JavaScript”选项,如下图所示。


简单代码示例

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>

运行代码

页面运行结果如下图所示(在Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的浏览器中)


创建自定义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(2013, 0, 2, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2013, 0, 3, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2013, 0, 4, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2013, 0, 5, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2013, 0, 6, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2013, 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(2013, 0, 1, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2013, 0, 2, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2013, 0, 3, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2013, 0, 4, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2013, 0, 5, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2013, 0, 6, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2013, 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(2013, 0, 1, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2013, 0, 2, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2013, 0, 3, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2013, 0, 4, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2013, 0, 5, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2013, 0, 6, 0, 0, 0); //触发time_updated事件
obj3.seen = new Date(2013, 0, 7, 0, 0, 0); //触发time_updated事件
</pre>
</div>
<div id="seen"></div>
<div id="results"></div>
</body>
</html>

运行代码

页面运行结果如下图所示(在Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的浏览器中)


从这个结果中我们可以看出,在回调函数中获取到的属性值为同一个属性值(2013年1月7日)。为了强制获取事件触发后立即设置的属性值,我们需要使用Object.deliverChangeRecords方法。

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

obj4.seen = new Date(2013, 0, 1, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output); //调用回调函数
obj4.seen = new Date(2013, 0, 2, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output); //调用回调函数
obj4.seen = new Date(2013, 0, 3, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output); //调用回调函数
obj4.seen = new Date(2013, 0, 4, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output); //调用回调函数
obj4.seen = new Date(2013, 0, 5, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output); //调用回调函数
obj4.seen = new Date(2013, 0, 6, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output); //调用回调函数
obj4.seen = new Date(2013, 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(2013, 0, 2, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output); //调用回调函数
obj4.seen = new Date(2013, 0, 3, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output); //调用回调函数
obj4.seen = new Date(2013, 0, 4, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output); //调用回调函数
obj4.seen = new Date(2013, 0, 5, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output); //调用回调函数
obj4.seen = new Date(2013, 0, 6, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output); //调用回调函数
obj4.seen = new Date(2013, 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(2013, 0, 1, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output); //调用回调函数
obj4.seen = new Date(2013, 0, 2, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output); //调用回调函数
obj4.seen = new Date(2013, 0, 3, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output); //调用回调函数
obj4.seen = new Date(2013, 0, 4, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output); //调用回调函数
obj4.seen = new Date(2013, 0, 5, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output); //调用回调函数
obj4.seen = new Date(2013, 0, 6, 0, 0, 0); //触发time_updated事件
Object.deliverChangeRecords(output); //调用回调函数
obj4.seen = new Date(2013, 0, 7, 0, 0, 0); //触发time_updated事件
</pre>
</div>
<div id="seen"></div>
<div id="results"></div>
</body>
</html>

运行代码

页面运行结果如下图所示(在Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的浏览器中)


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

使用Object.observe 实现数据绑定的更多相关文章

  1. Object.observe

    使用Object.observe 实现数据绑定

  2. 【JavaScript】Object.observe()带来的数据绑定变革

    Object.observe()带来的数据绑定变革 引言 一场变革即将到来.一项Javascript中的新特性将会改变你对于数据绑定的所有认识.它也将改变你所使用的MVC库观察模型中发生的修改以及更新 ...

  3. object.observe数据绑定

    object.observe方法格式如下: object.observe(object,callback) 监听object对象,当该对象有新增或更新或删除等操作,就会触发callback,就实现了双 ...

  4. Object.observe将不加入到ES7

    先请看 Object.observe 的 API Object.observe(obj, callback[, acceptList]) 它用来监听对象的变化,当给该对象添加属性,修改属性时都会被依次 ...

  5. [ES7] Object.observe + Microtasks

    ES6: If you know about the Javascirpt's event loop. You know that any asyns opreations will be throw ...

  6. Object.observe() 观察对象

    这个对象方法可以用来异步观察对javascript对象的改动: // Let's say we have a model with data var model = {};   // Which we ...

  7. Object.defineProperty 与数据绑定的简单实现

    对象是一个属性集合,对象的基本特征是属性名(name)和属性值(value).ES5 增加了属性描述符,包括数据属性描述符(configurable enumerable writable value ...

  8. Object.defineProperty实现数据绑定

    1.Object.defineProperty方法 Object.defineProperty(obj, prop, descriptor); (1)参数:  obj:目标对象 prop:需要定义的属 ...

  9. object.observe被废弃了怎么办

    用新的 Proxy 具体见:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

随机推荐

  1. jodaTime 的使用说明

    <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifact ...

  2. golang csv问题

    go语言自带的有csv文件读取模块,看起来好像不错,今天玩玩,也算是系统学习go语言的一部分--^_^ 一.写csv文件 函数: func NewWriter(w io.Writer) *Writer ...

  3. 分方式缓存常用的一致性hash是什么原理

    分方式缓存常用的一致性hash是什么原理 一致性hash是用来解决什么问题的?先看一个场景有n个cache服务器,一个对象object映射到哪个cache上呢?可以采用通用方法计算object的has ...

  4. php null o false ''

    php中很多还不懂php中0,"",null和false之间的区别,这些区别有时会影响到数据判断的正确性和安全性,给程序的测试运行造成很多麻烦.先看一个例子: <? $str ...

  5. javaWeb4 http

    状态码:服务器处理请求的结果(状态) 常见的状态: 200: 表示请求处理完成并完美返回.ok 302:表示请求需要进一步细化. 404:表示客户访问的资源找不到.Not Found 500:表示服务 ...

  6. Caché数据库学习笔记(4)

    目录 DeepSee的使用 数据.方法等的导入与导出 ======================================================== ================ ...

  7. 0020 Linux 文件操作命令

    1. 创建文件 touch 文件名 2. 删除文件 rm 文件名 3. 复制文件 cp 源文件 目录 4.剪切文件 mv 源文件 目标文件 5.重命名文件 mv 源文件名 新文件名 6.改变文件权限 ...

  8. 0010 Linux 目录操作命令

    01.更改目录 cd  /  返回根目录 cd ~  返回用户根目录 cd -  返回上个操作目录目录 ,等同于cd $OLDPWD 02.查看工作目录 pwd 03.创建目录 mkdir 目录名 0 ...

  9. centos 装VBOX

    #cd /etc/yum.repos.d/wget http://download.virtualbox.org/virtualbox/rpm/rhel/virtualbox.repoyum inst ...

  10. 读<jQuery 权威指南>[6]--实用工具函数

    官方地址:http://api.jquery.com/category/utilities/ 一.数组和对象操作 1. $.each——遍历 $.each(obj,function(param1,pa ...