一、介紹
官方文檔:
中文 - https://www.lodashjs.com/docs/latest
英文- https://lodash.com/docs/4.17.15
1、作用
lodash
是一套工具庫,內部封裝了很多字符串、數組、對象等常見數據類型的處理函數。
2、組成
-
lodash
:全部功能 -
lodash.core
:只有核心的一些函數,詳細見這兒https://github.com/lodash/lod... -
lodash.fp
:全部功能的函數式實現,文檔見 https://github.com/lodash/lodash/wiki/FP-Guide
lodash.fp 暫不介紹(待寫)
3、競品比較
Lodash最初是 Underscore
的分支,后來逐漸壯大后自立門戶。
Lodash 功能比 Underscore 更豐富,且 Underscore 已有3、4年沒有更新,所以推薦使用 Loadash。
二、安裝
1、browser
<script src="lodash.js"></script>
CDN:
https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js
2、Node.js
npm i lodash
// Load the full build.
var _ = require('lodash');
// Load the core build.
var _ = require('lodash/core');
// Load method categories.
var array = require('lodash/array');
// Load method.
var chunk = require('lodash.chunk');
三、使用
注:本人裝的是 latest 版本,_.VERSION
可查看版本號,為4.17.15
。
下面介紹的方法,是一些我認為屬於重難點的、常用的。並有一些解釋借鑒了 underscore 文檔。
1、Array
(1)集合運算
intersection
- 交集
union
- 並集
difference
- ( A - B )
xor
- 只要一個元素出現兩次及以上,則 remove 掉,其他的元素合並成一個新數組。
(2)difference
difference
- 沒有第三個參數
differenceBy
- 第三個參數傳的是 iteratee (value)
differenceWith
- 第三個參數傳的是 comparator (arrVal, othVal)
// 1、difference
_.difference([3, 2, 1], [4, 2]);
// => [3, 1]
// 2、differenceBy
_.differenceBy([3.1, 2.2, 1.3], [4.4, 2.5], Math.floor);
// => [3.1, 1.3]
// 3、differenceWith
var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
_.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual);
// => [{ 'x': 2, 'y': 1 }]
注:x、xBy、xWith 這三種模式在別的很多方法中也有體現。如 pullAll / pullAllBy / pullAllWith。
(3)drop
drop
- 從數組左邊 remove 元素,可指定個數
dropRight
- 從數組右邊 remove 元素,可指定個數
dropWhile
- 從數組左邊 按條件 remove 元素,遇到條件不符合則終止
dropRightWhile
- 從數組右邊 按條件 remove 元素,遇到條件不符合則終止
這里是 遇到條件不符合則終止,若想 遇到條件不符合不終止,也就沒有左右之分,一律用 filter 替換即可。
注:x、xWhile 這兩種模式在別的很多方法中也有體現。如 zip / zipWith。
(4)幾種 刪數組元素的方法
1、提供另一些 元素/數組/索引 來刪除
without
(提供元素)- 不改變原數組
difference
(提供數組) - 不改變原數組
pull
(提供元素)/pullAll
(提供數組)/ pullAt
(提供索引)- 改變了原數組
2、單純針對原數組去刪除
filter
- 不改變原數組
remove
- 改變了原數組
所以 lodash 提供的方法也不都是 Immutable 的。
(5)remove 類空值
remove 掉: false
, null
, 0
, ""
, undefined
, 和 NaN
。
_.compact([0, 1, false, 2, '', 3]);// => [1, 2, 3]
2、Collection
集合函數能在數組,對象,和類數組對象,比如 arguments, NodeList 和類似的數據類型 (如 string) 上正常工作。
但是它通過鴨子類型
工作,所以要避免傳遞一個不固定length屬性的對象。
拓展:什么是鴨子類型?
原話:“當看到一只鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那么這只鳥就可以被稱為鴨子。”
所以鴨子類型關注點在對象的行為,而不是類型。
怎么有種 “不管黑貓白貓,抓住老鼠就是好貓” 的既視感。
(1)判斷
every
- 全都符合返回 true
some
- 只要一條符合返回 true
注意:上面對空集合還是會返回 true。
(2)篩選
filter
- 正
reject
- 反
partition
- 同時輸出正與反
(3)排序
sortBy
- 只能升序
orderBy
- 可升序可降序
(4)遍歷
forEach
/ forEachRight
_([1, 2]).forEach(function(value) {
console.log(value);
});
// => Logs `1` then `2`.
_.forEach({ 'a': 1, 'b': 2 }, function(value, key) {
console.log(key);
});
// => Logs 'a' then 'b' (iteration order is not guaranteed).
(5)遍歷輸出
map
invokeMap
- this 相當於 map 的 item,此外還可以額外傳入多個參數參與運算
function square(item) {
return item * item;
}
function invokeSquare(n, m) {
return this * n * m;
}
re1 = _.map([1, 2], square);
re2 = _.invokeMap([1, 2], invokeSquare, 2, 3);
console.log(re1);
// [ 1, 4 ]
console.log(re2);
// [ 6, 12 ]
還有類似 map 的
flatMap
/flatMapDeep
/flatMapDepth
,在 map 的基礎上實現扁平化。
(6)聚合
countBy
- 只算出現的次數
groupBy
- 囊括出現的內容
keyBy
- 自定義程度更高
(7)迭代歸納
reduce
和transform
注:transform 應該歸屬於 Object 章節而不是 Collection,但因為跟 reduce 用法挺像,所以這里放在一起做對比。
_.reduce([2, 3, 4], function(sum, n) {
return sum + n; // return 值為下一次循環的 sum 值
}, 1); // 1 為初始值
// => 10
_.transform([2, 3, 4, 5 ,6], function(result, n) {
result.push(n);
return n < 4; // return false 即結束循環
}, [1]); // [1] 為初始值
// => [ 1, 2, 3, 4 ]
(8)針對字符串
includes
:_.includes('pebbles', 'eb'); 可以代替 indexOf
size
:_.size('pebbles'); 可以代替 length
建議還是用原生方法。
拓展:用 es6 原生替代 lodash
_.forEach([1, 2, 3], (i) => { console.log(i) })
_.map([1, 2, 3], (i) => i + 1)
_.filter([1, 2, 3], (i) => i > 1)
_.reduce([1, 2, 3], (sum, i) => sum + i, 0)
// 使用 ES6 改寫
[1, 2, 3].forEach((i) => { console.log(i) })
[1, 2, 3].map((i) => i + 1)
[1, 2, 3].filter((i) => i > 1)
[1, 2, 3].reduce((sum, i) => sum + i, 0)
3、Date
(1)獲取時間
now
- 獲取 unix 毫秒數
建議用原生的 Date.now() 。
4、Function
(1)函數調用的次數
after
- >=n 次后才能成功調用函數
應用:可以用作並行異步處理后的回調。
before
- 調用次數 <n 次
應用:可以用作次數限制。
once
- 只能調用一次函數
應用:用過既廢。
上面的方法,若函數不能執行,則返回最近一次的執行結果,如果沒有執行過,則為 undefined。
(2)延遲
delay
- 類似 setTimeout
defer
- 類似延時為0的setTimeout。對於執行開銷大的計算防止阻塞UI非常有用。
擴展:什么是 偏函數 / 科里化 / 偏應用 ?
- 偏函數:partial function
- 部分應用函數(偏應用):partially applied function
- 柯里化:currying
偏函數
:指的是僅能處理入參類型子集
的函數。
例如一個函數雖然形參定義的是整形,但只能處理正整形。(即你只能傳正整形才能正確調用)。
偏應用
:例如在 Scala 中,當函數調用的參數不足時,編譯器不會報錯,而是先應用已提供的參數,並返回一個新函數,該函數接受原函數剩余未提供的參數作為自己的參數。
未提供的參數可用
_
占位符表示。
柯里化
:在偏應用基礎上更進一步,將多參函數分解為一系列的單參函數,例如
-
curriedDivide(10)
調用第 1 個調用,並返回第 2 個函數 -
curriedDivide(10)(2)
等於先后調用兩個函數
偏應用和柯里化的區別:前者僅產生一個函數,后者可以產生多個函數。
上述概念,本是數學上的概念,但更多的用在了函數式編程上。
參考資料:http://songkun.me/2018/05/16/scala-partialfunction-partially-applied-function-currying/
(3)柯里化 or 偏應用
curry
/curryRight
:
var abc = function(a, b, c) {
return [a, b, c];
};
var curried = _.curry(abc);
// 柯里化
curried(1)(2)(3);
// => [1, 2, 3]
// 偏應用
curried(1, 2)(3);
// => [1, 2, 3]
// 偏應用提供占位符
curried(1)(_, 3)(2);
// => [1, 2, 3]
// 也可正常調用
curried(1, 2, 3);
// => [1, 2, 3]
(4)綁定上下文 or 偏應用
bind
/ bindKey
- 既可綁定上下文,也可實現偏應用
partial
/ partialRight
- 不可綁定上下文,僅實現偏應用 ( 即避免使用 .bind(null,…) )
應用:
-
給多個對象綁定共用函數
-
給函數預先指定默認參數
// bind - 不支持 bind 后修改 function 和 objcet
var greet = function(greeting, punctuation) {
return greeting + " " + this.user + punctuation;
};
var object = { user: "fred" };
var bound = _.bind(greet, object, "hi");
bound("!");
// => 'hi fred!'
// bind - 偏應用的占位符功能
var bound = _.bind(greet, object, _, "!");
bound("hi");
// => 'hi fred!'
// -----------------------------------
// bindKey - 支持 bindKey 后修改 object 和(object 中的)function
var object = {
user: "fred",
greet: function(greeting, punctuation) {
return greeting + " " + this.user + punctuation;
}
};
var bound = _.bindKey(object, "greet", "hi");
bound("!");
// => 'hi fred!'
// bibindKeynd - 偏應用的占位符功能
var bound = _.bindKey(object, "greet", _, "!");
bound("hi");
// => 'hi fred!'
// -----------------------------------
// partial
var greet = function(greeting, name) {
return greeting + " " + name;
};
var sayHelloTo = _.partial(greet, "hello");
sayHelloTo("fred");
// => 'hello fred'
// partial - 偏應用的占位符功能
var greetFred = _.partial(greet, _, "fred");
greetFred("hi");
// => 'hi fred'
(5)防止函數高頻調用
debounce
- 防抖動/防反跳
,該函數會從上一次被調用后,延遲 wait 毫秒后調用 func 方法。即將一定時間內的連續調用歸為一個。
應用:
-
當窗口停止改變大小之后重新計算布局
-
對用戶輸入的驗證,避免在輸入過程中處理,而是在停止輸入后處理
-
防止提交按鈕被瞬間點擊多次,導致大量的請求被重復發送
jQuery(window).on('resize', _.debounce(calculateLayout, 150));
// 取消一個 trailing 的防抖動調用
jQuery(window).on('popstate', debounced.cancel);
throttle
- 節流閥
,在 wait 秒內最多執行 func 一次的函數。
應用:避免在頁面滾動時頻繁的更新定位
jQuery(window).on('scroll', _.throttle(updatePosition, 100));
// 取消一個 trailing 的節流調用。
jQuery(window).on('popstate', throttled.cancel);
debounce 和 throttle 的區別:
debounce 和 throttle 都有 leading 和 trailing 的配置項。在都是默認值的情況下,使得這兩個函數適用場景不一樣,前者更多的是反抖,后者是節流。而當兩者配置項相同的話,可以理解是一致的。
var time_v = 2000;
// debounce leading/trailing 的默認值
_.debounce(() => console.log(_.random(10, 20)), time_v, {
leading: false,
trailing: true
});
// throttle leading/trailing 的默認值
_.throttle(() => console.log(_.random(10, 20)), time_v, {
leading: true,
trailing: true
});
(6)緩存結果
memoize
- 緩存函數的計算結果
應用:緩存耗時較長的計算
var object = { a: 1, b: 2 };
var other = { c: 3, d: 4 };
var values = _.memoize(_.values);
// usage
values(object);
// => [1, 2]
values(other);
// => [3, 4]
// 驗證緩存是否生效
object.a = 2;
values(object);
// => [1, 2] ( 證明把 object 的地址當成了緩存 key )
// 修改緩存結果
values.cache.set(object, [5, 6]);
values(object);
// => [ 5, 6 ]
// 清除緩存
values.cache.clear(object);
values(object);
// => [ 2, 2 ]
(7)翻轉斷言函數
negate
應用:可跟 filter 搭配
function isEven(n) {
return n % 2 == 0;
}
_.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));
// => [1, 3, 5]
5、Lang
(1)拷貝
clone
- 淺拷貝
cloneDeep
- 深拷貝
var objects = [{ a: 1 }, { b: 2 }];
var shallow = _.clone(objects);
console.log(shallow === objects); //false
console.log(shallow[0] === objects[0]); //true
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]); //false
(2)判斷相等
eq
- 淺比較
isEqual
- 深比較
var object = { 'a': 1 };
var other = { 'a': 1 };
_.eq(object, object);
// => true
_.eq(object, other);
// => false
_.isEqual(object, other);
// => true
object === other;
// => false
(3)判斷類型
isArray
/isArrayLike
/isArrayLikeObject
- isArrayLikeObject = ArrayLike or Object
注意:
isArray isArrayLike isArrayLikeObject [] T T T "123" F T F document.body.children F T T
isElement
- DOM 元素。
isError
isNil
- null or undefined。
isNaN
- NaN
推薦使用這個而不是原生的isNaN(),因為會把 undefined 當成 true。
isObject
/isObjectLike
/isPlainObject
注意:javaScript 中數組和函數也是對象,所以:
isObject isObjectLike isPlainObject {} T T T [] T T F function(){} T F F
isSafeInteger
- 基於 Number.isSafeInteger()
拓展:什么是安全整數?
首先,JavaScript 能夠准確表示的整數范圍在-2^53
到2^53
之間(不含兩個端點),超過這個范圍,無法精確表示這個值。
於是 ES6 引入了Number.MAX_SAFE_INTEGER
和Number.MIN_SAFE_INTEGER
這兩個常量,用來表示這個范圍的上下限。
而Number.isSafeInteger()
則是用來判斷一個整數是否落在這個范圍之內。在 lodash 里,可以用isSafeInteger()
代替。
注意:驗證運算結果是否落在安全整數的范圍內,不要只驗證運算結果,而要同時驗證參與運算的每個值。
(4)判斷空
isEmpty
-
對於object - 沒有可枚舉的屬性
-
對於字符串和類數組對象,如果length屬性為0
(5)類型轉換
toNumber
toInteger
toString
拓展:toInteger 跟 parseInt 的區別?
toInteger 更嚴格一些:
_.toInteger("123das")
// 0
_.parseInt("123das")
// 123
Number("123das")
// NaN
6、Math
略
7、Number
clamp
- 返回限制在 lower 和 upper之間的值。
挺適合做 讓接受參數落入合法區間。
random
- 生成隨機數,支持浮點。
8、Object
(1)對象合並
1、前者覆蓋后者
defaults
defaultsDeep
_.defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
// => { 'a': 1, 'b': 2 }
_.defaultsDeep({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } });
// => { 'a': { 'b': 2, 'c': 3 } }
2、后者覆蓋前者
assign
assignIn
- 包含原型鏈屬性
function Foo() {
this.a = 1;
}
function Bar() {
this.c = 3;
}
Foo.prototype.b = 2;
Bar.prototype.d = 4;
_.assign({ 'a': 0 }, new Foo, new Bar);
// => { 'a': 1, 'c': 3 }
_.assignIn({ 'a': 0 }, new Foo, new Bar);
// => { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }
注:x、xIn 這兩種模式在別的很多方法中也有體現。如 functions / functionsIn 。
注:defaults 沒有 defaultsIn ,assign 沒有 assignDeep。
merge
- 類似 assign(后者覆蓋前者),不同的是,defaults/assign 一碰到相同的 key 就去直接覆蓋 value,而 merge 碰到相同的 key 且 value 為對象時,則會遞歸合並這兩個對象。
_.assign({ a: { "1": 1 } }, { b: { "2": 2 } }, { a: { "3": 3 } });
// { a: { '3': 3 }, b: { '2': 2 } }
_.merge({ a: { "1": 1 } }, { b: { "2": 2 } }, { a: { "3": 3 } });
// { a: { '1': 1, '3': 3 }, b: { '2': 2 } }
(2)判斷
conformsTo
- 根據對象的 屬性-值 判斷
在 loadash 文檔里,把 conformsTo 並沒有歸到 Object 目錄下,而是放在 Lang。
var object = { 'a': 1, 'b': 2 };
_.conformsTo(object, { 'b': function(n) { return n > 1; } });
// => true
_.conformsTo(object, { 'b': function(n) { return n > 2; } });
// => false
(3)遍歷
forIn
/forInRight
- 遍歷自身可枚舉屬性(包含原型鏈)
forOwn
/forOwnRight
- 遍歷自身的可枚舉屬性
注意:上述都無法保證遍歷的順序。
原生方法:
遍歷自身可枚舉屬性(包含原型鏈):
for (let key in obj)
遍歷自身的可枚舉屬性:
Object.keys(obj)
或for (let key of Object.keys(obj))
(4)遍歷輸出
之前在 Collection 分類里提到過 map,但在 Object 分類里,另有兩個專屬的 類map 方法:
mapKeys
/mapValues
_.mapKeys({ a: 1, b: 2 }, function(value, key) {
return key + value;
});
// => { a1: 1, b2: 2 }
_.mapValues({ a: 1, b: 2 }, function(value, key) {
return key + value;
});
// => { a: 'a1', b: 'b2' }
(5)path 路徑
has
/hasIn
- 判斷 ( hasIn 包含原型鏈)
get
/result
/invoke
- 獲取(值)/調用(函數)【值本身就是函數】/調用(函數)【值不是函數,需自己提供函數+傳參】
set
/ update
/ unset
- 創建/更新/刪除 (set = create or update)
原生方法:
has =
object.hasOwnProperty(key)
hasIn =
"key" in object
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'
var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] };
re1 = _.result(object, 'a[0].b.c1');
// 3
var object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] };
re2 = _.invoke(object, 'a[0].b.c.slice', 1, 3);
// [ 2, 3 ]
(6)取對象子集
pick
- 正
omit
- 反
var object = { 'a': 1, 'b': '2', 'c': 3 };
_.pick(object, ['a', 'c']);
// => { 'a': 1, 'c': 3 }
_.omit(object, ['a', 'c']);
// => { 'b': '2' }
注意:如果對象有很多屬性,pick/omit 會比較耗性能(因為屬性會全部遍歷),建議原生直接獲取。
或者用 ES6 新特性 - 對象的解構賦值:
const { a, c } = { a: 1, b: 2, c: 3 }; return { a, c };
9、String
(1)case styles
camelCase
- 轉為駝峰寫法
kebabCase
- 轉為 kebab case 寫法
擴展:幾種 case styles
1、Camel case(駝峰)
-
upper camel case CamelCase - TheQuickBrownFoxJumpsOverTheLazyDog (首字母大寫)
-
lower camel case camelCase - theQuickBrownFoxJumpsOverTheLazyDog(首字母小寫)
2、Snake case (下划線)
-
the_quick_brown_fox_jumps_over_the_lazy_dog (小寫)
-
UPPER_CASE_EMBEDDED_UNDERSCORE (大寫)【常用做常量】
3、Kebab case (連字符)
-
the-quick-brown-fox-jumps-over-the-lazy-dog(小寫)
-
TRAIN-CASE(大寫)
4、Start case
- Foo Bar
5、Studly caps (大小寫隨機)
- tHeqUicKBrOWnFoXJUmpsoVeRThElAzydOG
(2)適合打 log 的方法
pad
/ padEnd
/ padStart
- 左右加符號
_.pad('abc', 8);
// => ' abc '
_.pad('abc', 8, '_-');
// => '_-abc_-_'
_.pad('abc', 3);
// => 'abc'
repeat
- 重復加符號
_.repeat('*', 3);
// => '***'
_.repeat('abc', 2);
// => 'abcabc'
_.repeat('abc', 0);
// => ''
(3)截斷顯示
truncate
- 截斷 string 字符串,如果字符串超出了限定的最大值。 被截斷的字符串后面會以 omission 代替,omission 默認是 "..."。
(4)轉義
escape
/ unescape
- 轉義 string 中的 "&", "<", ">", '"', "'", 和 "`" 字符為 HTML實體字符。
(5)模板
template
提供了三種渲染模板
:
-
interpolate -
<%= … %>
插入變量 -
escape - 如果您希望插入一個值, 並讓其進行HTML轉義,請使用
<%- … %>
-
evaluate -
<% … %>
執行任意的 JavaScript 代碼
// 1、使用 "interpolate" 分隔符創建編譯模板
var compiled = _.template('hello <%= user %>!');
compiled({ 'user': 'fred' });
// => 'hello fred!'
// 1.1、使用 ES 分隔符代替默認的 "interpolate" 的 ERB 分隔符(ERB:嵌入式Ruby)
var compiled = _.template('hello ${ user }!');
compiled({ 'user': 'pebbles' });
// => 'hello pebbles!'
// 1.2 使用自定義的模板分隔符
// 修改 _.templateSettings
// 略
// 2、使用 HTML "escape" 轉義數據的值
var compiled = _.template('<b><%- value %></b>');
compiled({ 'value': '<script>' });
// => '<b>&\lt;script&\gt;</b>'
// 3、使用 "evaluate" 分隔符執行 JavaScript 和 生成HTML代碼
var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>');
compiled({ 'users': ['fred', 'barney'] });
// => '<li>fred</li><li>barney</li>'
// ————————————————————————————————————————————
// 使用反斜杠符號作為純文本處理
var compiled = _.template('<%= "\\<%- value %\\>" %>');
compiled({ 'value': 'ignored' });
// => '<%- value %>'
// 使用 `imports` 選項導入 `jq` 作為 `jQuery` 的別名
var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>';
var compiled = _.template(text, { 'imports': { 'jq': jQuery } });
compiled({ 'users': ['fred', 'barney'] });
// => '<li>fred</li><li>barney</li>'
template 還提供預編譯
的功能:
1、提高性能
2、方便調試(可使用 sourceURLs 提供錯誤的代碼行號和堆棧跟蹤)
預編譯的詳細用法 待寫。
10、Util
(1)range - 生成范圍
range
/rangeRight
- 用來創建整數靈活編號的列表的函數。
應用:
-
便於 each 和 map 循環。
-
模擬測試數據
_.range(4);
// => [0, 1, 2, 3]
_.range(-4);
// => [0, -1, -2, -3]
_.range(1, 5);
// => [1, 2, 3, 4]
_.range(0, 20, 5);
// => [0, 5, 10, 15]
_.range(0, -4, -1);
// => [0, -1, -2, -3]
_.range(1, 4, 0);
// => [1, 1, 1]
_.range(0);
// => []
(2)defaultTo - 返回默認值
defaultTo
- 如果 value 為 NaN, null, undefined,那么返回 defaultValue(默認值)。
應用:替換非法值,保證程序可以順暢往下執行。
_.defaultTo(1, 10);
// => 1
_.defaultTo(undefined, 10);
// => 10
(3)times - 屢次調用
調用函數 n 次,每次調用返回的結果存入到數組中
應用:
-
快速模擬數據
-
實現無參數循環
_.times(4, _.constant(0));
// => [0, 0, 0, 0]
(4)attempt
attempt
- 調用 function,獲得返回的結果或錯誤對象。
應用:可替代寫出來繁瑣的 try-catch,如針對 JSON.parse。
var elements = _.attempt(
function(arr) {
return arr[4].a ;
},
[1, 2, 3]
);
if (_.isError(elements)) {
console.log(elements);
}
(5)overEvery / overSome
overEvery
/overSome
應用:校驗參數格式合法性
var func = _.overEvery([Boolean, isFinite]);
func('1');
// => true
func(null);
// => false
func(NaN);
// => false
var func = _.overSome([Boolean, isFinite]);
func('1');
// => true
func(null);
// => true
func(NaN);
// => false
(6)cond
cond
- 創建了一個函數,這個函數會迭代pairs
(下面會介紹什么是pairs):依次執行pairs左值的函數,若返回 true 則返回 執行pairs右值的函數 的結果並結束;若返回 false 則繼續往下,如果都是 false ,則最終返回 undefined。
應用:可以代替繁瑣的 ifesle / switch 。
var func = _.cond([
[_.matches({ 'a': 1 }), _.constant('matches A')],
[_.conforms({ 'b': _.isNumber }), _.constant('matches B')],
// 最好有個這樣的兜底,不然會返回undefined
[_.stubTrue, _.constant('no match')]
]);
func({ 'a': 1, 'b': 2 });
// => 'matches A'
func({ 'a': 0, 'b': 1 });
// => 'matches B'
func({ 'a': '1', 'b': '2' });
// => 'no match'
拓展:什么是 pairs ?
pairs
是一種用數組描述數據的格式。
如對象 { 'fred': 30, 'barney': 40 } 可以表示為 [['fred', 30], ['barney', 40]] 。
lodash 提供了兩個轉換方法:
fromPairs
_.fromPairs([['fred', 30], ['barney', 40]]);
// => { 'fred': 30, 'barney': 40 }
toPairs
function Foo() {
this.a = 1;
this.b = 2;
}
Foo.prototype.c = 3;
_.toPairs(new Foo);
// => [['a', 1], ['b', 2]] (iteration order is not guaranteed)
(7)flow - 連續調用
flow
/ flowRight
- 每一個調用,傳入的參數都是前一個函數返回的結果。
應用:通過 flow 對函數進行任意的組合,這樣可以極大的增加函數的靈活性和復用性。
let forA = function (a1, a2) {
return Math.pow(a1 - a2, 2);
};
let dist = _.flow([
function (x1, y1, x2, y2) {
return forA(x1, x2) + forA(y1, y2)
},
Math.sqrt,
Math.round
]);
console.log(dist(10, 15, 90, 22)); // 80
flow 跟 下面介紹的 chain 有異曲同工之妙。
(8)混入
mixin
- 添加來源對象自身的所有可枚舉函數屬性到目標對象。(默認目標對象為 lodash 自身)
還有一個跟 minxin 類似的 runInContext 函數,待寫。
注:下面例子涉及到 鏈式操作,下面一節會詳細介紹。
// 開啟鏈式操作(默認)
_.mixin({
capitalize_by_colin: function(string) {
return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
}
});
re1 = _.capitalize_by_colin("fabio");
re2 = _("fabio")
.capitalize_by_colin()
.value();
console.log(re1); // Fabio
console.log(re2); // Fabio
// 關閉鏈式操作
_.mixin(
{
capitalize_by_colin: function(string) {
return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
}
},
{ chain: false }
);
re3 = _.capitalize_by_colin("fabio");
re4 = _("fabio").capitalize_by_colin();
console.log(re3); // Fabio
console.log(re4); // Fabio
(9)其他更多
1、為了跟 filter 等方法合作, lodash 創造了一些便捷方法:
-
matches
-
matchesProperty
-
property
上面支持 簡寫 和 iteratee 形式的縮寫
conforms
var users = [
{ user: "barney", age: _.constant(36), active: true },
{ user: "fred", age: _.constant(40), active: false }
];
// origin
console.log(
_.filter(users, function(o) {
return !o.active;
})
);
// => objects for ['fred']
// _.matches - 針對 對象 or 子對象
console.log(_.filter(users, { user: "barney", active: true }));
console.log(_.filter(users, _.iteratee({ user: "barney", active: true })));
console.log(_.filter(users, _.matches({ user: "barney", active: true })));
// => objects for ['barney']
// _.matchesProperty - 針對 對象的單個屬性和值
console.log(_.filter(users, ["user", "fred"]));
console.log(_.filter(users, _.iteratee(["user", "fred"])));
console.log(_.filter(users, _.matchesProperty("user", "fred")));
// => objects for ['fred']
// _.property - 針對 對象的單個屬性
console.log(_.filter(users, "active"));
console.log(_.filter(users, _.iteratee("active")));
console.log(_.filter(users, _.property("active")));
// => objects for ['barney']
// _.conforms - 針對 對象的單個屬性和值(更靈活)
console.log(_.filter(users, _.conforms({ 'user': function(user) { return user === 'fred'; } })))
// => objects for ['fred']
2、為了跟 map 等方法合作, lodash 創造了一些便捷方法:
method
var users = [
{ user: "barney", age: _.constant(36), active: true },
{ user: "fred", age: _.constant(40), active: false }
];
// _.method - 針對 對象的單個屬性值(以函數的形式調用)
console.log(_.map(users, _.method("age")));
// => [ 36, 40 ]
上述介紹的 property 和 method 分別有相反的版本:
propertyOf
和methodOf
。用法待寫。
11、Seq
(1)創建鏈對象
1、通過_(value)
建立了一個隱式鏈對象
2、通過_.chain(value)
建立了一個顯式鏈對象
3、也可通過_(value).chain()
從隱式轉成顯式。
(2)顯式鏈 (Explicit Chaining) / 隱式鏈 (Implicit Chaining) 區別
顯式鏈調用
的話,需要通過commit()
手動結束鏈式反應,或者 value()
手動結束鏈式反應並提取值。
隱式鏈調用
的話,碰到能返回唯一值 (single value) 或原生數據類型(primitive value),才會自動結束鏈式反應並自動提取值。否則需要你像上面一樣手動操作。
例如 sum 可以觸發隱式鏈調用的自動結束,但是 filter 不行。
什么時候用顯式/隱式?
-
顯式對 commit 和 value 更可控,靈活度更高。
-
隱式可以簡潔代碼。
(3)鏈式(隊列)調用 與 Lazy evaluation(惰性計算)
鏈式隊列
調用的過程中,可以把很多操作串起來,然后一起做 Lazy evaluation
(惰性計算),這中間會允許一些方法 shortcut fusion
。
shortcut fusion 是一種通過合並鏈式 iteratee 調用從而大大降低迭代的次數以提高執行性能的方式。
所以推薦使用顯式鏈調用,這樣可以可控的、最大化的利用 Lazy evaluation。
注意:但也要謹慎創建鏈對象,因為會導致高內存使用率,從而降低性能。
但 lodash 有些方法不支持鏈式調用,如 reduce。詳細如下:
支持 鏈式調用 的方法: after, ary, assign, assignIn, assignInWith, assignWith, at, before, bind, bindAll, bindKey, castArray, chain, chunk, commit, compact, concat, conforms, constant, countBy, create, curry, debounce, defaults, defaultsDeep, defer, delay, difference, differenceBy, differenceWith, drop, dropRight, dropRightWhile, dropWhile, extend, extendWith, fill, filter, flatMap, flatMapDeep, flatMapDepth, flatten, flattenDeep, flattenDepth, flip, flow, flowRight, fromPairs, functions, functionsIn, groupBy, initial, intersection, intersectionBy, intersectionWith, invert, invertBy, invokeMap, iteratee, keyBy, keys, keysIn, map, mapKeys, mapValues, matches, matchesProperty, memoize, merge, mergeWith, method, methodOf, mixin, negate, nthArg, omit, omitBy, once, orderBy, over, overArgs, overEvery, overSome, partial, partialRight, partition, pick, pickBy, plant, property, propertyOf, pull, pullAll, pullAllBy, pullAllWith, pullAt, push, range, rangeRight, rearg, reject, remove, rest, reverse, sampleSize, set, setWith, shuffle, slice, sort, sortBy, splice, spread, tail, take, takeRight, takeRightWhile, takeWhile, tap, throttle, thru, toArray, toPairs, toPairsIn, toPath, toPlainObject, transform, unary, union, unionBy, unionWith, uniq, uniqBy, uniqWith, unset, unshift, unzip, unzipWith, update, updateWith, values, valuesIn, without, wrap, xor, xorBy, xorWith, zip, zipObject, zipObjectDeep, and zipWith。
默認 不支持 鏈式調用 的方法: add, attempt, camelCase, capitalize, ceil, clamp, clone, cloneDeep, cloneDeepWith, cloneWith, conformsTo, deburr, defaultTo, divide, each, eachRight, endsWith, eq, escape, escapeRegExp, every, find, findIndex, findKey, findLast, findLastIndex, findLastKey, first, floor, forEach, forEachRight, forIn, forInRight, forOwn, forOwnRight, get, gt, gte, has, hasIn, head, identity, includes, indexOf, inRange, invoke, isArguments, isArray, isArrayBuffer, isArrayLike, isArrayLikeObject, isBoolean, isBuffer, isDate, isElement, isEmpty, isEqual, isEqualWith, isError, isFinite, isFunction, isInteger, isLength, isMap, isMatch, isMatchWith, isNaN, isNative, isNil, isNull, isNumber, isObject, isObjectLike, isPlainObject, isRegExp, isSafeInteger, isSet, isString, isUndefined, isTypedArray, isWeakMap, isWeakSet, join, kebabCase, last, lastIndexOf, lowerCase, lowerFirst, lt, lte, max, maxBy, mean, meanBy, min, minBy, multiply, noConflict, noop, now, nth, pad, padEnd, padStart, parseInt, pop, random, reduce, reduceRight, repeat, result, round, runInContext, sample, shift, size, snakeCase, some, sortedIndex, sortedIndexBy, sortedLastIndex, sortedLastIndexBy, startCase, startsWith, stubArray, stubFalse, stubObject, stubString, stubTrue, subtract, sum, sumBy, template, times, toFinite, toInteger, toJSON, toLength, toLower, toNumber, toSafeInteger, toString, toUpper, trim, trimEnd, trimStart, truncate, unescape, uniqueId, upperCase, upperFirst, value, and words。
(4)demo
// 隱式鏈 - 自動結束
_([1, 2, 3]).head()
// 注意:_([1, 2, 3]).head().reverse() 會報錯,因為head()已經觸發了自動結束。
// 隱式鏈 - 需手動結束
_([1, 2, 3])
.reverse()
.value();
// ---------------------------------------------
var users = [
{ 'user': 'barney', 'age': 36 },
{ 'user': 'fred', 'age': 40 }
];
// 啟用顯式鏈 方法一
_(users)
.chain()
.head()
.pick('user')
.value();
// => { 'user': 'barney' }
// 啟用顯式鏈 方法二
_.chain(users)
.head()
.pick("user")
.value();
// => { 'user': 'barney' }
(5)處理中間結果
tap
- 適合打印中間結果
thru
- 適合修改中間結果
1、tap - 直接修改值
_([1, 2, 3])
.tap(function(array) {
// 改變傳入的數組
array.pop();
})
.reverse()
.value();
// => [ 2, 1 ]
2、thru - 需返回值
_(' abc ')
.chain()
.trim()
.thru(function(value) {
return [value];
})
.value();
// => ['abc']
(6)copy 鏈式隊列(可換新值傳入)
plant
re = _.chain([1, 2, 3]).head();
re2 = re4.plant([4, 5, 6]);
re2.value();
// => 4
對於 隱式鏈 - 自動結束 的鏈式隊列,plant 會無計可施,建議轉為顯式的鏈式隊列寫法,再用plant。
四、總結
1、lodash 的優勢
(1)支持函數式編程
函數式編程(functional programming)
是一種將計算建模為表達式求值的編程風格。與命令式編程
相反,命令式編程中的程序由在執行時更改全局狀態的語句組成。函數式編程通常避免使用可變狀態,而傾向於無副作用的函數和不可變數據。
lodash 提供了 lodash/fp ,可支持函數式編程。這個文章開頭有介紹,不贅述。
類似的庫還有 Ramda。
(2)支持按需加載
這個文章開頭有介紹,不贅述。
(3)Immutable
相反叫mutable
lodash 的大多數方法都不會改變傳入參數的原始對象,只會返回一個新的對象。
但也有少部分例外是mutable的,例如:fill,pull,pullAll,pullAllBy,pullAllWith,pullAt,remove,reverse,assign,assignIn,assignInWith,assignWith,defaults,defaultsDeep,merge,mergeWith,set,setWith,unset,update,updateWith…
(4)Compose(組合)
通過 flow,這個在上文有介紹,不贅述。
(5)lazy evaluation(惰性求值)
這個在 Sqe 章節有介紹,不贅述。
提一句,flow 也支持 lazy evaluation。
(6)null-safe
各種方法普遍對 null
值容錯。
(7)不侵入原型
拓展:市面上的 js 工具庫有幾派做法:
1、支持直接在原始類的 prototype 上直接擴展,以 sugar.js 和 prototype.js 為典型;
2、嚴格禁止在 prototype 上直接擴展,以 underscore 和 lodash 為典型;
3、中間派,先判斷 prototype 是否有重名的,已經有了就不加了,以 es6-shim.js 為代表,使 ES6 能兼容於老瀏覽器。
但越來越多的實踐表明,不推薦在 prototype 上拓展函數。原因是:
1、容易沖突
跟別人或者干脆跟官方更新的函數名沖突。
最著名的例子就是上面介紹的 prototype.js 庫,在 prototype 上拓展了一個叫 getElementsByClassName 的函數,返回的是 Array,結果后來 js 官方也更新了個getElementsByClassName 函數,返回的卻是 NodeList。這就沖突了。
后來 prototype.js 被迫給出了解決方案:https://johnresig.com/blog/getelementsbyclassname-pre-prototype-16/,感興趣的可以看看。
2、性能較差
所以還是推薦使用 underscore / lodash 這樣的工具庫。
2、ES6(原生)vs lodash ?
問1:lodash 的好處,上面都提到了,那到底什么時候用原生方法?
答:建議能用原生就用原生,因為原生的性能更高。
問2:那有沒有什么方法可以快速判斷有沒有原生方法支持呢?
答:有。
方法一:安裝 eslint 插件
npm install --save-dev eslint-plugin-you-dont-need-lodash-underscore
方法二:查閱這個別人整理的挺全的文檔
https://segmentfault.com/a/1190000004460234#articleHeader48
3、容我吐槽下官方文檔
-
編排的順序是按照字母順序而不是邏輯順序
-
(有部分是上一條的原因)例子不友好,一上來就是用還沒看到的方法
-
沒有 underscore 文檔詳細(導致我這篇 blog 其實參考了 [underscore 文檔](https://www.bootcss.com/p/underscore/ 互為補充)
五、拓展
《Lodash 嚴重安全漏洞背后 你不得不知道的 JavaScript 知識》:https://zhuanlan.zhihu.com/p/74625177
這篇文章介紹了原型污染的知識,感興趣可以了解一下。