搜索
首页web前端js教程跟我学习javascript的循环_javascript技巧

1、优先使用数组而不是Object类型来表示有顺序的集合

ECMAScript标准并没有规定对JavaScript的Object类型中的属性的存储顺序。

但是在使用for..in循环对Object中的属性进行遍历的时候,确实是需要依赖于某种顺序的。正因为ECMAScript没有对这个顺序进行明确地规范,所以每个JavaScript执行引擎都能够根据自身的特点进行实现,那么在不同的执行环境中就不能保证for..in循环的行为一致性了。

比如,以下代码在调用report方法时的结果就是不确定的:

function report(highScores) { 
  var result = ""; 
  var i = 1; 
  for (var name in highScores) { // unpredictable order 
    result += i + ". " + name + ": " + 
    highScores[name] + "\n"; 
    i++; 
  } 
  return result; 
} 
report([{ name: "Hank", points: 1110100 }, 
{ name: "Steve", points: 1064500 }, 
{ name: "Billy", points: 1050200 }]); 
// ? 

如果你确实需要保证运行的结果是建立在数据的顺序上,优先使用数组类型来表示数据,而不是直接使用Object类型。同时,也尽量避免使用for..in循环,而使用显式的for循环:

function report(highScores) { 
  var result = ""; 
  for (var i = 0, n = highScores.length; i < n; i++) { 
    var score = highScores[i]; 
    result += (i + 1) + ". " + 
    score.name + ": " + score.points + "\n"; 
  } 
  return result; 
} 
report([{ name: "Hank", points: 1110100 }, 
{ name: "Steve", points: 1064500 }, 
{ name: "Billy", points: 1050200 }]); 
// "1. Hank: 1110100 2. Steve: 1064500 3. Billy: 1050200\n" 

另一个特别依赖于顺序的行为是浮点数的计算:

var ratings = { 
  "Good Will Hunting": 0.8, 
  "Mystic River": 0.7, 
  "21": 0.6, 
  "Doubt": 0.9 
}; 

在Item 2中,谈到了浮点数的加法操作甚至不能满足交换律:
(0.1 + 0.2) + 0.3 的结果和 0.1 + (0.2 + 0.3)的结果分别是
0.600000000000001 和 0.6

所以对于浮点数的算术操作,更加不能使用任意的顺序了:

var total = 0, count = 0; 
for (var key in ratings) { // unpredictable order 
  total += ratings[key]; 
  count++; 
} 
total /= count; 
total; // &#63; 

当for..in的遍历顺序不一样时,最后得到的total结果也就不一样了,以下是两种计算顺序和其对应的结果:

(0.8 + 0.7 + 0.6 +0.9) / 4 // 0.75
(0.6 + 0.8 + 0.7 +0.9) / 4 // 0.7499999999999999

当然,对于浮点数的计算这一类问题,有一个解决方案是使用整型数来表示,比如我们将上面的浮点数首先放大10倍变成整型数据,然后计算结束之后再缩小10倍:

(8+ 7 + 6 + 9) / 4 / 10  // 0.75
(6+ 8 + 7 + 9) / 4 / 10  // 0.75

2、绝不要向Object.prototype中添加可列举的(Enumerable)属性

如果你的代码中依赖于for..in循环来遍历Object类型中的属性的话,不要向Object.prototype中添加任何可列举的属性。

但是在对JavaScript执行环境进行增强的时候,往往都需要向Object.prototype对象添加新的属性或者方法。比如可以添加一个方法用于得到某个对象中的所有的属性名:

Object.prototype.allKeys = function() { 
  var result = []; 
  for (var key in this) { 
    result.push(key); 
  } 
  return result; 
}; 

但是结果是下面这个样子的:

({ a: 1, b: 2, c: 3}).allKeys(); // ["allKeys", "a", "b","c"]

一个可行的解决方案是使用函数而不是在Object.prototype上定义新的方法:

function allKeys(obj) { 
  var result = []; 
  for (var key in obj) { 
    result.push(key); 
  } 
  return result; 
} 

