1. 程式人生 > >JavaScript初階(三)--------函數、閉包、立即執行函數

JavaScript初階(三)--------函數、閉包、立即執行函數

argument 預編譯 func span 參數 暗示 zhang 全部 所有

 函數 

    有時候我們的代碼重復了很多次,編程裏面稱為耦合,但是編程要講究高內聚,弱耦合。為了將重復多的聚在一起就出現了函數。

定義

    函數基本要素:函數聲明(function),函數名稱,參數(形參,實參),返回值。

   1.首先函數命名方式采用小駝峰式寫法,即第一個單詞小寫,後面的單詞首字母大寫,如 function oneNumber(){}

2.函數表達方式裏面有函數表達式,匿名函數表達式

var a = function lala() {}//函數表達式
var b = function () {}//匿名函數表達式

為了便於使用以及方便,我們之後基本都是采用匿名函數表達式,並且稱為函數表達式。

return返回值

   作用一:返回函數最終執行結果

   作用二:終止函數


  

函數作用域

    變量和函數生效的區域叫作用域,作用域分為全局作用域和局部作用域。訪問時,裏面的作用域可以訪問外面的,外面的不能訪問裏面的。

   [[scope]]:每個javascript函數都是一個對象,對象中有些屬性我們可以訪問,但有些不可以,這些屬性僅供javascript引擎存取,[[scope]]就是其中一個。

[[scope]]指的就是我們所說的作用域,其中存儲了運行期上下文的集合。

   作用域鏈:[[scope]]中所存儲的執行期上下文對象的集合,這個集合呈鏈式鏈接,我們把這種鏈式鏈接叫做作用域鏈。

   運行期上下文:當函數執行時,會創建一個稱為執行期上下文的內部對象。一個執行期上下文定義了一個函數執行時的環境,函數每次執行時產生對應的執行上下

文都是獨一無二的,所以多次調用一個函數會導致創建多個執行上下文,當函數執行完畢,執行上下文被銷毀。


預編譯

      js運行三步:語法分析,預編譯,開始執行

  暗示全局變量

     函數裏面沒有聲明直接賦值的變量稱為暗示全局變量,能夠被全局調用,而且全局變量都是window上的屬性,可以通過window.name來調用。

