Javascript學(xué)習(xí)筆記-underscore

JavaScript是函數(shù)式編程語(yǔ)言,支持高階函數(shù)和閉包。你會(huì)發(fā)現(xiàn)Array有map()和filter()方法,而Object沒(méi)有這些方法,那該如何解決呢?

  1. 自己把這些方法添加到Array.prototype中,然后給Object.prototype也加上mapObject()等類(lèi)似的方法。
  2. 直接找一個(gè)成熟可靠的第三方開(kāi)源庫(kù),使用統(tǒng)一的函數(shù)來(lái)實(shí)現(xiàn)map()、filter()這些操作,比如underscore。

正如jQuery統(tǒng)一了不同瀏覽器之間DOM操作的差異,讓我們可以簡(jiǎn)單地對(duì)DOM進(jìn)行操作,underscore則提供了一套完善的函數(shù)式編程的接口,讓我們更方便地在JavaScript中實(shí)現(xiàn)函數(shù)式編程。jQuery在加載時(shí)會(huì)把自身綁定到唯一的全局變量$上,underscore與其類(lèi)似也會(huì)把自身綁定到唯一的全局變量_上,這也是為啥它叫underscore的原因。
用underscore實(shí)現(xiàn)map()操作如下:

// 數(shù)組
_.map([1, 2, 3], (x) => x * x); // [1, 4, 9]
// 對(duì)象
_.map({ a: 1, b: 2, c: 3 }, (v, k) => k + '=' + v); // ['a=1', 'b=2', 'c=3']

Collection

underscore為集合類(lèi)對(duì)象(Array和Object,不支持Map和Set)提供了一致的接口。

map/filter

與Array的map()與filter()類(lèi)似,此外underscore的map()和filter()可以作用于Object,通過(guò)調(diào)用函數(shù)function (value, key),第一個(gè)參數(shù)接收value,第二個(gè)參數(shù)接收key:

var obj = {
    name: 'bob',
    school: 'No.1 middle school',
    address: 'xueyuan road'
};

var upper = _.map(obj, function (value, key) {
    return ???;
});

alert(JSON.stringify(upper));

對(duì)Object進(jìn)行map()操作返回的是Array,如果想要返回Object必須用_.mapObject。

every / some

當(dāng)集合的所有元素都滿足條件時(shí),.every()函數(shù)返回true,當(dāng)集合中至少有一個(gè)元素滿足條件時(shí),.some()函數(shù)返回true:

// 所有元素都大于0?
_.every([1, 4, 7, -3, -9], (x) => x > 0); // false
// 至少一個(gè)元素大于0?
_.some([1, 4, 7, -3, -9], (x) => x > 0); // true
// 當(dāng)集合是Object時(shí),我們可以同時(shí)獲得value和key:
var obj = {
    name: 'bob',
    school: 'No.1 middle school',
    address: 'xueyuan road'
};
// 判斷key和value是否全部是小寫(xiě):
var r1 = _.every(obj, function (value, key) {
    return ???;
});
var r2 = _.some(obj, function (value, key) {
    return ???;
});

alert('every key-value are lowercase: ' + r1 + '\nsome key-value are lowercase: ' + r2);
max / min

這兩個(gè)函數(shù)直接返回集合中最大和最小的數(shù):

var arr = [3, 5, 7, 9];
_.max(arr); // 9
_.min(arr); // 3

// 空集合會(huì)返回-Infinity和Infinity,所以要先判斷集合不為空:
_.max([])
-Infinity
_.min([])
Infinity
// 如果集合是Object,max()和min()只作用于value,忽略掉key:
_.max({ a: 1, b: 2, c: 3 }); // 3
groupBy

groupBy()把集合的元素按照key歸類(lèi),key由傳入的函數(shù)返回:

var scores = [20, 81, 75, 40, 91, 59, 77, 66, 72, 88, 99];
var groups = _.groupBy(scores, function (x) {
    if (x < 60) {
        return 'C';
    } else if (x < 80) {
        return 'B';
    } else {
        return 'A';
    }
});
// 結(jié)果:
// {
//   A: [81, 91, 88, 99],
//   B: [75, 77, 66, 72],
//   C: [20, 40, 59]
// }
shuffle / sample