但是如果你确实需要向Object.prototype上添加新的属性,同时也不希望该属性在for..in循环中被遍历到,那么可以利用ES5环境提供的Object.defineProject方法:

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 
}); 

以上代码的关键部分就是将enumerable属性设置为false。这样的话,在for..in循环中就无法遍历该属性了。

3、对于数组遍历,优先使用for循环,而不是for..in循环

虽然上个Item已经说过这个问题,但是对于下面这段代码,能看出最后的平均数是多少吗?

var scores = [98, 74, 85, 77, 93, 100, 89]; 
var total = 0; 
for (var score in scores) { 
  total += score; 
} 
var mean = total / scores.length; 
mean; // &#63; 

通过计算,最后的结果应该是88。

但是不要忘了在for..in循环中,被遍历的永远是key,而不是value,对于数组同样如此。因此上述for..in循环中的score并不是期望的98, 74等一系列值,而是0, 1等一系列索引。

所以你也许会认为最后的结果是:
(0 + 1+ …+ 6) / 7 = 21

但是这个答案也是错的。另外一个关键点在于,for..in循环中key的类型永远都是字符串类型,因此这里的+操作符执行的实际上是字符串的拼接操作:

最后得到的total实际上是字符串00123456。这个字符串转换成数值类型后的值是123456,然后再将它除以元素的个数7,就得到了最后的结果:17636.571428571428

所以,对于数组遍历,还是使用标准的for循环最好

4、优先使用遍历方法而非循环

