深入瞭解JavaScript基礎之函數與作用域

2022-02-07 19:03:01
本篇文章給大家帶來了關於JavaScript中函數和作用域的相關知識,函數就是封裝了一段可被重複呼叫執行的程式碼塊,可用性的程式碼範圍就是這個名字的作用域,希望對大家有幫助。

1、函數

函數:就是封裝了一段可被重複呼叫執行的程式碼塊。通過此程式碼塊可以實現大量程式碼的重複使用。

1.1、函數的使用

函數在使用時分為兩步:宣告函數呼叫函數

①宣告函數

//宣告函數function 函數名(){
     //函數體程式碼}
  • function是宣告函數的關鍵字,必須小寫
  • 由於函數一般是為了實現某個功能才定義的, 所以通常我們將函數名命名為動詞,比如getSum

②呼叫函數

//呼叫函數函數名(); //通過呼叫函數名來執行函數體程式碼
  • 呼叫的時候千萬不要忘記新增小括號
  • 口訣:函數不呼叫,自己不執行

注意:宣告函數本身並不會執行程式碼,只有呼叫函數時才會執行函數體程式碼。

1.2、函數的封裝

  • 函數的封裝是把一個或者多個功能通過函數的方式封裝起來,對外只提供一個簡單的函數介面

1.3、函數的引數

1.3.1、形參和實參

在宣告函數時,可以在函數名稱後面的小括號中新增一些引數,這些引數被稱為形參,而在呼叫該函數時,同樣也需要傳遞相應的引數,這些引數被稱為實參

引數說明
形參式上的函數定義的時候 傳遞的引數 當前並不知道是什麼
實參際上的函數呼叫的時候 傳遞的引數 實參是傳遞給形參的

引數的作用 : 在函數內部某些值不能固定,我們可以通過引數在呼叫函數時傳遞不同的值進去

// 帶引數的函數宣告function 函數名(形參1, 形參2 , 形參3...) { // 可以定義任意多的引數,用逗號分隔
  // 函數體}// 帶引數的函數呼叫函數名(實參1, 實參2, 實參3...);

例如:利用函數求任意兩個數的和

// 宣告函數function getSum(num1,num2){
    console.log(num1+num2)}// 呼叫函數getSum(1,3) //4getSum(6,5) //11
  • 函數呼叫的時候實參值是傳遞給形參的

  • 形參簡單理解為:不用宣告的變數

  • 實參和形參的多個引數之間用逗號(,)分隔,

1.3.2、形參和實參個數不匹配

引數個數說明
實參個數等於形參個數輸出正確結果
實參個數多於形參個數只取到形參的個數
實參個數小於形參個數多的形參定義為undefined,結果為NaN
function sum(num1, num2) {
    console.log(num1 + num2);
}
sum(100, 200);             // 300,形參和實參個數相等,輸出正確結果

sum(100, 400, 500, 700);   // 500,實參個數多於形參,只取到形參的個數

sum(200);                  // 實參個數少於形參,多的形參定義為undefined,結果為NaN

注意:在JavaScript中,形參的預設值是undefined

1.3.3、小結

  • 函數可以帶引數也可以不帶引數
  • 宣告函數的時候,函數名括號裡面的是形參,形參的預設值為 undefined
  • 呼叫函數的時候,函數名括號裡面的是實參
  • 多個引數中間用逗號分隔
  • 形參的個數可以和實參個數不匹配,但是結果不可預計,我們儘量要匹配

1.4、函數的返回值

1.4.1、return語句

有的時候,我們會希望函數將值返回給呼叫者,此時通過使用 return 語句就可以實現。

return 語句的語法格式如下:

// 宣告函數function 函數名(){
    ...
    return  需要返回的值;}// 呼叫函數函數名();    // 此時呼叫函數就可以得到函數體內return 後面的值
  • 在使用 return 語句時,函數會停止執行,並返回指定的值

  • 如果函數沒有 return,返回的值是undefined

function add(num1,num2){
    //函數體
    return num1 + num2; // 注意:return 後的程式碼不執行
    alert('我不會被執行,因為前面有 return');
}
var resNum = add(21,6); // 呼叫函數,傳入兩個實參,並通過 resNum 接收函數返回值
alert(resNum);          // 27

1.4.2、return 終止函數

return 語句之後的程式碼不被執行

