版本 Underscore.js 1.9.1html
一共 1693 行。註釋我就刪了,太長了…node
總體是一個 (function() {...}()); 這樣的東西,咱們應該知道這是一個 IIFE(當即執行函數)。git
var root = typeof self == 'object' && self.self === self && self || typeof global == 'object' && global.global === global && global || this || {};
獲取當前運行環境根對象。github
在瀏覽器中爲 self(=window) 在服務端中是 global 在一些虛擬機中是 this數組
var previousUnderscore = root._;
若是環境中已經定義了同名變量,防止對其形成覆蓋,先把這個變量緩存起來。瀏覽器
var ArrayProto = Array.prototype, ObjProto = Object.prototype; var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null; var push = ArrayProto.push, slice = ArrayProto.slice, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; var nativeIsArray = Array.isArray, nativeKeys = Object.keys, nativeCreate = Object.create;
定義一些變量來存儲 JS 定義的對象原型和方法,以便後續使用。緩存
var Ctor = function(){};
根據註釋,這個裸函數是用來代理原型交換的?英文很差……後面應該能夠看到用處,不急。app
var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; };
_ 是一個構造函數,傳入的對象若是已是 _ 實例就直接返回dom
咱們知道當咱們經過 new foo() 建立對象時會建立一個新的對象,而後將它的原型鏈綁定爲 foo.propotype ,而後把這個對象做爲 foo 調用的this,若是 foo 沒有返回值的話,就返回這個對象。ide
因此經過 this instanceof _ 能夠判斷是不是構造調用(是否加 new)若是不是的話 就手動加一個 new 調用一次。
經過foo生成一個對象,他有一個屬性 _wrapped 的值是傳入的obj。
if (typeof exports != 'undefined' && !exports.nodeType) { if (typeof module != 'undefined' && !module.nodeType && module.exports) { exports = module.exports = _; } exports._ = _; } else { root._ = _; }
由於 node 環境中會有 exports 變量,由此判斷是在瀏覽器仍是服務端。服務端的話就導出 _ ,不然在根元素上添加 _ 變量。
_.VERSION = '1.9.1';
版本號
var optimizeCb = function(func, context, argCount) { if (context === void 0) return func; switch (argCount == null ? 3 : argCount) { case 1: return function(value) { return func.call(context, value); }; // The 2-argument case is omitted because we’re not using it. case 3: return function(value, index, collection) { return func.call(context, value, index, collection); }; case 4: return function(accumulator, value, index, collection) { return func.call(context, accumulator, value, index, collection); }; } return function() { return func.apply(context, arguments); }; };
其實這個函數能夠簡化成
var optimizeCb = function(func, context) { return function() { return func.apply(context, arguments); }; };
因此說這就至關於實現了一個 bind 。 optimizeCb(func, context) = func.bind(context)
那爲何要分那麼多狀況呢?由於 apply 比 call 慢,並且某些狀況下,還會慢不少。
至於 void 0 是什麼,void + 表達式會返回 undefined 這個算常識吧。而不使用 undefined 由於在某些老的瀏覽器中 undefined 能夠被賦值,出於兼容性考慮。
var builtinIteratee; var cb = function(value, context, argCount) { if (_.iteratee !== builtinIteratee) return _.iteratee(value, context); if (value == null) return _.identity; // _.identity 函數: value => value if (_.isFunction(value)) return optimizeCb(value, context, argCount); if (_.isObject(value) && !_.isArray(value)) return _.matcher(value); return _.property(value); }; _.iteratee = builtinIteratee = function(value, context) { return cb(value, context, Infinity); };
這個看到有點懵比……
首先 builtinIteratee 就是一個用來判斷 iteratee 是否被用戶改變的臨時變量,沒有其餘用處。
_.iteratee() 是一個函數 默認返回 cb
cb 做爲操做集合的函數的回調函數使用
若是 _.iteratee 被修改就調用修改後的函數
若是 value == null 就返回 _.identity 一個傳入什麼就返回什麼的函數
若是 value 是函數 就返回 optimizeCb(value, context, argCount) 也就是 value.bind(context)
若是 value 是對象 且不是數組 就返回 _.matcher(value)
以上都不符合就返回 _.property(value);
_.isMatch = function(object, attrs) { var keys = _.keys(attrs), length = keys.length; if (object == null) return !length; var obj = Object(object); for (var i = 0; i < length; i++) { var key = keys[i]; if (attrs[key] !== obj[key] || !(key in obj)) return false; } return true; }; _.matcher = _.matches = function(attrs) { attrs = _.extendOwn({}, attrs); return function(obj) { return _.isMatch(obj, attrs); }; }; // e.g. var isZhangsan = _.matcher({ firstname: 'san', lastname: 'zhang' }); console.log(isZhangsan({ firstname: 'san', lastname: 'zhang', age: 55 })); // true console.log(isZhangsan({ firstname: 'si', lastname: 'zhang' })); // false
好了 如今知道不是正序寫的了 哭唧唧 先不看 _.extendOwn 是什麼鬼東西了 反正看名字確定是一個擴展對象的函數
首先看 isMatch , keys 至關於 Object.keys , isMatch 就是判斷 attrs 中的 key 是否在 object 中都存在且對應的值都相等。
那麼 _.matcher 就是設定 attrs 返回函數。返回的函數傳入 obj 看其是否符合 attrs。
var shallowProperty = function(key) { return function(obj) { return obj == null ? void 0 : obj[key]; }; }; var deepGet = function(obj, path) { var length = path.length; for (var i = 0; i < length; i++) { if (obj == null) return void 0; obj = obj[path[i]]; } return length ? obj : void 0; }; _.property = function(path) { if (!_.isArray(path)) { return shallowProperty(path); } return function(obj) { return deepGet(obj, path); }; };
若是傳入的不是數組,就返回獲取對象屬性path的值的函數,若是傳入一個數組,就返回獲取對象屬性[path]對應的值的函數。
var restArguments = function(func, startIndex) { startIndex = startIndex == null ? func.length - 1 : +startIndex; return function() { var length = Math.max(arguments.length - startIndex, 0), rest = Array(length), index = 0; for (; index < length; index++) { rest[index] = arguments[index + startIndex]; } switch (startIndex) { case 0: return func.call(this, rest); case 1: return func.call(this, arguments[0], rest); case 2: return func.call(this, arguments[0], arguments[1], rest); } var args = Array(startIndex + 1); for (index = 0; index < startIndex; index++) { args[index] = arguments[index]; } args[startIndex] = rest; return func.apply(this, args); }; };
至關於 ES6 的剩餘參數,從 startIndex 開始的全部參數當作一個數組傳入。分狀況使用 call 仍是上面提到的效率問題。
使用舉例:
function sum(arr) { return arr.reduce((previous, current) => { return previous + current; }); } var restArgumentsWrapperSum = restArguments(sum); console.log(restArgumentsWrapperSum(1, 2, 3));
// var nativeCreate = Object.create; var baseCreate = function(prototype) { if (!_.isObject(prototype)) return {}; if (nativeCreate) return nativeCreate(prototype); Ctor.prototype = prototype; var result = new Ctor; Ctor.prototype = null; return result; };
至關於手動實現了一個 Object.create 利用了上面不知道什麼用空函數 Ctor 。
new Ctor 沒有加括號,在構造調用的時候,若是不傳入參數,能夠不加括號。至關於 new Ctor() 。
Object.create(foo) 就是建立一個對象 對象的 [[Prototype]] 爲 foo.prototype,這裏經過 new 實現。結束以後再將 Ctor 的 protottype 賦值爲 null 。
var shallowProperty = function(key) { return function(obj) { return obj == null ? void 0 : obj[key]; }; }; // e.g. var getId = shallowProperty('id'); let obj = { id: 233, otherKey: 'who care' }; console.log(getId(obj)); // 233
傳入一個 key 生成一個 獲取對象屬性 key 的值 的函數。
// var hasOwnProperty = ObjProto.hasOwnProperty; var has = function(obj, path) { return obj != null && hasOwnProperty.call(obj, path); }
就是使用了 Object.prototype.hasOwnProperty 判斷對象 obj 是否存在屬性 path
var deepGet = function(obj, path) { var length = path.length; for (var i = 0; i < length; i++) { if (obj == null) return void 0; obj = obj[path[i]]; } return length ? obj : void 0; }; // e.g. var obj = { user: { name: { first: 'san', last: 'zhang', }, id: 3 } } console.log(deepGet(obj, ['user', 'name', 'last'])); // zhang
根據路徑獲取對象指定嵌套屬性的值。
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; var getLength = shallowProperty('length'); var isArrayLike = function(collection) { var length = getLength(collection); return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; };
若是一個對象有屬性 length ,屬性值爲數字且在 [0, 2^53-1] 之間,則判斷這個對象爲類數組。
類數組常見的有 arguments、HTML Collection,數組也是類數組。
到這裏是 174 行,下面就是集合相關函數了,明天再看 =。=
_.each = _.forEach = function(obj, iteratee, context) { // 首先將 iteratee 的 this 綁定到 context (若是 context 存在的話 iteratee = optimizeCb(iteratee, context); var i, length; if (isArrayLike(obj)) { // 若是 obj 是類數組 對 obj[0 ... obj.lenght-1] 執行 iteratee for (i = 0, length = obj.length; i < length; i++) { iteratee(obj[i], i, obj); } } else { // 不然獲取 obj 屬性的集合 而後進行操做 var keys = _.keys(obj); for (i = 0, length = keys.length; i < length; i++) { iteratee(obj[keys[i]], keys[i], obj); } } return obj; };
這個函數至關於實現了 ES 的 forEach,傳入(類)數組就遍歷對每一項執行傳入的函數 iteratee(item, idx, arr) ,若是傳入的是對象就對每個鍵值對執行 iteratee(value, key, obj)
舉個使用例子
function foo() { console.log('類數組 --->'); // arguments 是類數組 _.each(arguments, function iteratee(item, idx, arr) { // 傳入 arguments[i], i, arguments console.log(`item=${item}, idx=${idx}, arr=${JSON.stringify(arr)}, this=${this}`); }, '上下文'); console.log('對象 --->'); var obj = { k: 'v', kk: 'vv' }; // _.keys(obj) => ['k', 'kk'] _.each(obj, function iteratee(value, key, obj) { console.log(`value=${value}, key=${key}, obj=${JSON.stringify(obj)}, this=${this}`); }, '上下文'); } foo('one', [2], { three: false }); // 類數組 ---> // item=one, idx=0, arr={"0":"one","1":[2],"2":{"three":false}}, this=上下文 // item=2, idx=1, arr={"0":"one","1":[2],"2":{"three":false}}, this=上下文 // item=[object Object], idx=2, arr={"0":"one","1":[2],"2":{"three":false}}, this=上下文 // 對象 ---> // value=v, key=k, obj={"k":"v","kk":"vv"}, this=上下文 // value=vv, key=kk, obj={"k":"v","kk":"vv"}, this=上下文
_.map = _.collect = function(obj, iteratee, context) { iteratee = cb(iteratee, context); 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; };
這個和上面實現和功能差很少,無非兩個循環放到一塊兒寫了,且對每一項執行傳入函數後都有了返回值,並返回這些返回值組成的數組。
var createReduce = function(dir) { var reducer = function(obj, iteratee, memo, initial) { var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length, index = dir > 0 ? 0 : length - 1; if (!initial) { // 若是沒有傳入初始值就把第一項當作初始值 並從第二項開始執行函數 (反向 reduce 就是倒數第一項咯 memo = obj[keys ? keys[index] : index]; index += dir; } for (; index >= 0 && index < length; index += dir) { var currentKey = keys ? keys[index] : index; memo = iteratee(memo, obj[currentKey], currentKey, obj); } return memo; }; return function(obj, iteratee, memo, context) { var initial = arguments.length >= 3; // 根據參數個數 判斷有沒有初始值 return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial); }; }; _.reduce = _.foldl = _.inject = createReduce(1); _.reduceRight = _.foldr = createReduce(-1);
reduce的實現, createReduce 經過傳入 +1/-1 能夠返回 正向/反向 reduce ,返回函數的函數稱做高階函數 createReduce 就是。
源碼卻是不難理解。不過 reduce 的實現能夠參考一下。手寫代碼什麼的。。。
var cb = function(value, context, argCount) { if (_.iteratee !== builtinIteratee) return _.iteratee(value, context); if (value == null) return _.identity; // _.identity 函數: value => value if (_.isFunction(value)) return optimizeCb(value, context, argCount); if (_.isObject(value) && !_.isArray(value)) return _.matcher(value); return _.property(value); }; var createPredicateIndexFinder = function(dir) { return function(array, predicate, context) { predicate = cb(predicate, context); var length = getLength(array); 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 = createPredicateIndexFinder(1); _.findLastIndex = createPredicateIndexFinder(-1); _.findKey = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = _.keys(obj), key; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; if (predicate(obj[key], key, obj)) return key; } }; _.find = _.detect = function(obj, predicate, context) { var keyFinder = isArrayLike(obj) ? _.findIndex : _.findKey; var key = keyFinder(obj, predicate, context); if (key !== void 0 && key !== -1) return obj[key]; };
這一段可能比較複雜,能夠順便好好理解一下cb函數。
首先 _.find(obj, predicate, context) 若是 obj 不是類數組, keyFinder 就是 _.findKey 進入 _.findKey 先將 predicate 賦值爲 cb(predicate, context) 若是 predicate 是函數 就得到 predicate.bind(context) 若是 predicate 是 null 就得到 _.identity 若是 predicate 是 對象且不是數組 就得到 _.matcher(value) 這個上面看過 就是一個判斷傳入對象是否符合 value 的函數 以上都不符合 得到 _.property(value) 就是獲取一個對象屬性爲 value 的值的函數 (obj) => obj[value] 若是 value 是數組 就是得到嵌套屬性 遍歷 obj 每個屬性 若是屬性值符合 predicate 返回對用屬性名 若是 obj 是類數組, keyFinder 就是 _.findIndex 進入 _.findIndex 先將 predicate 賦值爲 cb(predicate, context) 過程同上 遍歷 obj 每一項 若是該項符合 predicate 就返回下標 獲取了 第一個 符合條件的屬性名或下標,而後獲取對應屬性值。 // =========如下幾個例子便於理解;========= var obj = { a: '2333', b: 666, c: 10086, d: { propertyName: 'mdzz' } }; var arr = ['2333', 666, 10086, { propertyName: 'mdzz' }]; /* 獲取 number 類型的值 */ function predicate(item, index, arr) { return typeof item === 'number'; } console.log( _.find(obj, predicate) ) // predicate 是函數, 獲取 obj 中第一個屬性值類型爲數字的值 > 666 console.log( _.find(obj) ) // 沒有傳入 predicate 就是 _.identity 對於每個 truly 值都符合 因此返回第一個值 > '2333' console.log( _.find(obj, { propertyName: 'mdzz' }) ) // predicate 是對象,查找符合對象的值 > { propertyName: 'mdzz' } console.log( _.find(obj, 'propertyName') ) // predicate 字符串,獲取含有對應屬性的值 > { propertyName: 'mdzz' } console.log( _.find(arr, predicate) ) // 666 console.log( _.find(arr) ) // '2333' console.log( _.find(arr, { propertyName: 'mdzz' }) ) // { propertyName: 'mdzz' } console.log( _.find(arr, 'propertyName') ) // { propertyName: 'mdzz' }
_.filter = _.select = function(obj, predicate, context) { var results = []; predicate = cb(predicate, context); _.each(obj, function(value, index, list) { if (predicate(value, index, list)) results.push(value); }); return results; }; _.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; };
看到這裏已經能理解 underscore 的這個邏輯了,若是是對象就 _.keys(obj) 遍歷key, 若是是 isLikeArray 就遍歷下標。
_.each 就是對遍歷的每一項執行傳入函數。_.filter 就是執行傳入函數後返回 true 項的放入返回對象。
_.reject = function(obj, predicate, context) { return _.filter(obj, _.negate(cb(predicate)), context); }; _.negate = function(predicate) { return function() { return !predicate.apply(this, arguments); }; };
_.negate 將傳入函數返回值取反,_reject 就是反向 _filter 全部不符合 predicate 的項放入返回數組返回。
_.every = _.all = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length; for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; if (!predicate(obj[currentKey], currentKey, obj)) return false; } return true; };
若是數組的每一項都符合 predicate 就返回 true 不然返回 false
_.some = _.any = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length; for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; if (predicate(obj[currentKey], currentKey, obj)) return true; } return false; };
數組中存在符合 predicate 的項就返回 true 不然返回 false
_.contains = _.includes = _.include = function(obj, item, fromIndex, guard) { if (!isArrayLike(obj)) obj = _.values(obj); if (typeof fromIndex != 'number' || guard) fromIndex = 0; return _.indexOf(obj, item, fromIndex) >= 0; }; var createIndexFinder = function(dir, predicateFind, sortedIndex) { return function(array, item, idx) { var i = 0, length = getLength(array); if (typeof idx == 'number') { // 若是 idx 是數字類型 表明開始查找的位置 if (dir > 0) { i = idx >= 0 ? idx : Math.max(idx + length, i); } else { length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; } } else if (sortedIndex && idx && length) { // 若是 idx 不是數字類型 則表明 isSorted idx = sortedIndex(array, item); return array[idx] === item ? idx : -1; } if (item !== item) { // 判斷 item 是否爲 NaN idx = predicateFind(slice.call(array, i, length), _.isNaN); return idx >= 0 ? idx + i : -1; } for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { if (array[idx] === item) return idx; } return -1; }; }; _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex); _.lastIndexOf = createIndexFinder(-1, _.findLastIndex); _.sortedIndex = function(array, obj, iteratee, context) { iteratee = cb(iteratee, context, 1); var value = iteratee(obj); var low = 0, high = getLength(array); while (low < high) { var mid = Math.floor((low + high) / 2); if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; } return low; };
又是比較長的的幾個函數。
首先 sortedIndex 二分查找符合 iteratee 的下標。是對有序的數組進行查找。注意返回的是 iteratee(obj) <= iteratee(idx) 的最小下標 idx 查找不到的時候不返回 -1
indexOf 正向查找符合 iteratee 的下標 不存在返回 -1
lastIndexOf 反向向查找符合 iteratee 的下標 不存在返回 -1
對於 _.contains 判斷 obj 中是否存在 item 從 fromIndex 開始查找
guard 存在的時候就必定是從 0 開始查找(不知道加這個參數是出於什麼考慮
_.invoke = restArguments(function(obj, path, args) { var contextPath, func; if (_.isFunction(path)) { func = path; } else if (_.isArray(path)) { contextPath = path.slice(0, -1); path = path[path.length - 1]; } return _.map(obj, function(context) { var method = func; if (!method) { if (contextPath && contextPath.length) { context = deepGet(context, contextPath); } if (context == null) return void 0; method = context[path]; } return method == null ? method : method.apply(context, args); }); }); // e.g. _.invoke({a: 1, b: '2'}, function (...args) { console.log(this, args); }, '參數1', '參數2'); // [Number: 1] [ '參數1', '參數2' ] // [String: '2'] [ '參數1', '參數2' ] let obj = { one: { a: { b: function () { console.log(this, arguments); } } }, two: { a: { b: function () { console.log('哈哈哈哈哈'); } } } }; _.invoke(obj, ['a', 'b']); // { b: [Function: b] } {} // 哈哈哈哈哈
_.invoke 首先經過 restArguments 將函數包裹起來 會將多餘的參數做爲一個數組 args 統一傳入
判斷 path 是不是一個函數 若是是數組的話 就獲取數組的除最後一項的全部項做爲 contextPath 而後得到最後一項爲 path 從新賦值
若是 path 是函數的話 就分別以 obj 的每一項做爲 this 調用 path 參數爲 ...args
不然 獲取到 obj 每一項中對應 path 的屬性值 而後每一項做爲 this 調用該屬性值 參數爲 ...args
320行 =。= 看不下去了 明天繼續……
_.pluck = function(obj, key) { return _.map(obj, _.property(key)); }; // e.g. let arr = [{id: 1}, {name: 'zs', id: 2}, {}]; console.log(_.pluck(arr, 'id')); // [ 1, 2, undefined ]
這個好理解,就是獲取數組每一項指定 key 的值。
_.where = function(obj, attrs) { return _.filter(obj, _.matcher(attrs)); }; // e.g. var attrs = { name: 'glory' }; var arr = [{name:'glory'}, {name:'saber'}, {name:'glory', id: 1}]; console.log(_.where(arr, attrs));
篩選出數組 obj (或者 _.keys(obj)) 中符合 attr 的項。
_.findWhere = function(obj, attrs) { return _.find(obj, _.matcher(attrs)); }; // e.g. var attrs = { name: 'glory' }; var arr = [{name:'glory'}, {name:'saber'}, {name:'glory', id: 1}]; console.log(_.findWhere(arr, attrs)); // { name: 'glory' }
_.find 查找的是第一個 因此區別就是上面查找全部符合的項組成的數組,這個返回的是符合 attr 的第一項。沒有就是 undefined
_.max = function(obj, iteratee, context) { var result = -Infinity, lastComputed = -Infinity, value, computed; if (iteratee == null || typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null) { obj = isArrayLike(obj) ? obj : _.values(obj); for (var i = 0, length = obj.length; i < length; i++) { value = obj[i]; if (value != null && value > result) { result = value; } } } else { iteratee = cb(iteratee, context); _.each(obj, function(v, index, list) { computed = iteratee(v, index, list); if (computed > lastComputed || computed === -Infinity && result === -Infinity) { result = v; lastComputed = computed; } }); } return result; }; // e.g. var arr = [{x:3,y:4}, {x:4,y:4}, {x:4,y:5}]; var comp = function (obj) { return obj.x * 3 + obj.y * 4; }; console.log(_.max(arr, comp)); // { x: 4, y: 5 }
若是傳入比較函數(iteratee)就用該函數對每一項進行處理,返回處理值最大那一項。(注意返回的原元素而不是通過 iteratee 處理後的)
若是不傳就直接比較。注意他判斷了 value != null 因此 null 和 undefined 是不參與比較的。(null > -1 值爲 true)
若是是傳入比較函數的時候,初始值和計算值設爲 -Infinity ,經過判斷 computed === -Infinity && result === -Infinity 來肯定是否是初始狀態,可是感受這樣會有 bug 看了下討論區確實有人提了 bug,尚未被解決。
可是鬼知道他判斷 number 什麼的是什麼意思……(忽然發現這個代碼都七個月沒更新了……
_.min = function(obj, iteratee, context) { var result = Infinity, lastComputed = Infinity, value, computed; if (iteratee == null || typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null) { obj = isArrayLike(obj) ? obj : _.values(obj); for (var i = 0, length = obj.length; i < length; i++) { value = obj[i]; if (value != null && value < result) { result = value; } } } else { iteratee = cb(iteratee, context); _.each(obj, function(v, index, list) { computed = iteratee(v, index, list); if (computed < lastComputed || computed === Infinity && result === Infinity) { result = v; lastComputed = computed; } }); } return result; };
和最大值的邏輯相同。
_.shuffle = function(obj) { return _.sample(obj, Infinity); }; _.sample = function(obj, n, guard) { if (n == null || guard) { if (!isArrayLike(obj)) obj = _.values(obj); return obj[_.random(obj.length - 1)]; } var sample = isArrayLike(obj) ? _.clone(obj) : _.values(obj); var length = getLength(sample); n = Math.max(Math.min(n, length), 0); var last = length - 1; for (var index = 0; index < n; index++) { var rand = _.random(index, last); var temp = sample[index]; sample[index] = sample[rand]; sample[rand] = temp; } return sample.slice(0, n); }; _.clone = function(obj) { if (!_.isObject(obj)) return obj; return _.isArray(obj) ? obj.slice() : _.extend({}, obj); }; _.random = function(min, max) { // max ? [min, max] : [0, min] if (max == null) { max = min; min = 0; } // min + floor( [0, (max - min + 1)) ) -> min + [0, (max - min)] -> [min, max] return min + Math.floor(Math.random() * (max - min + 1)); };
_.clone 淺複製一個數組或對象。
_.random(min, max) 隨機得到 [min, max] 之間的整數。
_.sample(obj, n, guard) 得到數組或對象全部值中隨機的 n 個值,若是 n 等於 obj.length 就至關於打亂數組了。
guard 存在的話就忽略 n 使得 sample 能夠直接應用到 map 中。
_.sortBy = function(obj, iteratee, context) { var index = 0; iteratee = cb(iteratee, context); return _.pluck(_.map(obj, function(value, key, list) { return { value: value, index: index++, criteria: iteratee(value, key, list) }; }).sort(function(left, right) { var a = left.criteria; var b = right.criteria; if (a !== b) { if (a > b || a === void 0) return 1; if (a < b || b === void 0) return -1; } return left.index - right.index; }), 'value'); };
雖然不難理解 可是以爲挺巧妙的。
首先映射成 { value, index, comp(value, ...)(比較函數的計算值) } 對象,而後經過 sort 比較大小,若是計算值相同,會根據 index 保持原順序。而後取 'value' 的值來獲取數組原值。
// 返回一個函數 使用 iteratee 處理 obj 每一項後 將計算值和原值按照 behavior 進行分類 var group = function(behavior, partition) { return function(obj, iteratee, context) { var result = partition ? [[], []] : {}; iteratee = cb(iteratee, context); _.each(obj, function(value, index) { var key = iteratee(value, index, obj); behavior(result, value, key); }); return result; }; }; _.groupBy = group(function(result, value, key) { if (has(result, key)) result[key].push(value); else result[key] = [value]; }); _.indexBy = group(function(result, value, key) { result[key] = value; }); _.countBy = group(function(result, value, key) { if (has(result, key)) result[key]++; else result[key] = 1; }); _.partition = group(function(result, value, pass) { result[pass ? 0 : 1].push(value); }, true); // e.g. function getScoreLevel(score) { if (score > 90) return 'A'; if (score > 60) return 'B'; return 'C'; } console.log(_.groupBy([30, 40, 50, 60, 70, 80, 90, 100], getScoreLevel)); // { C: [ 30, 40, 50, 60 ], B: [ 70, 80, 90 ], A: [ 100 ] } function isPass(score) { return score >= 60; } console.log(_.partition([30, 40, 50, 60, 70, 80, 90, 100], isPass)); // [ [ 60, 70, 80, 90, 100 ], [ 30, 40, 50 ] ]
groupBy 按照傳入函數的計算值進行分類。indexBy 後面的值會覆蓋前面的,因此應該是計算值惟一的時候使用。countBy 按照計算值統計個數。
partition 根據計算值分爲兩類。
var reStrSymbol = /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g; _.toArray = function(obj) { if (!obj) return []; if (_.isArray(obj)) return slice.call(obj); if (_.isString(obj)) { return obj.match(reStrSymbol); } if (isArrayLike(obj)) return _.map(obj, _.identity); return _.values(obj); };
把一個對象轉爲數組。若是是 falsely 返回空數組。若是是數組返回數組副本,若是字符串就返回字符串分割成每一個字符的數組,類數組轉成數組返回,對象返回值的集合。
reStrSymbol正則,能夠處理各類字符。
[^\ud800-\udfff] 是普通的 BMP 字符
[\ud800-\udbff][\udc00-\udfff] 是成對的代理項對
[\ud800-\udfff] 是未成對的代理項字
_.size = function(obj) { if (obj == null) return 0; return isArrayLike(obj) ? obj.length : _.keys(obj).length; };
獲取(類)數組的長度或者對象屬性的個數。
492行,我終於把集合部分看完了!!!明天繼續。
_.first = _.head = _.take = function(array, n, guard) { if (array == null || array.length < 1) return n == null ? void 0 : []; if (n == null || guard) return array[0]; return _.initial(array, array.length - n); }; _.initial = function(array, n, guard) { return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); };
initial 是獲取數組除去最後 n 個元素的剩餘部分,n 默認 1,若是傳入 guard 也至關於 n 爲 1。
first 是獲取數組前 n 個元素。不傳 n 或者傳入 guard 則獲取第一個元素。
guard 爲了可以直接在 map 函數中使用。
// 得到數組最後 n 個元素 _.last = function(array, n, guard) { if (array == null || array.length < 1) return n == null ? void 0 : []; if (n == null || guard) return array[array.length - 1]; return _.rest(array, Math.max(0, array.length - n)); }; // 得到數組除了前 n 個元素的剩餘部分 _.rest = _.tail = _.drop = function(array, n, guard) { return slice.call(array, n == null || guard ? 1 : n); };
見註釋,沒什麼好說的。
_.compact = function(array) { return _.filter(array, Boolean); };
返回數組中除了假值的元素。
var flatten = function(input, shallow, strict, output) { output = output || []; var idx = output.length; for (var i = 0, length = getLength(input); i < length; i++) { var value = input[i]; if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { if (shallow) { var j = 0, len = value.length; while (j < len) output[idx++] = value[j++]; } else { flatten(value, shallow, strict, output); idx = output.length; } } else if (!strict) { output[idx++] = value; } } return output; }; _.flatten = function(array, shallow) { return flatten(array, shallow, false); }; // e.g. let a = [ [1, 2, 3], [5, 6], 7, [ [8], [9] ] ]; console.log(_.flatten(a)); // [ 1, 2, 3, 5, 6, 7, 8, 9 ] console.log(_.flatten(a, true)) // [ 1, 2, 3, 5, 6, 7, [ 8 ], [ 9 ] ]
從名字來看 flatten 要作的就是把一個多維數組拍平
若是傳了 shallow 只會把二維數組拍平成一維數組 不然就遞歸所有拍平
shallow = false 的話 strict 應該不爲 true,strict 表示擴展二維數組時若是一項不是數組就不加入最後擴展的結果。
output 不須要傳 是在遞歸的時候用到的參數
_.without = restArguments(function(array, otherArrays) { return _.difference(array, otherArrays); }); _.difference = restArguments(function(array, rest) { rest = flatten(rest, true, true); return _.filter(array, function(value){ return !_.contains(rest, value); }); }); // e.g. console.log(_.difference([1, 5, 8], [1, 2, 3], 5, [[8], [9]])); // [1, 5, 8] 中不在 [ 1, 2, 3, [ 8 ], [ 9 ] ] 的元素 => [ 5, 8 ] console.log(_.without([1, 5, 8], 1, 2, 3)); // [1, 5, 8] 中不在 [ 1, 2, 3 ] 的元素 => [ 5, 8 ] let obj = [{ m: 'd' }, { z: 'z' }]; console.log(_.without(obj, obj[0], { z: 'z' })); // [ { z: 'z' } ]
difference 將多餘的參數做爲數組 [rest] 傳入 而後在拍平爲一維數組,也就是說 difference 的參數是一堆參數
而後求的是第一個數組中刪除後面全部數組都不包含的元素 剩下的部分
without 把多餘的參數做爲數組 [otherArrays] 傳入 而後在傳入 difference
也就是說 without 傳入一個數組和一些值 判斷數組中不包含在後面那些值的元素
_.uniq = _.unique = function(array, isSorted, iteratee, context) { if (!_.isBoolean(isSorted)) { // 若是 isSorted 不是布爾類型就認爲沒傳 isSorted 默認爲false context = iteratee; iteratee = isSorted; isSorted = false; } if (iteratee != null) iteratee = cb(iteratee, context); var result = []; var seen = []; for (var i = 0, length = getLength(array); i < length; i++) { var value = array[i], computed = iteratee ? iteratee(value, i, array) : value; // 沒有傳入 iteratee 就直接比較 value if (isSorted && !iteratee) { // 若是是有序的 直接比較和前一個不相同就能夠 // 若是傳入 iteratee 即便是有序的計算值也不必定有序因此忽略 isSorted 信息 if (!i || seen !== computed) result.push(value); seen = computed; } else if (iteratee) { // 不然要判斷以前沒有存過該元素 傳入計算函數比較計算值 if (!_.contains(seen, computed)) { seen.push(computed); result.push(value); } } else if (!_.contains(result, value)) { // 沒有傳入計算函數就直接比較原值 result.push(value); } } return result; }; _.union = restArguments(function(arrays) { return _.uniq(flatten(arrays, true, true)); }); // e.g. var getName = _.property('name'); // 獲取一個對象的 name 屬性 var staff = [ { name: 'joy', age: 19 }, { name: 'john', age: 19 }, { name: 'joy', age: 88 } ]; _.uniq(staff, getName); // [ { name: 'joy', age: 19 }, { name: 'john', age: 19 } ] _.union([1,2], [2, 3], [3, 4]); // [ 1, 2, 3, 4 ]
_.uniq 將數組去重,若是傳入計算函數就按照計算值去重 留第一個元素 不然直接比較元素
_.union 將傳入的多個數組合併成一個數組而後去重
_.intersection = function(array) { var result = []; var argsLength = arguments.length; for (var i = 0, length = getLength(array); i < length; i++) { var item = array[i]; if (_.contains(result, item)) continue; var j; for (j = 1; j < argsLength; j++) { if (!_.contains(arguments[j], item)) break; } if (j === argsLength) result.push(item); } return result; }; // e.g. _.intersection([1,2,3], [2, 3], [3, 4]); // [ 3 ]
取多個數組的交集。在遍歷第一個數組的元素,若是後面每一個數組都包含,就加入結果數組。
_.unzip = function(array) { // array 是一個二維數組 var length = array && _.max(array, getLength).length || 0; // 獲取 array[0..len-1]中最長的數組的長度 var result = Array(length); // result[i] 是一個數 由 array[0..len-1][i] 組成 for (var index = 0; index < length; index++) { result[index] = _.pluck(array, index); } return result; }; // 傳入多個數組 而後經過 restArguments 合成一個二維數組傳入 unzip _.zip = restArguments(_.unzip); // e.g. var obj = [ ['張三', 18, '男'], ['李四', 16, '女'], ['王五', 23, '男'] ]; _.unzip(obj); // [ [ '張三', '李四', '王五' ], [ 18, 16, 23 ], [ '男', '女', '男' ] ] _.zip([ '張三', '李四', '王五' ], [ 18, 16, 23 ], [ '男', '女', '男' ]); // [ [ '張三', 18, '男' ], [ '李四', 16, '女' ], [ '王五', 23, '男' ] ]
zip 和 unzip 功能差很少啊 就是傳入多個數組,而後把下標相同的元素放到同一個數組,不過 zip 傳入多個數組 unzip 傳入二維數組
目前不太理解有什麼用
_.object = function(list, values) { var result = {}; for (var i = 0, length = getLength(list); i < length; i++) { if (values) { result[list[i]] = values[i]; } else { result[list[i][0]] = list[i][1]; } } return result; }; // e.g. _.object([ ['LENGTH', 34], ['WIDTH', 43] ]); // { LENGTH: 34, WIDTH: 43 } _.object([ '葉修', '蘇沐橙' ], ['君莫笑', '沐雨橙風']); // { '葉修': '君莫笑', '蘇沐橙': '沐雨橙風' }
數組轉鍵值對,一種是二維數組,一種是兩個對應的數組。
_.range = function(start, stop, step) { if (stop == null) { // 只有一個參數做爲 stop 默認 start 爲 0 stop = start || 0; start = 0; } if (!step) { // 默認 step 爲 ±1 step = stop < start ? -1 : 1; } // 計算 range 長度 var length = Math.max(Math.ceil((stop - start) / step), 0); var range = Array(length); for (var idx = 0; idx < length; idx++, start += step) { range[idx] = start; } return range; }; // e.g. _.range(10); // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] _.range(13, 100, 17); // [ 13, 30, 47, 64, 81, 98 ]
設定初始值,結束值和步長,生成一個數組。
_.chunk = function(array, count) { if (count == null || count < 1) return []; var result = []; var i = 0, length = array.length; while (i < length) { result.push(slice.call(array, i, i += count)); } return result; }; //e.g. var _1To10 = _.range(1, 11); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] _.chunk(_1To10, 3); // [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ], [ 10 ] ]
將數組分塊,按順序,每 count 個分紅一塊。
755 行,下面是函數部分了。再開一篇博客寫,太長了。