前端筆記之JavaScript(四)關於函式、作用域、閉包那點事

mufengsm發表於2019-03-22

一、自定義函式function

函式就是功能、方法的封裝。函式能夠幫我們封裝一段程式程式碼,這一段程式碼會具備某一項功能,函式在執行時,封裝的這一段程式碼都會執行一次,實現某種功能。而且,函式可以多次呼叫。

1.1函式的定義和呼叫

語法:

定義:把需要實現的功能預先做好

執行:需要的時候執行這個功能,而且還可以執行多次

 

定義:function myName(){}

執行:myName()

 

【語法解釋】:

 function  定義函式的關鍵字

 myName    函式名稱

 ()         引數集

 {}        函式體,執行的程式碼都放在{}裡面

 

多條語句,組成一個“語句軍團”,集體作戰。

//定義一個函式,函式就是一組語句的集合
function haha(){
   console.log(1);
   console.log(2);
   console.log(3);
   console.log(4);
}
haha();//呼叫haha函式
haha();//呼叫haha函式
haha();//呼叫haha函式

函式必須先定義,然後才能呼叫

定義一個函式,用關鍵字function來定義function就是英語“功能”的意思。表示這裡面定義的語句,完成了一些功能。function後面有一個空格,後面就是函式名字,函式的名字也是關鍵字,命名規範和變數命名是一樣的。名字後面有一對兒圓括號,裡面放置引數。然後就是大括號,大括號裡面是函式的語句。

 function 函式名稱(){

 

 }

 

函式如果不呼叫,裡面的語句一輩子都不執行,等於白寫。

呼叫函式的方法,就是函式名稱加()()是一個運算子,表示執行一個函式。

 函式名稱()

一旦呼叫函式,函式內部的程式碼不管對錯,都會執行。

能感覺到,函式是一些語句的集合,讓語句稱為一個軍團,集體作戰。要不出動都不出動,要出動就全動。

函式的意義1:在出現大量程式程式碼相同時候,可以為它門封裝成一個function,這樣只呼叫一次,就能執行很多語句。

 


 

1.2函式的引數

定義在函式內部的語句,都是相同的,但是實際上可以通過“引數”這個東西,來讓語句有差別。

定義函式時,內部語句可能有一些懸而未決的量,就是變數,這些變數,要求在定義時都羅列在圓括號中:

 

function fun(a){
   console.log("我第"+a+"次說你好!");
}
fun(100);
fun(1);
fun(2);
fun(3);

呼叫的時候,把這個變數真實的值,一起寫在括號裡,這樣隨著函式的呼叫,這個值也傳給了a變數引數。

羅列在function圓括號中的引數,叫做形式引數;呼叫時傳遞的數值,叫做實際引數。

 

引數可以有多個,用逗號隔開。

 

function sum(a,b){
   console.log(a + b);
}
sum(3,5);     //8
sum(8,11);    //19
sum("5",12);  //512
sum(10);      //NaN,因為a的值是10,b沒有被賦值是undefined,10+undefined=NaN
sum(10,20,30,40,88); //30,後面的引數沒有變數接收

函式的意義2:在呼叫函式時,不用關心函式內部的實現細節,甚至這個函式是你網上抄的,可以執行。所以這個東西,給我們團隊開發帶來了好處。

 

定義函式的時候,引數是什麼型別,不需要指定型別:

呼叫的時候,傳進去什麼型別,ab變數就是什麼型別

 

 sum("5",12);  //512,做的是連字串運算

 

另外,定義和呼叫的時候引數個數可以不一樣多,不報錯。

 

 sum(10);      

因為只給a變數賦值,b沒有被賦值,b被隱式的varb的值是undefined10+undefined=NaN

 

 

 sum(10,20,30,40,88); //30

只有前面兩個引數被形參變數接收了,後面的引數沒有變數接收,就被忽略了。