function add(num1,num2){
    //函數體
    return num1 + num2; // 注意:return 後的程式碼不執行
    alert('我不會被執行,因為前面有 return');}var resNum = add(21,6); // 呼叫函數,傳入兩個實參,並通過 resNum 接收函數返回值alert(resNum);          // 27

1.4.3、return 的返回值

return只能返回一個值。如果用逗號隔開多個值,以最後一個為準

function add(num1,num2){
    //函數體
    return num1,num2;
}
var resNum = add(21,6); // 呼叫函數,傳入兩個實參,並通過 resNum 接收函數返回值
alert(resNum);          // 6

1.4.4、小結

函數都是有返回值的

  1. 如果有 return ,則返回 return 後面的值
  2. 如果沒有 return,則返回 undefined

1.4.5、區別

break、continue、return 的區別

  • break : 結束當前迴圈體(如 for、while)
  • continue :跳出本次迴圈,繼續執行下次迴圈(如for、while)
  • return :不僅可以退出迴圈,還能夠返回 return 語句中的值,同時還可以結束當前的函數體內的程式碼

1.4.5、練習

1.利用函數求任意兩個數的最大值

function getMax(num1, num2) {
    return num1 > num2 ? num1 : num2;}console.log(getMax(1, 2));console.log(getMax(11, 2));

2.求陣列 [5,2,99,101,67,77] 中的最大數值

//定義一個獲取陣列中最大數的函數function getMaxFromArr(numArray){
    var maxNum = numArray[0];
    for(var i = 0; i < numArray.length;i++){
        if(numArray[i]>maxNum){
            maxNum = numArray[i];
        }
    }
    return maxNum;}var arrNum = [5,2,99,101,67,77];var maxN = getMaxFromArr(arrNum);  //這個實參是個陣列alert('最大值為' + maxN);

3.建立一個函數,實現兩個數之間的加減乘除運算,並將結果返回

var a = parseFloat(prompt('請輸入第一個數'));var b = parseFloat(prompt('請輸入第二個數'));function count(a,b){
    var arr = [a + b, a - b, a * b, a / b];
    return arr;}var result = count(a,b);console.log(result)

1.5、arguments的使用

當我們不確定有多少個引數傳遞的時候,可以用arguments來獲取。在 JavaScript 中,arguments 實際上它是當前函數的一個內建物件。所有函數都內建了一個 arguments 物件,arguments 物件中儲存了傳遞的所有實參

  • arguments存放的是傳遞過來的實參

  • arguments展示形式是一個偽陣列,因此可以進行遍歷。偽陣列具有以下特點

①:具有 length 屬性

②:按索引方式儲存資料

③:不具有陣列的 push , pop 等方法

// 函數宣告function fn() {
    console.log(arguments);  //裡面儲存了所有傳遞過來的實參
    console.log(arrguments.length); // 3
    console.log(arrguments[2]); // 3}// 函數呼叫fn(1,2,3);

例如:利用函數求任意個數的最大值

 function maxValue() {
    var max = arguments[0];
    for (var i = 0; i < arguments.length; i++) {
        if (max < arguments[i]) {
            max = arguments[i];
        }
    }
    return max;}console.log(maxValue(2, 4, 5, 9)); // 9console.log(maxValue(12, 4, 9)); // 12

函數呼叫另外一個函數

因為每個函數都是獨立的程式碼塊,用於完成特殊任務,因此經常會用到函數相互呼叫的情況。具體演示在下面的函數練習中會有。

1.6、函數練習

1.利用函數封裝方式,翻轉任意一個陣列

function reverse(arr) {
    var newArr = [];
    for (var i = arr.length - 1; i >= 0; i--) {
        newArr[newArr.length] = arr[i];
    }
    return newArr;}var arr1 = reverse([1, 3, 4, 6, 9]);console.log(arr1);

2.利用函數封裝方式,對陣列排序 – 氣泡排序

function sort(arr) {
    for (var i = 0; i < arr.length - 1; i++) {
        for (var j = 0; j < arr.length - i - 1; j++) {
            if (arr[j] > arr[j+1]) { 
	            var temp = arr[j];
	            arr[j] = arr[j + 1]; 
	            arr[j + 1] = temp;
            }
        }
    }
    return arr;}

3.輸入一個年份,判斷是否是閏年(閏年:能被4整除並且不能被100整數,或者能被400整除)

function isRun(year) {
     var flag = false;
     if (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0) {
        flag = true;
     }
    return flag;}console.log(isRun(2010));console.log(isRun(2012));

