JavaScript 函數(shù)
在 JavaScript中,函數(shù)是頭等 (first-class) 對象,因為它們可以像任何其他對象一樣具有屬性和方法。它們與其他對象的區(qū)別在于函數(shù)可以被調(diào)用。簡而言之,它們是 Function 對象。(MDN)
函數(shù)就是一段代碼片段,調(diào)用函數(shù)就是執(zhí)行函數(shù)中的代碼。
1. 函數(shù)的使用
1.1 語法
函數(shù)使用前通常與變量一樣需要先進行聲明,用 function
關(guān)鍵字定義函數(shù)。
// 常見的函數(shù)的定義方式
function 函數(shù)名(參數(shù)1, 參數(shù)2, ...) {
代碼片段;
return 返回值;
}
// 調(diào)用函數(shù) (執(zhí)行函數(shù)中的代碼)
var 函數(shù)的返回值 = 函數(shù)名(參數(shù)1, 參數(shù)2, ...);
- 調(diào)用函數(shù)就是執(zhí)行函數(shù)中的代碼
- 參數(shù)是調(diào)用函數(shù)的時候傳遞過去的,在函數(shù)執(zhí)行過程中可以訪問到
- 函數(shù)執(zhí)行完畢后可以有一個返回值,調(diào)用函數(shù)的地方可以接收到這個返回值
1.2 調(diào)用函數(shù)
使用
函數(shù)名()
的方式即可調(diào)用一個函數(shù)
以下是一個最簡單的函數(shù):
function say() {
console.log('hello');
}
say(); // 輸出:"hello"
調(diào)用這個函數(shù)就會在控制臺輸出 hello
字符串。
這個函數(shù)沒有返回值,默認(rèn)會返回一個 undefined
。
1.3 帶有參數(shù)與返回值的函數(shù)
在聲明函數(shù)的時候,可以對參數(shù)也做上說明
假設(shè)有一個需求,需要一個計算三角形周長的函數(shù)。
計算三角形周長則需要知道三角形三條邊各自的長度,然后將他們求和。
定義函數(shù)的時候就可以將三條邊作為參數(shù)進行聲明。
function calcPerimeter(a, b, c) {
// a, b, c 分別代表三條邊
var sum = a + b + c;
return sum;
}
// 調(diào)用函數(shù) 并將返回值賦值給perimeter
var perimeter = calcPerimeter(3, 4, 5);
在調(diào)用函數(shù)的時可以傳遞值過去,這些值可以在函數(shù)中被訪問。
在以上 calcPerimeter
函數(shù)被調(diào)用的時,傳遞了 3, 4, 5
三個值。
三個值對應(yīng)到函數(shù)聲明時定義的三個參數(shù) a, b, c
。
所以函數(shù)執(zhí)行過程中 sum
的值為 3 + 4 + 5
,即 12
,隨后 sum
被作為返回值進行返回。
最終變量 perimeter
也會被賦值為12。
2. 怎么運用函數(shù)
2.1 合理包裝內(nèi)容
函數(shù)可以對代碼進行封裝,讓邏輯更加清晰。
比如如下代碼塊:
// 改寫前
var num = 10;
var flag = false;
var i;
var len;
for (i = 2, len = num - 1; i <= len; i++) {
if (num % i === 0) {
flag = true;
break;
}
}
console.log(flag);
以上代碼第一眼可能無法看出具體在做什么,僅需要做一點修改,就能有所改善。
// 改寫后
function isPrimeNumber(num) {
var flag = false;
var i;
var len;
for (i = 2, len = num - 1; i <= len; i++) {
if (num % i === 0) {
flag = true;
break;
}
}
return flag;
}
var num = 10;
var result = isPrimeNumber(num);
console.log(result);
改寫后的代碼似乎多了幾行,但是將其中核心部分包裝成了函數(shù)。
通過 isPrimeNumber
函數(shù)名可以很容易的了解到這一段代碼作用是用來判斷一個數(shù)是否為質(zhì)數(shù)
。
當(dāng)然有個前提就是起一個 可以讓大部分人看得懂 的函數(shù)名。
2.2 優(yōu)秀的函數(shù)名
優(yōu)秀的函數(shù)名可以幫助他人更容易理解代碼,同時當(dāng)自己一段時間后再回頭看代碼時,能更容易進入當(dāng)時寫代碼時候的思維模式等。
這里提供幾個函數(shù)命名的建議,具體的命名可以根據(jù)團隊規(guī)范、個人成長等做調(diào)整。
2.2.1 拼寫準(zhǔn)確
準(zhǔn)確的拼寫十分重要,絕大多數(shù)情況下函數(shù)名都會是英文單詞組成的。
當(dāng)然許多時候手一快可能就少了一個字母,或者錯將 wrap
進行乾坤大挪移拼寫成了 warp
。
許多情況是無法避免的,經(jīng)常需要自檢。
當(dāng)然可以借助一些單詞的檢查插件,如 Visual Studio Code
可以借助 Code Spell Checker
插件來檢查單詞的正確性。
再者碰到想起的函數(shù)名但是單詞拼寫不出來,盡可能翻詞典,日積月累能有大量的詞匯沉淀。
2.2.2 盡量不使用拼音或者混用拼寫
盡量不要使用拼音或者是首字母縮寫。
以下函數(shù)名或許會造成困擾:
function jslsh() {}
function jsNumber() {}
以上是計算兩數(shù)和
函數(shù)的命名,可能只有天和地知道這個是什么意思。
當(dāng)然,如果是自己寫 demo 或者測試代碼的時候,其實不需要考慮這么多。
2.2.3 有“狀態(tài)”的函數(shù)名
如碰到函數(shù)功能是判斷是否
、有沒有
、可以
的時候,可以帶上一些前綴,比如:
// 是否登入
function isLogin() {}
同時可以合理的使用動詞,比如打開文件
就可以使用 openFile
函數(shù)名,具體的狀態(tài)可以根據(jù)語境、函數(shù)作用、個人習(xí)慣等做調(diào)整使用。
2.2.4 合理使用縮寫
使用詞語的縮寫盡量使用通用的縮寫
如:
- pwd - password
- mgr - manager
- del - delete
- …
這些縮寫大部分開發(fā)者是可以看的懂的縮寫。
3. 函數(shù)示例
3.1 計算圓的面積
分析:根據(jù)圓面積公式 S=π·r·r,其中 S 就是要求的值,即函數(shù)的返回值,π 是常量(固定的一個值),半徑r是未知數(shù),所以r就可以設(shè)計成參數(shù)
function circleArea(r) {
var pi = 3.1415926;
return pi * r * r;
}
// 計算半徑為10的圓的面積
var area = circleArea(10);
3.2 判斷某個DOM元素是否含有某個類名
分析:
某個DOM
和某個類名
可以說明有兩個未知量,可以設(shè)計成兩個參數(shù)。
根據(jù)描述也可以確定一個 某個DOM
的類型是個 DOM
對象,某個類名
是個字符串
只要拿到這個DOM的 class
屬性,判斷里面是不是含有這個類型即可得到結(jié)果
function hasClass(el, className) {
// el 是 element的縮寫,表示一個dom元素
// 如果沒有元素 則返回
if (!el) {
return false;
}
// 根據(jù)空格分割成數(shù)組
// 可以不使用 split 方法,使用字符串也可以用indexOf匹配
var classList = el.className.split(' ');
// 判斷是否存在
if (classList.indexOf(className) >= 0) {
return true;
}
return false;
}
4. 函數(shù)的其他知識
以下擴展內(nèi)容可能需要一定的知識積累,遇到不懂的地方可以停下腳步,先學(xué)習(xí)下一章節(jié)
4.1 函數(shù)表達式
以上篇幅的函數(shù)其實都通過函數(shù)聲明
的方式來定義,還有一種方式就是使用函數(shù)表達式定義函數(shù)。
// 函數(shù)聲明
function add(a, b) {
return a + b;
}
// 函數(shù)表達式
var add = function(a, b) {
return a + b;
};
通過上述例子可以看出寫法上的區(qū)別就是函數(shù)表達式
是將函數(shù)賦值給了變量。
這兩種方式創(chuàng)建的函數(shù)最大的區(qū)別在于,不能提前調(diào)用使用函數(shù)表達式創(chuàng)建的函數(shù)
光看句子有點抽象,舉個例子?:
var num1 = add1(1, 2);
var num2 = add2(3, 4);
// 函數(shù)聲明
function add1(a, b) {
return a + b;
}
// 函數(shù)表達式
var add2 = function(a, b) {
return a + b;
};
上面一段代碼在執(zhí)行的時候會報 add2 is not a function
的錯誤,表示 add2
不是函數(shù),也就是說 add2
不能被提前使用,而 add1
可以。
具體原因可以查看執(zhí)行上下文
章節(jié)。
4.2 函數(shù)作用域
函數(shù)有他自己的作用域,函數(shù)內(nèi)聲明的變量等通常情況下
不能被外部訪問,但是函數(shù)可以訪問到外部的變量或者其他函數(shù)等
var a = 1;
function fn() {
var b = 2;
console.log(a); // 輸出:1
console.log(b); // 輸出:2
}
fn();
console.log(b); // ReferenceError: b is not defined
執(zhí)行以上代碼會報 b is not defined
錯誤。
4.3 匿名函數(shù)
沒有名字的函數(shù)就是一個匿名函數(shù)
var fn = function() {
console.log('我是一個匿名函數(shù)');
};
除了在函數(shù)表達式
中會出現(xiàn)匿名函數(shù),還有許多場景。
相對常見的一個就是自執(zhí)行匿名函數(shù)
,MDN官方翻譯為立即調(diào)用函數(shù)表達式
。
自執(zhí)行
就是這個函數(shù)聲明后就會立即執(zhí)行,自執(zhí)行的匿名函數(shù)通常會被用來形成獨立的作用域
。
如:
(function() {
var num = 1;
alert(num);
})();
這是一個自執(zhí)行的匿名函數(shù),這個匿名函數(shù)是被包裹了一段括號后才被調(diào)用的。
以下這段代碼會報錯:
// 報錯
function() {
var num = 1;
alert(num);
}();
瀏覽器會告訴你必須給函數(shù)一個名字。
通過括號包裹一段函數(shù),讓js引擎
識別成他是一個函數(shù)表達式,再對他進行執(zhí)行,就不會報錯,這是加括號的原因。
同理,可以使用 +
,!
等運算符代替括號,讓一個匿名函數(shù)成為一個函數(shù)表達式即可。
大部分第三方框架都會通過一個自執(zhí)行的匿名函數(shù)包裹代碼,與瀏覽器全局環(huán)境隔離,避免污染到全局環(huán)境。
4.4 具有函數(shù)名的函數(shù)表達式
函數(shù)表達式進行聲明的時候也可以使用具名函數(shù)
var count = function fn(num) {
console.log('我是一個函數(shù)');
};
以上這段代碼是不會報錯的,但是不能通過 fn
訪問到函數(shù),這里的 fn
只能在函數(shù)內(nèi)部進行訪問,通常在使用遞歸的形式做計算的時候會用到這種寫法。
var count = function fn(num) {
if (num < 0) {
return num;
}
return fn(num - 1) + num;
}
count(5);
上面這個例子,就是在函數(shù)內(nèi)部訪問 fn
調(diào)用自己,使用遞歸的形式求和。
注:遞歸相關(guān)的知識可以參考相關(guān)文獻進行學(xué)習(xí)
4.5 arguments
arguments 是一個對應(yīng)于傳遞給函數(shù)的參數(shù)的類數(shù)組對象。(MDN)
通常情況下函數(shù)都具有 arguments
對象,可以在函數(shù)內(nèi)部直接訪問到。
他是一個類數(shù)組,即長得很像數(shù)組,成員都是用數(shù)字編號,同時具有 length 屬性。
arguments 中存放著當(dāng)前函數(shù)被調(diào)用時,傳遞過來的所有參數(shù),即便不聲明參數(shù),也可以通過 arguments 取到傳遞過來的參數(shù)。
function sum() {
console.log(arguments);
}
sum(1, 2, 3, 4);
執(zhí)行上述代碼,可以看到在控制臺輸出了一個對象,存放的就是所有傳遞過去的參數(shù),利用這一特性,就可以不限制參數(shù)個數(shù),或者讓函數(shù)做中轉(zhuǎn)站(攔截函數(shù)),利用 arguments 將參數(shù)傳遞給另一個函數(shù)。
如,一個不確定用戶輸入的參數(shù)個數(shù)的求和函數(shù):
function sum() {
var total = 0;
var i;
var len;
for (i = 0, len = arguments.length; i < len; i++) {
total += arguments[i];
}
return total;
}
var total = sum(1, 2, 3, 4, 15);
console.log(total); // 輸出:25
通過循環(huán)遍歷 arguments
對象,就可以得到所有參數(shù),然后做累加就可以達到求和的目的。
4.6 函數(shù)和方法
方法在本質(zhì)上是個函數(shù)。
通常都能聽到“調(diào)用一下某個方法”,“取到某個方法的返回值”,這里的方法其實就是一個函數(shù)。
一般方法是用來描述對象的某個行為的,但是平時我們會混用,口頭交流的時候會經(jīng)常把函數(shù)直接稱作方法。
只要自己理解,不需要去糾結(jié)函數(shù)和方法到底是什么,也不用特意糾正別人的說法,大家都能聽得懂就行。
4.7 JS DOC 注釋
使用 JS DOC
描述函數(shù)是非常良好的習(xí)慣,良好的 JS DOC
書寫還可以使用工具快速生成文檔。
JS DOC
對函數(shù)的描述大體如下:
/**
* 這是這個求冪函數(shù) 計算 x 的 y 次方
* @param {Number} x - 底數(shù)
* @param {String} y - 指數(shù)
*/
function pow(x, y) {
// ...
}
除此之外還可以描述返回值等。
4.8 純函數(shù)與副作用
所謂純函數(shù),就是沒有副作用的函數(shù)
一個函數(shù)從執(zhí)行開始到結(jié)束,沒有對外部環(huán)境做任何操作,即對外部環(huán)境沒有任何影響(沒有副作用),這樣的函數(shù)就是純函數(shù)。
純函數(shù)只負(fù)責(zé)輸入輸出,對于一種輸入只有一種函數(shù)返回值。
如果函數(shù)中存在 Math.random
這種影響返回值的函數(shù),也不能算是純函數(shù)。
// 純函數(shù)
function add(a, b) {
return a + b;
}
// 非純函數(shù)
var person = { name: '小明' };
function changeName {
person.name = '小紅'; // 影響了函數(shù)外的內(nèi)容,產(chǎn)生了副作用
}
4.9 構(gòu)造函數(shù)
當(dāng)一個函數(shù)與 new
關(guān)鍵字一起被調(diào)用的時候,就會作為一個構(gòu)造函數(shù)。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function() {
console.log('我是' + this.name);
};
var person = new Person('阿梅', 12);
person.say();
console.log(person);
可以看到當(dāng)函數(shù)作為構(gòu)造函數(shù)調(diào)用的時候,默認(rèn)返回的是一個對象。
細(xì)心的讀者仔細(xì)觀察就能發(fā)現(xiàn),構(gòu)造函數(shù)的默認(rèn)返回值是函數(shù)體內(nèi)的 this。
事實上構(gòu)造函數(shù)的執(zhí)行有一定流程:
- 創(chuàng)建一個空對象,將函數(shù)的this指向這個空對象
- 執(zhí)行函數(shù)
- 如果函數(shù)沒有指定返回值,則直接返回 this(一開始創(chuàng)建的空對象),否則返回指定返回值
理解這個流程,就能理解構(gòu)造函數(shù)的返回值。
具體的函數(shù)的 prototype
屬性等可以參閱原型
章節(jié)。
5. 小結(jié)
函數(shù)特性相對較多,也是 JavaScript 的核心之一。
函數(shù)可以用于封裝代碼,提供代碼的復(fù)用率和可讀性,在大部分情況下,當(dāng)兩段代碼具有超高相似度時,應(yīng)當(dāng)設(shè)計成函數(shù),不同的部分使用參數(shù)進行區(qū)分。