shuffle()用洗牌算法隨機(jī)打亂一個(gè)集合:

// 注意每次結(jié)果都不一樣:
_.shuffle([1, 2, 3, 4, 5, 6]); // [3, 5, 4, 6, 2, 1]

sample()則是隨機(jī)選擇一個(gè)或多個(gè)元素,但每次結(jié)果都不一樣:

// 隨機(jī)選1個(gè):
_.sample([1, 2, 3, 4, 5, 6]); // 2
// 隨機(jī)選3個(gè):
_.sample([1, 2, 3, 4, 5, 6], 3); // [6, 1, 4]

Array

underscore為Array提供了許多工具類(lèi)方法,可以更方便快捷地操作Array。

first / last

顧名思義,這兩個(gè)函數(shù)分別取第一個(gè)和最后一個(gè)元素。

flatten

flatten()接收一個(gè)Array,無(wú)論這個(gè)Array里面嵌套了多少個(gè)Array,最后都會(huì)成為一個(gè)一維數(shù)組:

_.flatten([1, [2], [3, [[4], [5]]]]); // [1, 2, 3, 4, 5]
zip / unzip

zip()把兩個(gè)或多個(gè)數(shù)組的所有元素按索引對(duì)齊,然后按索引合并成新數(shù)組。例如,你有一個(gè)Array保存了名字,另一個(gè)Array保存了分?jǐn)?shù),現(xiàn)在,要把名字和分?jǐn)?shù)給對(duì)上,用zip()輕松實(shí)現(xiàn):

var names = ['Adam', 'Lisa', 'Bart'];
var scores = [85, 92, 59];
_.zip(names, scores);
// [['Adam', 85], ['Lisa', 92], ['Bart', 59]]

unzip()則是反過(guò)來(lái):

var namesAndScores = [['Adam', 85], ['Lisa', 92], ['Bart', 59]];
_.unzip(namesAndScores);
// [['Adam', 'Lisa', 'Bart'], [85, 92, 59]]
object

如果要想zip()那樣處理返回一個(gè)Object就要用object()函數(shù)了:

var names = ['Adam', 'Lisa', 'Bart'];
var scores = [85, 92, 59];
_.object(names, scores);
// {Adam: 85, Lisa: 92, Bart: 59}
range

range()讓你快速生成一個(gè)序列,不再需要用for循環(huán)實(shí)現(xiàn)了:

// 從0開(kāi)始小于10:
_.range(10); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

// 從1開(kāi)始小于11:
_.range(1, 11); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

// 從0開(kāi)始小于30,步長(zhǎng)5:
_.range(0, 30, 5); // [0, 5, 10, 15, 20, 25]

// 從0開(kāi)始大于-10,步長(zhǎng)-1:
_.range(0, -10, -1); // [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]

Function

為了充分發(fā)揮JavaScript的函數(shù)式編程特性,underscore也提供了大量JavaScript本身沒(méi)有的高階函數(shù)。

bind

bind()可以把系統(tǒng)方法與我們自己定義的方法綁定在一起,舉例如下:

// 輸出Hello, world!
var log = console.log;
// 調(diào)用call并傳入console對(duì)象作為this:
log.call(console, 'Hello, world!')

// 使用bind()
var log = _.bind(console.log, console);
log('Hello, world!');

// 錯(cuò)誤寫(xiě)法
console.log('Hello, world!');
var log = console.log;
log('Hello, world!');
partial

partial()就是為一個(gè)函數(shù)創(chuàng)建偏函數(shù),偏函數(shù)的作用可以通過(guò)例子來(lái)說(shuō)明:
假設(shè)我們要計(jì)算xy,這時(shí)只需要調(diào)用Math.pow(x, y)就可以了。但如果要經(jīng)常計(jì)算2y,每次都寫(xiě)Math.pow(2, y)就比較麻煩,如果創(chuàng)建一個(gè)新的函數(shù)能直接這樣寫(xiě)pow2N(y)就好了,這個(gè)新函數(shù)pow2N(y)就是根據(jù)Math.pow(x, y)創(chuàng)建出來(lái)的偏函數(shù),它固定住了原函數(shù)的第一個(gè)參數(shù)(始終為2):

