函式和迴圈閉包的理解
阿新 • • 發佈:2019-01-31
7.19
函式宣告週期:
函式建立階段:函式的內部屬性形成,即函式的[[scope]]屬性,包含了宣告該函式的作用域鏈。
函式執行階段:首先,進入執行上下文,預解析和提升變數,生成VO,之後把VO放入函式作用域鏈的頂端,之後,開始執行程式碼。
1:函式定義:
組成: function關鍵字
函式名稱
一對圓括號,其中包含由0個或多個用逗號分隔的形參識別符號
函式體 有return 語句,返回return後面的內容,沒有,返回undefined
1:函式宣告語句
function 關鍵字開頭
2:函式定義表示式
不是function關鍵字開頭
函式名稱(可選)
兩者區別:例如:
2:函式呼叫
在函式名後面加上一對圓括號,其中包含由0個或多個用逗號分隔的實參
4種方式呼叫函式
由於JavaScript中的this依賴於函式的呼叫方式所以,將this稱為呼叫上下文(invocation context)
1:作為函式
作為普通函式進行呼叫時,this->window/undefined
2:作為方法
作為方法進行呼叫時,this->擁有該方法的(一級)物件
【
function one(){...};
var two=function thr(){....}; //具名函式
var two=function(){....}; //匿名函式
(function four(){.....})();//立即執行函式表示式
1:函式名稱識別符號繫結在何處:函式宣告的函式名可在函式體外、內使用,函式定義表示式的函式名(如果有)只能在函式體內使用
2:函式是否有提示行為:函式宣告存在提升行為,函式定義表示式沒有。
例如:function one(){....} var obj1={ fun:one, }; obj1.fun(); //通過obj上下文去呼叫;this->one var obj2={ obj1:obj1, fun1:one, fun2:obj1.fun; }; obj2.obj1.fun(); == obj2.fun1(); == obj2.fun2();
//這幾種是等價的, 函式是引用的,本體只有一個,都是one的引用而已。
在JavaScript中,函式複製的不是副本,函式只有引用。
引用,不管有幾個不同的函式名指向同一個函式,函式只有一個,不會有兩個一樣的函式。 】 3:作為建構函式 作為構造器進行呼叫時,this-> 新分配的物件 4:通過call和apply方法呼叫 this->第一個引數 /arguments[0] 3:進入函式體 控制器進入函式執行環境,即建立函式執行環境(execution context/EC),把函式執行環境推入執行上下文棧(execution context stack / ECS)棧頂; ECS的棧底一直都是全域性執行環境,直到這個網頁退出 其中,執行環境的狀態元件 / 執行環境的屬性如下 (該圖源於網路) 【 VO:變數物件,執行環境中/該作用域中其中定義的所有變數和函式都儲存在這個物件中】 當建立EC,進入函式執行環境, 1:this值的繫結,根據函式的呼叫方式,確定this的值【這說明this的值是在函式執行的時候確定的】 2:將函式的AO作為VO,變數的提升/初始化執行環境的AO/VO 【 函式只有AO活動物件(activation object /AO) ,所以,把函式的AO作為VO,AO在一開始只有arguments 物件。並且在進入函式體時,arguments的值都已經被實參(若有)賦值】 3: [[scope]]屬性的值,就是在函式的內部屬性[[scope]]的前端加上AO/VO,即該值是一個棧,棧頂是VO/AO 之後,就開始執行函式體 4:函式呼叫結束 如果該函式沒有被引用,則被回收;若還有引用,則繼續保留。 總體過程: 引擎對全域性程式碼進行預解析,提升變數 普通變數,賦值undefined 函式宣告,整個函式程式碼塊提升 函式有一個內部屬性[[scope]]屬性,該屬性的值為一個作用域棧,從棧頂到棧底依次為: 外部函式的活動物件,外部函式的外部函式的活動物件,....,全域性作用域 。 引擎進入全域性執行環境,將全域性上下文推入執行上下文棧棧頂 根據ecma的描述: 1:將變數環境設定為全域性環境 2:將詞法環境設定為全域性環境 3:將this繫結設定為全域性物件 逐行/逐塊執行程式碼,遇到函式呼叫,就進入函式體,建立執行環境,將函式執行環境放到執行上下文棧 棧頂,然後對執行環境的屬性進行初始化賦值,之後,執行函式體程式碼。 執行完畢,將該函式執行環境推出執行上下文棧,函式呼叫結束;當前的ECS棧頂的執行環境即為當前執行 的執行環境(即全域性執行環境或者當前正在執行的函式執行環境)。 閉包: 在電腦科學中,閉包是詞法閉包,是引用了自由變數的函式。【自由變數:在作用域A中使用卻不是在A中宣告的變數】 閉包是有函式和其他相關引用環境組合而成的實體。 JavaScript權威指南: 函式物件可以通過作用域鏈相互關聯起來,函式體內部的變數都可以儲存在函式作用域內,這種特性在電腦科學文獻中成為“閉包”; 在同一個作用域鏈中定義兩個閉包,這兩個閉包共享同樣的私有變數或變數。 JavaScript忍者祕籍: 閉包是一個函式在建立時允許該自身函式訪問並操作該自身函式之外的變數時所建立的作用域。即閉包可以讓函式訪問所有的變數和函式,只要這些變數和函式存在於該函式宣告是的作用域內就行。 JavaScript高階程式設計3: 閉包只能取得包含函式中任何變數的最後一個值。 閉包所儲存的是整個變數物件,而不是某個特殊的變數。 當在函式內部定義了其他函式時,就建立了閉包。閉包有權訪問包含函式內部的所有變數,原理如下: 在後臺執行環境中,閉包的作用域鏈包含著自己的作用域,包含函式的作用域和全域性作用域; 通常,函式的作用域機器所有變數都會在函式執行結束後被撤銷; 但是,當函式返回了一個閉包時,這個函式的作用域將會一直在記憶體中儲存到閉包不存在為止 ; JavaScript核心概念及實踐 : 如果一個函式需要在其父級函式返回之後留住對父級作用域鏈的話,就必須要為此建立一個閉包; 函式所繫結的是作用域本身,而不是該作用域中變數或變數當前所返回的值。 JavaScript語言精粹: 一個內部函式除了可以訪問自己的引數和變數,同時他也能自由訪問把它巢狀在其中的複函式的引數與變數。 通過函式字面量建立的函式物件包含一個連到外部上下文的連線,這被稱為閉包。 函式可以訪問它被建立時所處的上下文環境,這杯稱為閉包。 (我只是摘抄了一些我覺得比較有意思的關於閉包的說明) 我個人覺得閉包就是一段本該消失但沒有消失的作用域鏈/作用域(只是個人理解) 圖解迴圈中的閉包 1:function f(){
//debugger; //除錯
var a=[],i;
for(i=0;i<3;i++){
a[i]=function(){
return i;
}
}
return a;
}
var a=f();
a[0](); //3
a[1](); //3
a[2](); //3
初始狀態:
f()呼叫結束後:
這是在chrome瀏覽器中截圖的,可以驗證上面的關係 f()呼叫結束後,陣列 a 內的3個函式指向的是同一個作用域,即共享的作用域,所以3個函式取到的自由變數 i 的值是一樣的,都來自f函式,並且都是3,這同時也說明,作用域儲存的是整個作用域而不是某個變數 (__parent__ :用於連線作用域,連成作用域鏈,這個屬性是自定義的,之前在網上看到,覺得挺好的) :2:function f(){
//debugger;
var a=[],i;
for(i=0;i<3;i++){
a[i]=(function(x){
return function(){return x;};
})(i);
}
return a;
}
var a=f();
a[0](); //0
a[1](); //1
a[2](); //2
初始狀態:
因為和1沒有什麼區別,所以,圖和1一樣。
f()呼叫結束後:
閉包的作用之一就是儲存私有變數 x是通過傳參給匿名函式,這個匿名函式將x作為區域性變數,附著在這個作用域上,儲存; 3:
function f(){
//debugger;
var a=[],i,m=90;
for(i=0;i<3;i++){
a[i]=(function(x){
return function(){console.log(m);return x;};
})(i);
}
return a;
}
var a=f();
a[0](); //90,0
a[1](); //90,1
a[2](); //90,2
初始狀態:
同上;
f()呼叫結束後:
有一點:這個函式和上面那個沒有什麼區別,但是,有很重要的一點就是,這裡的陣列函式的作用域有3個,而上面的只有2個; 我一直認為,上面那個也應該是3個,但是看到chrome調試出來只有2個; 然後,才有了第三個,我覺得應該是3個,只不過對第二個沒用,所有就沒顯示出來。(額,是這樣嗎?) 以上就是書裡的解釋和自己的理解,如果有寫錯的地方,還請各位前輩指正,謝謝。