天天看點

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

一、自定義函數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函數      
前端筆記之JavaScript(四)關于函數、作用域、閉包那點事

函數必須先定義,然後才能調用。

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

 function 函數名稱(){

 }

函數如果不調用,裡面的語句一輩子都不執行,等于白寫。

調用函數的方法,就是函數名稱加(),()是一個運算符,表示執行一個函數。

 函數名稱()

一旦調用函數,函數内部的代碼不管對錯,都會執行。

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

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

1.2函數的參數

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

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

function fun(a){
   console.log("我第"+a+"次說你好!");
}
fun(100);
fun(1);
fun(2);
fun(3);      
前端筆記之JavaScript(四)關于函數、作用域、閉包那點事

調用的時候,把這個變量真實的值,一起寫在括号裡,這樣随着函數的調用,這個值也傳給了a變量參數。

羅列在function圓括号中的參數,叫做形式參數;調用時傳遞的數值,叫做實際參數。

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

參數可以有多個,用逗号隔開。

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:在調用函數時,不用關心函數内部的實作細節,甚至這個函數是你網上抄的,可以運作。是以這個東西,給我們團隊開發帶來了好處。

定義函數的時候,參數是什麼類型,不需要指定類型:

調用的時候,傳進去什麼類型,a、b變量就是什麼類型

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

另外,定義和調用的時候參數個數可以不一樣多,不報錯。

前端筆記之JavaScript(四)關于函數、作用域、閉包那點事
 sum(10);      

因為隻給a變量指派,b沒有被指派,b被隐式的var。b的值是undefined,10+undefined=NaN

前端筆記之JavaScript(四)關于函數、作用域、閉包那點事
 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的值,那麼現在這個函數,實際上是一個表達式,換句話說這個函數就是一個值。

是以這個函數,可以當做其他的函數參數。

前端筆記之JavaScript(四)關于函數、作用域、閉包那點事
 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();      
前端筆記之JavaScript(四)關于函數、作用域、閉包那點事

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 1、1、2、3、5、8、13、21、34、55、89、144、233...

輸出斐波那契數列

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

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()會引發錯誤。

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

也就是說,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("我是函數!");
}      
前端筆記之JavaScript(四)關于函數、作用域、閉包那點事

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

函數優先:

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

面試題:

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

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

1.8函數是一個引用類型

基本類型:Number、String、Boolean、undefined、null

引用類型:Object、function、array、RegExp、Math、Date。

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

console.log(typeof fun); //引用類型中的function類型
console.log(typeof haha);//引用類型中的function類型      
前端筆記之JavaScript(四)關于函數、作用域、閉包那點事

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

基本類型:儲存值

引用類型:儲存位址

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

現在變量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);      
前端筆記之JavaScript(四)關于函數、作用域、閉包那點事

總結:

預解釋:在js中,代碼從上到下執行之前,(浏覽器預設)首先會把所有帶var和function關鍵字的進行提前聲明或者定義

var num=88;

聲明(declare):相當于種樹時"挖坑"  var num; 隻聲明沒有定義時,num的預設值是undefined

定義(defined):相當于種樹時"栽樹"  num=88;(給變量指派)

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

var:隻是提前的聲明(定義指派的部分是在代碼執行的時候完成的)

function:提前的聲明+定義

在浏覽器加載HTML頁面時,首先會開辟一個供js代碼執行的環境-->"全局作用域"(window/global)

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

堆記憶體:在js中,對于引用資料類型來說,首先會開辟一個新的記憶體空間,然後把代碼存儲到這個空間中,最後把空間的位址給相關的變量--->我們把新開辟的這個記憶體空間稱為"堆記憶體"。

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

二、作用域

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

2.1函數能封閉住作業域

變量的作用域無非就兩種:全局變量和局部變量。

2.1.1全局變量(全局作用域)

全局變量:在最外層函數定義的變量擁有全局作用域,即對任何内部函數來說,都是可以通路的。

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

var a = 100; //定義在全局的變量,在程式任何一個角落都有定義
function fn(){
   console.log("我是函數裡面的語句,我認識全局變量a值為:" + a);
}
fn();
console.log("我是函數外面的語句,我認識全局變量a值為:" + a);      
前端筆記之JavaScript(四)關于函數、作用域、閉包那點事

2.1.2局部變量(局部作用域)

局部變量:和全局作用域相反,局部作用域一般隻在固定的代碼片段内可通路,而對于函數外部是無法通路的。

例如:變量定義在function裡面,這個變量就是局部變量,隻在目前這個function函數内部能使用。在函數外部不能使用這個變量,出了這個function,就如同沒有定義過一樣。

function fn(){
   var a = 100; //定義在函數的變量,局部變量
   console.log("我是函數裡面的語句,我認識變量a值為:" + a);
}
fn();
console.log("我是函數外面的語句,我認識變量a值為:" + a);      
前端筆記之JavaScript(四)關于函數、作用域、閉包那點事

a被var在了function裡面,是以現在這個a變量隻能在紅框範圍内有定義:

在ES5文法中,JavaScript變量作用域非常簡單,能關住作用域的隻有一個,就是:函數。

前端筆記之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      
前端筆記之JavaScript(四)關于函數、作用域、閉包那點事

多層嵌套:如果有同名的變量,那麼就會發生“遮蔽效應”:

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      
前端筆記之JavaScript(四)關于函數、作用域、閉包那點事

一個變量在使用的時候得幾?就會在目前作用域去尋找它的定義,找不到,去上一層找,直到找到全局(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);      
前端筆記之JavaScript(四)關于函數、作用域、閉包那點事

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自己return掉inner。

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

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

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

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

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

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

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

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

使用全局變量接收,傳回函數:

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後面的代碼也不執行,但是需要把後面的代碼參預解釋;

銷毀的作用域:一般情況下,函數執行完成後,目前的作用域都立即銷毀;

不銷毀的作用域:

當函數執行時,在私有作用域中傳回了一個引用資料類型的值(例如:函數、對象、數組...等)

并且在函數外面,有變量接收了這個傳回值,此時目前的這個私有作用域就被占用了,這個作用域也不能銷毀了;

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

不立即銷毀的作用域:

但是并沒有變量在函數的外面接收,那麼浏覽器暫時先不銷毀,等到浏覽器空閑的時候,會自己銷毀這個作用域。

文章都是本人學習時的筆記整理,希望看完後能對您有所幫助,歡迎大家提意見,多多交流。

也有些文章是轉載的,如果存在轉載文章且沒有标注轉載位址的,請與我聯系,馬上處理。

自由轉載-非商用-非衍生-保持署名。

繼續閱讀