4.使用者輸入年份,輸出當前年份2月份的天數,如果是閏年,則2月份是 29天, 如果是平年,則2月份是 28天

function backDay() {
    var year = prompt('請您輸入年份:');
    if (isRun(year)) { //呼叫函數需要加小括號
        alert('你輸入的' + year + '是閏年,2月份有29天');
    } else {
        alert('您輸入的' + year + '不是閏年,2月份有28天');
    }}backDay();//判斷是否是閏年的函數function isRun(year) {
    var flag = false;
    if (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0) {
        flag = true;
    }
    return flag;}

1.7、函數的兩種宣告方式

1.7.1、自定義函數方式(命名函數)

利用函數關鍵字function自定義函數方式。

// 宣告定義方式function fn() {...}// 呼叫  fn();
  1. 因為有名字,所以也被稱為命名函數

  2. 呼叫函數的程式碼既可以放到宣告函數的前面,也可以放在宣告函數的後面

1.7.2、函數表示式方式(匿名函數)

利用函數表示式方式的寫法如下:

// 這是函數表示式寫法,匿名函數後面跟分號結束
var fn = function(){...};
// 呼叫的方式,函數呼叫必須寫到函數體下面
fn();
  • 因為函數沒有名字,所以也稱為匿名函數

  • 這個fn 裡面儲存的是一個函數

  • 函數呼叫的程式碼必須寫到函數體後面

2、作用域

通常來說,一段程式程式碼中所用到的名字並不總是有效和可用的,而限定這個名字的可用性的程式碼範圍就是這個名字的作用域。作用域的使用提高了程式邏輯的區域性性,增強了程式的可靠性,減少了名字衝突。

JavaScript (ES6前) 中的作用域有兩種:

  • 全域性作用域
  • 區域性作用域(函數作用域)

2.1、全域性作用域

作用於所有程式碼執行的環境(整個 script 標籤內部)或者一個獨立的 js 檔案

2.2、區域性(函數)作用域

作用於函數內的程式碼環境,就是區域性作用域。 因為跟函數有關係,所以也稱為函數作用域

2.3、JS 沒有塊級作用域

  • 塊作用域由 {} 包括
  • 在其他程式語言中(如 java、c#等),在 if 語句、迴圈語句中建立的變數,僅僅只能在本 if 語句、本回圈語句中使用,如下面的Java程式碼:
if(true){
    int num = 123;
    System.out.println(num);	// 123}System.out.println(num);		// 報錯

JS 中沒有塊級作用域(在ES6之前)

if(true){
    int num = 123;
    System.out.println(num);	// 123}System.out.println(num);		// 123

3、變數的作用域

在JavaScript中,根據作用域的不同,變數可以分為兩種:

  • 全域性變數
  • 區域性變數

3.1、全域性變數

在全域性作用域下宣告的變數叫做全域性變數在函數外部定義的變數

  • 全域性變數在程式碼的任何位置都可以使用

  • 在全域性作用域下var宣告的變數 是全域性變數

  • 特殊情況下,在函數內不使用 var 宣告的變數也是全域性變數(不建議使用)

3.2、區域性變數

在區域性作用域下宣告的變數叫做區域性變數在函數內部定義的變數

  • 區域性變數只能在該函數內部使用

  • 在函數內部 var 宣告的變數是區域性變數

  • 函數的形參實際上就是區域性變數

3.3、區別

  • 全域性變數:在任何一個地方都可以使用,只有在瀏覽器關閉時才會被銷燬,因此比較佔記憶體

  • 區域性變數:只在函數內部使用,當其所在的程式碼塊被執行時,會被初始化;當程式碼塊執行結束後,就會被銷燬,因此更節省記憶體空間

4、作用域鏈

  1. 只要是程式碼,就至少有一個作用域

  2. 寫在函數內部的叫區域性作用域

  3. 如果函數中還有函數,那麼在這個作用域中就又可以誕生一個作用域

  4. 根據在內部函數可以存取外部函數變數的這種機制,用鏈式查詢決定哪些資料能被內部函數存取,就稱作作用域鏈

// 作用域鏈: 內部函數存取外部函數的變數,採取的是鏈式查詢的方式來決定取哪個值,這種結構我們稱為作用域連結串列var num = 10;funtion fn() { //外部函數
    var num = 20;
    
    function fun() { //內部函數
        console.log(num);  // 20 ,一級一級存取
    }}
  • 作用域鏈:採取就近原則的方式來查詢變數最終的值。

5、預解析

首先來看幾段程式碼和結果:

console.log(num);  // 結果是多少?//會報錯 num is undefined
console.log(num);  // 結果是多少?var num = 10;   // undefined
// 命名函數(自定義函數方式):若我們把函數呼叫放在函數宣告上面fn();				//11function fn() {
    console.log('11');}
// 匿名函數(函數表示式方式):若我們把函數呼叫放在函數宣告上面fn();var  fn = function() {
    console.log('22'); // 報錯}//相當於執行了以下程式碼var fn;fn();      //fn沒賦值,沒這個,報錯var  fn = function() {
    console.log('22'); //報錯}

JavaScript 程式碼是由瀏覽器中的 JavaScript 解析器來執行的。JavaScript 解析器在執行 JavaScript 程式碼的時候分為兩步:預解析和程式碼執行。

  • 預解析:js引擎會把js裡面所有的 var 還有 function 提升到當前作用域的最前面

  • 程式碼執行:從上到下執行JS語句

預解析只會發生在通過 var 定義的變數和 function 上。學習預解析能夠讓我們知道為什麼在變數宣告之前存取變數的值是 undefined為什麼在函數宣告之前就可以呼叫函數。

5.1、變數預解析(變數提升)

變數預解析也叫做變數提升、函數提升

變數提升: 變數的宣告會被提升到當前作用域的最上面,變數的賦值不會提升

console.log(num);  // 結果是多少?
var num = 10;   
// undefined



//相當於執行了以下程式碼
var num;		// 變數宣告提升到當前作用域最上面
console.log(num);
num = 10;		// 變數的賦值不會提升

5.2、函數預解析(函數提升)

函數提升: 函數的宣告會被提升到當前作用域的最上面,但是不會呼叫函數。

fn();				//11function fn() {
    console.log('11');}

