你的位置:首页 > Java教程

[Java教程]( 译、持续更新 ) JavaScript 上分小技巧(三)


最近家里杂事较多,自学时间实在少的可怜,所以都在空闲时间看看老外写的内容,学习之外顺便翻译分享~等学习的时间充足些再写写自己的一些学习内容和知识点分析(最近有在接触的:复习(C#,SQL)、(学习)TypeScript,(基础操作)MongoDB。TypeScript之后入手AngularJs 2.0)

后续如有内容,本篇将会照常更新并排满15个知识点,以下是其他几篇译文的地址:

第一篇地址:( 译、持续更新 ) JavaScript 上分小技巧(一)

第二篇地址:( 译、持续更新 ) JavaScript 上分小技巧(二)

#34 - 执行异步调用的循环
让我们试着写一个异步函数,它每秒打印一次循环的索引值。

for (var i=0; i<5; i++) {  setTimeout(function(){    console.log(i);   }, 1000);}

以上程序将会输出:

_> 5_> 5_> 5_> 5_> 5

所以,它根本没有正确的执行。
原因:
因为timeout的异步,而每次的timeout指向的是原始的i,不是副本中的。所以循环直到i=5,然后timeout运行,并且打印当前的i(5)。
好吧,这个问题似乎很容易。一个马上就可以解决的方案是即可将当前的i值当作一个临时变量缓存在循环中。

for (var i=0; i<5; i++) {  var temp = i;  setTimeout(function(){    console.log(i);   }, 1000);}

但是上面的程序输出的还是:

_> 5_> 5_> 5_> 5_> 5

所以,那也行不通,因为代码块不会创建一个作用域并且变量的初始化被提升到作用域的前面。事实上,和之前的代码是一样的:
解决方案:
有很多种不同的方案来复制i。最常见的是创建一个闭包,通过声明一个函数,并将i作为参数传入。在这里,我们用自执行函数:

for (var i=0; i<5; i++) {  (function(num){    setTimeout(function(){      console.log(num);     }, 1000);   })(i); }

在JavaScript中,参数是按值传递给函数。像数字、日期和字符串的原始类型基本上是复制的。如果你在函数内改变了它们,不影响它在外部范围的值。对象是特殊的:如果内部函数改变了它的一个属性,则在所有作用域中的对应值跟着变化。

#33 - 使用一行代码简单的创建一个[1,...,N]的范围数组
下面这行代码能够创建一个[1,...,N]的范围数组

Array.apply(null,{length:N}).map(Fn,Context);

让我们将这行代码拆分成几部分。我们都知道javascript的apply()函数的作用。在apply()里,第一个参数是根据第二个参数而来的上下文对象,它是被我们称为apply()函数的参数列表。

function add(a, b){  return (a+b);}add.apply(null,[5, 6]);

将会返回5+6的和。
javascript中的数组的map()函数有2个参数,第一个是回调函数,第二个是回调函数的上下文对象。回调函数有3个参数:value(值)、index(索引)、我们处理的整个阵列。所以普通语法是:

[1, 2, 3].map(function(value, index, arr){  //Code}, this);

下面的这行代码创建一个给定长度的数组:

Array.apply(null,{length:N});

把所有部分放到一起就是解决方案:

Array.apply(null,{length:N}).map(Fn,Context);

如果你想创建一个[1,...,N]的范围数组:

Array.apply(null, {length: N}).map(function(value, index){ return index+1; });

#32 - Map() 有秩序的给对象填加属性
object是Object类型中的一个成员,它是个包含原始值、对象或者函数的一个无序的属性集合。储存在对象属性中的函数被成为方法。ECMAScript
看以下代码:

var myObject = {  z: 1,  '@': 2,  b: 3,  1: 4,  5: 5 };console.log(myObject) // Object {1: 4, 5: 5, z: 1, @: 2, b: 3}for (item in myObject) {...// 1// 5// z// @// b

每个浏览器针对对象技术都有着各自的规则,所以秩序是不确定的。
怎么解决这个问题呢?
Map
使用ES6中的一个新特性---Map。一个Map对象按插入的顺序来迭代它的元素---for...of循环为每个循环项返回一个[key,value]形式的数组。

var myObject = new Map();myObject.set('z', 1);myObject.set('@', 2);myObject.set('b', 3);for (var [key, value] of myObject) { console.log(key, value);...// z 1// @ 2// b 3

旧浏览器的hack
Mozilla 建议:
因此,如果你想在一个交叉的浏览器环境中模拟一个有序的关联数组,你不得不使用2个单独的数组(一个键的数组和一个值的数组),或构建一个单属性对象的数组等。

// 使用两个数组var objectKeys = [z, @, b, 1, 5];for (item in myObject) {  myObject[item]...// 创建一个单属性对象的数组var myData = [{z: 1}, {'@': 2}, {b: 3}, {1: 4}, {5: 5}];

#31 - 避免将"arguments"进行修改或者传递到其他函数 - 这将扼杀优化
背景:
在javascript的函数中,名为arguments的变量让你能够操作传入函数的所有参数。arguments是个类似数组的object,arguments能够使用数组符号来做操作,并且有length属性,但是它不能改使用一些数组内置的方法,比如filter、map和forEach。正是因为如此,将参数转换为数组来使用是一种相当普遍的做法:

var args = Array.prototype.slice.call(arguments);

从Array的prototypr中调用slice方法,传入arguments;slice方法返回一个新的浅度复制arguments的数组对象。这是一个常用的速记法:

var args = [].slice.call(arguments);

在这个案例中,我们更简单的用一个字面量的空数组来代替从Array的prototype调用slice方法。
优化:
不幸的是,在基于Chrome和Node的JavaScript V8引擎上,在函数内将arguments传入任何其他函数都会导致性能变慢。看这篇文章:优化杀手。将arguments传递给任何其他函数称为参数泄露。
作为替代,如果想要可以使用的参数数组,你需要做这些:

var args = new Array(arguments.length);for(var i = 0; i < args.length; ++i) { args[i] = arguments[i];}

这是更详细的,但在生产代码中,以这些来进行性能优化是值得的。

          ### 2016-02-04 更新 ###