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. 使用exec命令删除前几天产生的日志

    上次学习了访问某个网站并产生相应的日志保存在指定位置,但是时间长了,日志会占用磁盘大量的空间,下面使用exec这个命令删除之前的日志: 命令格式: find 目录绝对路径 -mtime +n(时间) ...

  2. [问题2014A12] 复旦高等代数 I(14级)每周一题(第十四教学周)

    [问题2014A12]  设 \(A,B\) 是 \(n\) 阶方阵且满足 \(AB=BA=0\), \(\mathrm{r}(A)=\mathrm{r}(A^2)\), 证明: \[\mathrm{ ...

  3. c#委托(1)

    // 委托声明 -- 定义一个签名:delegate double MathAction(double num); class DelegateTest{ // 符合委托声明的常规方法 static ...

  4. iOS OAuth2.0认证和SSO授权

    OAuth2.0和SSO授权   一.OAuth2.0授权协议 一种安全的登陆协议,用户提交的账户密码不提交到本APP,而是提交到授权服务器,待服务器确认后,返回本APP一个访问令牌,本APP即可用该 ...

  5. SPSS数据分析——t检验

    SPSS中t检验全都集中在分析—比较均值菜单中.关于t检验再简单说一下,我们知道一个统计结果需要表达三部分内容,即集中性.变异性.显著性. 集中性的表现指标是均值变异的的表现指标是方差.标准差或标准误 ...

  6. 自定义N维空间数组

    class Space : IEnumerable<Space> { public object Filler { get { return filler ?? (filler = Top ...

  7. Oracle job定时器的执行时间间隔学习汇总

      Oracle job 定时器的执行时间间隔也是定时器job 的关键设置,在这一设置上,开始还没掌握,总是不知道怎么写,现总结如下,其实主要是使用了TRUNC.NEXT_DAY .ADD_MONTH ...

  8. 博客打开慢?请禁用WordPress默认的谷歌字体!

    最近几天,谷歌中国挂了之后,发现我的博客打开极慢,原以为是空间问题,可一查,发现同台服务器的用户打开并不慢,排除了空间问题后,这边查询元素发现博客打开时加载了一个链接地址“fonts.googleap ...

  9. 远程实时调试手机上的Web页面

    1. 安装    需要Node.js平台, 先安装好后, 打开Node.js command prompt, 通过NPM来安装 weinre npm -g install weinre 2. 启动   ...

  10. “LC.exe”已退出,代码为 -1

    造成这个问题的原因一般是引入了第三方插件,自己遇到的问题是引入了devexpress...... 1.找到Properties文件夹licenses.licx文件,然后右键选择删除就可以了,调试运行正 ...