在使用循环的时候,很容易违反DRY(Don't Repeat Yourself)原则。这是因为我们通常会选择复制粘贴的方法来避免手写一段段的循环语句。但是这样做回让代码中出现大量重复代码,开发人员也在没有意义地”重复造轮子”。更重要的是,在复制粘贴的时候很容易忽视循环中的那些细节,比如起始索引值,终止判断条件等。

比如以下的for循环就存在这个问题,假设n是集合对象的长度:

for (var i = 0; i <= n; i++) { ... }
// 终止条件错误,应该是i < n
for (var i = 1; i < n; i++) { ... }
// 起始变量错误,应该是i = 0
for (var i = n; i >= 0; i--) { ... }
// 起始变量错误,应该是i = n - 1
for (var i = n - 1; i > 0; i--) { ... }
// 终止条件错误,应该是i >= 0

可见在循环的一些细节处理上很容易出错。而利用JavaScript提供的闭包(参见Item 11),可以将循环的细节给封装起来供重用。实际上,ES5就提供了一些方法来处理这一问题。其中的Array.prototype.forEach是最简单的一个。利用它,我们可以将循环这样写:

// 使用for循环
for (var i = 0, n = players.length; i < n; i++) {
  players[i].score++;
}

// 使用forEach
players.forEach(function(p) {
  p.score++;
});

除了对集合对象进行遍历之外,另一种常见的模式是对原集合中的每个元素进行某种操作,然后得到一个新的集合,我们也可以利用forEach方法实现如下:

// 使用for循环
var trimmed = [];
for (var i = 0, n = input.length; i < n; i++) {
  trimmed.push(input[i].trim());
}

// 使用forEach
var trimmed = [];
input.forEach(function(s) {
  trimmed.push(s.trim());
});

但是由于这种由将一个集合转换为另一个集合的模式十分常见,ES5也提供了Array.prototype.map方法用来让代码更加简单和优雅:

var trimmed = input.map(function(s) {
  return s.trim();
});

另外,还有一种常见模式是对集合根据某种条件进行过滤,然后得到一个原集合的子集。ES5中提供了Array.prototype.filter来实现这一模式。该方法接受一个Predicate作为参数,它是一个返回true或者false的函数:返回true意味着该元素会被保留在新的集合中;返回false则意味着该元素不会出现在新集合中。比如,我们使用以下代码来对商品的价格进行过滤,仅保留价格在[min, max]区间的商品:

listings.filter(function(listing) {
  return listing.price >= min && listing.price <= max;
});

当然,以上的方法是在支持ES5的环境中可用的。在其它环境中,我们有两种选择: 1. 使用第三方库,如underscore或者lodash,它们都提供了相当多的通用方法来操作对象和集合。 2. 根据需要自行定义。

比如,定义如下的方法来根据某个条件取得集合中前面的若干元素:

function takeWhile(a, pred) {
  var result = [];
  for (var i = 0, n = a.length; i < n; i++) {
    if (!pred(a[i], i)) {
      break;
    }
    result[i] = a[i];
  }
  return result;
}

var prefix = takeWhile([1, 2, 4, 8, 16, 32], function(n) {
  return n < 10;
}); // [1, 2, 4, 8]

为了更好的重用该方法,我们可以将它定义在Array.prototype对象上,具体的影响可以参考Item 42。

Array.prototype.takeWhile = function(pred) {
  var result = [];
  for (var i = 0, n = this.length; i < n; i++) {
    if (!pred(this[i], i)) {
      break;
    }
    result[i] = this[i];
  }
  return result; 
};

var prefix = [1, 2, 4, 8, 16, 32].takeWhile(function(n) {
  return n < 10;
}); // [1, 2, 4, 8]

只有一个场合使用循环会比使用遍历函数要好:需要使用break和continue的时候。 比如,当使用forEach来实现上面的takeWhile方法时就会有问题,在不满足predicate的时候应该如何实现呢?

function takeWhile(a, pred) {
  var result = [];
  a.forEach(function(x, i) {
    if (!pred(x)) {
      // &#63;
    }
    result[i] = x;
  });
  return result;
}

我们可以使用一个内部的异常来进行判断,但是它同样有些笨拙和低效:

function takeWhile(a, pred) {
  var result = [];
  var earlyExit = {}; // unique value signaling loop break
  try {
    a.forEach(function(x, i) {
      if (!pred(x)) {
        throw earlyExit;
      }
      result[i] = x;
    });
  } catch (e) {
    if (e !== earlyExit) { // only catch earlyExit
      throw e;
    }
  }
  return result;
}

可是使用forEach之后,代码甚至比使用它之前更加冗长。这显然是存在问题的。 对于这个问题,ES5提供了some和every方法用来处理存在提前终止的循环,它们的用法如下所示:

[1, 10, 100].some(function(x) { return x > 5; }); // true
[1, 10, 100].some(function(x) { return x < 0; }); // false

[1, 2, 3, 4, 5].every(function(x) { return x > 0; }); // true
[1, 2, 3, 4, 5].every(function(x) { return x < 3; }); // false

这两个方法都是短路方法(Short-circuiting):只要有任何一个元素在some方法的predicate中返回true,那么some就会返回;只有有任何一个元素在every方法的predicate中返回false,那么every方法也会返回false。

因此,takeWhile就可以实现如下:

function takeWhile(a, pred) {
  var result = [];
  a.every(function(x, i) {
    if (!pred(x)) {
      return false; // break
    }
    result[i] = x;
    return true; // continue
  });
  return result;
}

实际上,这就是函数式编程的思想。在函数式编程中,你很少能够看见显式的for循环或者while循环。循环的细节都被很好地封装起来了。

5、总结

  • 在使用for..in循环时,不要依赖于遍历的顺序。
  • 当使用Object类型来保存数据时,需要保证其中的数据是无序的。
  • 当需要表示带有顺序的集合时,使用数组类型而不是Object类型。
  • 避免向Object.prototype中添加任何属性。
  • 如果确实有必要向Object.prototype中添加方法属性,可以考虑使用独立函数替代。
  • 使用Object.defineProperty来添加可以不被for..in循环遍历到的属性。
  • 当遍历数组时,使用标准的for循环,而不要使用for..in循环。
  • 在必要的场合考虑预先保存数组的长度,以提高性能。
  • 使用遍历方法Array.prototype.forEach和Array.prototype.map来代替循环,从而让代码更加清晰可读。
  • 对于重复出现的循环,可以考虑将它们进行抽象。通过第三方提供的方法或者自己实现。
  • 显式的循环在一些场合下还是有用武之地的,相应的也可以使用some或者every方法。

以上就是本文的全部内容,希望通过这篇文章大家更加了解javascript循环的原理,大家共同进步。

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
Python vs. JavaScript:开发环境和工具Python vs. JavaScript:开发环境和工具Apr 26, 2025 am 12:09 AM

Python和JavaScript在开发环境上的选择都很重要。1)Python的开发环境包括PyCharm、JupyterNotebook和Anaconda,适合数据科学和快速原型开发。2)JavaScript的开发环境包括Node.js、VSCode和Webpack,适用于前端和后端开发。根据项目需求选择合适的工具可以提高开发效率和项目成功率。

