JS三座大山再學習 ---- 做用域和閉包

本文已發佈在西瓜君的我的博客,原文傳送門javascript

做用域

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

栗子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();
  1. name定義在全局,在全局能夠訪問到,因此 (2) 打印可以正確打印;
  2. 在函數fun中,若是沒有定義name屬性,那麼會到它的父做用域去找,因此 (3) 也能正確打印。
  3. 內部環境能夠經過做用域鏈訪問全部外部環境,但外部環境不能訪問內部環境的任何變量和函數。相似單向透明,這就是做用域鏈,因此 (4) 不行而 (5) 能夠。

那麼問題來了,爲何第一個打印是"undefined",而不是"ReferenceError: name is not defined"。原理簡單的說就是JS的變量提高java

變量提高: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();

至關於markdown

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();       //波妞

這樣一來就好理解了,函數表達式在聲明的時候還只是個變量post


栗子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定義後,纔有了塊級做用域。ui


栗子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同理spa

閉包

函數與對其狀態即詞法環境(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 的值。


若有錯誤,請斧正

以上

 

 

出處:http://www.javashuo.com/article/p-gflavopm-dq.html

相關文章
相關標籤/搜索