// 固定第一個(gè)參數(shù)
var pow2N = _.partial(Math.pow, 2);
pow2N(3); // 8
pow2N(5); // 32
pow2N(10); // 1024

// 固定第二個(gè)參數(shù)
var cube = _.partial(Math.pow, _, 3);
cube(3); // 27
cube(5); // 125
cube(10); // 1000
memoize

如果調(diào)用一個(gè)函數(shù)耗時(shí)較長(zhǎng),我們可能就希望能把結(jié)果緩存下來(lái),以便后續(xù)使用。比如說(shuō)計(jì)算階乘就比較耗時(shí):

function factorial(n) {
    console.log('start calculate ' + n + '!...');
    var s = 1, i = n;
    while (i > 1) {
        s = s * i;
        i --;
    }
    console.log(n + '! = ' + s);
    return s;
}

factorial(10); // 3628800
// 注意控制臺(tái)輸出:
// start calculate 10!...
// 10! = 3628800

// 用memoize()就可以自動(dòng)緩存函數(shù)計(jì)算的結(jié)果:
var factorial = _.memoize(function(n) {
    console.log('start calculate ' + n + '!...');
    var s = 1, i = n;
    while (i > 1) {
        s = s * i;
        i --;
    }
    console.log(n + '! = ' + s);
    return s;
});

// 第一次調(diào)用:
factorial(10); // 3628800
// 注意控制臺(tái)輸出:
// start calculate 10!...
// 10! = 3628800

// 第二次調(diào)用:
factorial(10); // 3628800
// 控制臺(tái)沒(méi)有輸出

對(duì)于相同的調(diào)用,比如連續(xù)兩次調(diào)用factorial(10),第二次調(diào)用并沒(méi)有計(jì)算,而是直接返回上次計(jì)算后緩存的結(jié)果。不過(guò),當(dāng)你計(jì)算factorial(9)的時(shí)候,仍然會(huì)重新計(jì)算。

// 對(duì)factorial()進(jìn)行遞歸調(diào)用:
var factorial = _.memoize(function(n) {
    console.log('start calculate ' + n + '!...');
    if (n < 2) {
        return 1;
    }
    return n * factorial(n - 1);
});

factorial(10); // 3628800
// 輸出結(jié)果說(shuō)明factorial(1)~factorial(10)都已經(jīng)緩存了:
// start calculate 10!...
// start calculate 9!...
// start calculate 8!...
// start calculate 7!...
// start calculate 6!...
// start calculate 5!...
// start calculate 4!...
// start calculate 3!...
// start calculate 2!...
// start calculate 1!...

factorial(9); // 362880
// console無(wú)輸出
once

顧名思義,once()保證某個(gè)函數(shù)執(zhí)行且僅執(zhí)行一次。如果你有一個(gè)方法叫register(),用戶在頁(yè)面上點(diǎn)兩個(gè)按鈕的任何一個(gè)都可以執(zhí)行的話,就可以用once()保證函數(shù)僅調(diào)用一次,無(wú)論用戶點(diǎn)擊多少次:

var register = _.once(function () {
    alert('Register ok!');
});

// 測(cè)試效果:
register();
register();
register();
delay

delay()可以讓一個(gè)函數(shù)延遲執(zhí)行,效果和setTimeout()是一樣的,但是代碼明顯簡(jiǎn)單了:

// 2秒后調(diào)用alert():
_.delay(alert, 2000);
// 如果要延遲調(diào)用的函數(shù)有參數(shù),把參數(shù)也傳進(jìn)去
var log = _.bind(console.log, console);
_.delay(log, 2000, 'Hello,', 'world!');
// 2秒后打印'Hello, world!':

Object

和Array類(lèi)似,underscore也提供了大量針對(duì)Object的函數(shù)。

keys / allKeys

