Effective JavaScript :第五章
1.使用Object的直接实例构造轻量级的字典
字典就是可变长的字符串与值得映射集合。JavaScript甚至提供了枚举一个对象属性名的利器——for...in循环。
var dict = { alice : 34 , bob : 24 , chris : 62 } ; var people = [ ] ; for(var name in dict){ people.push(name + ":"+ dict [name]) } people ;
坚持Object的直接实例原则可以使得for...in循环摆脱原型污染的影响。
2.防止原型污染的最简单的方式之一就是一开始就不适用原型。但在ES5之前没有标准的方式创建一个空原型的新对象。你可能会尝试设置一个构造函数的原型属性为null或者undefined。
function C() { } C.prototype = null ;
但实例化该构造函数仍然得到的是Object的实例。
var o = new C() ; Object.getPrototypeOf(o) === null ; Object.getPrototypeOf(o) === Object.prototype ;
ES5首先提供了标准方法来创建一个没有原型的对象。Object.create函数能够使用一个用户指定的原型链和一个属性描述符动态地构造对象。通过简单的传递一个null原型参数和一个空的描述符,我们就可以建立一个真正的空对象。
var x = Object.create(null) ; Object.getPrototypeOf(o) === null ;
原型污染无法影响这样的对象行为。一些不支持Object.create函数的旧的JavaScript环境可能支持另一种值得一提的方式。在许多环境中,__proto__提供了对对象内部原型链的读写访问。对象字面量语法也支持初始化一个原型链为null的新对象。
3.使用hasOwnProperty方法以避免原型污染
JavaScript的对象操作总是以继承的方式工作。即使是一个空的对象字面量也继承了Object.prototype的大量属性。但它提供的hasOwnProperty可以避免原型污染。
4.绝对不要在Object.prototype中增加可枚举的属性
到目前为止,for...in循环最常见的用法是枚举字典中的元素。如果想允许对字典对象使用for...in循环,那么不要在共享的Object.prototype中增加可枚举的属性。
如下代码:
Object.prototype.allKeys = function(){ var result = [ ] ; for(var key in this){ result.push(key) ; } return result ; }
遗憾的是,该方法污染了自身。如果你真的想在Object.prototype中增加属性,ES5提供了一种更友好的机制。Object.defineProperty方法可以定义一个对象的属性并指定该属性的元数据。例如,我们可以用与之前完全一样的方式定义上面的属性而通过设置可枚举属性为false使其在for...in循环中不可见。
Object.defineProperty(Object.prototype , “allkeys”,{ value : function(){ var result = [ ] ; for (var key in this){ result.push(key) ; } return result ; }, writable : true , enumerable : false , configurable : true });
每当你增加一个不应该在for...in循环中出现的属性时,Object.defineProperty便是你的选择。
5.避免在枚举期间修改对象
事实上,ECMAScript标准规定了:
如果被枚举的对象在枚举期间添加了新的属性,那么在枚举期间并不能保证新添加的属性能够被访问。当迭代一个对象时,如果该对象的内容可能会在循环期间被改变,应该使用while循环或经典的for循环来代替for...in循环。
6.数组迭代要优先使用for循环而不是for...in循环。
7.迭代方法优于循环。
①JavaScript的闭包是一种为这些模式建立迭代抽象方便的、富有表现力的手法,从而使我们避免复制,粘贴循环头部。ES5为最常用的一些模式提供了便利的方法。Array.prototype.forEach是其中最简单的一个。例如:
for(var i = 0 , n = players.length ; i < n ; i ++){ players[ i ].score++ ; }
我们可以用下面的代码替换上面的循环:
players.forEach(function(p){ p.score++ ; }) ;
这段代码不仅更简单可读,而且还消除了终止条件和任何数组索引。
②另一种常见的模式是对数组的每个元素进行一些操作后建立一个新的数组。ES5引入了Array.prototype.map方法使该模式更简单、更优雅。
var trimmed = input.map(function(s){ return s.trim() ; }) ;
③另一种常见的模式是计算一个新的数组,该数组只包含现有数组的一些元素。Array.prototype.filter使其变得很简便。它需要一个谓词,该谓词是一个函数,如果元素应该存在于新数组中则返回真值否则返回假值。例如,我们可以从价格表中提取出符合一个特定价格区间的列表:
listings.filter(function(listing){ return listing.price >= min && listing.price <=max ; }) ;
循环只有一点优于迭代函数,那就是前者有控制流操作,如break和continue。
此外,ES5的数组方法some和every可以用于提前终止循环。
8.在类数组对象上复用通用的数组方法
Array.ptototype中的标准方法被设计成其他对象可复用的方法,即使这些对象并没有继承Array。事实证明,许多这样的类数组对象接踵而至地出现在JavaScript的不同地方。在Web平台,DOM的NodeList类是另一个类数组对象的实例。类似document.getElementsByTagName这样的操作会查询Web页面中的节点,并返回NodeList作为搜索的结果。