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