lodash中有不少方法都涉及到了數組或者對象的遍歷,通常這些方法均可以傳遞自定義的遍歷方法,自定義的遍歷方法在普通狀況下都傳遞的是function,可是lodash也支持傳遞一個數組、一個對象,或者一個字符串。node
這個iteratee方法的任務就是把一個數組,一個對象,或者一個字符串變成一個有效的function來遍歷數組或對象找到符合要求的屬性。jquery
其中用到了isEqual方法來深度比較兩個對象的值是否相等。es6
也用到了property將字符串或者字符串形式的屬性路徑變成一個獲取對象的對應的屬性的function。web
如下是源代碼:npm
涉及Hash,ListCache,MapCache這三個自定義類型沒有註釋,其餘方法都註釋了。數組
Hash類型promise
//Hash類構造函數,接收參數是一個包含鍵值對數組的數組: //[['key1', 'values1'], ['key2', 'values2']......]
Hash就是用對象實現了一個帶有特定接口的哈希表
ListCache類型瀏覽器
//ListCache構造函數,ListCache其實就是一個本身實現的Map數據類型 //參數entries是鍵值對數組,結構以下 /* [ [key1, value1], [key2, value2], ... ] */
MapCache緩存
MapCache類型構造函數,建立一個map緩存對象來儲存鍵值對 /* SetCacheObject { __data__: { size: xxx, __data__: { hash: new Hash, map: new Map, string: new Hash } } } => { __data__: { size: xxx, __data__: { hash: { size: xxx, __data__: { values4: '__lodash_hash_undefined__', values6: '__lodash_hash_undefined__', ... } }, map: new Map, string: { size: xxx, __data__: { values3: '__lodash_hash_undefined__', values8: '__lodash_hash_undefined__', ... } } } } } */
下面是iteratee所有源碼:閉包
/** * lodash (Custom Build) <https://lodash.com/> * Build: `lodash modularize exports="npm" -o ./` * Copyright jQuery Foundation and other contributors <https://jquery.org/> * Released under MIT license <https://lodash.com/license> * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE> * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors */ /** Used as the size to enable large array optimizations. */ var LARGE_ARRAY_SIZE = 200; /** Used as the `TypeError` message for "Functions" methods. */ var FUNC_ERROR_TEXT = 'Expected a function'; /** Used to stand-in for `undefined` hash values. */ var HASH_UNDEFINED = '__lodash_hash_undefined__'; /** Used to compose bitmasks for comparison styles. */ var UNORDERED_COMPARE_FLAG = 1, PARTIAL_COMPARE_FLAG = 2; /** Used as references for various `Number` constants. */ var INFINITY = 1 / 0, MAX_SAFE_INTEGER = 9007199254740991; /** `Object#toString` result references. */ var argsTag = '[object Arguments]', arrayTag = '[object Array]', boolTag = '[object Boolean]', dateTag = '[object Date]', errorTag = '[object Error]', funcTag = '[object Function]', genTag = '[object GeneratorFunction]', mapTag = '[object Map]', numberTag = '[object Number]', objectTag = '[object Object]', promiseTag = '[object Promise]', regexpTag = '[object RegExp]', setTag = '[object Set]', stringTag = '[object String]', symbolTag = '[object Symbol]', weakMapTag = '[object WeakMap]'; var arrayBufferTag = '[object ArrayBuffer]', dataViewTag = '[object DataView]', float32Tag = '[object Float32Array]', float64Tag = '[object Float64Array]', int8Tag = '[object Int8Array]', int16Tag = '[object Int16Array]', int32Tag = '[object Int32Array]', uint8Tag = '[object Uint8Array]', uint8ClampedTag = '[object Uint8ClampedArray]', uint16Tag = '[object Uint16Array]', uint32Tag = '[object Uint32Array]'; /** Used to match property names within property paths. */ var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/, reIsPlainProp = /^\w*$/, reLeadingDot = /^\./, rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g; /** * Used to match `RegExp` * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). */ var reRegExpChar = /[\\^$.*+?()[\]{}|]/g; /** Used to match backslashes in property paths. */ var reEscapeChar = /\\(\\)?/g; /** Used to match `RegExp` flags from their coerced string values. */ var reFlags = /\w*$/; /** Used to detect host constructors (Safari). */ var reIsHostCtor = /^\[object .+?Constructor\]$/; /** Used to detect unsigned integer values. */ var reIsUint = /^(?:0|[1-9]\d*)$/; /** Used to identify `toStringTag` values of typed arrays. */ var typedArrayTags = {}; typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = typedArrayTags[uint32Tag] = true; typedArrayTags[argsTag] = typedArrayTags[arrayTag] = typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] = typedArrayTags[dataViewTag] = typedArrayTags[dateTag] = typedArrayTags[errorTag] = typedArrayTags[funcTag] = typedArrayTags[mapTag] = typedArrayTags[numberTag] = typedArrayTags[objectTag] = typedArrayTags[regexpTag] = typedArrayTags[setTag] = typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false; /** Used to identify `toStringTag` values supported by `_.clone`. */ var cloneableTags = {}; cloneableTags[argsTag] = cloneableTags[arrayTag] = cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] = cloneableTags[boolTag] = cloneableTags[dateTag] = cloneableTags[float32Tag] = cloneableTags[float64Tag] = cloneableTags[int8Tag] = cloneableTags[int16Tag] = cloneableTags[int32Tag] = cloneableTags[mapTag] = cloneableTags[numberTag] = cloneableTags[objectTag] = cloneableTags[regexpTag] = cloneableTags[setTag] = cloneableTags[stringTag] = cloneableTags[symbolTag] = cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] = cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true; cloneableTags[errorTag] = cloneableTags[funcTag] = cloneableTags[weakMapTag] = false; /** Detect free variable `global` from Node.js. */ var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; /** Detect free variable `self`. */ var freeSelf = typeof self == 'object' && self && self.Object === Object && self; /** Used as a reference to the global object. */ var root = freeGlobal || freeSelf || Function('return this')(); /** Detect free variable `exports`. */ var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports; /** Detect free variable `module`. */ var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module; /** Detect the popular CommonJS extension `module.exports`. */ var moduleExports = freeModule && freeModule.exports === freeExports; /** Detect free variable `process` from Node.js. */ var freeProcess = moduleExports && freeGlobal.process; /** Used to access faster Node.js helpers. */ var nodeUtil = (function() { try { return freeProcess && freeProcess.binding('util'); } catch (e) {} }()); /* Node.js helper references. */ var nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray; /** * Adds the key-value `pair` to `map`. * * @private * @param {Object} map The map to modify. * @param {Array} pair The key-value pair to add. * @returns {Object} Returns `map`. */ //添加鍵值對到map對象中 function addMapEntry(map, pair) { // Don't return `map.set` because it's not chainable in IE 11. map.set(pair[0], pair[1]);//調用map的set方法設置鍵值 return map; } /** * Adds `value` to `set`. * * @private * @param {Object} set The set to modify. * @param {*} value The value to add. * @returns {Object} Returns `set`. */ function addSetEntry(set, value) { // Don't return `set.add` because it's not chainable in IE 11. set.add(value); return set; } /** * A specialized version of `_.forEach` for arrays without support for * iteratee shorthands. * * @private * @param {Array} [array] The array to iterate over. * @param {Function} iteratee The function invoked per iteration. * @returns {Array} Returns `array`. */ //相似forEach function arrayEach(array, iteratee) { var index = -1,//循環索引 length = array ? array.length : 0;//array數組長度 while (++index < length) {//循環數組長度次數,調用iteratee,若是返回值是false,就提早跳出循環 if (iteratee(array[index], index, array) === false) { break; } } return array; } /** * Appends the elements of `values` to `array`. * * @private * @param {Array} array The array to modify. * @param {Array} values The values to append. * @returns {Array} Returns `array`. */ //將values數組的元素插入到array數組的結尾 function arrayPush(array, values) { var index = -1,//循環索引 length = values.length,//values的長度 offset = array.length;//插入的偏移值,就是array的長度 while (++index < length) {//循環插入元素 array[offset + index] = values[index]; } return array; } /** * A specialized version of `_.reduce` for arrays without support for * iteratee shorthands. * * @private * @param {Array} [array] The array to iterate over. * @param {Function} iteratee The function invoked per iteration. * @param {*} [accumulator] The initial value. * @param {boolean} [initAccum] Specify using the first element of `array` as * the initial value. * @returns {*} Returns the accumulated value. */ //對於數組類型實現的reduce方法 function arrayReduce(array, iteratee, accumulator, initAccum) { var index = -1,//循環索引 length = array ? array.length : 0;//數組長度 if (initAccum && length) {//第一次的累加值是數組第一個元素 accumulator = array[++index]; } while (++index < length) {//循環調用iteratee accumulator = iteratee(accumulator, array[index], index, array); } return accumulator;//返回累加值 } /** * A specialized version of `_.some` for arrays without support for iteratee * shorthands. * * @private * @param {Array} [array] The array to iterate over. * @param {Function} predicate The function invoked per iteration. * @returns {boolean} Returns `true` if any element passes the predicate check, * else `false`. */ //相似於數組的some方法 function arraySome(array, predicate) { var index = -1,//循環索引 length = array ? array.length : 0;//數組長度 while (++index < length) {//循環調用傳入的回調函數,參數是值,值的索引,數組自己 if (predicate(array[index], index, array)) { return true; } }//若是有一個返回true,就返回true,不然,返回false return false; } /** * The base implementation of `_.property` without support for deep paths. * * @private * @param {string} key The key of the property to get. * @returns {Function} Returns the new accessor function. */ //返回一個方法,這個方法返回object的對應key的對應value function baseProperty(key) { return function(object) { return object == null ? undefined : object[key]; }; } /** * The base implementation of `_.times` without support for iteratee shorthands * or max array length checks. * * @private * @param {number} n The number of times to invoke `iteratee`. * @param {Function} iteratee The function invoked per iteration. * @returns {Array} Returns the array of results. */ //_.times的基礎實現,循環n次,返回iteratee處理的index組成的數組 function baseTimes(n, iteratee) { var index = -1,//循環索引 result = Array(n);//結果數組,長度爲n while (++index < n) {//循環n次,結果數組每個元素是當前index被iteratee處理後的結果 result[index] = iteratee(index); } return result; } /** * The base implementation of `_.unary` without support for storing metadata. * * @private * @param {Function} func The function to cap arguments for. * @returns {Function} Returns the new capped function. */ //_.unary的基礎實現,建立一個函數只接受一個參數,忽略以後全部參數 function baseUnary(func) { return function(value) { return func(value); }; } /** * Gets the value at `key` of `object`. * * @private * @param {Object} [object] The object to query. * @param {string} key The key of the property to get. * @returns {*} Returns the property value. */ function getValue(object, key) { return object == null ? undefined : object[key]; } /** * Checks if `value` is a host object in IE < 9. * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a host object, else `false`. */ //判斷value是不是一個宿主對象,在IE9如下的瀏覽器中 function isHostObject(value) { // Many host objects are `Object` objects that can coerce to strings // despite having improperly defined `toString` methods. //不少宿主對象能夠強制轉成字符串儘管有一個不正確的toString方法 var result = false; if (value != null && typeof value.toString != 'function') { try { result = !!(value + ''); } catch (e) {} } return result; } /** * Converts `map` to its key-value pairs. * * @private * @param {Object} map The map to convert. * @returns {Array} Returns the key-value pairs. */ //轉換map對象變成鍵值對數組 function mapToArray(map) { var index = -1,//循環索引 result = Array(map.size);//結果數組,長度和map長度同樣 map.forEach(function(value, key) {//循環賦值到結果數組,每個元素是鍵值組成的兩個元素的數組 result[++index] = [key, value]; }); return result; } /** * Creates a unary function that invokes `func` with its argument transformed. * * @private * @param {Function} func The function to wrap. * @param {Function} transform The argument transform. * @returns {Function} Returns the new function. */ //建立一個只有一個參數的函數,引用func來處理,這個函數的參數會被transform處理 function overArg(func, transform) { return function(arg) { return func(transform(arg)); }; } /** * Converts `set` to an array of its values. * * @private * @param {Object} set The set to convert. * @returns {Array} Returns the values. */ //轉換set對象爲數組 function setToArray(set) { var index = -1,//循環索引 result = Array(set.size);//結果數組,長度和set長度同樣 set.forEach(function(value) { result[++index] = value;//循環賦值 }); return result; } /** Used for built-in method references. */ var arrayProto = Array.prototype, funcProto = Function.prototype, objectProto = Object.prototype; /** Used to detect overreaching core-js shims. */ var coreJsData = root['__core-js_shared__']; /** Used to detect methods masquerading as native. */ var maskSrcKey = (function() { var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || ''); return uid ? ('Symbol(src)_1.' + uid) : ''; }()); /** Used to resolve the decompiled source of functions. */ var funcToString = funcProto.toString; /** Used to check objects for own properties. */ var hasOwnProperty = objectProto.hasOwnProperty; /** * Used to resolve the * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) * of values. */ var objectToString = objectProto.toString; /** Used to detect if a method is native. */ var reIsNative = RegExp('^' + funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&') .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' ); /** Built-in value references. */ var Buffer = moduleExports ? root.Buffer : undefined, Symbol = root.Symbol, Uint8Array = root.Uint8Array, getPrototype = overArg(Object.getPrototypeOf, Object), objectCreate = Object.create, propertyIsEnumerable = objectProto.propertyIsEnumerable, splice = arrayProto.splice; /* Built-in method references for those with the same name as other `lodash` methods. */ var nativeGetSymbols = Object.getOwnPropertySymbols, nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined, nativeKeys = overArg(Object.keys, Object); /* Built-in method references that are verified to be native. */ var DataView = getNative(root, 'DataView'), Map = getNative(root, 'Map'), Promise = getNative(root, 'Promise'), Set = getNative(root, 'Set'), WeakMap = getNative(root, 'WeakMap'), nativeCreate = getNative(Object, 'create'); /** Used to detect maps, sets, and weakmaps. */ var dataViewCtorString = toSource(DataView), mapCtorString = toSource(Map), promiseCtorString = toSource(Promise), setCtorString = toSource(Set), weakMapCtorString = toSource(WeakMap); /** Used to convert symbols to primitives and strings. */ var symbolProto = Symbol ? Symbol.prototype : undefined, symbolValueOf = symbolProto ? symbolProto.valueOf : undefined, symbolToString = symbolProto ? symbolProto.toString : undefined; /** * Creates a hash object. * * @private * @constructor * @param {Array} [entries] The key-value pairs to cache. */ function Hash(entries) { var index = -1, length = entries ? entries.length : 0; this.clear(); while (++index < length) { var entry = entries[index]; this.set(entry[0], entry[1]); } } /** * Removes all key-value entries from the hash. * * @private * @name clear * @memberOf Hash */ function hashClear() { this.__data__ = nativeCreate ? nativeCreate(null) : {}; } /** * Removes `key` and its value from the hash. * * @private * @name delete * @memberOf Hash * @param {Object} hash The hash to modify. * @param {string} key The key of the value to remove. * @returns {boolean} Returns `true` if the entry was removed, else `false`. */ function hashDelete(key) { return this.has(key) && delete this.__data__[key]; } /** * Gets the hash value for `key`. * * @private * @name get * @memberOf Hash * @param {string} key The key of the value to get. * @returns {*} Returns the entry value. */ function hashGet(key) { var data = this.__data__; if (nativeCreate) { var result = data[key]; return result === HASH_UNDEFINED ? undefined : result; } return hasOwnProperty.call(data, key) ? data[key] : undefined; } /** * Checks if a hash value for `key` exists. * * @private * @name has * @memberOf Hash * @param {string} key The key of the entry to check. * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. */ function hashHas(key) { var data = this.__data__; return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key); } /** * Sets the hash `key` to `value`. * * @private * @name set * @memberOf Hash * @param {string} key The key of the value to set. * @param {*} value The value to set. * @returns {Object} Returns the hash instance. */ function hashSet(key, value) { var data = this.__data__; data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value; return this; } // Add methods to `Hash`. Hash.prototype.clear = hashClear; Hash.prototype['delete'] = hashDelete; Hash.prototype.get = hashGet; Hash.prototype.has = hashHas; Hash.prototype.set = hashSet; /** * Creates an list cache object. * * @private * @constructor * @param {Array} [entries] The key-value pairs to cache. */ function ListCache(entries) { var index = -1, length = entries ? entries.length : 0; this.clear(); while (++index < length) { var entry = entries[index]; this.set(entry[0], entry[1]); } } /** * Removes all key-value entries from the list cache. * * @private * @name clear * @memberOf ListCache */ function listCacheClear() { this.__data__ = []; } /** * Removes `key` and its value from the list cache. * * @private * @name delete * @memberOf ListCache * @param {string} key The key of the value to remove. * @returns {boolean} Returns `true` if the entry was removed, else `false`. */ function listCacheDelete(key) { var data = this.__data__, index = assocIndexOf(data, key); if (index < 0) { return false; } var lastIndex = data.length - 1; if (index == lastIndex) { data.pop(); } else { splice.call(data, index, 1); } return true; } /** * Gets the list cache value for `key`. * * @private * @name get * @memberOf ListCache * @param {string} key The key of the value to get. * @returns {*} Returns the entry value. */ function listCacheGet(key) { var data = this.__data__, index = assocIndexOf(data, key); return index < 0 ? undefined : data[index][1]; } /** * Checks if a list cache value for `key` exists. * * @private * @name has * @memberOf ListCache * @param {string} key The key of the entry to check. * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. */ function listCacheHas(key) { return assocIndexOf(this.__data__, key) > -1; } /** * Sets the list cache `key` to `value`. * * @private * @name set * @memberOf ListCache * @param {string} key The key of the value to set. * @param {*} value The value to set. * @returns {Object} Returns the list cache instance. */ function listCacheSet(key, value) { var data = this.__data__, index = assocIndexOf(data, key); if (index < 0) { data.push([key, value]); } else { data[index][1] = value; } return this; } // Add methods to `ListCache`. ListCache.prototype.clear = listCacheClear; ListCache.prototype['delete'] = listCacheDelete; ListCache.prototype.get = listCacheGet; ListCache.prototype.has = listCacheHas; ListCache.prototype.set = listCacheSet; /** * Creates a map cache object to store key-value pairs. * * @private * @constructor * @param {Array} [entries] The key-value pairs to cache. */ function MapCache(entries) { var index = -1, length = entries ? entries.length : 0; this.clear(); while (++index < length) { var entry = entries[index]; this.set(entry[0], entry[1]); } } /** * Removes all key-value entries from the map. * * @private * @name clear * @memberOf MapCache */ function mapCacheClear() { this.__data__ = { 'hash': new Hash, 'map': new (Map || ListCache), 'string': new Hash }; } /** * Removes `key` and its value from the map. * * @private * @name delete * @memberOf MapCache * @param {string} key The key of the value to remove. * @returns {boolean} Returns `true` if the entry was removed, else `false`. */ function mapCacheDelete(key) { return getMapData(this, key)['delete'](key); } /** * Gets the map value for `key`. * * @private * @name get * @memberOf MapCache * @param {string} key The key of the value to get. * @returns {*} Returns the entry value. */ function mapCacheGet(key) { return getMapData(this, key).get(key); } /** * Checks if a map value for `key` exists. * * @private * @name has * @memberOf MapCache * @param {string} key The key of the entry to check. * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. */ function mapCacheHas(key) { return getMapData(this, key).has(key); } /** * Sets the map `key` to `value`. * * @private * @name set * @memberOf MapCache * @param {string} key The key of the value to set. * @param {*} value The value to set. * @returns {Object} Returns the map cache instance. */ function mapCacheSet(key, value) { getMapData(this, key).set(key, value); return this; } // Add methods to `MapCache`. MapCache.prototype.clear = mapCacheClear; MapCache.prototype['delete'] = mapCacheDelete; MapCache.prototype.get = mapCacheGet; MapCache.prototype.has = mapCacheHas; MapCache.prototype.set = mapCacheSet; /** * * Creates an array cache object to store unique values. * * @private * @constructor * @param {Array} [values] The values to cache. */ function SetCache(values) { var index = -1, length = values ? values.length : 0; this.__data__ = new MapCache; while (++index < length) { this.add(values[index]); } } /** * Adds `value` to the array cache. * * @private * @name add * @memberOf SetCache * @alias push * @param {*} value The value to cache. * @returns {Object} Returns the cache instance. */ function setCacheAdd(value) { this.__data__.set(value, HASH_UNDEFINED); return this; } /** * Checks if `value` is in the array cache. * * @private * @name has * @memberOf SetCache * @param {*} value The value to search for. * @returns {number} Returns `true` if `value` is found, else `false`. */ function setCacheHas(value) { return this.__data__.has(value); } // Add methods to `SetCache`. SetCache.prototype.add = SetCache.prototype.push = setCacheAdd; SetCache.prototype.has = setCacheHas; /** * Creates a stack cache object to store key-value pairs. * * @private * @constructor * @param {Array} [entries] The key-value pairs to cache. */ function Stack(entries) { this.__data__ = new ListCache(entries); } /** * Removes all key-value entries from the stack. * * @private * @name clear * @memberOf Stack */ function stackClear() { this.__data__ = new ListCache; } /** * Removes `key` and its value from the stack. * * @private * @name delete * @memberOf Stack * @param {string} key The key of the value to remove. * @returns {boolean} Returns `true` if the entry was removed, else `false`. */ function stackDelete(key) { return this.__data__['delete'](key); } /** * Gets the stack value for `key`. * * @private * @name get * @memberOf Stack * @param {string} key The key of the value to get. * @returns {*} Returns the entry value. */ function stackGet(key) { return this.__data__.get(key); } /** * Checks if a stack value for `key` exists. * * @private * @name has * @memberOf Stack * @param {string} key The key of the entry to check. * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. */ function stackHas(key) { return this.__data__.has(key); } /** * Sets the stack `key` to `value`. * * @private * @name set * @memberOf Stack * @param {string} key The key of the value to set. * @param {*} value The value to set. * @returns {Object} Returns the stack cache instance. */ function stackSet(key, value) { var cache = this.__data__; if (cache instanceof ListCache) { var pairs = cache.__data__; if (!Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) { pairs.push([key, value]); return this; } cache = this.__data__ = new MapCache(pairs); } cache.set(key, value); return this; } // Add methods to `Stack`. Stack.prototype.clear = stackClear; Stack.prototype['delete'] = stackDelete; Stack.prototype.get = stackGet; Stack.prototype.has = stackHas; Stack.prototype.set = stackSet; /** * Creates an array of the enumerable property names of the array-like `value`. * * @private * @param {*} value The value to query. * @param {boolean} inherited Specify returning inherited property names. * @returns {Array} Returns the array of property names. */ //建立一個array-like對象的可枚舉的屬性名組成的數組 //inherited布爾值,是否指定須要返回繼承來的屬性 function arrayLikeKeys(value, inherited) { // Safari 8.1 makes `arguments.callee` enumerable in strict mode. // Safari 9 makes `arguments.length` enumerable in strict mode. var result = (isArray(value) || isArguments(value)) ? baseTimes(value.length, String) : []; //結果數組初始化,若是value是數組或者arguments對象,使用baseTimes處理,baseTimes返回的數組的元素是索引的字符串值組成的數組,不然爲空數組 var length = result.length,//結果數組長度 skipIndexes = !!length;//是否須要跳過數字索引 for (var key in value) {//for in循環value的可枚舉屬性 if ((inherited || hasOwnProperty.call(value, key)) && !(skipIndexes && (key == 'length' || isIndex(key, length)))) { //若是須要返回繼承來的屬性值,或者當前key是value自身屬性 //而且判斷是不是不是length屬性或者數字索引屬性,由於這兩種屬性須要跳過 result.push(key); } } return result; } /** * Assigns `value` to `key` of `object` if the existing value is not equivalent * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) * for equality comparisons. * * @private * @param {Object} object The object to modify. * @param {string} key The key of the property to assign. * @param {*} value The value to assign. */ //給object上指定key賦值value,使用SameValueZero規則比較值是否相等 function assignValue(object, key, value) { var objValue = object[key];//賦值前object上key屬性的值 if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) || (value === undefined && !(key in object))) { //若是object上有key鍵,可是對應值和新值不相等,或者object上沒有key鍵,直接賦值 object[key] = value; } } /** * Gets the index at which the `key` is found in `array` of key-value pairs. * * @private * @param {Array} array The array to inspect. * @param {*} key The key to search for. * @returns {number} Returns the index of the matched value, else `-1`. */ function assocIndexOf(array, key) { var length = array.length; while (length--) { if (eq(array[length][0], key)) { return length; } } return -1; } /** * The base implementation of `_.assign` without support for multiple sources * or `customizer` functions. * * @private * @param {Object} object The destination object. * @param {Object} source The source object. * @returns {Object} Returns `object`. */ //_.assign的基礎實現,將source的屬性複製到object上,不支持多個source參數或者自定義function function baseAssign(object, source) { return object && copyObject(source, keys(source), object); //調用copyObject } /** * The base implementation of `_.clone` and `_.cloneDeep` which tracks * traversed objects. * * @private * @param {*} value The value to clone. * @param {boolean} [isDeep] Specify a deep clone. * @param {boolean} [isFull] Specify a clone including symbols. * @param {Function} [customizer] The function to customize cloning. * @param {string} [key] The key of `value`. * @param {Object} [object] The parent object of `value`. * @param {Object} [stack] Tracks traversed objects and their clone counterparts. * @returns {*} Returns the cloned value. */ //_.clone和_.cloneDeep的基礎實現,追蹤遍歷對象 function baseClone(value, isDeep, isFull, customizer, key, object, stack) { var result;//克隆結果 if (customizer) {//若是傳遞了自定義克隆方法,就調用來克隆value result = object ? customizer(value, key, object, stack) : customizer(value); } if (result !== undefined) {//若是自定義克隆方法處理後結果不是undefined,就返回克隆結果 return result; } if (!isObject(value)) {//若是value不是array,object,function,regexp對象或者字符串或數字對象,直接返回value return value; } var isArr = isArray(value);//value是不是數組的標記 if (isArr) {//若是value是數組 result = initCloneArray(value);//調用initCloneArray初始化數組克隆 if (!isDeep) {//若是不須要深度克隆,直接返回copyArray操做後的結果 return copyArray(value, result); } } else {//若是value不是數組 var tag = getTag(value),//value的toStringTag isFunc = tag == funcTag || tag == genTag;//value是不是函數的標記 if (isBuffer(value)) {//若是是buffer對象,就調用cloneBuffer來克隆 return cloneBuffer(value, isDeep); } if (tag == objectTag || tag == argsTag || (isFunc && !object)) { //value的toStringTag是[object Object]或者[object Arguments],或者value是函數而且沒有父對象 if (isHostObject(value)) {//若是value是宿主對象,若是有父級對象,就返回原value不然返回空對象 return object ? value : {}; } result = initCloneObject(isFunc ? {} : value); //調用initCloneObject初始化克隆對象,若是value是函數,賦值爲空對象,不然不變 if (!isDeep) { //若是不是深度克隆,先用baseAssign將value的屬性複製到result上,而後再調用copySymbols複製symbols屬性 //複製完普通屬性和symbol屬性後返回結果 return copySymbols(value, baseAssign(result, value)); } } else {//若是toStringTag不是[object Object]也不是[object Arguments],也不是沒有父對象的函數 if (!cloneableTags[tag]) {//若是當前value的類型不支持克隆,返回value或者空對象 return object ? value : {}; } //不然調用initCloneByTag處理 result = initCloneByTag(value, tag, baseClone, isDeep); } } // Check for circular references and return its corresponding clone. //檢查循環引用而且返回它對應的克隆 stack || (stack = new Stack);//新實例化一個stack對象,用於存儲key-value鍵值對 var stacked = stack.get(value);//若是stack中已經存了value先獲取到 if (stacked) {//若是有直接返回 return stacked; } stack.set(value, result);//若是沒有存,就存下當前的value和對應的result if (!isArr) {//若是value不是數組 //isFull標識指定是否須要克隆symbol屬性 //若是須要克隆symbol,調用getAllKeys獲取value全部屬性名組成的數組,包括symbol //若是不須要克隆symbol,調用keys獲取全部普通屬性名組成的數組,不包括symbol //props是value的全部可枚舉屬性組成的數組 var props = isFull ? getAllKeys(value) : keys(value); } arrayEach(props || value, function(subValue, key) { //forEach循環對象的鍵數組或者數組自己 if (props) {//若是是鍵數組 key = subValue;//key鍵 subValue = value[key];//value值 } // Recursively populate clone (susceptible to call stack limits). //調用assignValue給result上指定key賦值value,遞歸調用vaseClone繼續深層克隆 assignValue(result, key, baseClone(subValue, isDeep, isFull, customizer, key, value, stack)); }); return result; } /** * The base implementation of `_.create` without support for assigning * properties to the created object. * * @private * @param {Object} prototype The object to inherit from. * @returns {Object} Returns the new object. */ //_.create的基礎實現,不支持分配屬性到建立好的對象上 function baseCreate(proto) { return isObject(proto) ? objectCreate(proto) : {}; //若是proto是對象,就調用objectCreate建立新的繼承自proto的對象,不然返回空對象 } /** * The base implementation of `_.get` without support for default values. * * @private * @param {Object} object The object to query. * @param {Array|string} path The path of the property to get. * @returns {*} Returns the resolved value. */ //_.get方法的基礎實現,獲取object上path路徑對應的值 function baseGet(object, path) { path = isKey(path, object) ? [path] : castPath(path); //判斷path是否是合法key,若是是,就變成[path],不然用castPath處理 var index = 0,//循環路徑數組的索引 length = path.length;//路徑數組的長度 while (object != null && index < length) {//若是object不爲空,循環路徑數組 object = object[toKey(path[index++])];//根據當前路徑獲取到深一層的對象或值賦值給object } return (index && index == length) ? object : undefined; //循環結束後返回找到的值,找不到返回undefined } /** * The base implementation of `getAllKeys` and `getAllKeysIn` which uses * `keysFunc` and `symbolsFunc` to get the enumerable property names and * symbols of `object`. * * @private * @param {Object} object The object to query. * @param {Function} keysFunc The function to get the keys of `object`. * @param {Function} symbolsFunc The function to get the symbols of `object`. * @returns {Array} Returns the array of property names and symbols. */ //獲取對象的可枚舉屬性名和symbol屬性名組成數組返回 function baseGetAllKeys(object, keysFunc, symbolsFunc) { var result = keysFunc(object);//用keysFunc獲取objcet的鍵 return isArray(object) ? result : arrayPush(result, symbolsFunc(object)); //判斷object是不是數組,若是是就返回result //若是不是,說明是對象,可能會有symbol屬性,使用symbolsFunc獲取symbol屬性而後插入結果數組 } /** * The base implementation of `getTag`. * * @private * @param {*} value The value to query. * @returns {string} Returns the `toStringTag`. */ //getTap基礎實現,獲取值的toStringTag function baseGetTag(value) { return objectToString.call(value); } /** * The base implementation of `_.hasIn` without support for deep paths. * * @private * @param {Object} [object] The object to query. * @param {Array|string} key The key to check. * @returns {boolean} Returns `true` if `key` exists, else `false`. */ //_.hasIn的基礎實現,不支持深層屬性路徑判斷 function baseHasIn(object, key) { return object != null && key in Object(object); } /** * The base implementation of `_.isEqual` which supports partial comparisons * and tracks traversed objects. * * @private * @param {*} value The value to compare. * @param {*} other The other value to compare. * @param {Function} [customizer] The function to customize comparisons. * @param {boolean} [bitmask] The bitmask of comparison flags. * The bitmask may be composed of the following flags: * 1 - Unordered comparison * 2 - Partial comparison * @param {Object} [stack] Tracks traversed `value` and `other` objects. * @returns {boolean} Returns `true` if the values are equivalent, else `false`. */ //_.isEqual的基礎實現,深度比較兩個對象是不是相等的 //value要比較的對象,other要比較的另外一個對象,customizer自定義比較方法 //bitmask爲1說明是無序的比較,爲2說明是部分比較 //stack追蹤傳入的參數 function baseIsEqual(value, other, customizer, bitmask, stack) { if (value === other) {//若是value和other嚴格相等,直接返回true return true; } if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) { //判斷有值爲空或者NaN,反正不是數組或對象的狀況的狀況 return value !== value && other !== other; } return baseIsEqualDeep(value, other, baseIsEqual, customizer, bitmask, stack); //調用baseIsEqualDeep來深度循環比較value和other是否相等 } /** * A specialized version of `baseIsEqual` for arrays and objects which performs * deep comparisons and tracks traversed objects enabling objects with circular * references to be compared. * * @private * @param {Object} object The object to compare. * @param {Object} other The other object to compare. * @param {Function} equalFunc The function to determine equivalents of values. * @param {Function} [customizer] The function to customize comparisons. * @param {number} [bitmask] The bitmask of comparison flags. See `baseIsEqual` * for more details. * @param {Object} [stack] Tracks traversed `object` and `other` objects. * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. */ //深度比較數組和對象是否相等 function baseIsEqualDeep(object, other, equalFunc, customizer, bitmask, stack) { var objIsArr = isArray(object),//判斷object是否是數組 othIsArr = isArray(other),//判斷other是否是數組 objTag = arrayTag,//'[object Array]' othTag = arrayTag;//'[object Array]' if (!objIsArr) { objTag = getTag(object);//若是object不是數組,就獲取object的toStringTag objTag = objTag == argsTag ? objectTag : objTag; } if (!othIsArr) { othTag = getTag(other);//若是other不是數組,就獲取other的toStringTag othTag = othTag == argsTag ? objectTag : othTag; } var objIsObj = objTag == objectTag && !isHostObject(object),//object是對象類型且不是宿主對象 othIsObj = othTag == objectTag && !isHostObject(other),//other是對象類型且不是宿主對象 isSameTag = objTag == othTag;//object和other的toStringTag是否同樣 if (isSameTag && !objIsObj) {//object和other的tag相等且它們不是本地對象類型 stack || (stack = new Stack);//新建一個ListCache類型數據,ListCache其實就是一個本身實現的Map數據類型 return (objIsArr || isTypedArray(object)) ? equalArrays(object, other, equalFunc, customizer, bitmask, stack) : equalByTag(object, other, objTag, equalFunc, customizer, bitmask, stack); //若是都是數組類型或者typedArray類型就調用equalArrays方法判斷 //不然調用equalByTag判斷 } if (!(bitmask & PARTIAL_COMPARE_FLAG)) {//若是不是部分比較,且是object類型 var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'), othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__'); //判斷object和other上有沒有__wrapped__屬性,就是判斷它們是否是lodash包裹對象 //單文件的lodash代碼中有LodashWrapper方法會建立`lodash` wrapper objects if (objIsWrapped || othIsWrapped) {//若是object和other其中有一個是lodash包裹對象 var objUnwrapped = objIsWrapped ? object.value() : object, othUnwrapped = othIsWrapped ? other.value() : other; //獲取lodash包裹對象的值,而後再傳遞給baseIsEqual處理 stack || (stack = new Stack); return equalFunc(objUnwrapped, othUnwrapped, customizer, bitmask, stack); } } if (!isSameTag) {//若是toStringTag不同,直接返回false return false; } stack || (stack = new Stack); return equalObjects(object, other, equalFunc, customizer, bitmask, stack); //其餘狀況使用equalObjects來判斷 } /** * The base implementation of `_.isMatch` without support for iteratee shorthands. * * @private * @param {Object} object The object to inspect. * @param {Object} source The object of property values to match. * @param {Array} matchData The property names, values, and compare flags to match. * @param {Function} [customizer] The function to customize comparisons. * @returns {boolean} Returns `true` if `object` is a match, else `false`. */ //object,給定對象 //source,用來比較的源對象 //matchData,source參數的[key, value, boolean]的數組的形式,第三個布爾值代表當前值是否適合用===比較 //自定義比較函數 function baseIsMatch(object, source, matchData, customizer) { var index = matchData.length,//循環索引,從結尾開始循環 length = index,//source長度 noCustomizer = !customizer;//有沒有傳遞自定義比較方法的標識 if (object == null) {//若是object爲空,若source有屬性,返回false,若source無屬性,返回true return !length; } object = Object(object);//強制轉換object while (index--) {//按source屬性長度循環 var data = matchData[index];//當前matchData if ((noCustomizer && data[2]) ? data[1] !== object[data[0]] : !(data[0] in object) ) {//若是沒有自定義比較方法且當前source屬性值適合===判斷,也就是說是簡單類型數據,就直接用!==判斷是否不相等 //不然用in來判斷object是否不含有source key屬性 return false; } } //上面循環先循環一遍判斷適合使用===直接判斷的屬性,下面循環再循環一遍判斷其餘須要深度循環判斷的屬性 //上面循環結束後index循環索引變成0,如今再次正向循環 while (++index < length) { data = matchData[index];//當前matchData var key = data[0],//當前source key objValue = object[key],//當前object 對應source key 的值 srcValue = data[1];//當前source value if (noCustomizer && data[2]) {//若是沒有自定義比較方法,且是簡單類型,就判斷objValue上是否不存在此屬性 if (objValue === undefined && !(key in object)) { return false;//若是不存在直接返回false } } else { var stack = new Stack; if (customizer) {//若是有自定義比較方法,就用自定義的比較 var result = customizer(objValue, srcValue, key, object, source, stack); } if (!(result === undefined ? baseIsEqual(srcValue, objValue, customizer, UNORDERED_COMPARE_FLAG | PARTIAL_COMPARE_FLAG, stack) : result )) {//若是沒有自定義比較方法,就用baseIsEqual來比較 return false; } } } return true; } /** * The base implementation of `_.isNative` without bad shim checks. * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a native function, * else `false`. */ function baseIsNative(value) { if (!isObject(value) || isMasked(value)) { return false; } var pattern = (isFunction(value) || isHostObject(value)) ? reIsNative : reIsHostCtor; return pattern.test(toSource(value)); } /** * The base implementation of `_.isTypedArray` without Node.js optimizations. * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. */ function baseIsTypedArray(value) { return isObjectLike(value) && isLength(value.length) && !!typedArrayTags[objectToString.call(value)]; } /** * The base implementation of `_.iteratee`. * * @private * @param {*} [value=_.identity] The value to convert to an iteratee. * @returns {Function} Returns the iteratee. */ //_.iteratee的基礎實現 function baseIteratee(value) { // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9. // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details. if (typeof value == 'function') {//若是value是function直接返回無需處理 return value; } if (value == null) {//若是value是空,返回identity,identity方法返回第一個接收到的參數 return identity; } if (typeof value == 'object') {//若是typeof值是object,則value有多是數組或對象 return isArray(value) ? baseMatchesProperty(value[0], value[1]) : baseMatches(value); //若是value是數組,調用baseMatchesProperty處理 //若是value是對象,不然調用baseMatches處理 } return property(value);//不然調用property處理 } /** * The base implementation of `_.keys` which doesn't treat sparse arrays as dense. * * @private * @param {Object} object The object to query. * @returns {Array} Returns the array of property names. */ //_.keys的基礎實現,相似於Object.keys() function baseKeys(object) { if (!isPrototype(object)) {//若是object不是一個prototype對象,用nativekeys處理,也就是原生的Object.keys return nativeKeys(object); } var result = [];//結果數組 for (var key in Object(object)) {//遍歷object的屬性 if (hasOwnProperty.call(object, key) && key != 'constructor') { //若是當前key是object自身屬性,而且不是構造函數,就插入結果數組 result.push(key); } } return result; } /** * The base implementation of `_.matches` which doesn't clone `source`. * * @private * @param {Object} source The object of property values to match. * @returns {Function} Returns the new spec function. */ //_.matches方法的基礎實現 //建立一個方法,這個方法會使用部分的(partial)深度比較來比較給定的對象和source對象,若是給定對象擁有相等的屬性值,就返回true,不然false function baseMatches(source) { var matchData = getMatchData(source); //getMatchData,把對象變成[key, value, boolean]的數組的形式,第三個布爾值代表當前值是否適合用===比較 if (matchData.length == 1 && matchData[0][2]) {//若是source對象只有一個屬性 return matchesStrictComparable(matchData[0][0], matchData[0][1]); //matchesStrictComparable返回一個方法,這個方法用於比較傳入對象object key值對應的值是否和給定值srcValue相等 //matchData[0][0]就是key,matchData[0][1]就是srcValue } return function(object) {//若是source有多個屬性,那麼就調用baseIsMatch return object === source || baseIsMatch(object, source, matchData); }; } /** * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`. * * @private * @param {string} path The path of the property to get. * @param {*} srcValue The value to match. * @returns {Function} Returns the new spec function. */ //_.matchesProperty的基礎實現,不克隆srcValue /** * * // The `_.matchesProperty` iteratee shorthand. * _.filter(users, _.iteratee(['user', 'fred'])); * // => [{ 'user': 'fred', 'age': 40 }] * 處理傳入的iteratee是數組的狀況,數組有兩個元素,第一個元素是對象的鍵key或者深層鍵的路徑path,第二個元素是鍵對應的值value * path參數:對象的鍵key或者深層鍵的路徑path * srcValue參數:鍵對應的值value */ function baseMatchesProperty(path, srcValue) { if (isKey(path) && isStrictComparable(srcValue)) { //第一種狀況,若是path是合法鍵,而且srcValue適合用嚴格等於判斷,調用matchesStrictComparable處理 return matchesStrictComparable(toKey(path), srcValue); } return function(object) { //第二種狀況,path參數多是屬性路徑,或者srcValue是對象類型須要使用深度循環比較 var objValue = get(object, path);//獲取到object上對應path路徑的值存入objValue return (objValue === undefined && objValue === srcValue) ? hasIn(object, path) : baseIsEqual(srcValue, objValue, undefined, UNORDERED_COMPARE_FLAG | PARTIAL_COMPARE_FLAG); //若是objValue是undefined而且和提供的數組的第二個元素srcValue相等,調用hasIn判斷path是不是object自身的或者繼承的屬性 //不然使用baseIsEqual深層循環判斷兩個對象是否相等 }; } /** * A specialized version of `baseProperty` which supports deep paths. * * @private * @param {Array|string} path The path of the property to get. * @returns {Function} Returns the new accessor function. */ //返回一個方法,這個方法根據path返回object的對應屬性值 function basePropertyDeep(path) { return function(object) { return baseGet(object, path); }; } /** * The base implementation of `_.toString` which doesn't convert nullish * values to empty strings. * * @private * @param {*} value The value to process. * @returns {string} Returns the string. */ //toString方法的基礎實現 function baseToString(value) { // Exit early for strings to avoid a performance hit in some environments. if (typeof value == 'string') {//若是value已是字符串,直接返回 return value; } if (isSymbol(value)) {//若是value是symbol對象,就調用symbol原型上的toString方法轉換成字符串 return symbolToString ? symbolToString.call(value) : ''; } var result = (value + '');//轉換成字符串 return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;//處理-0狀況後返回結果字符串 } /** * Casts `value` to a path array if it's not one. * * @private * @param {*} value The value to inspect. * @returns {Array} Returns the cast property path array. */ //將值計算成一個路徑數組 function castPath(value) { return isArray(value) ? value : stringToPath(value); } /** * Creates a clone of `buffer`. * * @private * @param {Buffer} buffer The buffer to clone. * @param {boolean} [isDeep] Specify a deep clone. * @returns {Buffer} Returns the cloned buffer. */ //建立一個buffer對象的克隆 function cloneBuffer(buffer, isDeep) { if (isDeep) { return buffer.slice(); } var result = new buffer.constructor(buffer.length); buffer.copy(result); return result; } /** * Creates a clone of `arrayBuffer`. * * @private * @param {ArrayBuffer} arrayBuffer The array buffer to clone. * @returns {ArrayBuffer} Returns the cloned array buffer. */ //建立一個arrayBuffer對象的克隆 function cloneArrayBuffer(arrayBuffer) { var result = new arrayBuffer.constructor(arrayBuffer.byteLength); new Uint8Array(result).set(new Uint8Array(arrayBuffer)); return result; } /** * Creates a clone of `dataView`. * * @private * @param {Object} dataView The data view to clone. * @param {boolean} [isDeep] Specify a deep clone. * @returns {Object} Returns the cloned data view. */ //建立DataView對象的克隆 function cloneDataView(dataView, isDeep) { var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer; return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength); } /** * Creates a clone of `map`. * * @private * @param {Object} map The map to clone. * @param {Function} cloneFunc The function to clone values. * @param {boolean} [isDeep] Specify a deep clone. * @returns {Object} Returns the cloned map. */ //建立map對象的克隆 function cloneMap(map, isDeep, cloneFunc) { var array = isDeep ? cloneFunc(mapToArray(map), true) : mapToArray(map); return arrayReduce(array, addMapEntry, new map.constructor); //調用arrayReduce數組累加遍歷方法,累加初始值是一個新map對象 } /** * Creates a clone of `regexp`. * * @private * @param {Object} regexp The regexp to clone. * @returns {Object} Returns the cloned regexp. */ //建立正則對象的克隆 function cloneRegExp(regexp) { var result = new regexp.constructor(regexp.source, reFlags.exec(regexp)); result.lastIndex = regexp.lastIndex; return result; } /** * Creates a clone of `set`. * * @private * @param {Object} set The set to clone. * @param {Function} cloneFunc The function to clone values. * @param {boolean} [isDeep] Specify a deep clone. * @returns {Object} Returns the cloned set. */ //建立set對象的克隆 function cloneSet(set, isDeep, cloneFunc) { var array = isDeep ? cloneFunc(setToArray(set), true) : setToArray(set); return arrayReduce(array, addSetEntry, new set.constructor); } /** * Creates a clone of the `symbol` object. * * @private * @param {Object} symbol The symbol object to clone. * @returns {Object} Returns the cloned symbol object. */ //建立symbol對象的克隆 function cloneSymbol(symbol) { return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {}; } /** * Creates a clone of `typedArray`. * * @private * @param {Object} typedArray The typed array to clone. * @param {boolean} [isDeep] Specify a deep clone. * @returns {Object} Returns the cloned typed array. */ //建立typedArray對象的克隆 function cloneTypedArray(typedArray, isDeep) { var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer; return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length); } /** * Copies the values of `source` to `array`. * * @private * @param {Array} source The array to copy values from. * @param {Array} [array=[]] The array to copy values to. * @returns {Array} Returns `array`. */ //複製source數組的元素到array數組 function copyArray(source, array) { var index = -1,//循環索引, length = source.length;//source數組長度 array || (array = Array(length));//若是沒有array參數就新建和source長度同樣的做爲array while (++index < length) {//循環source複製值到array array[index] = source[index]; } return array; } /** * Copies properties of `source` to `object`. * * @private * @param {Object} source The object to copy properties from. * @param {Array} props The property identifiers to copy. * @param {Object} [object={}] The object to copy properties to. * @param {Function} [customizer] The function to customize copied values. * @returns {Object} Returns `object`. */ //複製source上的屬性到object上 function copyObject(source, props, object, customizer) { object || (object = {});//目標對象若是是undefined就默認爲空對象 var index = -1,//循環索引 length = props.length;//source對象的鍵組成的數組的長度 while (++index < length) {//循環鍵數組 var key = props[index];//當前鍵 var newValue = customizer ? customizer(object[key], source[key], key, object, source) : undefined; //若是傳遞了customizer,就調用生成新的當前屬性值,不然新屬性值是undefined assignValue(object, key, newValue === undefined ? source[key] : newValue); //調用assignValue將新屬性值賦到object的對應key上 //若是新值是undefined,就用source上對應key的值 } return object; } /** * Copies own symbol properties of `source` to `object`. * * @private * @param {Object} source The object to copy symbols from. * @param {Object} [object={}] The object to copy symbols to. * @returns {Object} Returns `object`. */ //將source上的symbol屬性複製到object上 function copySymbols(source, object) { return copyObject(source, getSymbols(source), object); } /** * A specialized version of `baseIsEqualDeep` for arrays with support for * partial deep comparisons. * * @private * @param {Array} array The array to compare. * @param {Array} other The other array to compare. * @param {Function} equalFunc The function to determine equivalents of values. * @param {Function} customizer The function to customize comparisons. * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual` * for more details. * @param {Object} stack Tracks traversed `array` and `other` objects. * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`. */ //深度比較數組是否相等 //partical部分比較的意思就是,other的長度能夠比array長,other在包含array全部元素的狀況下,還能夠有本身的獨特於array的元素 function equalArrays(array, other, equalFunc, customizer, bitmask, stack) { //bitmask內部使用的時候值只能是1或2,1表明無序比較,2表明部分比較 //bitmask按位與後,只有按位與操做符兩邊數字相等的狀況下才會返回整數,不然返回0 var isPartial = bitmask & PARTIAL_COMPARE_FLAG,//是否部分比較 arrLength = array.length,//array長度 othLength = other.length;//other長度 if (arrLength != othLength && !(isPartial && othLength > arrLength)) {//長度不符合要求,直接返回false return false; } // Assume cyclic values are equal. //假設循環值都是相等的 //stack默認是listCache對象,是本身實現的Map數據類型 var stacked = stack.get(array); if (stacked && stack.get(other)) {//若是是遞歸調用到了equalArrays,stack上就可以獲取到值 return stacked == other; } var index = -1,//循環索引 result = true,//一開始先假設循環值都是相等的 seen = (bitmask & UNORDERED_COMPARE_FLAG) ? new SetCache : undefined; //若是是無序比較,就實例化一個SetCache對象 stack.set(array, other);//stack上存下array和other stack.set(other, array); // Ignore non-index properties. while (++index < arrLength) {//循環array的長度 var arrValue = array[index],//當前循環的array值 othValue = other[index];//當前循環的other值 if (customizer) {//若是提供了customizer比較方法,就用它來比較,partial部分比較要反過來傳遞array和other var compared = isPartial ? customizer(othValue, arrValue, index, other, array, stack) : customizer(arrValue, othValue, index, array, other, stack); } if (compared !== undefined) {//若是自定義比較有告終果,且爲真,就continue繼續下一次循環,不然result=false,跳出循環 if (compared) { continue; } result = false; break; } // Recursively compare arrays (susceptible to call stack limits). //遞歸地比較數組元素值是否相等(容易溢出調用棧) if (seen) {//若是建立了seen變量,說明是無序比較 //seen是SetCache對象,也是利用key-value形式存儲值 //遍歷other,若是有值和當前array的值相等,相等的otherValue的index就存入seen中,而且arraySome返回true,不然返回false跳出循環 if (!arraySome(other, function(othValue, othIndex) { if (!seen.has(othIndex) && (arrValue === othValue || equalFunc(arrValue, othValue, customizer, bitmask, stack))) { return seen.add(othIndex); } })) { result = false; break; } } else if (!( arrValue === othValue || equalFunc(arrValue, othValue, customizer, bitmask, stack) )) {//若是不是無序比較,就先用嚴格等於比較,若是嚴格等於不合適,就遞歸調用baseIsEqual繼續判斷獲取結果 result = false; break; } } stack['delete'](array);//清除stack stack['delete'](other); return result; } /** * A specialized version of `baseIsEqualDeep` for comparing objects of * the same `toStringTag`. * * **Note:** This function only supports comparing values with tags of * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`. * * @private * @param {Object} object The object to compare. * @param {Object} other The other object to compare. * @param {string} tag The `toStringTag` of the objects to compare. * @param {Function} equalFunc The function to determine equivalents of values. * @param {Function} customizer The function to customize comparisons. * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual` * for more details. * @param {Object} stack Tracks traversed `object` and `other` objects. * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. */ //比較toStringTag相等的兩個對象是否相等 //object用來比較的對象 //other用來比較的另外一個對象 //tag比較對象的toStringTag //equalFunc基礎比較相等方法,用來遞歸調用 //customizer自定義比較方法 //bitmask是否無序比較或者部分比較的標誌 //stack,key-value結構跟蹤比較的對象 function equalByTag(object, other, tag, equalFunc, customizer, bitmask, stack) { switch (tag) {//根據toStringTag來比較object和other對象 case dataViewTag://'[object DataView]' if ((object.byteLength != other.byteLength) || (object.byteOffset != other.byteOffset)) { return false; } object = object.buffer; other = other.buffer; case arrayBufferTag://'[object ArrayBuffer]' if ((object.byteLength != other.byteLength) || !equalFunc(new Uint8Array(object), new Uint8Array(other))) { return false; } return true; case boolTag: //'[object Boolean]' case dateTag://'[object Date]' case numberTag://'[object Number]' // Coerce booleans to `1` or `0` and dates to milliseconds. // Invalid dates are coerced to `NaN`. //強制轉換布爾值到1或0,時間對象轉換爲毫秒,無效的時間轉換爲NaN return eq(+object, +other); case errorTag://'[object Error]' return object.name == other.name && object.message == other.message; case regexpTag://'[object RegExp]' case stringTag://'[object String]' // Coerce regexes to strings and treat strings, primitives and objects, // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring // for more details. return object == (other + ''); case mapTag://'[object Map]' var convert = mapToArray; //將map轉換成數組的方法 /* {key1: value1, key2: value2, ...} 變成: [[key1, value1], [key2, value2], ...] */ case setTag://'[object Set]' var isPartial = bitmask & PARTIAL_COMPARE_FLAG;//是否部分比較 convert || (convert = setToArray);//將map或者set轉換成數組的方法,mapToArray或者setToArray if (object.size != other.size && !isPartial) {//若是不是部分比較,且長度不同,直接返回false return false; } // Assume cyclic values are equal. //假設循環值都相等,從中判斷不相等的狀況 var stacked = stack.get(object); if (stacked) { return stacked == other; } bitmask |= UNORDERED_COMPARE_FLAG; // Recursively compare objects (susceptible to call stack limits). stack.set(object, other); var result = equalArrays(convert(object), convert(other), equalFunc, customizer, bitmask, stack); //把object和other都轉換成數組,而後用equalArrays方法來比較是否相等 stack['delete'](object); return result; case symbolTag://'[object Symbol]' if (symbolValueOf) {//Symbol.prototype.valueOf return symbolValueOf.call(object) == symbolValueOf.call(other); } } return false; } /** * A specialized version of `baseIsEqualDeep` for objects with support for * partial deep comparisons. * * @private * @param {Object} object The object to compare. * @param {Object} other The other object to compare. * @param {Function} equalFunc The function to determine equivalents of values. * @param {Function} customizer The function to customize comparisons. * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual` * for more details. * @param {Object} stack Tracks traversed `object` and `other` objects. * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. */ //判斷object類型深度比較是否相等,支持部分比較 function equalObjects(object, other, equalFunc, customizer, bitmask, stack) { var isPartial = bitmask & PARTIAL_COMPARE_FLAG,//是否部分比較 objProps = keys(object),//Object.keys(),返回key組成的數組 objLength = objProps.length, othProps = keys(other),//Object.keys(),返回key組成的數組 othLength = othProps.length; if (objLength != othLength && !isPartial) {//若是不是部分比較,且object和other的key長度不同,返回false return false; } var index = objLength;//循環索引是object key數組的長度 while (index--) { var key = objProps[index];//object 的key if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) { //若是是部分比較,就用in來判斷,不然用Object.prototype.hasOwnProperty //判斷object的key是否other裏也有 //若是沒有,返回false return false; } } // Assume cyclic values are equal. var stacked = stack.get(object); if (stacked && stack.get(other)) { return stacked == other; } var result = true;//開始循環以前假設循環的值都是相等的 stack.set(object, other); stack.set(other, object); var skipCtor = isPartial;//部分比較,跳過constructor while (++index < objLength) { key = objProps[index];//object的key var objValue = object[key],//object key對應的value othValue = other[key];//other key對應的value if (customizer) {//若是有自定義比較方法,就用自定義的比較 var compared = isPartial ? customizer(othValue, objValue, key, other, object, stack) : customizer(objValue, othValue, key, object, other, stack); } // Recursively compare objects (susceptible to call stack limits). if (!(compared === undefined ? (objValue === othValue || equalFunc(objValue, othValue, customizer, bitmask, stack)) : compared )) { //若是自定義比較失敗就跳出循環,result=false //若是不是自定義比較,就用===比較,或者繼續遞歸調用baseIsEqual來深度比較 result = false; break; } skipCtor || (skipCtor = key == 'constructor'); } if (result && !skipCtor) {//不是部分比較且object中有constructor屬性,不跳過constructor屬性,判斷constructor是否同樣 var objCtor = object.constructor, othCtor = other.constructor; // Non `Object` object instances with different constructors are not equal. if (objCtor != othCtor && ('constructor' in object && 'constructor' in other) && !(typeof objCtor == 'function' && objCtor instanceof objCtor && typeof othCtor == 'function' && othCtor instanceof othCtor)) { result = false; } } stack['delete'](object); stack['delete'](other); return result; } /** * Creates an array of own enumerable property names and symbols of `object`. * * @private * @param {Object} object The object to query. * @returns {Array} Returns the array of property names and symbols. */ //建立指定對象的全部可枚舉屬性(包括symbol屬性)組成的數組 function getAllKeys(object) { return baseGetAllKeys(object, keys, getSymbols); } /** * Gets the data for `map`. * * @private * @param {Object} map The map to query. * @param {string} key The reference key. * @returns {*} Returns the map data. */ function getMapData(map, key) { var data = map.__data__; return isKeyable(key) ? data[typeof key == 'string' ? 'string' : 'hash'] : data.map; } /** * Gets the property names, values, and compare flags of `object`. * * @private * @param {Object} object The object to query. * @returns {Array} Returns the match data of `object`. */ //獲取對象中的屬性名,屬性值,和用來匹配的標記 function getMatchData(object) { var result = keys(object),//object的key組成的數組 length = result.length;//key數組的長度 while (length--) {//循環key數組 var key = result[length],//當前key value = object[key];//當前值 result[length] = [key, value, isStrictComparable(value)]; //重寫result的當前值,變成[key, value, boolean]的形式,第三個布爾值代表當前值是否適合用===比較 } return result; } /** * Gets the native function at `key` of `object`. * * @private * @param {Object} object The object to query. * @param {string} key The key of the method to get. * @returns {*} Returns the function if it's native, else `undefined`. */ function getNative(object, key) { var value = getValue(object, key); return baseIsNative(value) ? value : undefined; } /** * Creates an array of the own enumerable symbol properties of `object`. * * @private * @param {Object} object The object to query. * @returns {Array} Returns the array of symbols. */ //建立一個對象的自身symbol屬性組成的數組 //若是支持原生的Object.getOwnPropertySymbols就直接用,不然使用stubArray,返回一個空數組 var getSymbols = nativeGetSymbols ? overArg(nativeGetSymbols, Object) : stubArray; /** * Gets the `toStringTag` of `value`. * * @private * @param {*} value The value to query. * @returns {string} Returns the `toStringTag`. */ var getTag = baseGetTag; // Fallback for data views, maps, sets, and weak maps in IE 11, // for data views in Edge < 14, and promises in Node.js. if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) || (Map && getTag(new Map) != mapTag) || (Promise && getTag(Promise.resolve()) != promiseTag) || (Set && getTag(new Set) != setTag) || (WeakMap && getTag(new WeakMap) != weakMapTag)) { getTag = function(value) { var result = objectToString.call(value), Ctor = result == objectTag ? value.constructor : undefined, ctorString = Ctor ? toSource(Ctor) : undefined; if (ctorString) { switch (ctorString) { case dataViewCtorString: return dataViewTag; case mapCtorString: return mapTag; case promiseCtorString: return promiseTag; case setCtorString: return setTag; case weakMapCtorString: return weakMapTag; } } return result; }; } /** * Checks if `path` exists on `object`. * * @private * @param {Object} object The object to query. * @param {Array|string} path The path to check. * @param {Function} hasFunc The function to check properties. * @returns {boolean} Returns `true` if `path` exists, else `false`. */ //判斷path路徑是否在object中存在 function hasPath(object, path, hasFunc) { path = isKey(path, object) ? [path] : castPath(path); //將path變成路徑數組 var result,//結果,true或false index = -1,//循環路徑數組的索引 length = path.length;//路徑數組的長度 while (++index < length) {//循環路徑數組 var key = toKey(path[index]);//當前key if (!(result = object != null && hasFunc(object, key))) { //若是當前key在object中不存在,跳出循環,而且result賦值爲false break; } object = object[key];//下一層object賦值 } if (result) {//若是爲真,返回true return result; } var length = object ? object.length : 0;//object的length return !!length && isLength(length) && isIndex(key, length) && (isArray(object) || isArguments(object)); //判斷object是數組的狀況 //有length屬性,length屬性是有效數字,判斷最後一個key值是不是一個數組有效索引,object是數組或者是一個arguments對象 } /** * Initializes an array clone. * * @private * @param {Array} array The array to clone. * @returns {Array} Returns the initialized clone. */ //初始化數組的克隆 function initCloneArray(array) { var length = array.length,//數組長度 result = array.constructor(length);//結果數組,和array長度相同 // Add properties assigned by `RegExp#exec`. if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) { //爲結果數組上賦值index屬性和input屬性 result.index = array.index; result.input = array.input; } return result; } /** * Initializes an object clone. * * @private * @param {Object} object The object to clone. * @returns {Object} Returns the initialized clone. */ //初始化對象克隆 function initCloneObject(object) { return (typeof object.constructor == 'function' && !isPrototype(object)) ? baseCreate(getPrototype(object)) : {}; //若是object有構造函數且object不是做爲prototype存在,就調用baseCreate處理,先獲取object的原型 //不然返回空對象 } /** * Initializes an object clone based on its `toStringTag`. * * **Note:** This function only supports cloning values with tags of * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`. * * @private * @param {Object} object The object to clone. * @param {string} tag The `toStringTag` of the object to clone. * @param {Function} cloneFunc The function to clone values. * @param {boolean} [isDeep] Specify a deep clone. * @returns {Object} Returns the initialized clone. */ //基於對象的toStringTag初始化對象克隆 //這個方法只支持克隆`Boolean`, `Date`, `Error`, `Number`, `RegExp`, `String`這幾種類型 function initCloneByTag(object, tag, cloneFunc, isDeep) { var Ctor = object.constructor;//要克隆的對象的構造函數 switch (tag) { case arrayBufferTag://[object ArrayBuffer] return cloneArrayBuffer(object); case boolTag://[object Boolean] case dateTag://[object Date] return new Ctor(+object);//將布爾值和時間對象轉換成數字而後調用對應構造函數新建克隆返回 case dataViewTag://[object DataView] return cloneDataView(object, isDeep); case float32Tag: case float64Tag: case int8Tag: case int16Tag: case int32Tag: case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag: //[object Float32Array] [object Float64Array] [object Int8Array] [object Int16Array] [object Int32Array] [object Uint8Array] [object Uint8ClampedArray] [object Uint16Array] [object Uint32Array] return cloneTypedArray(object, isDeep); case mapTag://[object Map] return cloneMap(object, isDeep, cloneFunc); case numberTag://[object Number] case stringTag://[object String] return new Ctor(object);//調用數字和字符串的對應構造函數新建克隆 case regexpTag://[object RegExp] return cloneRegExp(object); case setTag://[object Set] return cloneSet(object, isDeep, cloneFunc); case symbolTag://[object Symbol] return cloneSymbol(object); } } /** * Checks if `value` is a valid array-like index. * * @private * @param {*} value The value to check. * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. */ //判斷一個值是不是一個有效的array-like對象的索引 function isIndex(value, length) { length = length == null ? MAX_SAFE_INTEGER : length; return !!length && (typeof value == 'number' || reIsUint.test(value)) && (value > -1 && value % 1 == 0 && value < length); } /** * Checks if `value` is a property name and not a property path. * * @private * @param {*} value The value to check. * @param {Object} [object] The object to query keys on. * @returns {boolean} Returns `true` if `value` is a property name, else `false`. */ //檢查一個值是一個合法屬性名而不是屬性路徑 //value須要判斷的值,object須要查詢鍵的對象 function isKey(value, object) { if (isArray(value)) {//若是value是數組,返回false return false; } var type = typeof value;//value的typeof值 if (type == 'number' || type == 'symbol' || type == 'boolean' || value == null || isSymbol(value)) { //若是value是number、sumbol、boolean、null,說明是合法key,返回true return true; } return reIsPlainProp.test(value) || !reIsDeepProp.test(value) || (object != null && value in Object(object)); //reIsPlainProp = /^\w*$/ 匹配單詞,說明是屬性名 //reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/ 若是含有點.或者含有數組說明不是屬性名 //若是value在object裏能找到說明是屬性名 } /** * Checks if `value` is suitable for use as unique object key. * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is suitable, else `false`. */ function isKeyable(value) { var type = typeof value; return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean') ? (value !== '__proto__') : (value === null); } /** * Checks if `func` has its source masked. * * @private * @param {Function} func The function to check. * @returns {boolean} Returns `true` if `func` is masked, else `false`. */ function isMasked(func) { return !!maskSrcKey && (maskSrcKey in func); } /** * Checks if `value` is likely a prototype object. * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a prototype, else `false`. */ //判斷一個值是否相似一個prototype對象 function isPrototype(value) { var Ctor = value && value.constructor, proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto; return value === proto; } /** * Checks if `value` is suitable for strict equality comparisons, i.e. `===`. * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` if suitable for strict * equality comparisons, else `false`. */ //判斷一個值是否適合使用嚴格等於===來比較,NaN和對象引用類型不適合嚴格比較 function isStrictComparable(value) { return value === value && !isObject(value); } /** * A specialized version of `matchesProperty` for source values suitable * for strict equality comparisons, i.e. `===`. * * @private * @param {string} key The key of the property to get. * @param {*} srcValue The value to match. * @returns {Function} Returns the new spec function. */ //iteratee傳遞的是一個兩個元素的數組,而且第一個元素是合法屬性鍵,第二個元素是適合===判斷的值 //返回一個方法,這個方法用於比較傳入對象object key值對應的值是否和給定值srcValue相等 function matchesStrictComparable(key, srcValue) { return function(object) {//object是iteratee接收到的對象 if (object == null) {//若是object爲空,返回false return false; } return object[key] === srcValue && (srcValue !== undefined || (key in Object(object))); //若是object上key對應的值和srcValue相等,就返回true }; } /** * Converts `string` to a property path array. * * @private * @param {string} string The string to convert. * @returns {Array} Returns the property path array. */ //將路徑字符串轉換成路徑數組 var stringToPath = memoize(function(string) { string = toString(string);//將string轉換成字符串格式 var result = [];//結果數組 if (reLeadingDot.test(string)) { //reLeadingDot = /^\./ //若是字符串的開頭若是是一個點,結果數組第一個路徑值就push一個空字符串 result.push(''); } //rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g string.replace(rePropName, function(match, number, quote, string) { result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match)); //若是捕獲到了quote,處理轉義字符後賦值給key而後push進結果 //不然插入捕獲到的number }); return result; }); /** * Converts `value` to a string key if it's not a string or symbol. * * @private * @param {*} value The value to inspect. * @returns {string|symbol} Returns the key. */ //將一個值轉換成字符串鍵,若是它不是字符串或者symbol對象 function toKey(value) { if (typeof value == 'string' || isSymbol(value)) {//若是value已是字符串或者symbol,直接返回 return value; } var result = (value + '');//轉成字符串 return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;//處理-0的狀況 } /** * Converts `func` to its source code. * * @private * @param {Function} func The function to process. * @returns {string} Returns the source code. */ function toSource(func) { if (func != null) { try { return funcToString.call(func); } catch (e) {} try { return (func + ''); } catch (e) {} } return ''; } /** * Creates a function that memoizes the result of `func`. If `resolver` is * provided, it determines the cache key for storing the result based on the * arguments provided to the memoized function. By default, the first argument * provided to the memoized function is used as the map cache key. The `func` * is invoked with the `this` binding of the memoized function. * * **Note:** The cache is exposed as the `cache` property on the memoized * function. Its creation may be customized by replacing the `_.memoize.Cache` * constructor with one whose instances implement the * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object) * method interface of `delete`, `get`, `has`, and `set`. * * @static * @memberOf _ * @since 0.1.0 * @category Function * @param {Function} func The function to have its output memoized. * @param {Function} [resolver] The function to resolve the cache key. * @returns {Function} Returns the new memoized function. * @example * * var object = { 'a': 1, 'b': 2 }; * var other = { 'c': 3, 'd': 4 }; * * var values = _.memoize(_.values); * values(object); * // => [1, 2] * * values(other); * // => [3, 4] * * object.a = 2; * values(object); * // => [1, 2] * * // Modify the result cache. * values.cache.set(object, ['a', 'b']); * values(object); * // => ['a', 'b'] * * // Replace `_.memoize.Cache`. * _.memoize.Cache = WeakMap; */ //建立一個函數能夠緩存func方法的結果。若是提供了resolver參數,它被用來計算緩存對象上的key,基於傳遞給已經memoize化的函數的參數。 //將計算結果緩存在閉包中的私有變量中,若是再次計算一樣的值就直接獲取 function memoize(func, resolver) { if (typeof func != 'function' || (resolver && typeof resolver != 'function')) { //func和resolver(若是提供的話)若是不是函數,拋錯誤 throw new TypeError(FUNC_ERROR_TEXT); } var memoized = function() { var args = arguments, key = resolver ? resolver.apply(this, args) : args[0], //若是提供了resolver,就用resolver來肯定key,不然key就是memoized的第一個參數 cache = memoized.cache;//cashe對象 if (cache.has(key)) {//若是cache中已經有此key,就直接獲取後返回 return cache.get(key); } var result = func.apply(this, args);//不然調用func計算出key對應的value memoized.cache = cache.set(key, result);//將新value存入cache後返回 return result; }; memoized.cache = new (memoize.Cache || MapCache); //cache對象被定義到memoized方法上,cache使用自定義的MapCache類型 return memoized;//返回被memoize化的方法 } // Assign cache to `_.memoize`. memoize.Cache = MapCache; /** * Performs a * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) * comparison between two values to determine if they are equivalent. * * @static * @memberOf _ * @since 4.0.0 * @category Lang * @param {*} value The value to compare. * @param {*} other The other value to compare. * @returns {boolean} Returns `true` if the values are equivalent, else `false`. * @example * * var object = { 'a': 1 }; * var other = { 'a': 1 }; * * _.eq(object, object); * // => true * * _.eq(object, other); * // => false * * _.eq('a', 'a'); * // => true * * _.eq('a', Object('a')); * // => false * * _.eq(NaN, NaN); * // => true */ //用SameValueZero規則判斷兩個值是否相等 function eq(value, other) { return value === other || (value !== value && other !== other); } /** * Checks if `value` is likely an `arguments` object. * * @static * @memberOf _ * @since 0.1.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is an `arguments` object, * else `false`. * @example * * _.isArguments(function() { return arguments; }()); * // => true * * _.isArguments([1, 2, 3]); * // => false */ //判斷一個值是不是一個arguments對象 function isArguments(value) { // Safari 8.1 makes `arguments.callee` enumerable in strict mode. return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') && (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag); } /** * Checks if `value` is classified as an `Array` object. * * @static * @memberOf _ * @since 0.1.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is an array, else `false`. * @example * * _.isArray([1, 2, 3]); * // => true * * _.isArray(document.body.children); * // => false * * _.isArray('abc'); * // => false * * _.isArray(_.noop); * // => false */ //判斷一個值是不是數組 var isArray = Array.isArray; /** * Checks if `value` is array-like. A value is considered array-like if it's * not a function and has a `value.length` that's an integer greater than or * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. * * @static * @memberOf _ * @since 4.0.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is array-like, else `false`. * @example * * _.isArrayLike([1, 2, 3]); * // => true * * _.isArrayLike(document.body.children); * // => true * * _.isArrayLike('abc'); * // => true * * _.isArrayLike(_.noop); * // => false */ //判斷一個值是不是array-like對象 function isArrayLike(value) { return value != null && isLength(value.length) && !isFunction(value); } /** * This method is like `_.isArrayLike` except that it also checks if `value` * is an object. * * @static * @memberOf _ * @since 4.0.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is an array-like object, * else `false`. * @example * * _.isArrayLikeObject([1, 2, 3]); * // => true * * _.isArrayLikeObject(document.body.children); * // => true * * _.isArrayLikeObject('abc'); * // => false * * _.isArrayLikeObject(_.noop); * // => false */ //判斷一個值是不是一個array-like對象而且也是一個object-like對象 function isArrayLikeObject(value) { return isObjectLike(value) && isArrayLike(value); } /** * Checks if `value` is a buffer. * * @static * @memberOf _ * @since 4.3.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a buffer, else `false`. * @example * * _.isBuffer(new Buffer(2)); * // => true * * _.isBuffer(new Uint8Array(2)); * // => false */ //判斷一個值是不是一個buffer對象 var isBuffer = nativeIsBuffer || stubFalse; /** * Checks if `value` is classified as a `Function` object. * * @static * @memberOf _ * @since 0.1.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a function, else `false`. * @example * * _.isFunction(_); * // => true * * _.isFunction(/abc/); * // => false */ //判斷一個值是不是function對象 function isFunction(value) { // The use of `Object#toString` avoids issues with the `typeof` operator // in Safari 8-9 which returns 'object' for typed array and other constructors. var tag = isObject(value) ? objectToString.call(value) : ''; return tag == funcTag || tag == genTag; } /** * Checks if `value` is a valid array-like length. * * **Note:** This method is loosely based on * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). * * @static * @memberOf _ * @since 4.0.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. * @example * * _.isLength(3); * // => true * * _.isLength(Number.MIN_VALUE); * // => false * * _.isLength(Infinity); * // => false * * _.isLength('3'); * // => false */ //判斷一個值是不是一個有效的array-like對象的length屬性 function isLength(value) { return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; } /** * Checks if `value` is the * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * * @static * @memberOf _ * @since 0.1.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is an object, else `false`. * @example * * _.isObject({}); * // => true * * _.isObject([1, 2, 3]); * // => true * * _.isObject(_.noop); * // => true * * _.isObject(null); * // => false */ //判斷一個值是不是一個語言層面的對象 function isObject(value) { var type = typeof value; return !!value && (type == 'object' || type == 'function'); } /** * Checks if `value` is object-like. A value is object-like if it's not `null` * and has a `typeof` result of "object". * * @static * @memberOf _ * @since 4.0.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is object-like, else `false`. * @example * * _.isObjectLike({}); * // => true * * _.isObjectLike([1, 2, 3]); * // => true * * _.isObjectLike(_.noop); * // => false * * _.isObjectLike(null); * // => false */ //判斷一個值是不是一個object-like對象 function isObjectLike(value) { return !!value && typeof value == 'object'; } /** * Checks if `value` is classified as a `Symbol` primitive or object. * * @static * @memberOf _ * @since 4.0.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. * @example * * _.isSymbol(Symbol.iterator); * // => true * * _.isSymbol('abc'); * // => false */ //判斷一個值是不是一個symbol對象 function isSymbol(value) { return typeof value == 'symbol' || (isObjectLike(value) && objectToString.call(value) == symbolTag); } /** * Checks if `value` is classified as a typed array. * * @static * @memberOf _ * @since 3.0.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. * @example * * _.isTypedArray(new Uint8Array); * // => true * * _.isTypedArray([]); * // => false */ //判斷一個值是不是一個typedArray對象 var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray; /** * Converts `value` to a string. An empty string is returned for `null` * and `undefined` values. The sign of `-0` is preserved. * * @static * @memberOf _ * @since 4.0.0 * @category Lang * @param {*} value The value to process. * @returns {string} Returns the string. * @example * * _.toString(null); * // => '' * * _.toString(-0); * // => '-0' * * _.toString([1, 2, 3]); * // => '1,2,3' */ //將值轉換爲字符串,若是value是null或者undefined,返回空字符串。-0的符號會被保留 function toString(value) { return value == null ? '' : baseToString(value); } /** * Gets the value at `path` of `object`. If the resolved value is * `undefined`, the `defaultValue` is returned in its place. * * @static * @memberOf _ * @since 3.7.0 * @category Object * @param {Object} object The object to query. * @param {Array|string} path The path of the property to get. * @param {*} [defaultValue] The value returned for `undefined` resolved values. * @returns {*} Returns the resolved value. * @example * * var object = { 'a': [{ 'b': { 'c': 3 } }] }; * * _.get(object, 'a[0].b.c'); * // => 3 * * _.get(object, ['a', '0', 'b', 'c']); * // => 3 * * _.get(object, 'a.b.c', 'default'); * // => 'default' */ //獲取object上path路徑對應的值 function get(object, path, defaultValue) { var result = object == null ? undefined : baseGet(object, path); //若是object爲空,則結果值是undefined,不然調用baseGet獲取 return result === undefined ? defaultValue : result; } /** * Checks if `path` is a direct or inherited property of `object`. * * @static * @memberOf _ * @since 4.0.0 * @category Object * @param {Object} object The object to query. * @param {Array|string} path The path to check. * @returns {boolean} Returns `true` if `path` exists, else `false`. * @example * * var object = _.create({ 'a': _.create({ 'b': 2 }) }); * * _.hasIn(object, 'a'); * // => true * * _.hasIn(object, 'a.b'); * // => true * * _.hasIn(object, ['a', 'b']); * // => true * * _.hasIn(object, 'b'); * // => false */ //判斷path路徑表明的屬性是不是object的自身直接屬性或者繼承的屬性 function hasIn(object, path) { return object != null && hasPath(object, path, baseHasIn); } /** * Creates an array of the own enumerable property names of `object`. * * **Note:** Non-object values are coerced to objects. See the * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) * for more details. * * @static * @since 0.1.0 * @memberOf _ * @category Object * @param {Object} object The object to query. * @returns {Array} Returns the array of property names. * @example * * function Foo() { * this.a = 1; * this.b = 2; * } * * Foo.prototype.c = 3; * * _.keys(new Foo); * // => ['a', 'b'] (iteration order is not guaranteed) * * _.keys('hi'); * // => ['0', '1'] */ //建立一個對象的自身可枚舉屬性名組成的數組,相似原生的Object.keys() function keys(object) { return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object); //若是object是array-like對象,調用arrayLikeKeys處理,不然使用baseKeys處理 } /** * This method returns the first argument it receives. * * @static * @since 0.1.0 * @memberOf _ * @category Util * @param {*} value Any value. * @returns {*} Returns `value`. * @example * * var object = { 'a': 1 }; * * console.log(_.identity(object) === object); * // => true */ //返回第一個接收到的參數 function identity(value) { return value; } /** * Creates a function that invokes `func` with the arguments of the created * function. If `func` is a property name, the created function returns the * property value for a given element. If `func` is an array or object, the * created function returns `true` for elements that contain the equivalent * source properties, otherwise it returns `false`. * * @static * @since 4.0.0 * @memberOf _ * @category Util * @param {*} [func=_.identity] The value to convert to a callback. * @returns {Function} Returns the callback. * @example * * var users = [ * { 'user': 'barney', 'age': 36, 'active': true }, * { 'user': 'fred', 'age': 40, 'active': false } * ]; * * // The `_.matches` iteratee shorthand. * _.filter(users, _.iteratee({ 'user': 'barney', 'active': true })); * // => [{ 'user': 'barney', 'age': 36, 'active': true }] * * // The `_.matchesProperty` iteratee shorthand. * _.filter(users, _.iteratee(['user', 'fred'])); * // => [{ 'user': 'fred', 'age': 40 }] * * // The `_.property` iteratee shorthand. * _.map(users, _.iteratee('user')); * // => ['barney', 'fred'] * * // Create custom iteratee shorthands. * _.iteratee = _.wrap(_.iteratee, function(iteratee, func) { * return !_.isRegExp(func) ? iteratee(func) : function(string) { * return func.test(string); * }; * }); * * _.filter(['abc', 'def'], /ef/); * // => ['def'] */ //建立一個迭代器方法,這個方法和參數func有關,還和這個被建立的方法接收的參數有關。若是func是一個屬性名,那麼這個方法就返回指定元素的對應屬性值;若是func是一個數組或對象,這個方法就根據元素是否包含與這個數組或對象相等的數組或對象來返回true或false function iteratee(func) { return baseIteratee(typeof func == 'function' ? func : baseClone(func, true)); //若是func是function就不作變化,不然調用baseClone處理func } /** * Creates a function that returns the value at `path` of a given object. * * @static * @memberOf _ * @since 2.4.0 * @category Util * @param {Array|string} path The path of the property to get. * @returns {Function} Returns the new accessor function. * @example * * var objects = [ * { 'a': { 'b': 2 } }, * { 'a': { 'b': 1 } } * ]; * * _.map(objects, _.property('a.b')); * // => [2, 1] * * _.map(_.sortBy(objects, _.property(['a', 'b'])), 'a.b'); * // => [1, 2] */ //建立一個方法,這個方法能夠根據給定path屬性路徑返回給定對象的屬性值 function property(path) { return isKey(path) ? baseProperty(toKey(path)) : basePropertyDeep(path); } /** * This method returns a new empty array. * * @static * @memberOf _ * @since 4.13.0 * @category Util * @returns {Array} Returns the new empty array. * @example * * var arrays = _.times(2, _.stubArray); * * console.log(arrays); * // => [[], []] * * console.log(arrays[0] === arrays[1]); * // => false */ //返回一個新的空數組 function stubArray() { return []; } /** * This method returns `false`. * * @static * @memberOf _ * @since 4.13.0 * @category Util * @returns {boolean} Returns `false`. * @example * * _.times(2, _.stubFalse); * // => [false, false] */ function stubFalse() { return false; } module.exports = iteratee;