JavaScript是用C编写的吗?检查证据JavaScript是用C编写的吗?检查证据Apr 25, 2025 am 12:15 AM

是的,JavaScript的引擎核心是用C语言编写的。1)C语言提供了高效性能和底层控制,适合JavaScript引擎的开发。2)以V8引擎为例,其核心用C 编写,结合了C的效率和面向对象特性。3)JavaScript引擎的工作原理包括解析、编译和执行,C语言在这些过程中发挥关键作用。

JavaScript的角色:使网络交互和动态JavaScript的角色:使网络交互和动态Apr 24, 2025 am 12:12 AM

JavaScript是现代网站的核心,因为它增强了网页的交互性和动态性。1)它允许在不刷新页面的情况下改变内容,2)通过DOMAPI操作网页,3)支持复杂的交互效果如动画和拖放,4)优化性能和最佳实践提高用户体验。

C和JavaScript:连接解释C和JavaScript:连接解释Apr 23, 2025 am 12:07 AM

C 和JavaScript通过WebAssembly实现互操作性。1)C 代码编译成WebAssembly模块,引入到JavaScript环境中,增强计算能力。2)在游戏开发中,C 处理物理引擎和图形渲染,JavaScript负责游戏逻辑和用户界面。

从网站到应用程序:JavaScript的不同应用从网站到应用程序:JavaScript的不同应用Apr 22, 2025 am 12:02 AM

JavaScript在网站、移动应用、桌面应用和服务器端编程中均有广泛应用。1)在网站开发中,JavaScript与HTML、CSS一起操作DOM,实现动态效果,并支持如jQuery、React等框架。2)通过ReactNative和Ionic,JavaScript用于开发跨平台移动应用。3)Electron框架使JavaScript能构建桌面应用。4)Node.js让JavaScript在服务器端运行,支持高并发请求。

Python vs. JavaScript:比较用例和应用程序Python vs. JavaScript:比较用例和应用程序Apr 21, 2025 am 12:01 AM

Python更适合数据科学和自动化,JavaScript更适合前端和全栈开发。1.Python在数据科学和机器学习中表现出色,使用NumPy、Pandas等库进行数据处理和建模。2.Python在自动化和脚本编写方面简洁高效。3.JavaScript在前端开发中不可或缺,用于构建动态网页和单页面应用。4.JavaScript通过Node.js在后端开发中发挥作用,支持全栈开发。

C/C在JavaScript口译员和编译器中的作用C/C在JavaScript口译员和编译器中的作用Apr 20, 2025 am 12:01 AM

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。 1)C 用于解析JavaScript源码并生成抽象语法树。 2)C 负责生成和执行字节码。 3)C 实现JIT编译器,在运行时优化和编译热点代码,显着提高JavaScript的执行效率。

JavaScript在行动中:现实世界中的示例和项目JavaScript在行动中:现实世界中的示例和项目Apr 19, 2025 am 12:13 AM

JavaScript在现实世界中的应用包括前端和后端开发。1)通过构建TODO列表应用展示前端应用,涉及DOM操作和事件处理。2)通过Node.js和Express构建RESTfulAPI展示后端应用。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)