預編譯四部曲

        1.創建AO對象(執行上下文對象)

        2.找函數聲明和函數中的變量聲明,並賦值undefined

        3.賦值(形參和實參統一)

        4.函數體賦值給對應的AO屬性

  舉個栗子看看吧

    function fn(a){
        console.log(a);        // ?unction a(){ }
        var a = 123;
        console.log(a);        // 123
        function a(){ }      
        console.log(a);        //123
        console.log(b);        //undefined
        var b = function(){};      
        console.log(b);          //function () {}
        console.log(d);         // function d(){}
        function d(){}
    }
    fn(1);

      首先產生執行期上下文,然後找函數聲明和變量聲明a,b,d,下面是整個預編譯過程。

        a --> undefined --> 1 --> function a ( ) { } --> 123

        b --> undefined --> function ( ) { }

        d --> undefined --> function d( ) { }  

      最後得出各AO屬性對應的值

        function與var之間覆蓋的問題,例如:

         

 function bar(){
    return foo;
    function foo(){ }
    var foo = 111
    }
    console.log(bar());   // 一開始就被return

  

 function bar(){
    foo = 10; 
    function foo(){ {;
    var foo = 11;
    return foo;
    }
    console.log(bar());     // 11 foo被賦值為11,最後才被return!!

  

以上就是整個預編譯過程


閉包

    說了那麽多其實就是為了給閉包做鋪墊,閉包會導致多個執行函數共用一個公有變量。所以一般如果不是特殊需要,盡量少用。容易汙染全局變量。

舉個栗子     

function a(){
   function b(){
   var bbb  = 234;
   console.log(aaa);
   }
   var aaa = 123;
   return b;
  }
  a();    //*****************************function b() {} 
var demo = a();
  demo(); 

  最後我們會發現,demo --> function b () { },demo() --> 123。這是因為當b執行完了相當於a也執行完了,這樣a會把執行上下文銷毀,但是b已經被return出去並且給了demo,當demo執行時也就是相當於function b () { }執行,得出123。這就是內存泄露,原有的作用域鏈不釋放導致的。

來個栗子鞏固一下:

 function a() { 
      var aaa = 100;
      function b() {
          console.log(aaa);
          }
       return b;
    }
    var demo = a();
    demo(); //100  b的勞動成果已經保存到了demo裏面

  當a函數定義的時候,產生一個執行上下文,裏面有一個aaa,和一個函數b,當b定義的時候,已經含有a的勞動成果,意思就是它已經有a的執行上下文,並且在b執行的時候,產生它自己的執行上下文,最後當a函數執行完之後,把函數b返回到了全局作用域,雖然a執行完,並且銷毀了它自己的執行上下文,但是因為其內部b函數的存在,仍然有a的全部執行上下文,所以,仍然可以通過demo來訪問function a裏面的aaa變量。

閉包的應用

  1.實現公有變量

     function add() {
         var num = 0;
         function demo(){
            num++;
            console.log(num);
            }
            return demo;
       }
      var test = add();
      test();//1
      test();//2

   對於上述栗子,公有變量就是num。add函數將demo函數返回出去,demo函數依然有add函數的執行上下文,每次執行test(),就相當於執行demo函數,每次訪問的num都是同一個

num變量,這樣num就是一個公有變量了,通過這種方式就能利用閉包產生一個累加器了。

2.做緩存機構

  

 function test(){
           var num = 0;
           function a() {
              console.log(++num);
            }
           function b() {
              console.log(--num);
            }

            return [a,b];
    }
        var arr = test();
        arr[0]();//1
        arr[1]();//0

    a函數和b函數都被return到了外部,這樣a函數和b函數都與num產生了一個閉包,並且a和b執行的都是同一個變量,當a改變num的時候,b的num也會發生改變,同理,b操作了

num,a的num也會發生改變,因為它們指向的num是同一個num,這就相當於一個緩存。

再來一個栗子

function eater() {
   var food = "";
   var obj = {
       eat : function() {
           if(food == "" ){
               console.log(‘empty‘);
               }else{
                   console.log("I am eating " + food);
                   food = "";
               }
           },
       push : function (myFood) {
               food = muFood
           }
       }
       return obj;
   }
   var eater1 = eater();
   eater1.eat();
   eater1.push(‘orange‘);
   eater1.eat();

    和上一個栗子一樣,obj對象被return到了外部,並且用eater1來接收。eater1.eat()和eater1.push()所對應的food是同一個,這就是利用閉包產生緩存機構。

  

  3.私有化變量

      這個作用的了解需要先了解一下構造函數

      function Deng(){
                var prepareWife = "xiaozhang";
                var obj = {
                    name : "Laodeng",
                    age : 40,
                    sex : "male",
                    wife : "xiaoliu",
                    divorce : function () {
                        this.wife = delete this.wife;
                    },
                    getMarried :function () {
                        this.wife = prepareWife;
                    },
                    changePrepare : function (someone) {
                    preparewife = someone;
                        },
                    sayMywife : function (){
                        console.log(this.wife);
                    }
                }
                return obj ;
            }
            deng = Deng();

  

  運行一下看看

            deng.sayMyWife()         //"xiaoliu"
            deng.divorce()           //undefined (沒有返回值)
            deng.sayMywife()           // true已經刪除
            deng.changePrepare(‘xiaoxiaozhang‘)   //undefined (函數沒有返回值)********
            deng.getMarried()        //undefined
            deng.sayMywife()         //"xiaoxiaozhang"
            deng.prepareWife         //undefined              

 

  prepareWife函數裏面的變量,不在全局作用域裏,所以我們訪問不了,但是我們所有的操作都是圍繞prepareWife來進行的,它們都可以正常訪問這個變量,所以,像這種只能通過與

這個變量產生閉包的方法,屬性,才能給對那個變量進行訪問,所以,我們就稱之為,私有化變量,我們可以通過定制接口(各種方法),來對變量的安全程度進行設置。


立即執行函數

    此類函數沒有聲明,在一次執行過後即釋放。適合做初始化工作。比如有些數學公式,或者是其他一些常數的計算,我們沒有必要把它一直放在全局的空間裏,這樣會很好內存,於

是就誕生了立即執行函數。

var x = (function(a, b){
                    return(a + b) ;
            }(1, 2)) // x = 3

   立即執行函數特點就是當JavaScript引擎解析到這個語句的時候就會馬上執行,執行結束後馬上把自己的執行上下文都銷毀。這樣就可以釋放這裏的內存,立即執行函數可以有返回值

以及形參等。

我們要知道函數聲明不能執行,只有函數才能執行,所以

函數聲明----->表達式

function test() { } // 函數聲明,不是表達式

  var test = function () ; // 函數表達式

我們可以把函數聲明轉換成表達式

        1.+function test(){ } -----> +號運算符,這樣就將函數聲明轉換成表達式,就可以執行了

2. !function test(){ } ------> !

        3. (function test(){ })( ) -------> ( )


利用立即執行函數解決閉包問題

  想要實現打印0-9,結果卻出人意料。。。。。。。(出現了10個10)。這個我想了好久才想通了,唉,腦子不夠用。。。

  function test() {
      var arr = [ ];
      for(var i = 0 ;i < 10; i++){
             arr[i] = function (){
                 console.log(i + ",");
                 }
              }
              return arr;
          }
      var demo = test();
      for(var j = 0; j < 10 ;j ++ ){
          demo[j]();
          }// 10 * 10

這是為什麽呢?輸出的10個全是10,說明這裏的i都是同一個i,為什麽會這樣呢,原來是function執行的時候i已經就是10了,由於test與arr之間產生了閉包,先這樣說吧,每次return出

一個arr,但是function現在還沒有執行,也就是都是arr[i],當終止循環的時候i=10,這時候function執行的時候10個i都是相加到10的那個i,是同一個i,所以最後打印出10個10。理解了

嗎?感覺說的太通俗了,希望大家能夠理解哈。

    

      如果覺得我說的有問題,歡迎提出來,大家一起進步哈!

  function test() {
         var arr = [];
         for(var i = 0; i < 10; i++){
            (function (j) {
                console.log(j);
                }(i))
             }
             return arr;
         }

利用立即執行函數,每次訪問的i都不一樣,所以打印出來的就是0-9了。

最後再來一個栗子

          a = 100 ;
          function demo(e) {
             function e() {}    
             arguments[0] = 2;
             document.write(e);   //2 因為形參列表將e改變為2
             if(a) {
                 var b = 123;
                 function c() { }
             }
             var c ;   
             a = 10 ;
             var a ;
             document.write(b);  // undefined
             f = 123;
             docuemnt.write(c);  //function (){}
             docuemnt.write(a); // 10
          }
          var a;
          demo(1)
          docuemnt.write(a);
          document.write(f);

JavaScript初階(三)--------函數、閉包、立即執行函數