[Effective JavaScript 笔记]第48条:避免在枚举期间修改对象
注册列表示例
一个社交网络有一组成员,每个成员有一个存储其朋友信息的注册列表。
function Member(name){
this.name=name;
this.friends=[];
}
var a=new Member('钟二'),
b=new Member('张三'),
c=new Member('赵四'),
d=new Member('王五'),
e=new Member('阮六'),
f=new Member('耿七');
a.friends.push(b);
b.friends.push(c);
c.friends.push(e);
d.friends.push(b);
e.friends.push(d,f);
搜索该网络意味着需要遍历该社交网络图。
通常通过工作集来实现。工作集以单个根节点开始,然后添加发现的节点,移除访问过的节点。
for...in遍历图
使用for...in循环来实现该遍历是很方便的。
Member.prototype.inNetwork=function(other){
var visited={};
var workset={};
workset[this.name]=this;
for(var name in workset){
var member=workset[name];
delete workset[name];
if(name in visited){
continue;
}
visited[name]=member;
if(member===other){
return true;
}
member.friends.forEach(function(friend){
workset[friend.name]=friend;
});
}
return false;
}
上面代码有什么问题嘛?在大多环境下可以工作,但有一些环境这段代码就不能工作了。
a.inNetwork(f);//false
这里为什么呢。这里说明for...in循环在运行时出错了错误,并没有要求枚举对象的修改与当前保持一致。事实上,ES对并发修改在不同js环境下的行为的规范留有余地。标准规定:
如果被枚举的对象在枚举期间添加了新的属性,那么在枚举期间并不能保证新添加的属性能被访问。
上面规范的实际后果:如果我们修改了被枚举的对象,则不能保证for...in循环的行为是可预见的。
另一种遍历图
自己管得循环控制。当使用循环时,应该使用自己的字典抽象以避免原型污染。可以将字典放置在WorkSet类中来追踪当前集合中的元素数量。
function WorkSet(){
this.entries=new Dict();
this.count=0;
}
WorkSet.prototype.isEmpty=function(){
return this.count===0;
};
WorkSet.prototype.add=function(key,val){
if(this.entries.has(key)){
return;
}
this.entries.set(key,val);
this.count++;
};
WorkSet.prototype.get=function(key){
return this.entries.get(key);
};
WorkSet.prototype.remove=function(key){
if(!this.entries.has(key)){
return;
}
this.entries.remove(key);
this.count--;
};
为了提取集合的任意一个元素,给Dict类添加一个新方法
Dict.prototype.pick=function(){
for(var key in this.elements){
if(this.has(key)){
return key;
}
}
throw new Error('empty dictionary');
};
WorkSet.prototype.pick=function(){
return this.entries.pick();
};
下面改写上一版本的inNetwork方法,这里使用while来循环。每次选择任意一个元素并从工作集中删除。
Member.prototype.inNetwork=function(other){
var visited={};
var workset=new WorkSet();
workset.add(this.name,this);
while(!workset.isEmpty()){
var name=workset.pick();
var member=workset.get(name);
workset.remove(name);
if(name in visited){
continue;
}
visited[name]=member;
if(member === other){
return true;
}
member.friends.forEach(function(friend){
workset.add(friend.name,friend);
})
}
return false;
};
其中pick方法是一个不确定性的例子。不确定性是指一个操作并不能保证使用语言的主义产生一个单一的可预见的结果。这个不确定性是因为for...in循环可能在不同的js环境中选择不同的枚举顺序。使用不确定性可能会使你的程序引入一个不可预测的元素。测试可能在某个平台通过,某些平台不通过,或同一平台不同时候,结果也可能不同。
工用列表算法
不确定性的来源是难以避免的,考虑使用一个确定的工作集算法替代方案。即工作列表算法。将工作条目存储到数组中而不是集合中,则inNetwork方法,将总是以相同的顺序遍历图。
Member.prototype.inNetwork=function(other){
var visited={};
var worklist=[this];
while(worklist.length>0){
var member=worklist.pop();
if(member.name in visited){
continue;
}
visited[memeber.name]=member;
if(member === other){
return true;
}
member.friends.forEach(function(friend){
worklist.push(friend);
})
}
return false;
};
这一版本inNetwork方法会确定性地添加和删除工作条目。无论发现什么路径,该方法对于连接的成员总是返回true,所以最终结果是一样的。
提示
当使用for...in循环枚举一个对象的属性时,确保不要修改对象
当迭代一个对象时,如果该对象的内容可能会在循环期间被改变,应该使用while循环或经典的for循环来代替for...in循环
为了在不断变化的数据结构中能够预测枚举,考虑使用一个有序的数据结构,例如数组,而不要使用字典
附录:示例完整版
function Member(name){
this.name=name;
this.friends=[];
}
Member.prototype.inNetwork=function(other){
var visited={};
var worklist=[this];
while(worklist.length>0){
var member=worklist.pop();
if(member.name in visited){
continue;
}
visited[member.name]=member;
if(member === other){
return true;
}
member.friends.forEach(function(friend){
worklist.push(friend);
})
}
return false;
};
//测试代码
var a=new Member('钟二'),
b=new Member('张三'),
c=new Member('赵四'),
d=new Member('王五'),
e=new Member('阮六'),
f=new Member('耿七');
a.friends.push(b);
b.friends.push(c);
c.friends.push(e);
d.friends.push(b);
e.friends.push(d,f);
a.inNetwork(f);//true
a.inNetwork(d);//true
f.inNetwork(a);//false
[Effective JavaScript 笔记]第48条:避免在枚举期间修改对象的更多相关文章
- [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象
js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...
- [Effective JavaScript 笔记] 第8条:尽量少用全局对象
初学者容易使用全局变量的原因 创建全局变量毫不费力,不需要任何形式的声明(只要在非函数里用var 你就可以得到一个全局变量) 写得代码简单,不涉及到大的项目或配合(写hello world是不会有什么 ...
- [Effective JavaScript 笔记]第24条:使用变量保存arguments对象
迭代器(iterator)是一个可以顺序存取数据集合的对象.其一个典型的API是next方法.该方法获得序列中的下一个值. 迭代器示例 题目:希望编写一个便利的函数,它可以接收任意数量的参数,并为这些 ...
- [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法
js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...
- [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符
“1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...
- [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码
函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...
- [Effective JavaScript 笔记]第65条:不要在计算时阻塞事件队列
第61条解释了异步API怎样帮助我们防止一段程序阻塞应用程序的事件队列.使用下面代码,可以很容易使一个应用程序陷入泥潭. while(true){} 而且它并不需要一个无限循环来写一个缓慢的程序.代码 ...
- [Effective JavaScript 笔记]第51条:在类数组对象上复用通用的数组方法
前面有几条都讲过关于Array.prototype的标准方法.这些标准方法被设计成其他对象可复用的方法,即使这些对象并没有继承Array. arguments对象 在22条中提到的函数argument ...
- [Effective JavaScript 笔记]第60条:支持方法链
无状态的API的部分能力是将复杂操作分解为更小的操作的灵活性.一个很好的例子是字符串的replace方法.由于结果本身也是字符串,可以对前一个replace操作重复执行替换.这种模式的一个常见用例是在 ...
随机推荐
- WCF入门 (13)
前言 公司略无聊,周三前同事推荐跳槽,于是会去更新了一下简历,突然发现,快一年了,我竟然想不出我可以往简历上添加点什么值得自豪的东西.下午和小伙伴聊了一会天,他告诉我,可以往简历上写上“英语口语水平有 ...
- Graphics samples2
为图形填充渐变色: Graphics2D g2=(Graphics2D)g; GradientPaint gra=new GradientPaint(20, 20, Color.BLUE, 100,8 ...
- u1-nav-js
'use strict';define([ 'jquery'], function($) { var nav = { init : function() { $("#burger-menu& ...
- hdu1686 KMP
简单KMP 求单词出现的次数.直接可以考虑,在每一次匹配成功时,让ans++,k=next[k],直到结束. #include<stdio.h> #include<string.h& ...
- poj1703 并查集
输入是2个不在一起的人,可以用一个数组来保存和他矛盾的人.这样find的时候就find(hash[]);就可以: #include<stdio.h> #include<string. ...
- Java算法-符号&
&与运算符 与运算符用符号“&”表示,其使用规律如下:两个操作数中位都为1,结果才为1,否则结果为0 例如下面的程序段. public class data13 { public s ...
- 【HDU 5438】Ponds
题意 不断删去度数为1的点,最后求有奇数个点的联通块的权值之和. 分析 存边的时候,要头尾都存这个边.用dfs或者队列删点,再用并查集或者dfs确定联通块,然后统计联通块的点数,最后累加. 我自己写的 ...
- DLX模型问题
问题:sevenzero liked Warcraft very much, but he haven't practiced it for several years after being add ...
- 如何使用lessc编译.less文件
LESS :一种动态样式语言. LESS 将 CSS 赋予了动态语言的特性,如 变量, 继承, 运算, 函数. LESS 既可以在 客户端 上运行 (支持IE 6+, Webkit, Firefox) ...
- JSP登录验证并显示信息
加入C标签: 加入jstl.jar 和standard.jar加入Lib文件夹中 将c.tld放入WEB-Info文件夹中 index.jsp <%@ page language="j ...