硬剛 lodash 源碼之路,_.chunk
- 2020 年 7 月 3 日
- 筆記
- javascript, lib, 源碼
前置
chunk
函數內部藉助其他函數實現,所以從其他函數開始,chunk
在最後。
你可能需要一些 JavaScript 基礎知識才能看懂一些沒有注釋的細節。
isObject
判斷是否為 Object 類型
/**
* Checks if `value` is the
* [language type](//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('')`)
*
* @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(Function)
* // => true
*
* isObject(null)
* // => false
*/
function isObject(value) {
const type = typeof value
// 將 function 作為 Object 類型
return value != null && (type === 'object' || type === 'function')
}
export default isObject
getTag
getTag 獲取給定值的 toStringTag
。
Symbol.toStringTag
是一個內置 symbol,它通常作為對象的屬性鍵使用,對應的屬性值應該為字元串類型,這個字元串用來表示該對象的自定義類型標籤,通常只有內置的 Object.prototype.toString()
方法會去讀取這個標籤並把它包含在自己的返回值里。
許多內置的 JavaScript 對象類型即便沒有 toStringTag
屬性,也能被 toString()
方法識別並返回特定的類型標籤,比如:
Object.prototype.toString.call('foo'); // "[object String]"
Object.prototype.toString.call([1, 2]); // "[object Array]"
Object.prototype.toString.call(3); // "[object Number]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(null); // "[object Null]"
// ... and more
另外一些對象類型則不然,toString()
方法能識別它們是因為引擎為它們設置好了 toStringTag
標籤:
Object.prototype.toString.call(new Map()); // "[object Map]"
Object.prototype.toString.call(function* () {}); // "[object GeneratorFunction]"
Object.prototype.toString.call(Promise.resolve()); // "[object Promise]"
// ... and more
對於你自己創建的類,toString()
找不到 toStringTag
屬性時只好返回默認的 Object 標籤:
class ValidatorClass {}
Object.prototype.toString.call(new ValidatorClass()); // "[object Object]"
加上 toStringTag
屬性,你的類也會有自定義的類型標籤了:
class ValidatorClass {
get [Symbol.toStringTag]() {
return "Validator";
}
}
Object.prototype.toString.call(new ValidatorClass()); // "[object Validator]"
const toString = Object.prototype.toString
/**
* Gets the `toStringTag` of `value`.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the `toStringTag`.
*/
function getTag(value) {
// 處理 value 能夠轉化為 null 的值
if (value == null) {
return value === undefined ? '[object Undefined]' : '[object Null]'
}
return toString.call(value)
}
export default getTag
isSymbol
_.isSymbol(value)
檢查 value 是否是原始 Symbol 或者對象。
import getTag from './.internal/getTag.js'
/**
* Checks if `value` is classified as a `Symbol` primitive or object.
*
* @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
*/
function isSymbol(value) {
// typeof 比 Object.prototype.toString 效率高
const type = typeof value
return type == 'symbol' || (type === 'object' && value != null && getTag(value) == '[object Symbol]')
}
export default isSymbol
toNumber
_.toNumber(value)
轉換 value 為一個數字。
import isObject from './isObject.js'
import isSymbol from './isSymbol.js'
/** Used as references for various `Number` constants. */
const NAN = 0 / 0
/** Used to match leading and trailing whitespace. */
const reTrim = /^\s+|\s+$/g
/** Used to detect bad signed hexadecimal string values. */
// 用於檢測錯誤的有符號十六進位字元串值
const reIsBadHex = /^[-+]0x[0-9a-f]+$/i
/** Used to detect binary string values. */
// 二進位。
const reIsBinary = /^0b[01]+$/i
/** Used to detect octal string values. */
// 八進位
const reIsOctal = /^0o[0-7]+$/i
/** Built-in method references without a dependency on `root`. */
// 不依賴於 root 的內置方法引用
// 防止全局作用域下的parseInt被用戶替換
const freeParseInt = parseInt
/**
* Converts `value` to a number.
*
* @since 4.0.0
* @category Lang
* @param {*} value The value to process.
* @returns {number} Returns the number.
* @see isInteger, toInteger, isNumber
* @example
*
* toNumber(3.2)
* // => 3.2
*
* toNumber(Number.MIN_VALUE)
* // => 5e-324
*
* toNumber(Infinity)
* // => Infinity
*
* toNumber('3.2')
* // => 3.2
*/
function toNumber(value) {
if (typeof value === 'number') {
return value
}
if (isSymbol(value)) {
return NAN // Number 的引用
}
// Object.prototype.valueOf() 方法返回指定對象的原始值
// 默認情況下,valueOf方法由Object後面的每個對象繼承。
// 每個內置的核心對象都會覆蓋此方法以返回適當的值。
// 如果對象沒有原始值,則valueOf將返回對象本身。
if (isObject(value)) {
// value 沒有 valueOf 函數或者 valueOf 函數返回一個對象,
// 將 other 轉換成 string 類型,留待後面處理。
const other = typeof value.valueOf === 'function' ? value.valueOf() : value
value = isObject(other) ? `${other}` : other
}
if (typeof value !== 'string') {
return value === 0 ? value : +value
}
// @example
// const a = function() {}
// console.log(a.valueOf());
// -> [Function: a]
// console.log(typeof a.valueOf());
// -> function
// @example
// const a = {}
// console.log(a.valueOf();
// -> {}
// 16進位返回NAN
// 10進位數(+)確保返回值是數值類型
value = value.replace(reTrim, '') // 用''替換掉字元串中符合reTrim的項
const isBinary = reIsBinary.test(value) // 二進位
return (isBinary || reIsOctal.test(value)) // 二進位或八進位
? freeParseInt(value.slice(2), isBinary ? 2 : 8) // 刪除字元串前兩位並解析為十進位的整數
: (reIsBadHex.test(value) ? NAN : +value) // 十六進位字元串值返回 NAN,否則返回十進位(+)
}
export default toNumber
toFinite
_.toFinite(value)
轉換 value 為一個有限數字。
import toNumber from './toNumber.js'
/** Used as references for various `Number` constants. */
const INFINITY = 1 / 0 // 無窮
const MAX_INTEGER = 1.7976931348623157e+308 // 最大整數
/**
* Converts `value` to a finite number.
*
* @since 4.12.0
* @category Lang
* @param {*} value The value to convert.
* @returns {number} Returns the converted number.
* @example
*
* toFinite(3.2)
* // => 3.2
*
* toFinite(Number.MIN_VALUE)
* // => 5e-324
*
* toFinite(Infinity)
* // => 1.7976931348623157e+308
*
* toFinite('3.2')
* // => 3.2
*/
function toFinite(value) {
// undefined & null -> 0
if (!value) {
return value === 0 ? value : 0
}
value = toNumber(value)
// 正負無窮取正負最大值
if (value === INFINITY || value === -INFINITY) {
const sign = (value < 0 ? -1 : 1)
return sign * MAX_INTEGER
}
return value === value ? value : 0
}
export default toFinite
toInteger
_.toInteger(value)
轉換 value 為一個整數。
import toFinite from './toFinite.js'
/**
* Converts `value` to an integer.
*
* **Note:** This method is loosely based on
* [`ToInteger`](//www.ecma-international.org/ecma-262/7.0/#sec-tointeger).
*
* @since 4.0.0
* @category Lang
* @param {*} value The value to convert.
* @returns {number} Returns the converted integer.
* @see isInteger, isNumber, toNumber
* @example
*
* toInteger(3.2)
* // => 3
*
* toInteger(Number.MIN_VALUE)
* // => 0
*
* toInteger(Infinity)
* // => 1.7976931348623157e+308
*
* toInteger('3.2')
* // => 3
*/
function toInteger(value) {
const result = toFinite(value)
// result 為小數時,則 remainder 不為 0
const remainder = result % 1 // 餘數
// 抹掉小數位
return remainder ? result - remainder : result
}
export default toInteger
chunk
_.chunk(array, [size=1])
將數組拆分成多個 size 長度的區塊,並將這些區塊組成一個新數組。 如果array 無法被分割成全部等長的區塊,那麼最後剩餘的元素將組成一個區塊。
import slice from './slice.js'
import toInteger from './toInteger.js'
/**
* @since 3.0.0
* @category Array
* @param {Array} array The array to process.
* @param {number} [size=1] The length of each chunk
* @returns {Array} Returns the new array of chunks.
* @example
*
* chunk(['a', 'b', 'c', 'd'], 2)
* // => [['a', 'b'], ['c', 'd']]
*
* chunk(['a', 'b', 'c', 'd'], 3)
* // => [['a', 'b', 'c'], ['d']]
*/
function chunk(array, size = 1) {
// 令 size >= 0
size = Math.max(toInteger(size), 0)
const length = array == null ? 0 : array.length
if (!length || size < 1) {
return []
}
// 構建新數組
let index = 0
let resIndex = 0
const result = new Array(Math.ceil(length / size))
while (index < length) {
result[resIndex++] = slice(array, index, (index += size))
}
return result
}
export default chunk
參考資料:MDN