5.3、解決函數表示式宣告呼叫問題

對於函數表示式宣告呼叫需要記住:函數表示式呼叫必須寫在函數宣告的下面

// 匿名函數(函數表示式方式):若我們把函數呼叫放在函數宣告上面
fn();
var  fn = function() {
    console.log('22'); // 報錯
}


//相當於執行了以下程式碼
var fn;
fn();      //fn沒賦值,沒這個,報錯
var  fn = function() {
    console.log('22'); //報錯
}

5.4、預解析練習

預解析部分十分重要,可以通過下面4個練習來理解。
Pink老師的視訊講解預解析:https://www.bilibili.com/video/BV1Sy4y1C7ha?p=143

// 練習1var num = 10;fun();function fun() {
    console.log(num);	//undefined
    var num = 20;}// 最終結果是 undefined

上述程式碼相當於執行了以下操作

var num;function fun() {
    var num;
    console.log(num);
    num = 20;}num = 10;fun();

// 練習2var num = 10;function fn(){
    console.log(num);		//undefined
    var num = 20;
    console.log(num);		//20}fn();// 最終結果是 undefined 20

上述程式碼相當於執行了以下操作

var num;function fn(){
    var num;
    console.log(num);
    num = 20;
    console.log(num);}num = 10;fn();

// 練習3var a = 18;f1();function f1() {
    var b = 9;
    console.log(a);
    console.log(b);
    var a = '123';}

上述程式碼相當於執行了以下操作

var a;function f1() {
    var b;
    var a
    b = 9;
    console.log(a);	//undefined
    console.log(b);	//9
    a = '123';}a = 18;f1();

// 練習4f1();console.log(c);console.log(b);console.log(a);function f1() {
    var a = b = c = 9;
    // 相當於 var a = 9; b = 9;c = 9;  b和c的前面沒有var宣告,當全域性變數看
    // 集體宣告 var a = 9,b = 9,c = 9;
    console.log(a);
    console.log(b);
    console.log(c);}

上述程式碼相當於執行了以下操作

function f1() {
    var a;
    a = b = c = 9;
    console.log(a);	//9
    console.log(b);	//9
    console.log(c);	//9
}
f1();
console.log(c);	//9
console.log(b);	//9
console.log(a);	//報錯 a是區域性變數

相關推薦:

以上就是深入瞭解JavaScript基礎之函數與作用域的詳細內容,更多請關注TW511.COM其它相關文章!