一、函數作用域查找1、定義說明1)、函數當前作用域查找不到,可以訪問外層函數作用域的活動對象(參數、局部變數、定義在外層函數體里的函數)2)、外層的外層函數。。。一直到全局 第一條說明:定義在外層函數體里的函數,包括當前函數,當前函數調用自己的時候,就是遞歸調用。 2、原理執行環境、作用域鏈、作用域 ...
一、函數作用域查找
1、定義說明
1)、函數當前作用域查找不到,可以訪問外層函數作用域的活動對象(參數、局部變數、定義在外層函數體里的函數)
2)、外層的外層函數。。。一直到全局
第一條說明:定義在外層函數體里的函數,包括當前函數,當前函數調用自己的時候,就是遞歸調用。
2、原理
執行環境、作用域鏈、作用域、活動對象
1)、調用內層函數,會創建一個執行環境,執行環境會關聯一個作用域鏈
2)、調用內層函數時,所有的外層函數都已經調用完畢或者外層函數調用中,所有隻要把所用外層函數作用域(包括最外層全局)的活動對象,關聯到當前內層函數的作用域鏈上。
3)、最後創建內層函數作用域的活動對象,並且關聯到作用域鏈的最前端。
活動對象註釋:
函數參數,函數體裡面定義的局部變數,函數體裡面定義的函數
3、代碼示例
var str1 = '全局變數'; function func(arg) { var str2 = '外層局部變數'; function funcInner1() { console.log('外層函數的其他函數'); } function funcInner2(argInner2) { var str3 = '內層函數變數'; console.log(str3); console.log(argInner2); console.log(arg); console.log(str2); funcInner1(); console.log(str1); } return funcInner2; } var result = func('外層函數參數') result('內層函數參數'); 執行結果: 內層函數變數 內層函數參數 外層函數參數 外層局部變數 外層函數的其他函數 全局變數
二、閉包
1、定義
定義在函數體里的內部函數,這些內部函數可以訪問它們所在的外部函數中聲明的所有局部變數、參數和聲明的其他內部函數。當其中一個這樣的內部函數在包含它們的外部函數之外被調用時,就會形成閉包。
2、通俗解釋
內層函數作為外層函數執行結果被返回,當外層函數執行完畢後,定義在其內部的局部變數、參數和聲明的其他內部函數並沒有被回收,而是可以通過返回的函數的執行被獲取,即形成了閉包。
3、形成閉包的條件
1)、嵌套函數
2)、內層函數訪問外層函數的活動對象
3)、內層函數子在外層函數之外的地方被調用
第三條件解釋:一般是內層函數作為外層函數的返回值,外層函數之外的地方,可以先調用外層函數,拿到對應返回值,再通過返回值調用內層函數。
4、特點:
1)、作為一個函數變數的一個引用,當函數返回時,其處於激活狀態。
2)、一個閉包就是當一個函數返回時,一個沒有釋放資源的棧區。
5、原理
函數作用域查找
6、代碼示例
function outer() { var scope = 10; return function inner() { scope += 10; console.log(scope); } } var fn = outer(); fn(); 執行結果: 20 //inner函數作為outer函數執行結果被返回,當outer函數執行完畢後,定義在其內部的變數scope並沒有被回收,而是可以通過函數fn的執行被獲取,這裡的inner函數,即形成了閉包。
7、好處:
1)、變數長期駐扎在記憶體中;
2)、避免全局變數的污染;
避免全局變數的污染說明:
上面代碼示例中,我們也可以把scope定義成全局變數,但是這樣我們就沒法控制僅允許inner函數可以修改scope的值,因為全局變數,所有地方都可以訪問。
8、壞處:
1)、記憶體消耗
通常來說,函數的活動對象會隨著執行期上下文一起銷毀,但是,由於閉包引用另外一個函數的活動對象,因此這個活動對象無法被銷毀,這意味著,閉包比一般的函數需要更多的記憶體消耗。
進一步說明:尤其在IE瀏覽器中需要關註。由於IE使用非原生javascript對象實現DOM對象,因此閉包會導致記憶體泄露問題。
2)、性能問題
使用閉包時,會涉及到跨作用域訪問,每次訪問都會導致性能損失。
進一步說明:因此在腳本中,最好小心使用閉包,它同時會涉及到記憶體和速度問題。不過我們可以通過把跨作用域變數存儲在局部變數中,然後直接訪問局部變數,來減輕對執行速度的影響。
10、閉包常用案例
1)、延遲輸出
2)、累加器
代碼1: //延遲輸出 for (var i = 0; i < 5; ++i) { (function (i) { setTimeout(function () { console.log(i + ' '); }, 100); })(i); } 執行結果: 0 1 2 3 4 代碼2: //累加器 //initNum: 初始值, step: 步長 function getTotalizer(initNum, step) { var num = initNum; return function() { num = num + step; return num; } } var add = getTotalizer(10, 5); console.log(add()); console.log(add()); console.log(add()); console.log(add()); 執行結果: 15 20 25 30
三、匿名函數
1、定義:
匿名函數,就是沒有函數名的函數
2、三種調用方法
1)、可以定義一個匿名函數,並且立即調用。
2)、可以定義一個匿名函數,賦值給一個變數,通過這個變數調用函數。
3)、匿名函數作為函數返回值返回,通過返回值調用匿名函數。
3、匿名函數與閉包關係
1)、匿名函數和閉包沒有實際關係
2)、閉包的條件,需要返回一個內層函數,這個內層函數訪問外層函數的活動對象,這個內層函數在外層函數之外的地方被調用。
3)、上面內層函數只能通過函數的返回值被調用,不能通過方法名直接調用,這種情況下,把這個內層函數聲明成匿名函數更合理,通常也是這麼用的。
4、代碼示例
//1、通過表達式自我執行 (function() { console.log('執行'); })(); //2、將匿名函數賦給變數 var result = function () { console.log('執行'); }; result(); //3、匿名函數作為函數返回值 function func() { var i = '局部變數i'; return function () { return i; }; } console.log(func()());