第三章 函數(shù)是根基
函數(shù)的獨(dú)特之處
函數(shù)是第一型(first-class)對(duì)象
對(duì)象在 javascript 中有如下功能:
- 可以通過字面量進(jìn)行創(chuàng)建汗洒。
- 可以賦值給變量、數(shù)組或其他對(duì)象的屬性吵瞻。
- 可以作為參數(shù)傳遞給函數(shù)。
- 可以作為函數(shù)的返回值進(jìn)行返回甘磨。
- 可以擁有動(dòng)態(tài)創(chuàng)建并賦值的屬性橡羞。
在 javascript 中,函數(shù)擁有全部這些功能济舆。除了可以像其它對(duì)象類型一樣使用外卿泽,函數(shù)還可以被調(diào)用。這些調(diào)用通常以異步方式進(jìn)行滋觉。
瀏覽器的事件輪詢
桌面應(yīng)用程序(GUI)大多采用如下方式:
- 創(chuàng)建用戶界面签夭。
- 進(jìn)入輪詢,等待事件觸發(fā)椎侠。
- 調(diào)用事件的處理程序(監(jiān)聽器[listener])第租。
瀏覽器編程唯一的不同就是:代碼不負(fù)責(zé)事件輪詢和事件派發(fā),而是瀏覽器處理我纪。
我們的職責(zé)是為瀏覽器中發(fā)生的各種時(shí)間建立事件的處理程序(handler)慎宾。這些事件在觸發(fā)時(shí)被放置在一個(gè)事件隊(duì)列(先進(jìn)先出列表[FIFO])中,然后瀏覽器將調(diào)用已經(jīng)為這些事件建立好的處理程序(handler)浅悉。因?yàn)檫@些事件發(fā)生的時(shí)間和順序都是不可預(yù)知的趟据,所以事件處理函數(shù)的調(diào)用也是異步的。
瀏覽器的事件輪詢是單線程的术健。每個(gè)事件都是按照在隊(duì)列中所放置的順序來處理的汹碱。這就是所謂的FIFO(先進(jìn)先出)列表,或者一個(gè)使用古老定時(shí)器的筒倉(silo)荞估。每個(gè)事件都在自己的生命周期內(nèi)進(jìn)行處理咳促,所有其他事件必須等到這個(gè)事件處理結(jié)束后才能繼續(xù)處理。執(zhí)行過程如圖所示:
瀏覽器把事件放到隊(duì)列上的機(jī)制是在事件輪詢模型之外跪腹。確定事件何時(shí)發(fā)生并把它們放到事件隊(duì)列上的過程所處的線程,并不參與事件本身的處理娇昙。
回調(diào)的概念
定義一個(gè)函數(shù)尺迂,以便其它一些代碼在適當(dāng)?shù)臅r(shí)候調(diào)用它。
函數(shù)聲明
聲明一個(gè)函數(shù)時(shí),該名稱在整個(gè)函數(shù)聲明范圍內(nèi)時(shí)有效的噪裕。此外蹲盘,如果一個(gè)命名函數(shù)聲明在頂層,window 對(duì)象上的同名屬性則會(huì)引用到該函數(shù)膳音。
所有的函數(shù)都有一個(gè) name
屬性召衔,該屬性保存的是該函數(shù)名稱的字符串。沒有名稱的函數(shù)也仍然有 name
屬性祭陷,只是該屬性值為空字符串苍凛。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>證明函數(shù)聲明相關(guān)內(nèi)容</title>
<style>
li.pass{color: green;}
li.fail{color: red;}
</style>
</head>
<body>
<ul id="results"></ul>
<script>
// 定義assert方法
function assert(value,desc){
var li = document.createElement('li');
li.className = value ? "pass" : "fail";
li.appendChild(document.createTextNode(desc));
document.getElementById("results").appendChild(li);
}
// 聲明一個(gè)命名函數(shù),該名稱在當(dāng)前作用域有效兵志,并隱式在window上添加一個(gè)同名屬性
function isNimble(){return true;}
// 判斷window屬性是確定的
assert(typeof window.isNimble === "function", "isNimble() defined");
// 判斷函數(shù)的 name 屬性
assert(isNimble.name === "isNimble", "isNimble() has a name");
// 創(chuàng)建一個(gè)匿名函數(shù)醇蝴,并賦值給canFly變量
var canFly = function(){return true;};
assert(typeof window.canFly === "function", "canFly() defined");
assert(canFly.name === "", "canFly() has no name");
// 創(chuàng)建一個(gè)匿名函數(shù)并引用到window的一個(gè)屬性上
window.isDeadly = function(){return true;};
assert(typeof window.isDeadly === "function", "isDeadly() defined");
// 在outer函數(shù)內(nèi)定義一個(gè)inner函數(shù),測(cè)試該inner()在其定義之前和之后都可以訪問到想罕,并且沒有創(chuàng)建全局的inner()
function outer(){
assert(typeof inner === "function", "inner() in scope before declaration");
function inner(){}
assert(typeof inner === "function", "inner() in scope after declaration");
assert(window.inner === undefined, "inner() not in global scope");
}
// outer()可以在全局作用域內(nèi)訪問到悠栓,而inner()則不可以
outer();
assert(window.inner === undefined, "inner() still not in global scope");
// 這里聲明的函數(shù)名無效,真正起到控制作用的是變量名
window.wieldsSword = function swingsSword(){return true;};
assert(window.wieldsSword.name === 'swingsSword', "wieldsSword's real name is swingsSword");
</script>
</body>
</html>
作用域和函數(shù)
在 JavaScript 中按价,作用域是由 function 進(jìn)行聲明的惭适,而不是代碼塊。聲明的作用域創(chuàng)建于代碼塊楼镐,但不是終結(jié)于代碼塊癞志。
- 變量聲明的作用域開始于聲明的地方,結(jié)束于所在函數(shù)的結(jié)尾框产,于代碼嵌套無關(guān)凄杯。
- 命名函數(shù)的作用域是指聲明該函數(shù)的整個(gè)函數(shù)范圍,于代碼嵌套無關(guān)茅信。(機(jī)制提升)
- 對(duì)于作用域聲明盾舌,全局上下文就像一個(gè)包含頁面所有代碼的超大型函數(shù)。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>作用域斷言測(cè)試</title>
<style>
li.pass{color: green;}
li.fail{color: red;}
</style>
</head>
<body>
<ul id="results"></ul>
<script>
// 定義assert方法
function assert(value,desc){
var li = document.createElement('li');
li.className = value ? "pass" : "fail";
li.appendChild(document.createTextNode(desc));
document.getElementById("results").appendChild(li);
}
assert(true,"|------BEFORE OUTER ------|")
assert(typeof outer == 'function', "outer() is in scope");
assert(typeof inner === 'function', "inner() is scope");
assert(typeof a === 'number', "a is in scope");
assert(typeof b === 'number', "b is in scope");
assert(typeof c === 'number', "c is in scope");
assert(typeof d === 'number', "d is not in scope");
assert(typeof e === 'number', "e is not in scope");
function outer(){
assert(true,"|------ INSIDE OUTER,BEFORE a ------|")
assert(typeof outer == 'function', "outer() is in scope");
assert(typeof inner === 'function', "inner() is scope");
assert(typeof a === 'number', "a is in scope");
assert(typeof b === 'number', "b is in scope");
assert(typeof c === 'number', "c is in scope");
assert(typeof d === 'number', "d is not in scope");
assert(typeof e === 'number', "e is not in scope");
var a = 1;
assert(true,"|------ INSIDE OUTER,AFTER a ------|")
assert(typeof outer == 'function', "outer() is in scope");
assert(typeof inner === 'function', "inner() is scope");
assert(typeof a === 'number', "a is in scope");
assert(typeof b === 'number', "b is in scope");
assert(typeof c === 'number', "c is in scope");
assert(typeof d === 'number', "d is not in scope");
assert(typeof e === 'number', "e is not in scope");
function inner(){/******/}
var b = 2;
assert(true,"|------ INSIDE OUTER,AFTER inner() AND b ------|")
assert(typeof outer == 'function', "outer() is in scope");
assert(typeof inner === 'function', "inner() is scope");
assert(typeof a === 'number', "a is in scope");
assert(typeof b === 'number', "b is in scope");
assert(typeof c === 'number', "c is in scope");
assert(typeof d === 'number', "d is not in scope");
assert(typeof e === 'number', "e is not in scope");
if(a == 1){
assert(true,"|------ INSIDE OUTER,INSIDE if AND INSIDE if ------|")
assert(typeof outer == 'function', "outer() is in scope");
assert(typeof inner === 'function', "inner() is scope");
assert(typeof a === 'number', "a is in scope");
assert(typeof b === 'number', "b is in scope");
assert(typeof c === 'number', "c is in scope"); // 這里c 被初始化為undefined蘸鲸,所有斷言會(huì)失敗
var c = 3;
// 不能再let const 聲明之前調(diào)用 assert 會(huì)報(bào)錯(cuò)
let d=4; // 添加 ES6 的let
const e=5; // 添加 ES6 的const
assert(true,"|------ INSIDE OUTER,INSIDE if AND AFTER let const ------|")
assert(typeof outer == 'function', "outer() is in scope");
assert(typeof inner === 'function', "inner() is scope");
assert(typeof a === 'number', "a is in scope");
assert(typeof b === 'number', "b is in scope");
assert(typeof c === 'number', "c is in scope");
assert(typeof d === 'number', "d is not in scope");
assert(typeof e === 'number', "e is not in scope");
}
assert(true,"|------ INSIDE OUTER,OUTSIDE if ------|")
assert(typeof outer == 'function', "outer() is in scope");
assert(typeof inner === 'function', "inner() is scope");
assert(typeof a === 'number', "a is in scope");
assert(typeof b === 'number', "b is in scope");
assert(typeof c === 'number', "c is in scope");
assert(typeof d === 'number', "d is not in scope");
assert(typeof e === 'number', "e is not in scope");
}
outer();
assert(true,"|------ INSIDE OUTER,OUTSIDE if ------|")
assert(typeof outer == 'function', "outer() is in scope");
assert(typeof inner === 'function', "inner() is scope");
assert(typeof a === 'number', "a is in scope");
assert(typeof b === 'number', "b is in scope");
assert(typeof c === 'number', "c is in scope");
assert(typeof d === 'number', "d is not in scope");
assert(typeof e === 'number', "e is not in scope");
</script>
</body>
</html>
函數(shù)調(diào)用
有四種不同的方式進(jìn)行函數(shù)調(diào)用,每種方式都有細(xì)微的差別(主要區(qū)別在于如何定義每種調(diào)用類型的this
):
- 作為一個(gè)函數(shù)進(jìn)行調(diào)用窿锉,是最簡(jiǎn)單的形式酌摇。
- 作為一個(gè)方法進(jìn)行調(diào)用,在對(duì)象上進(jìn)行調(diào)用嗡载,支持面向?qū)ο缶幊獭?/li>
- 作為構(gòu)造器進(jìn)行調(diào)用窑多,創(chuàng)建一個(gè)新對(duì)象。
- 通過
apply()
或call()
方法進(jìn)行調(diào)用洼滚。
從參數(shù)到函數(shù)形參
- 如果實(shí)際傳遞的參數(shù)數(shù)量大于函數(shù)聲明的形參數(shù)量埂息,超出的參數(shù)不會(huì)配給形參。
- 如果聲明的形參數(shù)量大于實(shí)際傳遞的參數(shù)數(shù)量,沒有對(duì)應(yīng)參數(shù)的形參會(huì)賦值為undefined千康。
所有的函數(shù)調(diào)用都會(huì)傳遞兩個(gè)隱式參數(shù):arguments 和 this享幽。
-
arguments
:是傳遞給函數(shù)的所有參數(shù)的一個(gè)集合,該集合有一個(gè)length
屬性拾弃。arguments 不是JS的數(shù)組值桩,不能使用數(shù)組方法。 -
this
:this
參數(shù)引用了與該函數(shù)調(diào)用進(jìn)行隱式關(guān)聯(lián)的一個(gè)對(duì)象豪椿,被稱為函數(shù)上下文奔坟。它依賴于函數(shù)的調(diào)用方式,因此搭盾,this
又稱為調(diào)用上下文咳秉。
下面的代碼中展示了作為函數(shù)調(diào)用和作為方法調(diào)用的區(qū)別:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>函數(shù)調(diào)用和方法調(diào)用的區(qū)別</title>
<style>
/*定義結(jié)果樣式*/
li.pass{color: green;}
li.fail{color: red;}
</style>
</head>
<body>
<!--顯示測(cè)試結(jié)果-->
<ul id="results"></ul>
<script>
// 定義assert方法
function assert(value,desc){
var li = document.createElement('li');
li.className = value ? "pass" : "fail";
li.appendChild(document.createTextNode(desc));
document.getElementById("results").appendChild(li);
}
function creep(){return this;}
assert(creep() === window, "Creeping in the window");
var sneak = creep; // 創(chuàng)建變量引用creep
assert(sneak() === window, "Sneaking in the window");
var ninja1 = {
skulk: creep // 創(chuàng)建屬性引用creep
};
assert(ninja1.skulk() === ninja1, "The 1st ninja is skulking");
var ninja2 = {
skulk: creep
};
assert(ninja2.skulk() === ninja2, "The 2nd ninja is skulking");
</script>
</body>
</html>
將函數(shù)作為構(gòu)造器(constructor)進(jìn)行調(diào)用,要在函數(shù)調(diào)用前使用 new
關(guān)鍵字鸯隅。構(gòu)造器調(diào)用時(shí)澜建,會(huì)發(fā)生如下特殊行為:
- 創(chuàng)建一個(gè)新的空對(duì)象。
- 傳遞給構(gòu)造器的對(duì)象時(shí)
this
參數(shù)滋迈,從而成為構(gòu)造器的函數(shù)上下文霎奢。 - 如果沒有顯示的返回值,新創(chuàng)建的對(duì)象則作為構(gòu)造器的返回值進(jìn)行返回饼灿。
構(gòu)造器的目的是創(chuàng)建一個(gè)新對(duì)象并對(duì)其進(jìn)行設(shè)置幕侠,然后將其作為構(gòu)造器的返回值進(jìn)行返回。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>使用構(gòu)造器設(shè)置通用對(duì)象</title>
<style>
/*定義結(jié)果樣式*/
li.pass{color: green;}
li.fail{color: red;}
</style>
</head>
<body>
<!--顯示測(cè)試結(jié)果-->
<ul id="results"></ul>
<script>
// 定義assert方法
function assert(value,desc){
var li = document.createElement('li');
li.className = value ? "pass" : "fail";
li.appendChild(document.createTextNode(desc));
document.getElementById("results").appendChild(li);
}
// 聲明一個(gè)構(gòu)造器碍彭,在函數(shù)上下文對(duì)象上創(chuàng)建一個(gè)skulk屬性晤硕。該屬性方法又返回了上下文自身
function Ninja(){
this.skulk = function(){return this;};
}
var ninja1 = new Ninja();
var ninja2 = new Ninja();
assert(ninja1.skulk() === ninja1, "The 1st ninja is skulking");
assert(ninja2.skulk() === ninja2, "The 1st ninja is skulking");
</script>
</body>
</html>
函數(shù)和方法的命名通常以動(dòng)詞開頭,來描述它們所做的事情庇忌,并以小寫字母開頭舞箍。而構(gòu)造器的命名通常是由一個(gè)描述所構(gòu)造對(duì)象的名詞來命名,并以大寫字母開頭皆疹。
JavaScript 的每個(gè)函數(shù)都有 apply()
和call()
方法疏橄,使用任何一個(gè)方法,都可以顯式指定任何一個(gè)對(duì)象作為其函數(shù)上下文略就。
函數(shù)作為第一型對(duì)象捎迫,可以向其它任何類型的對(duì)象一樣,擁有屬性和方法表牢。
-
apply()
:接收兩個(gè)參數(shù)窄绒,一個(gè)是作為函數(shù)上下文的對(duì)象,另一個(gè)是作為函數(shù)參數(shù)所組成的數(shù)組崔兴。例如:f.apply(o,[1,2,3]);
-
call()
:接收的參數(shù)包括作為函數(shù)上下文的對(duì)象和一個(gè)參數(shù)列表而不是單個(gè)數(shù)組彰导。例如:f.call(o,1,2,3);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>使用apply()和call()指定函數(shù)上下文</title>
<style>
/*定義結(jié)果樣式*/
li.pass{color: green;}
li.fail{color: red;}
</style>
</head>
<body>
<!--顯示測(cè)試結(jié)果-->
<ul id="results"></ul>
<script>
// 定義assert方法
function assert(value,desc){
var li = document.createElement('li');
li.className = value ? "pass" : "fail";
li.appendChild(document.createTextNode(desc));
document.getElementById("results").appendChild(li);
}
function juggle(){
var result = 0;
for(var n=0; n<arguments.length; n++){
result += arguments[n];
}
this.result = result; // 在上下文保存結(jié)果
}
var ninja1 = {};
var ninja2 = {};
juggle.apply(ninja1,[1,2,3,4]);
juggle.call(ninja2,5,6,7,8);
assert(ninja1.result === 10, "juggled via apply");
assert(ninja2.result === 26, "juggled via call");
</script>
</body>
</html>
函數(shù)式編程和命令式編程的區(qū)別在于思維層面:函數(shù)式程序的構(gòu)建塊而不是命令式語句蛔翅。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>構(gòu)建for-each函數(shù)演示函數(shù)上下文功能</title>
<style>
/*定義結(jié)果樣式*/
li.pass{color: green;}
li.fail{color: red;}
</style>
</head>
<body>
<!--顯示測(cè)試結(jié)果-->
<ul id="results"></ul>
<script>
// 定義assert方法
function assert(value,desc){
var li = document.createElement('li');
li.className = value ? "pass" : "fail";
li.appendChild(document.createTextNode(desc));
document.getElementById("results").appendChild(li);
}
function forEach(list,callback){
for(var n=0; n<list.length; n++){
callback.call(list[n],n);
}
}
// 創(chuàng)建測(cè)試對(duì)象
var weapons = ['shuriken','katana','nunchuncks'];
forEach(weapons,function(index){
assert(this == weapons[index], "Got the expected value of " + weapons[index]);
});
</script>
</body>
</html>