//封裝一個函式,計算m+....+n的和
//比如m是4,n是15,4+5+6+7...+15
function sum(m,n){
   var s = 0; //累加器,累加和
   for(var i = m;i <= n;i++){
       s+=i //s = s + i;
   }
   console.log(s);
}
sum(1,100); //計算1到100的和
sum(10,13); //計算10+11+12+13的和
sum(13,10); //輸出0,所以你就知道函式順序關機,定義順序是什麼,傳遞順序就是什麼。

1.3函式的返回值

函式可以通過引數來接收東西,還可以通過return關鍵字來返回值,“吐出”東西。

 

function sum(a,b){
   return a+b; //現在這個函式的返回值就是a+b的和
}

//sum沒有輸出功能,就要用console.log輸出
console.log(sum(3,8));       //計算sum(3,8);實際上稱為表示式,需要計算,計算後是11
console.log(sum(3,sum(4,5)));//輸出12,實際上兩次執行了sum函式,先執行內層的,計算出9,然後sum(3,9)就是12

函式有一個return的值,那麼現在這個函式,實際上是一個表示式,換句話說這個函式就是一個值。

所以這個函式,可以當做其他的函式引數。

 

 

 sum(3,sum(4,5));

 

程式從內層執行到外層,sum(3,9)

函式可以接收很多值,但是返回一個值。

 

函式的意義3:模組化程式設計,讓複雜的邏輯變得更簡單。

函式只能有唯一的return,有if語句除外。

 

程式遇見return,會做兩件事:

1、立即返回結果,返回到呼叫它的地方

2、不執行return後面的程式碼。

 