keys()可以非常方便地返回一個(gè)object自身所有的key,但不包含從原型鏈繼承下來(lái)的:

function Student(name, age) {
    this.name = name;
    this.age = age;
}

var xiaoming = new Student('小明', 20);
_.keys(xiaoming); // ['name', 'age']

allKeys()除了object自身的key,還包含從原型鏈繼承下來(lái)的:

function Student(name, age) {
    this.name = name;
    this.age = age;
}
Student.prototype.school = 'No.1 Middle School';
var xiaoming = new Student('小明', 20);
_.allKeys(xiaoming); // ['name', 'age', 'school']
values

和keys()類(lèi)似,values()返回object自身但不包含原型鏈繼承的所有值,但沒(méi)有allValues():

var obj = {
    name: '小明',
    age: 20
};

_.values(obj); // ['小明', 20]
mapObject

mapObject()就是針對(duì)object的map()版本:

var obj = { a: 1, b: 2, c: 3 };
// 注意傳入的函數(shù)簽名,value在前,key在后:
_.mapObject(obj, (v, k) => 100 + v); // { a: 101, b: 102, c: 103 }
invert

invert()把object的每個(gè)key-value來(lái)個(gè)交換,key變成value,value變成key:

var obj = {
    Adam: 90,
    Lisa: 85,
    Bart: 59
};
_.invert(obj); // { '59': 'Bart', '85': 'Lisa', '90': 'Adam' }
extend / extendOwn

extend()把多個(gè)object的key-value合并到第一個(gè)object并返回:

var a = {name: 'Bob', age: 20};
_.extend(a, {age: 15}, {age: 88, city: 'Beijing'}); // {name: 'Bob', age: 88, city: 'Beijing'}
// 變量a的內(nèi)容也改變了:
a; // {name: 'Bob', age: 88, city: 'Beijing'}

注意:如果有相同的key,后面的object的value將覆蓋前面的object的value。extendOwn()和extend()類(lèi)似,但獲取屬性時(shí)忽略從原型鏈繼承下來(lái)的屬性。

clone

如果我們要復(fù)制一個(gè)object對(duì)象,就可以用clone()方法,它會(huì)把原有對(duì)象的所有屬性都復(fù)制到新的對(duì)象中:

var source = {
    name: '小明',
    age: 20,
    skills: ['JavaScript', 'CSS', 'HTML']
};

var copied = _.clone(source);
alert(JSON.stringify(copied, null, '  '));

注意,clone()是“淺復(fù)制”,兩個(gè)對(duì)象相同的key所引用的value其實(shí)是同一對(duì)象:
source.skills === copied.skills; // true也就是說(shuō)修改source.skills會(huì)影響copied.skills。

isEqual

isEqual()對(duì)兩個(gè)object進(jìn)行深度比較,如果內(nèi)容完全相同,則返回true:

var o1 = { name: 'Bob', skills: { Java: 90, JavaScript: 99 }};
var o2 = { name: 'Bob', skills: { JavaScript: 99, Java: 90 }};

o1 === o2; // false
_.isEqual(o1, o2); // true

isEqual()其實(shí)對(duì)Array也可以比較:

var o1 = ['Bob', { skills: ['Java', 'JavaScript'] }];
var o2 = ['Bob', { skills: ['Java', 'JavaScript'] }];

o1 === o2; // false
_.isEqual(o1, o2); // true

Chaining

underscore提供了把對(duì)象包裝成能進(jìn)行鏈?zhǔn)秸{(diào)用的方法,就是chain()函數(shù):

_.chain([1, 4, 9, 16, 25])
 .map(Math.sqrt)
 .filter(x => x % 2 === 1)
 .value();
// [1, 3, 5]

因?yàn)槊恳徊椒祷氐亩际前b對(duì)象,所以最后一步的結(jié)果需要調(diào)用value()獲得最終結(jié)果。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,772評(píng)論 3 422
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,947評(píng)論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,201評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,960評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,350評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,549評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,104評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,914評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,089評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,340評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,753評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,007評(píng)論 1 289
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,834評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,106評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容