首頁 web前端 js教程 js作用域和閉包詳解

js作用域和閉包詳解

Nov 29, 2019 pm 02:02 PM
作用域 閉包

js作用域和閉包詳解

作用域

JS中有兩種作用域:全域作用域|局部作用域

#栗子1

console.log(name);      //undefined
var name = '波妞';
var like = '宗介'
console.log(name);      //波妞
function fun(){
    console.log(name);  //波妞
    console.log(eat)    //ReferenceError: eat is not defined
    (function(){
        console.log(like)   //宗介
        var eat = '肉'
    })()
}
fun();
登入後複製

【相關課程推薦:JavaScript影片教學】  

1. name定義在全域,在全域可以存取到,所以(​​2)列印能夠正確列印;

2. 在函數fun中,如果沒有定義name屬性,那麼會到它的父作用域去找,所以(3) 也能正確列印。

3. 內部環境可以透過作用域鏈存取所有外部環境,但外部環境無法存取內部環境的任何變數和函數。類似單向透明,這就是作用域鏈,所以 (4) 不行而 (5) 可以。

那麼問題來了,為什麼第一個列印是"undefined",而不是"ReferenceError: name is not defined"。原理簡單的說就是JS的變數提升

變數提升:JS在解析程式碼時,會將所有的宣告提前到所在作用域的最前面


栗子2

console.log(name);      //undefined
var name = '波妞';
console.log(name);      //波妞
function fun(){
    console.log(name)   //undefined
    console.log(like)   //undefined
    var name = '大西瓜';
    var like = '宗介'
}
fun();
登入後複製

#相當於

var name;
console.log(name);      //undefined
name = '波妞';
console.log(name);      //波妞
function fun(){
    var name;
    var like;
    console.log(name)   //undefined
    console.log(like)   //undefined
    name = '大西瓜';
    like = '宗介'
    console.log(name)   //大西瓜
    console.log(like)   //宗介
}
fun();
登入後複製

注意:是提前到目前作用域的最前面


栗子3

printName();     //printName is not a function
var printName = function(){
    console.log('波妞')
}
printName();       //波妞
登入後複製

#相當於

var printName;
printName();     //printName is not a function
printName = function(){
    console.log('波妞')
}
printName();       //波妞
登入後複製

這樣就好理解了,函數表達式在宣告的時候還只是個變數


栗子4

{
    var name = '波妞';
}
console.log(name)   //波妞

(function(){
    var name = '波妞';
})()
console.log(name)   //ReferenceError: name is not defined

{
    let name = '波妞';
}
console.log(name)   //ReferenceError: name is not defined
登入後複製

從上面的栗子可以看出,不可以草率的認為JS中var聲明的變數的作用範圍就是大括號的起止範圍,ES5並沒有區塊級作用域,實質是函數作用域;ES6中有了let、const定義後,才有了區塊級作用域。


栗子5

function p1() { 
    console.log(1);
}
function p2() { 
    console.log(2);
}
(function () { 
    if (false) {
        function p1() {
            console.log(3);
        }
    }else{
        function p2(){
            console.log(4)
        }
    }
    p2();
    p1()
})();       
//4
//TypeError: print is not a function
登入後複製

這是一個非常經典的栗子,宣告提前了,但是因為判斷條件為否,所以沒有執行函數體。所以會出現"TypeError: print is not a function"。 while,switch,for同理

閉包

函數與對其狀態即詞法環境(lexical environment)的引用共同構成閉包(closure )。也就是說,閉包可以讓你從內部函數存取外部函數作用域。在JavaScript中,函數在每次建立時產生閉包。

上面的定義來自MDN,簡單講,閉包就是指有權存取另一個函數作用域中變數的函數。


● 閉包的關鍵在於:外部函數呼叫之後其變數物件本來應該被銷毀,但閉包的存在使我們仍然可以存取外部函數的變數物件. ,

//举个例子
function makeFunc() {
    var name = "波妞";
    function displayName() {
        console.log(name);
    }
    return displayName;
}

var myFunc = makeFunc();
myFunc();
登入後複製

JavaScript中的函數會形成閉包。閉包是由函數以及創建該函數的詞法環境組合而成。這個環境包含了這個閉包建立時所能存取的所有局部變數

在範例中,myFunc 是執行makeFunc 時所建立的displayName 函式實例的引用,而displayName 實例仍可存取其詞法作用域中的變量,即可以存取name 。由此,當 myFunc 被呼叫時,name 仍可被訪問,其值 '波妞' 就被傳遞到console.log中。 建立閉包最常見方式,就是在一個函數內部建立另一個函數


#● 通常,函數的作用域及其所有變數都會在函數執行結束後被銷毀。但是,在創建了一個閉包以後,這個函數的作用域就會一直保存到閉包不存在為止

//例二
function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

//释放对闭包的引用
add5 = null;
add10 = null;
登入後複製

