函數(shù)用于指定對(duì)象的行為。所謂的編程询筏,就是將一組需求分解為一組函數(shù)和數(shù)據(jù)結(jié)構(gòu)的技能破花。
1 函數(shù)對(duì)象
JavaScript 函數(shù)就是對(duì)象。對(duì)象是名值對(duì)的集合玉控,它還擁有一個(gè)連接到原型對(duì)象的鏈接飞主。對(duì)象字面量產(chǎn)生的對(duì)象連接到 Object.prototype,而函數(shù)對(duì)象連接到 Function.prototype(這個(gè)對(duì)象本身連接到 Object.prototype)高诺。每個(gè)函數(shù)在創(chuàng)建時(shí)會(huì)附加兩個(gè)隱藏屬性:函數(shù)的上下文以及實(shí)現(xiàn)函數(shù)的代碼碌识。
函數(shù)對(duì)象在創(chuàng)建后會(huì)有一個(gè) prototype 屬性,它的值是一個(gè)擁有 constructor 屬性虱而、且值既是該函數(shù)的對(duì)象筏餐。
因?yàn)楹瘮?shù)是對(duì)象,所以可以被當(dāng)做參數(shù)傳遞給其他函數(shù)牡拇。它也可以再返回函數(shù)魁瞪。
2 函數(shù)字面量
函數(shù)可以通過字面量進(jìn)行創(chuàng)建:
var add = function (a, b) {
return a + b;
}
這里沒有給函數(shù)命名穆律,所以稱它為匿名函數(shù)。
一個(gè)內(nèi)部函數(shù)除了可以訪問自己的參數(shù)和變量之外导俘,還可以訪問它的父函數(shù)的參數(shù)和變量众旗。通過函數(shù)字面量創(chuàng)建的函數(shù)對(duì)象包含一個(gè)連接到外部上下文的連接,這被稱為閉包趟畏。它是 JavaScript 強(qiáng)大表現(xiàn)力的來源贡歧。
3 調(diào)用
調(diào)用一個(gè)函數(shù)會(huì)暫停當(dāng)前函數(shù)的執(zhí)行,它會(huì)傳遞控制權(quán)和參數(shù)給這個(gè)被調(diào)用的函數(shù)赋秀。
當(dāng)函數(shù)的實(shí)際參數(shù)的個(gè)數(shù)與形式參數(shù)的個(gè)數(shù)不匹配時(shí)利朵,不會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤。如果實(shí)際參數(shù)的個(gè)數(shù)過多猎莲,那么超出的參數(shù)會(huì)被忽略绍弟;如果實(shí)際參數(shù)的個(gè)數(shù)過少,那么缺失的值會(huì)是 undefined著洼。不會(huì)對(duì)參數(shù)類型進(jìn)行檢查樟遣,所以任何類型的值都可以被傳遞給任何參數(shù)。
3.1 方法調(diào)用模式
當(dāng)一個(gè)函數(shù)被保存為對(duì)象的一個(gè)屬性時(shí)身笤,就稱它為方法豹悬。當(dāng)方法被調(diào)用時(shí),this 被綁定到這個(gè)對(duì)象液荸。如果調(diào)用表達(dá)式包含一個(gè)提取屬性的動(dòng)作(即包含一個(gè)"." 點(diǎn)表達(dá)式或 "[]" 下標(biāo)表達(dá)式)瞻佛,那么它就是被當(dāng)做一個(gè)方法被調(diào)用。
var myObject = {
value: 0,//屬性
increment: function (inc) {//方法
this.value += typeof inc === 'number' ? inc : 1;
}
};
myObject.increment();
console.log(myObject.value);//1
myObject.increment(2);
console.log(myObject.value);//3
這里可以使用 this 來訪問自己所屬的對(duì)象娇钱。通過 this 可以取得它們所屬對(duì)象的上下文的方法被稱為公共方法伤柄。
3.2 函數(shù)調(diào)用模式
var add = function (a, b) {
return a + b;
}
var sum = add(3, 4);//7;this 被綁定到全局對(duì)象
這里的 this 被綁定到全局對(duì)象文搂,這其實(shí)是語言設(shè)計(jì)上的失誤适刀!如果設(shè)計(jì)正確,那么當(dāng)內(nèi)部函數(shù)被調(diào)用時(shí)煤蹭,this 應(yīng)該被綁定到外部函數(shù)的 this 變量才是笔喉。可以這樣解決:為這個(gè)方法定義一個(gè)變量并給它賦值為 this疯兼,這樣內(nèi)部函數(shù)就可以通過這個(gè)變量訪問到 this 啦然遏,一般把這個(gè)變量命名為 that:
myObject.double = function () {
var that = this;//讓內(nèi)部函數(shù)可以通過這個(gè)變量訪問到 this (myObject)
var helper = function () {
that.value = add(that.value, that.value);
};
helper();//以函數(shù)形式調(diào)用 helper
};
myObject.double();//以方法形式調(diào)用 helper
console.log(myObject.value);//6
3.3 構(gòu)造器調(diào)用模式
JavaScript 是基于原型繼承的語言,所以對(duì)象可以從其他對(duì)象繼承它們的屬性吧彪。
如果在函數(shù)之前加上 new 待侵,那么 JavaScript 就會(huì)創(chuàng)建一個(gè)連接到該函數(shù)的 prototype 屬性的新對(duì)象,而 this 會(huì)綁定到這個(gè)新對(duì)象姨裸。
/**
* 構(gòu)造器調(diào)用模式(不推薦)
*/
var Quo = function (string) {//定義構(gòu)造器函數(shù)秧倾;按照約定怨酝,變量名首字母必須大寫
this.status = string;//屬性
};
/**
* 為 Quo 的所有實(shí)例提供一個(gè)名為 get_status 的公共方法
* @returns {*}
*/
Quo.prototype.get_status = function () {
return this.status;
};
var myQuo = new Quo("confused");//定義一個(gè) Quo 實(shí)例
console.log(myQuo.get_status());//"confused"
按照約定,構(gòu)造器函數(shù)被保存在以大寫字母命名的變量中那先。因?yàn)槿绻{(diào)用構(gòu)造器函數(shù)時(shí)沒有加上 new农猬,問題很大,所以才以大寫字母的命名方式讓大家記住調(diào)用時(shí)要加上 new售淡。
3.4 Apply 調(diào)用模式
因?yàn)?JavaScript 是函數(shù)式的面向?qū)ο笳Z言斤葱,所以函數(shù)可以擁有方法。
apply 方法可以構(gòu)建一個(gè)參數(shù)數(shù)組揖闸,然后再傳遞給被調(diào)用的函數(shù)揍堕。這個(gè)方法接收兩個(gè)參數(shù):要綁定給 this 的值以及參數(shù)數(shù)組。
//相加
var array = [3, 4];
var sum = add.apply(null, array);//7
console.log(sum);
//調(diào)用 Quo 的 get_status 方法汤纸,給 this 綁定 statusObject 上下文
var statusObject = {
status: 'A-OK'
};
var status = Quo.prototype.get_status.apply(statusObject);
console.log(status);//'A-OK'
4 參數(shù)
當(dāng)函數(shù)被調(diào)用時(shí)衩茸,會(huì)有一個(gè) arguments 數(shù)組。它是函數(shù)被調(diào)用時(shí)贮泞,傳遞給這個(gè)函數(shù)的參數(shù)列表楞慈,包含那些傳入的、多出來的參數(shù)啃擦∧依叮可以利用這一點(diǎn),編寫一個(gè)無須指定參數(shù)個(gè)數(shù)的函數(shù):
//構(gòu)造一個(gè)能夠接收大量參數(shù)议惰,并相加的函數(shù)
var sum = function () {
var i, sum = 0;
for (i = 0; i < arguments.length; i += 1) {
sum += arguments[i];
}
return sum;
};
console.log(sum(4, 5, 6, 7, 8, 9));//39
arguments 不是一個(gè)真正的數(shù)組慎颗,它只是一個(gè)類數(shù)組的對(duì)象,它擁有 length 屬性言询,但沒有數(shù)組的相關(guān)方法。
5 返回
return 語句可以讓函數(shù)提前返回傲宜。return 被執(zhí)行時(shí)运杭,函數(shù)會(huì)立即返回。
一個(gè)函數(shù)總會(huì)返回一個(gè)值函卒,如果沒有指定這個(gè)值辆憔,它就會(huì)返回 undefined。
如果使用 new 前綴來調(diào)用一個(gè)函數(shù)报嵌,那么它的返回值是:創(chuàng)建的一個(gè)連接到該函數(shù)的 prototype 屬性的新對(duì)象虱咧。
6 異常
異常是干擾程序正常流程的事故。發(fā)生事故時(shí)锚国,我們要拋出一個(gè)異常:
var add = function (a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw{
name: 'TypeError',
message: 'add needs numbers'
};
}
return a + b;
}
throw 語句會(huì)中斷函數(shù)的執(zhí)行腕巡,它要拋出一個(gè) exception 對(duì)象,這個(gè)對(duì)象包含一個(gè)用來識(shí)別異常類型的 name 屬性和一個(gè)描述性的 message 屬性血筑。也可以根據(jù)需要绘沉,擴(kuò)展這個(gè)對(duì)象煎楣。
這個(gè) exception 對(duì)象會(huì)被傳遞到 try 語句的 catch 從句:
var try_it = function () {
try {
add("seven");
} catch (e) {
console.log(e.name + ": " + e.message);
}
};
try_it();
一個(gè) try 語句只會(huì)有一個(gè)捕獲所有異常的 catch 從句。所以如果處理方式取決于異常的類型车伞,那么我們就必須檢查異常對(duì)象的 name 屬性择懂,來確定異常的類型。
7 擴(kuò)充類型的功能
可以給 Function.prototype 增加方法來使得這個(gè)方法對(duì)所有的函數(shù)都可用:
/**
* 為 Function.prototype 新增 method 方法
* @param name 方法名稱
* @param func 函數(shù)
* @returns {Function}
*/
Function.prototype.method = function (name, func) {
if (!this.prototype[name])//沒有該方法時(shí)另玖,才添加
this.prototype[name] = func;
return this;
};
通過這個(gè)方法困曙,我們給對(duì)象新增方法時(shí),就可以省去 prototype 字符啦O(∩_∩)O~
有時(shí)候需要提取數(shù)字中的整數(shù)部分谦去,我們可以為 Number.prototype 新增一個(gè) integer 方法:
Number.method('integer', function () {
return Math[this < 0 ? 'ceil' : 'floor'](this);
});
它會(huì)根據(jù)數(shù)字的正負(fù)來決定是使用 Math.ceiling 還是 Math.floor赂弓。
然后再為 String 添加一個(gè)移除字符串首尾空白的方法:
String.method('trim', function () {
return this.replace(/^\s+|\s+$/g, '');
});
這里使用了正則表達(dá)式。
通過為基本類型增加方法哪轿,可以極大地提高 JavaScript 的表現(xiàn)力盈魁。因?yàn)樵屠^承的動(dòng)態(tài)本質(zhì),新的方法立刻被賦予所有的對(duì)象實(shí)例上(甚至包括那些在方法被增加之前的那些對(duì)象實(shí)例)
基本類型的原型是公用的窃诉,所以在使用其他類庫時(shí)要小心杨耙。一個(gè)保險(xiǎn)的做法是:只在確定沒有該方法時(shí)才添加它。
Function.prototype.method = function (name, func) {
if (!this.prototype[name])//沒有該方法時(shí)飘痛,才添加
this.prototype[name] = func;
return this;
};
8 遞歸
遞歸函數(shù)是會(huì)直接或間接地調(diào)用自身的函數(shù)珊膜。它會(huì)把一個(gè)問題分解為一組相似的子問題,而每一個(gè)子問題都會(huì)用一個(gè)尋常的解來解決宣脉。
漢諾塔的游戲規(guī)則是:塔上有 3 根柱子和一套直徑不相同的空心圓盤车柠。開始時(shí),源柱子上的所有圓盤都是按照從小到大的順序堆疊的塑猖。每次可以移動(dòng)一個(gè)圓盤到另一個(gè)柱子竹祷,但不允許把較大的圓盤放置在嬌小的圓盤之上。最終的目標(biāo)是把一堆圓盤移動(dòng)到目標(biāo)柱子上羊苟。我們可以用遞歸解決這個(gè)問題:
/**
* 漢若塔
* @param disc 圓盤編號(hào)
* @param src 源柱子
* @param aux 輔助用的柱子
* @param dst 目的柱子
*/
var hanoi = function (disc, src, aux, dst) {
if (disc > 0) {
hanoi(disc - 1, src, dst, aux);
console.log('Move disc ' + disc + ' from ' + src + ' to ' + dst);
hanoi(disc - 1, aux, src, dst);
}
};
hanoi(3, 'Src', 'Aux', 'Dst');
這里演示了如果圓盤數(shù)為 3 的解法:
這個(gè)問題可以分解為 3 個(gè)子問題塑陵。首先移動(dòng)一對(duì)圓盤中較小的圓盤到輔助的柱子上,從而露出下面較大的圓盤蜡励;然后再移動(dòng)下面的圓盤到目標(biāo)柱子令花。最后再將較小的圓盤從輔助柱子再移動(dòng)到目標(biāo)柱子上。通過遞歸調(diào)用自身來處理圓盤的移動(dòng)凉倚,就可以解決這些子問題兼都。
上面這個(gè)函數(shù)最終會(huì)以一個(gè)不存在的圓盤編號(hào)被調(diào)用,但它不執(zhí)行任何操作稽寒,所以不會(huì)導(dǎo)致死循環(huán)扮碧。
遞歸函數(shù)可以非常高效地操作樹型結(jié)構(gòu)。比如文檔對(duì)象模型(DOM),我們可以在每次遞歸調(diào)用時(shí)處理指定樹的一小段:
/**
* 從某個(gè)節(jié)點(diǎn)開始瓦胎,按照 HTML 源碼中的順序芬萍,訪問該樹的每一個(gè)節(jié)點(diǎn)
* @param node 開始的節(jié)點(diǎn)
* @param func 被訪問到的每一個(gè)節(jié)點(diǎn)尤揣,會(huì)作為參數(shù)傳入這個(gè)函數(shù),然后這個(gè)函數(shù)被調(diào)用
*/
var walk_the_DOM = function walk(node, func) {
func(node);
node = node.firstChild;
while (node) {
walk(node, func);
node = node.nextSibling;
}
};
/**
* 查找擁有某個(gè)屬性的元素
* @param att 屬性名稱字符串
* @param value 匹配值(可選)
* @return 匹配的元素?cái)?shù)組
*/
var getElementsByAttributes = function (att, value) {
var results = [];
walk_the_DOM(document.body, function (node) {
var actual = node.nodeType === 1 && node.getAttribute(attr);
if (typeof actual === 'string' && (actual === value) || typeof value != 'string') {
results.push(node);
}
});
return results;
};
注意: 深度遞歸的函數(shù)會(huì)因?yàn)槎褩R绯龆\(yùn)行失敗柬祠。比如一個(gè)會(huì)返回自身調(diào)用函數(shù)結(jié)果的函數(shù)北戏,它被稱為尾遞歸函數(shù)。
/**
* 求階乘(帶尾遞歸的函數(shù))
*
* JavaScript 當(dāng)前沒有對(duì)尾遞歸進(jìn)行優(yōu)化漫蛔,所以如果遞歸過深會(huì)導(dǎo)致堆棧溢出
* @param i
* @param a
* @returns {*} 返回自身調(diào)用的結(jié)果
*/
var factorial = function factorial(i, a) {
a = a || 1;
if (i < 2) {
return a;
}
return factorial(i - 1, a * i);
};
console.log(factorial(4));
9 作用域
作用域控制著變量和參數(shù)的可見性以及生命周期嗜愈,它減少了名稱沖突,而且提供自動(dòng)內(nèi)存管理機(jī)制莽龟。
var foo = function () {
var a = 3, b = 5;
var bar = function () {
var b = 7, c = 11;
console.log("a:" + a + ";b:" + b + ";c:" + c);//a:3;b:7;c:11
a += b + c;
console.log("a:" + a + ";b:" + b + ";c:" + c);//a:21;b:7;c:11
};
console.log("a:" + a + ";b:" + b);//a:3;b:5
bar();
console.log("a:" + a + ";b:" + b);//a:21;b:5
};
foo();
JavaScript 支持函數(shù)作用域蠕嫁,但要注意一點(diǎn),就是在一個(gè)函數(shù)內(nèi)部的任何位置定義的變量毯盈,都這個(gè)函數(shù)的任何地方都是可見的剃毒!
注意:JavaScript 不支持塊級(jí)作用域。所以最好的做法是在函數(shù)體的頂部搂赋,聲明函數(shù)中可能會(huì)用到的所有變量赘阀。
10 閉包
作用域的好處是:內(nèi)部函數(shù)可以訪問定義它們外部函數(shù)的參數(shù)和變量(除了 this 和 arguments)。
注意:內(nèi)部函數(shù)擁有比它的外部函數(shù)更長的生命周期脑奠。
var myObject = (function () {
var value = 0;//只對(duì) increment 與 getValue 可見
return {
increment: function (inc) {
value += typeof inc === 'number' ? inc : 1;
},
getValue: function () {
return value;
}
}
}());
通過一個(gè)函數(shù)的形式來初始化 myObject 對(duì)象基公,它會(huì)返回一個(gè)對(duì)象字面量。函數(shù)定義了一個(gè) value 變量宋欺,這個(gè)變量對(duì) increment 和 getValue 方法總是可見的轰豆,但它的函數(shù)作用域卻使得這個(gè)變量對(duì)其他的代碼來說是不可見的!
**
* quo 構(gòu)造函數(shù)
* @param status 私有屬性
* @returns {{get_status: Function}}
*/
var quo = function (status) {
return {
get_status: function () {//方法
return status;
}
};
};
var myQuo = quo("amazed");
console.log(myQuo.get_status());//amazed
當(dāng)我們調(diào)用 quo 時(shí)齿诞,它會(huì)返回一個(gè)包含 get_status 方法的新對(duì)象酸休,它的引用保存在 myQuo 中。所以即使 quo 函數(shù)已經(jīng)返回了掌挚,但 get_status 方法仍然享有訪問 quo 對(duì)象的 status 屬性的特權(quán)雨席。get_status 方法訪問的可是 status 屬性本身,這就是閉包哦O(∩_∩)O~
再看一個(gè)例子:
/**
* 設(shè)置一個(gè) DOM 節(jié)點(diǎn)為黃色吠式,然后漸變?yōu)榘咨? * @param node
*/
var fade = function (node) {
var level = 1;
var step = function () {
var hex = level.toString(16);//轉(zhuǎn)換為 16 位字符
node.style.backgroundColor = '#FFFF' + hex + hex;
if (level < 15) {
level += 1;
setTimeout(step, 100);
}
};
setTimeout(step, 100);
};
fade(document.body);
fade 函數(shù)在最后一行被調(diào)用后已經(jīng)返回,但只要 fade 的內(nèi)部函數(shù)又需要抽米,它的變量就會(huì)持續(xù)保留特占。
注意:內(nèi)部函數(shù)能夠訪問外部函數(shù)的實(shí)際變量:
var add_the_handlers_error = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = function (e) {
alert(i);//綁定的是變量 i 本身,而不是函數(shù)在構(gòu)造時(shí)的變量 i 的值T迫住J悄俊!
}
}
};
add_the_handlers_error 函數(shù)的本意是:想傳遞給每個(gè)事件處理器一個(gè)唯一的 i 值标捺,但因?yàn)槭录幚砥骱瘮?shù)綁定了變量 i 本身懊纳,而不是它的值揉抵!
/**
* 給數(shù)組中的節(jié)點(diǎn)設(shè)置事件處理程序(點(diǎn)擊節(jié)點(diǎn),會(huì)彈出一個(gè)顯示節(jié)點(diǎn)序號(hào)的對(duì)話框)
* @param nodes
*/
var add_the_handlers = function (nodes) {
var helper = function (i) {//輔助函數(shù)嗤疯,綁定了當(dāng)前的 i 值
return function (e) {
alert(i);
};
};
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = helper(i);
}
};
我們在循環(huán)之外向構(gòu)造一個(gè)輔助函數(shù)冤今,讓這個(gè)函數(shù)返回一個(gè)綁定了當(dāng)前 i 值的函數(shù),這樣就可以解決問題啦O(∩_∩)O~
11 回調(diào)
假設(shè)用戶觸發(fā)了一個(gè)請(qǐng)求茂缚,瀏覽器向服務(wù)器發(fā)送這個(gè)請(qǐng)求戏罢,然后最終顯示服務(wù)器的響應(yīng)結(jié)果:
request = prepare_the_request();
response = send_request_synchronously(request);
display(response);
這種方式的問題在于,網(wǎng)絡(luò)上的同步請(qǐng)求可能會(huì)導(dǎo)致客戶端進(jìn)入假死狀態(tài)脚囊。
所以建議使用異步請(qǐng)求龟糕,并為服務(wù)端的響應(yīng)創(chuàng)建一個(gè)回調(diào)函數(shù)。這個(gè)異步請(qǐng)求函數(shù)會(huì)立即返回悔耘,這樣我們的客戶端就不會(huì)被阻塞啦:
request = prepare_the_request();
send_request_asynchronously(request, function(response){
display(response);
)};
一旦接收到服務(wù)端的響應(yīng)讲岁,傳給 send_request_asynchronously 的匿名函數(shù)就會(huì)被調(diào)用啦O(∩_∩)O~
12 模塊模式
模塊是一個(gè)提供接口但卻隱藏狀態(tài)與實(shí)現(xiàn)的函數(shù)〕囊裕可以使用函數(shù)和閉包來構(gòu)建模塊缓艳。通過函數(shù)來生成模塊,就可以不用全局變量啦泄鹏。
假設(shè)我們想給 String 增加一個(gè) deentityify 方法郎任。它會(huì)尋找字符串中的 HTML 字符,并把它們替換為對(duì)應(yīng)的字符备籽。這就需要在對(duì)象中保存字符實(shí)體的名字和它對(duì)應(yīng)的字符舶治。不能用全局變量,因?yàn)樗悄Ч沓碘∪绻x在函數(shù)內(nèi)部霉猛,那么就會(huì)帶來運(yùn)行時(shí)的損耗,因?yàn)槊看螆?zhí)行函數(shù)時(shí)珠闰,這個(gè)字面量就會(huì)被求值一次惜浅。所以理想的方式是把它放入閉包:
String.method('deentityify', function () {
//字符實(shí)體表:映射字符實(shí)體的名字到對(duì)應(yīng)的字符
var entity = {
quot: '"',
lt: '<',
gt: '>'
};
//返回 deentityify 方法
return function () {
/**
* 返回以'&'開頭 和 以';'結(jié)尾的子字符串
*/
return this.replace(/&([^$;]+);/g, function (a, b) {
var r = entity[b];//b:映射字符實(shí)體名字
return typeof r === 'string' ? r : a;//a:原始字符串
});
};
}());
注意:最后一行是 (),所以我們是立即調(diào)用剛剛創(chuàng)建的函數(shù)伏嗜!
模塊模式利用函數(shù)的作用域和閉包來創(chuàng)建被綁定對(duì)象和私有成員的關(guān)聯(lián)坛悉。這個(gè)例子中只有 deentityify 方法才有權(quán)訪問 entity (字符實(shí)體表)。
模塊模式是一個(gè)定義了私有變量和函數(shù)的函數(shù)承绸。先利用閉包創(chuàng)建一個(gè)可以訪問私有變量和函數(shù)的特權(quán)函數(shù)裸影,最后返回這個(gè)函數(shù)军熏,或者把它保存到一個(gè)可以被訪問到的地方。
模塊模式做到了信息隱藏均践,是一種優(yōu)秀的設(shè)計(jì)實(shí)踐晤锹,特別適合于封裝應(yīng)用程序或者構(gòu)造單例哦O(∩_∩)O~
模塊模式還可以產(chǎn)生安全對(duì)象。比如我們想構(gòu)造一個(gè)能夠產(chǎn)生序列號(hào)的對(duì)象:
/**
* 產(chǎn)生唯一字符串的對(duì)象(安全的對(duì)象)
* 唯一字符串由 (前綴 + 序列號(hào)) 組成
* @returns {{set_prefix: Function, set_seq: Function, gensym: Function}}
*/
var serial_number = function () {
var prefix = '';
var seq = 0;
return {
/**
* 設(shè)置前綴
* @param p
*/
set_prefix: function (p) {
prefix = String(p);
},
/**
* 設(shè)置序列號(hào)
* @param s
*/
set_seq: function (s) {
seq = s;
},
/**
* 產(chǎn)生唯一的字符串
* @returns {string}
*/
gensym: function () {
var result = prefix + seq;
seq += 1;
return result;
}
};
};
var seqer = serial_number();
seqer.set_prefix('Q');
seqer.set_seq(1000);
var unique = seqer.gensym();
console.log(unique);//Q1000
sequer 包含的方法沒有用到 this 或 that鞭铆,所以很安全葫慎。sequer 是一組函數(shù)的集合衔彻,只有那些特權(quán)函數(shù)才能夠獲取或修改私有屬性哦O(∩_∩)O~
如果把 sequer.gensym 作為值傳遞給第三方函數(shù),那么那個(gè)函數(shù)可以使用它產(chǎn)生唯一的字符串偷办,但卻不能通過這個(gè)函數(shù)改變 prefix 或 seq 的值艰额。因?yàn)檫@個(gè)函數(shù)的功能只是“產(chǎn)生唯一的字符串”呀柄沮!
13 級(jí)聯(lián)
如果我們讓某些方法返回 this废岂,那么就會(huì)啟動(dòng)級(jí)聯(lián)。在級(jí)聯(lián)中拯欧,我們可以在單條語句中依次調(diào)用同一個(gè)對(duì)象的多個(gè)方法,最著名的例子就是 jQuery 哦O(∩_∩)O~
形如:
getElement('myDiv')
.move(350,150)
.width(100);
級(jí)聯(lián)可以產(chǎn)生出極富表現(xiàn)力的接口镐作。
14 柯里化
柯里化指的是:把函數(shù)與傳遞給它的參數(shù)結(jié)合该贾,產(chǎn)生出新的函數(shù):
Function.method('curry', function () {
var slice = Array.prototype.slice,
args = slice.apply(arguments),//創(chuàng)建一個(gè)真正的數(shù)組
that = this;
return function () {
return that.apply(null, args.concat(slice.apply(arguments)));
};
});
var add1 = add.curry(1);
console.log(add1(6));//7
因?yàn)?arguments 不是真正的數(shù)組捌臊,所以沒有 concat 方法,因此我們使用 slice 創(chuàng)建出了真正的數(shù)組逞力。
15 記憶
有時(shí)候可以把先前計(jì)算過的結(jié)果記錄在某個(gè)對(duì)象中糠爬,避免重復(fù)計(jì)算。
假設(shè)我們想通過遞歸來計(jì)算 Fibonacci 數(shù)列。一個(gè) Fibonacci 數(shù)字是之前的兩個(gè) Fibonacci 數(shù)字之和殴玛,最前面的兩個(gè)數(shù)字是 0 和 1:
var fiboncci = function () {
var memo = [0, 1];//存儲(chǔ)結(jié)果(已經(jīng)計(jì)算過的值)
var fib = function (n) {
var result = memo[n];
if (typeof result !== 'number') {
result = fib(n - 1) + fib(n - 2);
memo[n] = result;
}
return result;
};
return fib;
}();
for (var i = 0; i <= 10; i += 1) {
console.log(i + ": " + fiboncci(i));
}
我們把計(jì)算結(jié)果存儲(chǔ)在 memo 數(shù)組中,它被隱藏在閉包里寻仗。函數(shù)被調(diào)用時(shí)凡壤,它會(huì)先檢查計(jì)算結(jié)果是否已存在,如果存在就立即返回曹体。
我們對(duì)這個(gè)例子進(jìn)行擴(kuò)展硝烂,構(gòu)造一個(gè)帶記憶功能的函數(shù):
/**
* 帶記憶功能的函數(shù)
* @param memo 初始的 memo 數(shù)組
* @param formula 公式函數(shù)
* @returns {Function}
*/
var memoizer = function (memo, formula) {
var recur = function (n) {
var result = memo[n];
if (typeof result != 'number') {
result = formula(recur, n);
memo[n] = result;
}
return result;
};
return recur;
};
這個(gè)函數(shù)會(huì)返回一個(gè)可以管理 meno 存儲(chǔ)參數(shù)和在需要時(shí)調(diào)用 formula 函數(shù)的 recur 函數(shù)滞谢。recur 函數(shù)和它的參數(shù)會(huì)被傳遞給 formula 函數(shù)(就是公式)。是不是感覺有點(diǎn)繞母截,我們來看看實(shí)例就會(huì)清楚啦橄教。
現(xiàn)在我們使用 memoizer 函數(shù)來重新定義 fibonacci 函數(shù):
/**
* 斐波那契
* @type {Function}
*/
var fibonacci = memoizer([0, 1], function (recur, n) {
return recur(n - 1) + recur(n - 2);
});
console.log(fibonacci(10));//55
這樣清楚了吧颤陶,使用這個(gè)函數(shù)可以極大地減少我們的工作量哦,比如下面的這個(gè)階乘函數(shù):
/**
* 階乘
* @type {Function}
*/
var factorial = memoizer([1, 1], function (recur, n) {
return n * recur(n - 1);
});
console.log(factorial(10));//3628800