for-in循环

for-in循环用于对非数组对象进行遍历。通过for-in进行循环也被称作“枚举”(enumeration)。

从技术上讲,for-in循环同样可以用于数组(JavaScript中数组也是对象),但不推荐这样做。当数组对象被扩充了自定义函数时,可能会产生逻辑错误。另外,for-in循环中属性的遍历顺序是不固定的,所以最好数组使用普通的for循环,对象使用for-in循环。

可以使用对象的hasOwnProperty()方法过滤来自原型链中继承来的属性,这一点非常重要。看一下这段代码:

  1. // 对象
  2. var man = {
  3. hands: 2,
  4. legs: 2,
  5. heads: 1
  6. };
  7. // 在代码的另一个地方给所有的对象添加了一个方法
  8. if (typeof Object.prototype.clone === "undefined") {
  9. Object.prototype.clone = function () {};
  10. }

在这个例子中,我们使用对象字面量定义了一个名叫man的对象。在代码中的某个地方(可以是man定义之前也可以是之后),给Object的原型增加了一个方法clone()。原型链是实时的,这意味着所有的对象都可以访问到这个新方法。要想在枚举man的时候避免枚举出clone()方法,就需要调用hasOwnProperty()来过滤来自原型的属性。如果不做过滤,clone()也会被遍历到,这是我们不希望看到的:

  1. // 1.for-in循环
  2. for (var i in man) {
  3. if (man.hasOwnProperty(i)) { // filter
  4. console.log(i, ":", man[i]);
  5. }
  6. }
  7. /*
  8. 控制台中的结果
  9. hands : 2
  10. legs : 2
  11. heads : 1
  12. */
  13. // 2.反模式:
  14. // 不使用hasOwnProperty()过滤的for-in循环
  15. for (var i in man) {
  16. console.log(i, ":", man[i]);
  17. }
  18. /*
  19. 控制台中的结果
  20. hands : 2
  21. legs : 2
  22. heads : 1
  23. clone: function()
  24. */

另外一种调用hasOwnProperty()的方法是通过Object.prototype来调用,像这样:

  1. for (var i in man) {
  2. if (Object.prototype.hasOwnProperty.call(man, i)) { // 过滤
  3. console.log(i, ":", man[i]);
  4. }
  5. }

这种做法的好处是,在man对象中重新定义了hasOwnProperty方法的情况下,可以避免调用时的命名冲突。为了避免查找属性时从Object对象一路找到原型的冗长过程,你可以定义一个变量来“缓存”住它:

  1. var i,
  2. hasOwn = Object.prototype.hasOwnProperty;
  3. for (i in man) {
  4. if (hasOwn.call(man, i)) { // 过滤
  5. console.log(i, ":", man[i]);
  6. }
  7. }

严格说来,省略hasOwnProperty()并不是一个错误。根据具体的任务以及你对代码的自信程度,你可以省略掉它以提高一些程序执行效率。但当你对当前要遍历的对象不确定的时候,添加hasOwnProperty()则更加保险些。

这里介绍一种格式上的变种(这种写法无法通过JSLint检查),这种写法在for循环所在的行加入了if判断条件,他的好处是能让循环语句读起来更完整和通顺(“如果元素包含属性X,则对X做点什么”):

  1. // 警告:无法通过JSLint检查
  2. var i,
  3. hasOwn = Object.prototype.hasOwnProperty;
  4. for (i in man) if (hasOwn.call(man, i)) { // 过滤
  5. console.log(i, ":", man[i]);
  6. }