一行行读源码之Async.js(1)
处理async库内部变量与全局变量冲突问题
(function() { // Async.js 实现代码 )()
async的所有实现代码被一个立即执行函数所包围,所有的变量都是在该立即函数的范围内定义,没有泄露到全局作用域中
var async = {};
第一行定义的async空对象是Async.js的命名空间,对外的函数全部定义在该命名空间之中,这样就不会产生命名冲突
处理可能的async变量冲突问题
var root, previous_async; root = this; if (root != null) { previous_async = root.async; }
接下来定义的root以及previous_async变量:
通过把封装async的立即函数执行时的this赋值给了root,把root定义成了一个全局变量,在服务端(NodeJS)中是global对象,在浏览器中则是window对象。
而previous_async变量则保存了对root对象原有async变量的引用。
async.noConflict = function() { root.async = previous_async; return async; }
然后就是async库的第一个共有函数,async.noConflict,它负责处理async库的命名空间已经存在的情况,将前面保存的原有async引用重新赋值回root对象,然后返回async库的命名空间。将这个函数的返回值赋给新变量,并通过新的变量调用async库的功能,使得不与已有的async变量冲突。
function only_once(fn) { var called = false; return function() { if (called) throw new Error("Callback was already called."); called = true; fn.apply(root, arguments); } }
only_once函数接受一个函数作为参数,使得这个函数只能被调用一次,可以看做是一个函数装饰器模式,也是一个闭包。在函数里头定义了一个called变量,该变量用来标识传入的函数是否已调用,默认值false表示未调用。它返回另外一个函数,在返回的函数中,通过判断called变量的值来确定函数是否已调用。没有调用过的话在函数中变更called变量的值,called变量的值是通过闭包来保存的。然后调用传入的函数参数,这是函数的执行空间是root变量。
处理跨浏览器兼容性问题
接下来为了跨浏览器兼容性而定义了_each, _map, _reduce以及_keys这四个函数,这几个函数前面几行都是判断是否具备相应的原函数,有的话,就直接利用原函数实现功能,否则就自己去模拟。我们可以看看如果浏览器中没有这四种函数的话,要怎么样去模拟这四种函数。
_each函数
var _each = function (arr, iterator) { if (arr.forEach) { return arr.forEach(iterator); } for (var i = 0; i < arr.length; i += 1) { iterator(arr[i], i, arr); } };
_each函数的实现较为简单,首先判断传入的数组对象中是否存在forEach方法,如果有的话,就把iterator传给forEach方法,利用forEach方法来处理。我们知道forEach方法接收一个函数作为参数,这个函数有三个参数,分别是
当前处理的元素
当前处理元素的偏移
当前处理的整个数组对象
所以iterator函数也应该接收这三个参数,这一点我们可以在接下来async库自己模拟的forEach功能中看到。如果arr数组没有forEach方法,那么就用一个for循环来模拟,在for循环中调用iterator函数处理,这时传给iterator的三个参数就是前面说过的三个参数。
_map函数
var _map = function (arr, iterator) { if (arr.map) { return arr.map(iterator); } var results = []; _each(arr, function (x, i, a) { results.push(iterator(x, i, a)); }); return results;
_map函数中中,定义了一个results数组,用来存储即将返回的结果。我们可以看到,在数组中不存在原生map方法时,可以通过前面定义的_each方法来实现map的功能。
不过这时_each函数调用的函数中,把iterator函数调用后的返回值push到了前面定义的results数组中。在处理完了所有元素之后,把results数组返回了。这样就与原生map函数表现一致,接收一个数组作为参数,返回的结果是另外一个数组,这个数组中的元素依次对应iterator函数处理原有传入数组元素后的函数返回值。这里的iterator函数与前面_each函数的iterator定义一致,也是接收前面说过的三个参数。
_reduce函数
var _reduce = function (arr, iterator, memo) { if (arr.reduce) { return arr.reduce(iterator, memo); } _each(arr, function (x, i, a) { memo = iterator(memo, x, i, a); }); return memo; };
_reduce函数接收的参数与前面定义的两个略有不同,除了数组arr以及迭代处理函数iterator之外,还接收一个memo参数作为reduce操作的初始值。如果原生的数组reduce方法存在,该方法接收一个迭代处理函数以及一个初始值作为参数,在初始值参数不存在的情况下,以数组的第一个元素作为初始值。
在_reduce函数中,也是利用前面定义的_each函数来处理数组元素的。可以看到这时iterator函数接收的是四个参数,第一个是初始值,接下来才是前面说明过的三个参数,即
处理初始值memo
当前处理元素x
当前处理元素偏移i
当前处理的数组a
在_reduce函数中,memo参数不仅仅充当了初始值的角色,还扮演了一个累加器的角色,这里累加器不仅仅指得是加法,而是所有的二元操作。iterator函数调用时,接收前一次的memo值,跟当前处理的值x做某些操作后,返回值重新赋值回了memo变量,作为下一次迭代的参数,就这样依次处理arr数组中的元素,实现了reduce功能
_keys函数
var _keys = function (obj) { if (Object.keys) { return Object.keys(obj); } var keys = []; for (var k in obj) { if (obj.hasOwnProperty(k)) { keys.push(k); } } return keys; };
这个函数也很简单,将传入对象obj的key值集合以数组形式返回,从obj.hasOwnProperty(k)可以看出来,这里的key值集合,是obj对象自己的key值,而不会是obj对象从继承链上继承的key值。



















