幾年前就有人說javascript是最被低估一種程式語言,自從nodejs出來後,全棧(All Stack/Full Stack)概念日漸興起,現在恐怕沒人再敢低估它了。 javascrip是一種類C的語言,有C語言基礎就能大體理解javascript的程式碼,但是作為一種腳本語言,javascript的彈性是C所遠遠不及的,這也會造成學習上的一些困難。
一、集合
1.首先是幾個迭代的方法。
_.each = _.forEach = function(obj, iteratee, context) { iteratee = optimizeCb(iteratee, context); var i, length; if (isArrayLike(obj)) { for (i = 0, length = obj.length; i < length; i++) { iteratee(obj[i], i, obj); } } else { var keys = _.keys(obj); for (i = 0, length = keys.length; i < length; i++) { iteratee(obj[keys[i]], keys[i], obj); } } // 链式调用 return obj; };
ES為陣列同樣加入了原生的forEach()方法。不同的是這裡的each(forEach)方法可以對所有集合使用,函數接受三個參數(集合、迭代函數、執行環境)。
optimizeCb函數根據迭代函數參數個數的不同為不同的迭代方法綁定了對應的執行環境,forEach迭代函數同樣接受三個參數(值,索引,集合)。
接下來就是for迴圈呼叫迭代函數了。
_.map中一種更優雅的判斷isArrayLike的實作方式:(只用一個for迴圈)
var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length, results = Array(length); for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; results[index] = iteratee(obj[currentKey], currentKey, obj); } return results; // 合理使用&&、||、?:可以大大减少代码量
還有兩個特別的地方:
•將集合分成了類別數組集合和物件集合。使用了isArrayLike函數:
// js的最大精确整数 var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; var isArrayLike = function(collection) { var length = collection != null && collection.length; return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; }; // 如果集合有Length属性且为数字并且大于0小于最大的精确整数,则判定是类数组
•使用了_.keys函數,Object同樣有原生的keys函數,用來傳回一個集合obj可被列舉的屬性陣列。實作比較簡單,for in加上hasOwnProperty()方法。
----------------------------------------------- ---------------------------------
_.map,_.reduce方法原理類似.
_.find函數和Array.some()類似,不同的是返回的是第一個使迭代結果為真的那個元素,而不是Array.some()那樣返回布林值。
_.find = _.detect = function(obj, predicate, context) { var key; if (isArrayLike(obj)) { key = _.findIndex(obj, predicate, context); } else { key = _.findKey(obj, predicate, context); } if (key !== void 0 && key !== -1) return obj[key]; }; function createIndexFinder(dir) { return function(array, predicate, context) { predicate = cb(predicate, context); var length = array != null && array.length; // 如果dir为1,index为0,index+=1,index正序循环 // 如果dir 为-1,index为length-1,index += -1反序循环 // 判断循环条件则用了index >= 0 && index < length方法兼顾两种循环方式 var index = dir > 0 ? 0 : length - 1; for (; index >= 0 && index < length; index += dir) { if (predicate(array[index], index, array)) return index; } return -1; }; } _.findIndex = createIndexFinder(1); _.findLastIndex = createIndexFinder(-1);
值得借鏡的地方是這裡的一個for迴圈能夠根據傳入的參數不同配置不同的循環順序。
1.集合中的其他方法基本上都是基於迭代方法來實現的。
_.max = function(obj, iteratee, context) { var result = -Infinity, lastComputed = -Infinity, value, computed; if (iteratee == null && obj != null) { obj = isArrayLike(obj) ? obj : _.values(obj); for (var i = 0, length = obj.length; i < length; i++) { value = obj[i]; if (value > result) { result = value; } } } else { iteratee = cb(iteratee, context); _.each(obj, function(value, index, list) { computed = iteratee(value, index, list); if (computed > lastComputed || computed === -Infinity && result === -Infinity) { result = value; lastComputed = computed; } }); } return result; };
max方法用於尋找集合中的最大值,透過循環list中的所有項,然後比較目前項和結果項,如果目前項大於結果,則將其賦給結果項,最後傳回結果項。
2.集合轉換為陣列
_.toArray = function(obj) { if (!obj) return []; // 如果是数组,采用了Array.prototype.slice.call(this,obj)这种方法 if (_.isArray(obj)) return slice.call(obj); // 类数组对象,这里没有采用Slice方法,而是利用.map对集合进行迭代,从而返回一个数组。 _.identity该方法传入的值和返回的值相等。(主要用于迭代) if (isArrayLike(obj)) return _.map(obj, _.identity); // 普通对象,则返回由属性值组成的数组。 return _.values(obj); };
資料型別
STL需要區分vector、list等是因為不同的資料結構需要或可以進行不同的實現,但underscore裡面Collections和Arrays分開是什麼道理呢?這也要從javascript的資料型別說起,看下圖。