function fun(){
   console.log(1);
   console.log(2);
   //return; //返回一個空值,undefined
   return "你好啊!";
   console.log(3); //這行語句不執行,因為函式已經return,所以會終止執行後面的程式碼。
}
console.log(fun();


1.4函式模組化程式設計

實現前提:函式有返回值,可以作為其他函式執行時傳的實參。

習慣將複雜工作,進行一步步的分工,將一部分工作的結果作為下一步工作的條件。

將程式中某個單獨的功能製作成單獨函式,這就是造輪子的過程。

 

業務邏輯上:將所有的輪子進行拼裝。

將程式分成有層次的模組,製作過程中一部分函式要有返回值,執行結果作為另一些模組的引數、條件。

 

現在做一個程式,輸出2~100的所有質數,所謂的質數,就是隻有1和原數本身兩個約數,沒有其他約數。

把一個複雜的問題,拆分成一個個小問題,每個都是一個單獨的函式:

邏輯思維:約數個數函式 → 判斷質數函式 → 高層業務

程式設計需要逆向思維程式設計:製作約數個數函式 → 製作判斷質數函式 → 高層業務

 

函式思維找質數:

//約數個數函式:能夠傳入一個數字,返回它的約數個數
function yueshugeshu(a){
   //計算這個數字的約數個數
   var count = 0; //累加約數個數
   for(var i = 1;i <= a;i++){
       if(a % i == 0){ //判斷是否為約數
           count++;
       }
   }
   return count;
}
//判斷是否是質數,如果一個函式名字取名為is,就暗示了將返回布林值
//要麼返回true,要麼返回false。
//接收一個引數m,返回是否為質數(true或false)
function isZhishu(m){
   if(yueshugeshu(m) == 2){
       return true;
   }else{
       return false;
   }
}
//尋找1~100的質數
for(var i = 1;i <= 100; i++){
   if(isZhishu(i)){ //可以省略==true的判斷
       //isZhishu()給我們返回了true和false
       console.log(i);
   }
}

利用函式驗證哥德巴赫猜想:使用者輸入偶數拆分兩個質數和:

哥德巴赫猜想:任何一個偶數,都可以拆分為兩個質數的和。

現在要求,使用者輸入一個偶數,你把所有的質數拆分可能,寫出來。

 

比如:

4 = 2 + 2

6 = 3 + 3

8 = 3 + 5

48 = 5 + 43

 

程式碼見案例:

約數個數函式,裡面的細節不需要關心,它足夠的魯棒,就能返回約數個數。

上層的函式,可以使用下層的API

 

//約數個數函式:能夠傳入一個數字,返回它的約數個數
function yueshugeshu(a){
   //計算這個數字的約數個數
   var count = 0; //累加約數個數
   for(var i = 1;i <= a;i++){
       if(a % i == 0){ //判斷是否為約數
           count++;
       }
   }
   return count;
}
//判斷是否是質數,如果一個函式名字取名為is,就暗示了將返回布林值
//要麼返回true,要麼返回false。
//接收一個引數m,返回是否為質數(true或false)
function isZhishu(m){
   if(yueshugeshu(m) == 2){
       return true;
   }else{
       return false;
   }
}
//哥德巴赫猜想,使用者輸入一個數字
//驗證偶數是否能被拆分兩個質數的和,拆分的思想就是窮舉法
//比如使用者輸入48,那麼就:
//看看1、47是不是都質數
//看看2、46是不是都質數
//看看3、45是不是都質數
//...
var even = parseInt(prompt("請輸入一個偶數"));
for(var i = 4;i < even;i++){
   if(isZhishu(i) && isZhishu(even - i)){
       console.log(even + "可以拆分為" + i + "與" + (even - i) + "的和");
   }
}

利用函式驗證哥德巴赫猜想-一百萬以內的偶數拆分:優化

 

function yueshugeshu(a){
    ...
}
function isZhishu(m){
    ...
}
//注意驗證,驗證偶數能否被拆成兩個質數
waiceng:for(var i = 4 ; i <= 1000000 ; i+=2){
    for(var j = 2 ; j < i ; j++){
        if(isZhishu(j) && isZhishu(i - j)){
            console.log(i +"可以拆分為"+  j +"與"+  (i - j) + "的和");
            continue waiceng;
        }
    }
}

1.5函式遞迴

函式可以自己呼叫自己,就是遞迴。

function haha(){
   console.log("哈哈");
   haha();//呼叫自己
}
haha();
function sum(a){
   if(a == 1){
       return 1;
   }else{
       return a + sum(a-1); //10 + 9 + 8 + sum(7)
   }
}
console.log(sum(10));

斐波那契數列就是經典的遞迴演算法:

1 1123581321345589144233...

輸出斐波那契數列

 

只需要一個函式,就可以搞定全部問題。

fib(n); 就能得到第n位的數字

fib(2) = 1

fib(3) = 2

fib(4) = 3

fib(5) = 5

...

fib(10) = 55

 

function fib(n){
   if(n == 1 || n == 2){
       return 1;
   }else{
       return fib(n - 1) + fib(n - 2);
   }
}
// console.log(fib(10));

for(var i = 1;i <= 50;i++){
   console.log(fib(i));
}

1.6函式表示式

定義函式除了使用function之外,還有一種方法,就是函式表示式。就是函式沒有名字,稱為“匿名函式”,為了今後能夠呼叫它,我們把這個匿名函式,直接賦值給一個變數。

var haha = function(){
   console.log("哈哈");
}
// console.log(haha);
haha(); //以後要呼叫這個函式,就可以直接使用haha變數呼叫。

 

等價於:

function haha(){
   console.log("哈哈");
}
haha();

如果這個函式表示式的function不是匿名,而是有名字的:

var haha = function xixi(){
   console.log("哈哈");
}
xixi();  //這是錯誤的
haha();  //這的對的

 

那麼JS表現非常奇怪,在外部只能用haha()呼叫,xixi()會引發錯誤。

也就是說,JS這個奇怪的特性,給我們提了個醒,定義函式,只能用以下兩種方法,不能雜糅:

 function haha(){}

 

 var haha = function(){}

 

錯誤的:

var haha = function xixi(){}

 


1.7函式宣告的提升(預解析)

 

//先呼叫,可以輸出,因為有函式宣告提升的特性
fun();
fun();
fun();
//後定義
function fun(){
   console.log("我是函式!");
}

不會報錯。

JS在執行前,會有一個預解析的過程,把所有的函式宣告和變數的宣告,都提升到了最開頭,然後再執行第一行程式碼。所以function定義在哪裡,都不重要,程式總能找到這個函式。

 

函式宣告頭可以提升,JS程式執行前,都會有一個函式預解釋階段,預解釋階段是自動進行的

函式優先:函式宣告和變數宣告都會被提升,但是面試常考的一個細節是:函式會被首先提升,然後才是變數。

函式提升是沒節操的,無視if等語句的判斷,強制提升

JavaScript世界中,函式是一等公民。

 

函式宣告會被提升,但是函式表示式卻不會被提升:

fun();
var fun = function(){  //因為它是函式表示式,而不是function定義法
   alert("我是函式!");
}

又給我們提了個醒,沒有特殊的理由,都要用function haha(){}來定義函式。

 

函式優先:

aaa(); //現在這個aa到底是函式,還是變數5?
console.log(aaa);//函式優先,遇見同名的識別符號,預解析階段一定把這個識別符號給函式
var aaa = 5; //定義一個變數,是5
function aaa(){
   alert("我是aaa函式!")
}

面試題:

函式優先,現在foo這個識別符號衝突了,一個函式叫foo,一個變數也叫foo。預解析階段,如果遇見識別符號衝突,這個識別符號給函式。

 


1.8函式是一個引用型別

基本型別:NumberStringBooleanundefinednull

引用型別:ObjectfunctionarrayRegExpMathDate

 

function fun(){}
var haha = function (){}

console.log(typeof fun); //引用型別中的function型別
console.log(typeof haha);//引用型別中的function型別

函式也是一種型別,這個型別叫function,是引用型別的其中一種。

 

基本型別:儲存值

引用型別:儲存地址

 

 

現在變數a = 1,那麼這個a變數裡面儲存1這個數字

 

//基本型別的賦值
var a = 1;
var b = a; //b得到的值是a的副本,a把自己複製了一份,給了b
b = 3;     //改變了b的值,a不受影響
console.log(a); //1
console.log(b); //3
//引用型別的賦值:
//定義了一變數a,引用了一個function
//這個a變數儲存的是這個匿名函式的記憶體地址
var a = function(){
   alert("我是一根函式");
}
var b = a;  //就是把匿名函式的地址也給了b。
b.xixi = 1; //給b新增一個屬性
console.log(a.xixi); //輸出a的xixi屬性,a也有這個屬性了
//b的xixi屬性和a的變數都改變了,因為都是指向同一個物件(同一個記憶體地址)
b.xixi++;
b.xixi++;
b.xixi++;
console.log(a.xixi);
console.log(b.xixi);

 

 


總結:

預解釋:在js中,程式碼從上到下執行之前,(瀏覽器預設)首先會把所有帶varfunction關鍵字的進行提前宣告或者定義

var num=88;

宣告(declare):相當於種樹時"挖坑"  var num; 只宣告沒有定義時,num的預設值是undefined

定義(defined):相當於種樹時"栽樹"  num=88;(給變數賦值)

 

在預解釋的時候,帶var和帶function的還不一樣:

var:只是提前的宣告(定義賦值的部分是在程式碼執行的時候完成的)

function:提前的宣告+定義

 

在瀏覽器載入HTML頁面時,首先會開闢一個供js程式碼執行的環境-->"全域性作用域"(window/global)

棧記憶體(作用域):儲存基本資料型別的值;提供js程式碼執行的環境;

 

堆記憶體:在js中,對於引用資料型別來說,首先會開闢一個新的記憶體空間,然後把程式碼儲存到這個空間中,最後把空間的地址給相關的變數--->我們把新開闢的這個記憶體空間稱為"堆記憶體"

堆記憶體的作用:儲存引用資料型別值

 


 

二、作用域

 

 

2.1函式能封閉住作業域

變數的作用域無非就兩種:全域性變數和區域性變數。

2.1.1全域性變數(全域性作用域)

全域性變數:在最外層函式定義的變數擁有全域性作用域,即對任何內部函式來說,都是可以訪問的。

言外之意:如果變數沒有定義在任何的function中,那麼它將在程式中任意範圍內都有定義:

 

var a = 100; //定義在全域性的變數,在程式任何一個角落都有定義
function fn(){
   console.log("我是函式裡面的語句,我認識全域性變數a值為:" + a);
}
fn();
console.log("我是函式外面的語句,我認識全域性變數a值為:" + a);


2.1.2區域性變數(區域性作用域)

區域性變數:和全域性作用域相反,區域性作用域一般只在固定的程式碼片段內可訪問,而對於函式外部是無法訪問的。

例如:變數定義在function裡面,這個變數就是區域性變數,只在當前這個function函式內部能使用。在函式外部不能使用這個變數,出了這個function,就如同沒有定義過一樣。

 

function fn(){
   var a = 100; //定義在函式的變數,區域性變數
   console.log("我是函式裡面的語句,我認識變數a值為:" + a);
}
fn();
console.log("我是函式外面的語句,我認識變數a值為:" + a);

avar在了function裡面,所以現在這個a變數只能在紅框範圍內有定義:

ES5語法中,JavaScript變數作用域非常簡單,能關住作用域的只有一個,就是:函式。

 

【總結】:

● 定義在function裡面的變數,叫做區域性變數,只在function裡面有定義,出了function沒有定義的。

● 定義在全域性範圍內的,沒寫在任何function裡面的,叫做全域性變數,都認識。

【原理】:

全域性變數在定義時,就會直接生成一個新的變數,在任何位置查詢變數都有定義。

區域性變數定義在函式內部,函式如果不執行,相當於內部的程式碼沒寫,區域性變數等於從未定義過,在函式執行時,會在函式作用域內部立即定義了一個變數,使用完之後,變數立即被銷燬。所以在外部永遠找不到區域性變數定義。

 


 

2.2作用域鏈

作用域鏈:根據在內部函式可以訪問外部函式變數的這種機制,用鏈式查詢決定哪些資料能被內部函式訪問。

當遇見變數時,JS引擎會從其所在的作用域依次向外層查詢,查詢會在找到第一個匹配的識別符號時停止。

 

在私有作用域中出現了變數,首先看是否為私有的,如果是私有變數,那麼就用私有的即可。如果不是私有變數,則往當前作用域的上級作用域查詢,如果上級作用域也沒有,則繼續往上查詢....一直找到window為止。

 

//變數的作用域,就是它var的時候最內層的function
function outer(){
   var a = 1; //a的作用域是outer
   inner();
   function inner(){
       var b = 2;  //b的作用域是inner
       console.log(a); //能輸出1,a在本層沒有定義,就往上找
       console.log(b); //能輸出2
   }
}
outer();
console.log(a); //報錯,因為a的作用域是outer

多層巢狀:如果有同名的變數,那麼就會發生“遮蔽效應”:

 

var a = 10; //全域性變數
function fn(){
   console.log(a); //undefined,提升宣告瞭區域性變數
   var a = 13;     //把外層的a變數遮蔽了,這函式內部看不見外層的a
   console.log(a); //輸出13,變數在當前作用域尋找,找到a定義為13
}
fn();
fn();
fn();
console.log(a); //10,變數在當前作用域尋找,找到全域性a

 

 

一個變數在使用的時候得幾?就會在當前作用域去尋找它的定義,找不到,去上一層找,直到找到全域性(window),如果全域性也沒有,就報錯。這就是作用域鏈。

 

題目:

var a = 1;        //全域性變數
var b = 2;        //全域性變數
function outer(){
    var a = 3;        //遮蔽了外層的a,a區域性變數
        function inner(){
            var b = 4;  //遮蔽了外層的b,b區域性變數
            console.log(a);   //① 輸出3,a現在在當前層找不到定義的,所以就上一層尋找
            console.log(b);   //② 輸出4
    }
    inner();        //呼叫函式
    console.log(a);    //③ 輸出3
    console.log(b); //④ 輸出2 b現在在當前層找不到定義的,所以就上一層尋找
}
outer();        //執行函式,控制權交給了outer
console.log(a);    // ⑤ 輸出1
console.log(b); // ⑥ 輸出2

2.3不寫var就自動成為全域性變數了

需要注意,函式內部宣告的時候,一定要用var命令,如果不用,實際上宣告瞭一個全域性變數。

 function fn(){

    a = 100;//這個a第一次賦值時,沒有var,所以就自動在全域性作用域var了一次

 }

 fn();

 console.log(a);//100

 

這是JS的機理,如果遇見一個識別符號,從來沒有var過,並賦值了:

 a = 100;

那麼就會自動在全域性作用域定義var a;

 


2.4函式的形參變數,會預設定義為這個函式的區域性變數

var a = 0;
var b = 0;
function fn(a,b){
   a = 3;
   b = 4;
   console.log(a,b);
}
fn();
console.log(a);
console.log(b);

a,b就是fn內部的區域性變數,只能在當前function函式內部使用,出了fn就沒有定義。

 


2.5全域性變數的作用

在函式內部使用自己的變數,儘量定義為區域性。

全域性變數有自己獨特的用途:累加、傳遞。

累加:函式沒執行一次,都要求變數在原來基礎上發生變化。

功能1:通訊,共同操作傳遞同一個變數

兩個函式同時操作一個變數,一個增加,一個減少,函式和函式通訊。

 

var num = 0; //全域性變數
function add(){
   num++;
}
function remove(){
   num--;
}
add();
add();
add();
add();
remove();
remove();
add();
add();
console.log(num); //4

功能2:累加,重複呼叫函式的時候,不會重置

 var num = 0;
 function baoshu(){
    num++;
    console.log(num);
 }
 baoshu();//1
 baoshu();//2
 baoshu();//3
 

 

如果num定義在baoshu裡面,每次執行完函式,作用域就被銷燬,所以裡面變數都是全新的。

function baoshu(){
    var num = 0;
    num++;
    console.log(num);
}

baoshu();    //1
baoshu();    //1
baoshu();    //1

 2.6函式定義也有作用域

function outer(){
   var a = 10;
   function inner(){ //區域性函式
       console.log("哈哈");
   }
}
outer();
inner(); //報錯,因為全域性作用域下,沒有inner函式的定義
console.log(a);//報錯

公式:

function 大(){
   function 小(){
 
   }
   小(); 可以執行
}
小(); //不能執行,因為小函式定義在大函式裡面,離開大函式就沒有作用域。

三、閉包

閉包有兩個作用:

1、可以讀取自身函式外部的變數(沿著作用域鏈尋找)

2、可以讓這些外部變數始終儲存在記憶體中

3.1閉包

推導過程:之前已經學習過,inner這個函式不能在outer外面呼叫,因為outer外面沒有inner定義。

當函式執行的時候,會形成一個新的私有作用域,來保護裡面的私有變數不受外界干擾,我們把函式的這種保護機制--->"閉包"

 

function fn(){

}
fn();

function outer(){
   var a = 100;
   function inner(){
       console.log(a);
   }
}
outer();
inner(); //在全域性作用域呼叫inner,全域性沒有inner的定義,所以報錯

但是我們就想在全域性作用域下,執行outer內部的inner,此時我們必須想一些奇奇怪怪的方法。

有一個簡單可行的方法,就是讓outer自己returninner

非常經典的閉包案例,任何培訓機構、書、講閉包,一定是下面的案例:

 

function outer(){
   var a = 888;
   function inner(){
       console.log(a); //888
   }
   return inner; //outer返回了inner的引用
}
var inn = outer();//inn就是inner函式了
inn(); //執行inn,全域性作用域下沒有a的定義,但是函式閉包,能夠把定義函式時的作用域一起記憶住,輸出888

一個函式可以把自己內部的語句,和自己宣告時,所處的作用域一起封裝成了一個密閉的環境,就叫“閉包”。

 

每個函式都是閉包,每個函式天生都能夠記憶自己定義時所處的作用域環境。但是,我們必須將這個函式,挪到別的作用域,才能更好的觀察閉包。這樣才能實驗它有沒有把作用域給記住

我們發現,把一個函式從它定義的那個作用域,挪走,執行。嘿,這個函式居然能夠記憶住定義時的那個作用域。不管函式走到哪裡,定義時的作用域就帶到了哪裡。這就是閉包。

 

閉包在工作中是一個用來防止產生隱患的事情,而不是加以利用的性質。

因為我們總喜歡在函式定義的環境中執行函式。從來不會把函式往外挪。那為啥學習閉包,防止一些隱患,面試絕對考。

 

使用全域性變數接收,返回函式:

 

var inn; //全域性變數
function outer(){
    var a = 250;
    var b = 500;
    //全域性變數inn此時被賦值了一個函式
    //這個函式此時將立即生成閉包,記憶住此時所處的環境
    inn = function inner(){
        console.log(a);
        console.log(b);
    }
}
outer();
var a = 300;
var b = 400;
inn();//一個函式在執行時,找閉包裡面的變數,不會理會當前作用域

閉包題目1

 

function outer(x){
   function inner(y){
       console.log(x+y)
   }
   return inner;
}
var inn = outer(3);//接收到了inner函式,inn就是inner
inn(5); //8
inn(7); //10
inn(10); //13

閉包題目2

 

function fun1(x,y){
   function fun2(x){
       console.log(x + y);
   }
   return fun2;
}
var f = fun1(3,4); //f就代表fun2
f();  //NaN
f(6); //10

一般情況下:當函式執行會形成一個私有的作用域(形參賦值→預解析→程式碼執行),當這三步都進行完成後,瀏覽器會剛剛開闢的這個私有作用域進行回收,也就是說,函式執行完成,作用域立即銷燬。

 


3.2閉包的性質

每次重新接收引用的函式時,閉包都是全新。

 

function outer(){
   var count = 0;
   function inner(){
       count++;
       console.log(count);
   }
   return inner;
}
var inn1 = outer();
var inn2 = outer(); //兩個變數引用的是同一個inner函式,實際上兩個引用變數中,都是全新閉包
inn1(); //1
inn1(); //2
inn1(); //3
inn1(); //4
inn2(); //1
inn2(); //2
inn1(); //5
inn1(); //6
inn1(); //7

無論它在何處被呼叫,它總是能訪問定義時所處作用域中的全域性變數。

每個新的函式,不管通過何種結構生成,閉包都是新的,作用域也是新的,語句也是新的。通過同一個結構組成兩個不同的函式,直接不會互相影響。

實際應用:要考慮閉包對程式造成影響,瞭解原因,平時不會寫個特殊結構。

 


3.3作用域銷燬的問題

在函式中,return後面返回的值如果是一個函式,這個函式是不參與預解釋的;函式體中return後面的程式碼也不執行,但是需要把後面的程式碼參預解釋;

 

銷燬的作用域:一般情況下,函式執行完成後,當前的作用域都立即銷燬;

不銷燬的作用域:

當函式執行時,在私有作用域中返回了一個引用資料型別的值(例如:函式、物件、陣列...)

並且在函式外面,有變數接收了這個返回值,此時當前的這個私有作用域就被佔用了,這個作用域也不能銷燬了;

作用域不銷燬,裡面的私有變數也不再銷燬了。

 

不立即銷燬的作用域:

當函式執行時,在私有作用域中返回了一個引用資料型別的值(例如:函式、物件、陣列...)

但是並沒有變數在函式的外面接收,那麼瀏覽器暫時先不銷燬,等到瀏覽器空閒的時候,會自己銷燬這個作用域。

 


 

相關文章