從本質上講,makeAdder 是一個函數工廠— 他創建了將指定的值和它的參數相加求和的函數。在上面的範例中,我們使用函數工廠建立了兩個新函數 — 一個將其參數和 5 求和,另一個和 10 求和。

add5 和 add10 都是閉包。它們共享相同的函數定義,但是保存了不同的詞法環境。在 add5 的環境中,x 為 5。而在 add10 中,x 則為 10。

閉包的作用域鏈包含它自己的作用域,以及包含它的函數的作用域和全域作用域。


● 閉套件只能取得包含函數中的任何變數的最後一個值

//栗子1
function arrFun1(){
    var arr = [];
    for(var i = 0 ; i < 10 ; i++){
        arr[i] = function(){
            return i
        }
    }
    return arr
}
console.log(arrFun1()[9]());     //10
console.log(arrFun1()[1]());     //10

//栗子2
function arrFun2(){
    var arr = [];
    for(var i = 0 ; i < 10 ; i++){
        arr[i] = function(num){
            return function(){
                return num
            };
        }(i)
    }
    return arr
}
console.log(arrFun2()[9]());     //9
console.log(arrFun2()[1]());     //1
登入後複製

栗子1 中,arr陣列中包含10個匿名函數,每個函數都可以存取外部的變數i , arrFun1 執行後,其作用域被銷毀,但它的變數依然存在記憶體中,能被循環中的匿名函數訪問,這是的i 為10;

栗子2 中,arr數組中有是個匿名函數,其匿名函數內還有匿名函數,最內層匿名函數訪問的num 被上一級匿名函數保存在了內存中,所以可以訪問到每次的i 的值。

本文來自 js教學 欄目,歡迎學習!  

以上是js作用域和閉包詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

c語言中typedef struct的用法 c語言中typedef struct的用法 May 09, 2024 am 10:15 AM

typedef struct 在 C 語言中用於建立結構體類型別名,簡化結構體使用。它透過指定結構體別名將一個新的資料類型作為現有結構體的別名。優點包括增強可讀性、程式碼重複使用和類型檢查。注意:在使用別名前必須定義結構體,別名在程式中必須唯一且僅在其宣告的作用域內有效。

java中的variable expected怎麼解決 java中的variable expected怎麼解決 May 07, 2024 am 02:48 AM

Java 中的變數期望值異常可以透過以下方法解決:初始化變數;使用預設值;使用 null 值;使用檢查和賦值;了解局部變數的作用域。

js中閉包的優缺點 js中閉包的優缺點 May 10, 2024 am 04:39 AM

JavaScript 閉包的優點包括維持變數作用域、實作模組化程式碼、延遲執行和事件處理;缺點包括記憶體洩漏、增加了複雜性、效能開銷和作用域鏈影響。

c++中的include什麼意思 c++中的include什麼意思 May 09, 2024 am 01:45 AM

C++ 中的 #include 預處理器指令將外部來源檔案的內容插入到目前原始檔案中,以複製其內容到目前原始檔案的相應位置。主要用於包含頭文件,這些頭文件包含程式碼中所需的聲明,例如 #include <iostream> 是包含標準輸入/輸出函數。

C++ 智慧指標:全面剖析其生命週期 C++ 智慧指標:全面剖析其生命週期 May 09, 2024 am 11:06 AM

C++智慧指標的生命週期:建立:分配記憶體時建立智慧指標。所有權轉移:透過移動操作轉移所有權。釋放:智慧指標離開作用域或被明確釋放時釋放記憶體。物件銷毀:所指向物件被銷毀時,智慧型指標成為無效指標。

C++ Lambda 表達式如何實作閉包? C++ Lambda 表達式如何實作閉包? Jun 01, 2024 pm 05:50 PM

C++Lambda表達式支援閉包,即保存函數作用域變數並供函數存取。語法為[capture-list](parameters)->return-type{function-body}。 capture-list定義要捕獲的變量,可以使用[=]按值捕獲所有局部變量,[&]按引用捕獲所有局部變量,或[variable1,variable2,...]捕獲特定變量。 Lambda表達式只能存取捕獲的變量,但無法修改原始值。

c++中函數的定義和呼叫可以巢狀嗎 c++中函數的定義和呼叫可以巢狀嗎 May 06, 2024 pm 06:36 PM

可以。 C++ 允許函數巢狀定義和呼叫。外部函數可定義內建函數,內部函數可在作用域內直接呼叫。巢狀函數增強了封裝性、可重複用性和作用域控制。但內部函數無法直接存取外部函數的局部變量,且傳回值類型需與外部函數宣告一致,內部函數不能自遞歸。

vue中let和var的區別 vue中let和var的區別 May 08, 2024 pm 04:21 PM

在 Vue 中,let 和 var 宣告變數時在作用域上存在差異:作用域:var 具有全域作用域,let 具有區塊級作用域。區塊級作用域:var 不會建立區塊級作用域,let 建立區塊級作用域。重新宣告:var 允許在同一作用域內重新宣告變數,let 不允許。

See all articles