一、函數基本使用
1. 什么是函數
- 函數就是語句的封裝,可以讓這些代碼方便地被復用。
- 函數具有"一次定義,多次調用"的優點。
- 使用函數,可以簡化代碼,讓代碼更具有可讀性。
2. 函數的定義和調用
-
和變量類似,函數
必須先定義然后才能使用
-
使用
function關鍵字
定義函數,function是"功能"的意思。function fun() {// 函數體語句}
function
:表示定義函數。fun
:函數名,函數名必須符合JS標識符命名規則。()
:圓括號中是形參列表,即使沒有形參,也必須書寫圓括號。{}
:大括號中就是函數體語句。
var fun = function () {// 函數體語句}
function ()
:匿名函數
-
函數的調用
- 執行函數體中的所有語句,就稱為"調用函數"
- 調用函數非常簡單,只需在函數名字后書寫圓括號對即可
fun() // 調用函數,圓括號中是實參列表,如果沒有實參,也要書寫圓括號。
-
函數聲明的提升
-
和變量聲明提升類似,函數聲明也可以被提升
fun();function fun() {alert('函數被執行');}
function fun()
在預解析階段會被提升。
-
函數優先提升
-
3. 函數的參數和返回值
- 參數是函數內的一些
待定值
,在調用函數時,必須傳入這些參數的具體值
。 - 函數的
參數可多可少
,函數可以沒有參數,也可以有多個參數,多個參數之間需要用逗號隔開。 - 函數的參數
function add(a, b) { // 圓括號中定義"形式參數"var sum = a + b;console.log('兩個數字的和是' + sum); }add(3, 5); // 調用函數傳入"實際參數"
- 形參和實參個數不同的情況
- arguments
- 函數內arguments表示它接收到的
實參列表
,它是一個類數組對象
- 類數組對象:所有屬性均為從0開始的自然數序列,并且有length屬性,和數組類似可以用方括號書寫下標訪問對象的某個屬性值,
但是不能調用數組的方法。
- 函數內arguments表示它接收到的
- 形參和實參個數不同的情況
- 函數的返回值
- 函數體內可以使用return關鍵字表示"函數的返回值"
function sum(a, b) {return a + b; // 函數的返回值}var result = sum(3, 5); // 函數的返回值可以被變量接受
- 調用一個有返回值的函數,可以被當做一個普通值,從而可以出現在任何可以書寫值的地方。
function sum(a, b) {return a + b;}var result = sum(3, 4) * sum(2, 6);
function sum(a, b) {return a + b;}var result = sum(3, sum(4, 5));
- 遇見return即退出函數
- 調用函數時,
一旦遇見return語句則會立即退出函數
,將執行權交給調用者。
- 調用函數時,
- 函數體內可以使用return關鍵字表示"函數的返回值"
二、函數算法題
1. 尋找喇叭花數
-
喇叭花樹是這樣的三位數:其每一位數字的階乘之和恰好等于它本身。即abc = a! + b! + c!,其中abc表示一個三位數。試尋找所有喇叭花樹。
-
思路:將計算某個數字的階乘封裝成函數,這樣可以讓問題簡化。
// 計算一個數字的階乘function factorial(n) {// 累乘器var result = 1;for (var i = 1; i <= n; i++) {result *= i;}return result;}// 窮舉法,從100到999尋找喇叭花數for (var i = 100; i <= 999; i++) {// 把數字i變為字符串var i_str = i.toString();// abc分別表示百位、十位、個位var a = Number(i_str[0]);var b = Number(i_str[1]);var c = Number(i_str[2]);// 根據喇叭花數的條件,來判斷if(factorial(a) + factorial(b) + factorial(c) == i){console.log(i);}}
-
sort內置排序函數
- 數組排序可以使用
sort()
方法,這個方法的參數又是一個函數。var arr = [33, 22, 55, 11];arr.sort(function (a, b) {});
- 這個函數中的a、b分別表述數組中靠前和靠后的項,如果需要它們交換位置,則返回任意正數;否則就返回負數。
var arr = [33, 22, 55, 11];arr.sort(function (a, b) {if (a > b) {return 1;} else {return -1;}}) // 從小到大arr.sort(function (a, b) {return a - b; })// 從大到小arr.sort(function (a, b) {return b - a;})
- 數組排序可以使用
三、遞歸
1. 什么是遞歸
- 函數的內部語句可以
調用這個函數自身
,從而發起對函數的一次迭代
。在新的迭代中,又會執行調用函數自身的語句,從而又產生一
次迭代。當函數執行到某一次時,不再進行新的迭代,函數被一層一層返回,函數被遞歸。 - 遞歸是一種較為
高級的變成技巧
,它把一個大型復雜的問題層層轉化為一個原問題相似較小的問題來解決。 - 遞歸的要素
- 邊界條件:確定遞歸到何時終止,也稱為遞歸的出口。
- 遞歸模式:大問題是如何分解為小問題的,也稱為遞歸體。
2. 遞歸常見算法
- 斐波那契數列
- 斐波那契數列是這樣的數列:1、1、2、3、5、8、13、21。
- 數列下標為0和1的項的值都是1,從下標為2的項開始,每項等于前面兩項的和。
@code
3. 實現深克隆
@code
四、全局變量和局部變量
-
變量作用域:
JavaScript
是函數作用域編程語言:變量只在其定義時所在的function內部有意義。function fun() {var a = 10;}fun();console.log(a); // 報錯
- 變量a是在fun函數中被定義的,所以變量a只在fun函數內部有定義,fun函數就是a的"作用域"變量a被稱為
局部變量
。
- 變量a是在fun函數中被定義的,所以變量a只在fun函數內部有定義,fun函數就是a的"作用域"變量a被稱為
-
全局變量
- 如果不將變量定義在任何函數的內部,此時這個變量就是全局變量,它在任何函數內都可以被訪問和更改。
var a = 10;function fun() {a++;console.log(a); // 輸出11}fun();console.log(a); // 輸出11
- 變量a沒有定義在任何函數內部,它是"全局變量"。
- 如果不將變量定義在任何函數的內部,此時這個變量就是全局變量,它在任何函數內都可以被訪問和更改。
-
遮蔽效應
- 如果函數中也定義了和全局同名的變量,則函數內的變量會將全局的變量"遮蔽"
var a = 10;function fun() {var a = 5;a++;console.log(a); // 輸出6}fun();console.log(a); // 輸出 10
- 局部變量a將全局變量a"遮蔽"了
- 注意考慮變量聲明提升的情況
var a = 10;function fun() {a++; // 局部變量a被自增1,a此時是undefined,自增1結果是NaNvar a = 5; // 局部變量a會被提升到a++之前, 重新將a賦值為5console.log(a); // 輸出5}fun();console.log(a); // 輸出10
- 如果函數中也定義了和全局同名的變量,則函數內的變量會將全局的變量"遮蔽"
-
形參也是局部變量
var a = 10;function fun(a) {a++;console.log(a); // 輸出8}fun(7);console.log(a); // 輸出10
- 形參a也是函數內部的局部變量
五、作用域鏈
-
函數的嵌套:一個函數內部也可以定義一個函數。和局部變量類似,定義在一個函數內部的函數是局部函數。
function fun() {function inner() {console.log('你好');}inner(); // 調用內部函數}fun(); // 調用外部函數
-
作用域鏈
- 在函數嵌套中,變量會從內到外逐層尋找它的定義。
var a = 10;var b = 20;function fun() {var c = 30;function inner() {var a = 40;var d = 50;console.log(a, b, c, d); // 使用變量時,JS會從當前層開始,逐層向上尋找定義。}inner();}fun();
- 不加var將定義全局變量
- 在初次給變量賦值時,如果沒有加var,則將定義全局變量。
function fun() {a = 3;}fun();console.log(a); // 3
var a = 1;var b = 2;function fun() {c = 3;var b = 4;b++;console.log(b); // 5c++;}fun();console.log(b); // 2console.log(c); // 4
- 在初次給變量賦值時,如果沒有加var,則將定義全局變量。
- 在函數嵌套中,變量會從內到外逐層尋找它的定義。
六、閉包
-
什么是閉包
// 創建一個函數function fun() {// 定義局部變量var name = '張三';function innerFun() {alert(name);} return innerFun; // 返回了內部函數}var innerFun = fun(); // 內部函數被移動到了外部執行innerFun();
-
JavaScript中函數會產生
閉包(closure)
。閉包是函數本身
和該函數聲明時所處的環境狀態
的組合。
-
函數能夠"記憶住"其定義時所處的環境,即使函數不在其定義的環境中被調用,也能訪問定義時所處環境的變量。
-
-
觀察閉包現象
- 在JavaScript中,
每次創建函數時都會創建閉包
。 - 但是,閉包特性往往需要將函數"換一個地方"執行,才能被觀察出來。
- 在JavaScript中,
-
閉包的作用
- 閉包很有用,因為它允許我們將數據與操作該數據的函數關聯起來,這與"面向對象編程"有少許相似之處。
- 閉包的功能:記憶性、模擬私有變量。
-
閉包的用途[記憶性]
- 當閉包產生時,函數所處環境的狀態
會始終保持在內存中,不會在外層函數調用后被自動清除
。這就是閉包的記憶性。 - 舉例:創建體溫檢測函數checkTemp(n),可以檢查體溫n是否正常,函數會返回布爾值。
但是不同的小區有不同的體溫檢測標準,比如A小區體溫合格線是37.1℃,而B小區體溫合格線是37.3℃function createCheckTemp(standardTemp) {function checkTemp(n) {if (n <= standardTemp) {alert('你的體溫正常');} else {alert('你的體溫偏高');}}return checkTemp;}var checkTemp_A = createCheckTemp(37.1);var checkTemp_B = createCheckTemp(37.3);checkTemp_A(37.2);checkTemp_B(37.0);checkTemp_A(37.5);
- 當閉包產生時,函數所處環境的狀態
-
閉包的用途[模擬私有變量]
- 請定義一個變量a,要求是能保證這個a只能被進行指定操作(如加1、乘2),而不能進行其他操作。
// 封裝一個函數,這個函數的功能就是私有化變量function fun() {// 定義一個局部變量avar a = 0;return {getA: function() {return a;},add: function () {a++;},pow: function () {a *=2;} } }var obj = fun();// 如果想在fun函數外面使用變量a,唯一的方法就是調用getA()方法。console.log(obj.getA());
- 請定義一個變量a,要求是能保證這個a只能被進行指定操作(如加1、乘2),而不能進行其他操作。
-
閉包的注意點
- 不能濫用閉包,否則會造成網頁的性能問題,嚴重時可能導致
內存泄漏
。所謂內存泄漏是指程序中已動態分配的內存由于某種原因未釋放或
無法釋放。
- 不能濫用閉包,否則會造成網頁的性能問題,嚴重時可能導致
-
閉包的一道面試題
function addCount() {var count = 0;return function () {count = count + 1;console.log(count);};}var fun1 = addCount();var fun2 = addCount();fun1(); // 1fun2(); // 1fun2(); // 2fun1(); // 2
七、什么是IIFE
-
IIFE(Immediately Invoked Function Expression,
立即調用函數表達式
)是一種特殊的JavaScript函數寫法,
一旦被定義,就立即被調用
。(function () {statements})();
包裹function的括號
:將函數變成表達式()
:運行函數
-
形成IIFE的方法
- 函數不能直接加圓括號被調用
function () {alert(1) }()
- 函數必須轉為
函數表達式
才能被調用。(function (){alert(1)})();+function () {alert(1)}(); -function () {alert(1)}();
- 函數不能直接加圓括號被調用
-
IIFE的作用[為變量賦值]
- 為變量賦值:當給變量賦值需要一些較為復雜的計算時(如if語句),
使用IIFE顯的語法更緊湊
。<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title> </head><body><script>var age = 42;var sex = '女';var title = (function () {if (age < 18) {return '小朋友';} else {if (sex == '男') {return '先生';} else {return '女士';}}})();alert(title);</script> </body></html>
- 為變量賦值:當給變量賦值需要一些較為復雜的計算時(如if語句),
-
IIFE的作用[將全局變量變為局部變量]
- IIFE可以在一些場合(如for循環中)將全局變量為局部變量,語法顯得緊湊。
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title> </head><body><script>var arr = [];for (var i = 0; i < 5; i++) {(function(i){arr.push(function () {alert(i);});})(i);}arr[0]();arr[1]();arr[2]();arr[3]();arr[4]();</script> </body></html>
- IIFE可以在一些場合(如for循環中)將全局變量為局部變量,語法顯得緊湊。
八、內容難點
- 什么是函數?函數為開發帶來了哪些便利?
- 函數的參數和返回值
- 函數的相關算法題
- 遞歸、遞歸算法題
- 作用域和閉包
- IIFE