js之函數

1、定義:java

  1.函數聲明   function func () {}   數組

  2.函數表達式   var func = function () {} 瀏覽器

  注意點:緩存

  

var func = function test () {}
func(); // ok
test(); // 報錯, test isn't defined

匿名函數表達式 和 命名函數表達式 區別閉包

  (1)命名函數表達式app

function test () {}
console.log(test.name); // test

  (2)匿名函數表達式函數

var test  = function func () {}
console.log(func.name); // func is not defined
console.log(test.name); // func

 

 2、return做用this

  1.返回通過函數一系列處理的結果值spa

  2.終止函數的運行code

3、實參傳遞的數目和設定的形參數目相比,可多,可少,都不算錯

  爲何? 由於函數的形式上下文(一個對象)中有個名爲arguments屬性,其值爲一個類數組,儲存着全部傳遞過來的實參,因此調用函數傳實參時直接將全部實參按形參名做爲屬性名存入argumengs這個類數組中,而不會去在乎實參的數目和形參設定的數目是否同樣

   可經過funcName.length 查看形參數目, 經過arguments.length 查看實參數目

4、做用域

  一、定義:變量(變量做用域又稱爲上下文)和函數生效(能被訪問)的區域

  2.做用於相關定義:

    (1)執行期上下文:當函數執行時,會建立一個稱爲執行期上下文的內部對象。一個執行期上下文定義了一個函數執行時的環境,函數每次執行時對應的執行上下文都是獨一無二的,因此屢次調用一個函數會致使建立多個執行上下文,當函數執行完畢,本身的執行上下文被銷燬(注意僅僅銷燬本身自己的執行期上下文)。

    (2)查找變量:從做用域鏈(scope chain的頂端依次向下查找。

  三、經過函數來簡單理解的做用域

 以下js代碼:

  

function a() {
            var aa = 'aa';
            function b() {
                var bb = 'bb';
                console.log(aa);
          console.log(b); }
return b; } var demo = a(); demo(); // aa function b(){...}

  首先,在a函數定義時會生成一個將全局執行期上下文存入scope chain(做用域鏈),但不會產生本身自己的執行期上下文。

  而後,在a函數執行後,會再次造成一個新的屬於本身的scope chain(做用域鏈),同時將全局執行期上下文(執行期上下文是一個對象,爲引用值)存入剛造成的scope chain頂部,而後再將本身自己的執行期上下文存入剛造成的scope chain頂部,也可這樣理解,scope chain是一個數組,如今0序號位置存的是a函數本身自己的執行期上下文引用,1序號位置存的是全局執行器上下文引用(從數組頭部插入)

(若在a函數在內部訪問一個變量,則去scope chain中尋找該變量,先從0位置開始尋找,若0位置存儲的執行期上下文沒有該變量,則再看1序號位置存儲的,直到找到該變量爲止,最壞的狀況,scope chain 中沒有改變量的信息,則確定是報錯了唄,嘿嘿),

咱們須要注意一個狀況,在a函數執行的過程當中在a函數內定義了一個函數b,因此b函數會和a函數同樣造成scope chain,再將全局執行期上下文的引用、a的執行器上下文的引用按順序存入scope chain中

  (只有a函數執行,纔會產生a函數本身自己的執行器上下文,纔會b函數被定義,  也正由於如此,b函數定義時才能將a函數的執行期上下文存入本身的scope chain中,  因此最終在b函數內部才能打印出b函數,這是串行操做,中間一個環節斷了都不行,這是個人我的理解,僅限參考,嘿嘿)

  (最後最重要的,a函數執行的最終結果是將定義好的b函數返回給demo,此過程是將b函數的引用賦值給demo(因此demo函數執行打印的是b函數),須要注意的是,此賦值過程同時是變量demo將b函數定義時產生的scope chain也一樣完整的繼承了過去,即demo的scope chain中1和0序號位置分別存儲了全局執行期上下文的引用的函數a的執行期上下文的引用,因此demo函數才能打印出a函數內定義的變量aa和a函數內定義的函數b)。

 

  ( 注意這裏的存入,值得是scope chain該索引位置存的是執行期上下文對象的引用,能夠形象的想象爲scope chain該索引位置用箭頭指向了執行期上下文)

  最後一點,其實其中所說的執行期上下文就是預編譯中所建立的AO對象(Activation Object),不懂預編譯的能夠去了解一下,將做用域和預編譯一塊兒理解感受會好一些

5、閉包

  一、定義:當內部函數(在函數內定義的函數)被保存到外部時,將會生成閉包。這是最多見的一種 狀況而已,廣義來講,就是一個做用域引用或保存了另外一個做用域的值,致使另外一個做用域 本該銷燬被沒法銷燬,這就是閉包。

  二、閉包是這樣的:

  仍然經過上邊的例子解釋:

function a() {
            var aa = 'aa';
            function b() {
                var bb = 'bb';
                console.log(aa);
          console.log(b);
            }
            return b;
       }
       var demo = a();
       demo(); // aa  function b(){...}

 

   首先,問一個問題?大家是否發如今a函數中完畢後,按定義說a的執行器上下文應該被銷燬啊,那爲何demo執行後仍然能打印出aa和b函數呢?

   緣由就是造成了閉包,致使a函數執行結束後a的執行期上下文沒有被銷燬,全部demo執行仍能正常打印,也就是因爲函數b被return出來給了demo,使demo和a的執行期上下文有聯繫,也就是demo佔用了a的執行期上下文,因此a執行完畢後a的執行期上下文不會被銷燬,最重要的是demo執行結束後,demo自己的執行期上下文被銷燬,但其鏈接的a的執行期上下文中存儲了b函數,即b函數的引用(至關於demo),因此a的執行期上下文永遠不會被銷燬(能夠經過賦值demo爲null,表示demo不佔用a的執行期上下文,那麼a的執行期上下文就能夠被銷燬了),這就造成了閉包,這就是閉包的基本原理。

  3.缺點:閉包會致使原有做用域鏈不釋放,形成內存泄露。

    解釋: 上面原本a函數執行完畢後其執行期上下文應該被銷燬的,但由於閉包緣由沒有被銷燬,因此致使可以使用的總內存量減小,即內存泄漏。

  4.簡單應用:

    (1)實現公有變量:

  eg: 函數累加器

  

  // 閉包造成的累加器,變量num就相似java中說的共有變量
      function bibao() {
          var num = 0;
          return function () {
              console.log(num++);
          }
      }
      var add1 = bibao();
      add1(); //0
      add1(); //1
      add1(); //2
      add1(); //3
      add1(); //4  

  有於閉包緣由,致使bibao函數的執行期上下文不會被銷燬,因此num一直能夠在以前基礎上累加    

  (2)能夠作緩存:

  

//    兩個函數公用eater函數的做用域鏈
        function eater() {
            var food;
            var obj = {
                eat: function () {
                    if (food) {
                        console.log('I eat ' + food);
                        food = null;
                    }else {
                        console.log('There is nothing');
                    }
                },
                push: function (myFood) {
                    food = myFood;
                }
            }
            return obj;
        }   
        var oEater = eater();
        oEater.eat(); //There is nothing
        oEater.push('apple');
        oEater.eat(); //I eat apple

  (3)、實現封裝,屬性私有化

   又是咱們須要這樣一些需求,咱們但願函數內部的某些屬性沒法被外部寫,僅僅只能被外部讀而已等等。

  最初,你們是共同制定了一個約定,那就是在函數內部不但願被外部寫的屬性錢加一個下劃線,標示着該屬性不能被其餘人更改,可是該約定約束性不強。

  後來,你們想到了一個方法,那就是用閉包來實現屬性的私有化,此方法具備很強的約束性。以下demo舉例:

function demo () {
     var num = 1,
         name = 'lyl';

      // 和返回一個函數造成的閉包是同樣的,只不過此處是返回多個函數,且多個函數用一個對象包裝了。
      // 該對象中的多個函數都佔用了demo執行時產生的執行期上下文,致使demo執行時產生的執行期上下文不會被銷燬,從而產生閉包
      return  {
         sayName: function () {
           console.log(name);
         },
         sayNum: function () {
           console.log(num);
         }
      }
   }

   var obj = demo();
   obj.sayName(); // lyl
   console.log(obj.num); //undefined

 

 

  5.閉包的防範:閉包會致使多個執行函數共用一個公有變量,若是不是特殊須要,應儘可能防止這種狀況發生。

 以下例子:arr數組中多個值公用一個test函數執行期上下文的變量 i,致使打印結果超出意料

  

 function test() {
            var arr = [];
            for(var i = 0; i < 10; i++ ) {
            //arr中全部值對應一個執行期上下文,其中的i是隨循環不斷累加變化的,最終i固定爲10不變,因此打印出的都爲10
                arr[i] = function () {
                    console.log(i);
                }
            }
            return arr;
        }
        var retArr = test();
        for(var i = 0, len = retArr.length; i < len; i++ ) {
            retArr[i]();
        } 
        // 打印結果是10個10

  解決方法: 利用當即執行函數(下面會講),讓arr數組中每一個值都對應一個獨立的執行期上下文,避免多個值公用一個執行期上下文的變量

function test() {
            var arr = [];
            for(var i = 0; i < 10; i++) {
               (function(j) {
                //    每次都將對應的i轉換爲j,因此每次arr[j]對應一個本身獨有的執行期上下文,其中的j是固定不變的,因此能從0打印到9
                // 即arr數組中有10個數,則分別對應10個獨立的執行期上下文
                   arr[j] = function () {
                       console.log(j);
                   }
               } (i) );
            }
            return arr;
        }
        var retArr = test();
        for(var i = 0, len = retArr.length; i < len; i++) {
            retArr[i]();
        }
        // 打印結果爲0 1 2 3 4 5 6 7 8 9 

 

  

  

 6、當即執行函數

  1.定義:此類函數沒有聲明,在一次執行事後即釋放。適合作初始化工做。(即此函數不需調用,直接執行)

  二、使用形式以下:

 

 //    當即執行函數基本形式
        // 第一種,推薦
       (function (形參) {
           //handle code
       } (實參) );
        // 第二種
        (function (形參) {
            // handle code
        } )(實參);   
    // demo
        var ret = (function (a, b) {
            return a + b;
        } (1, 2) );
        console.log(ret); //3

 

   三、當即執行函數的原理:

   爲何這樣寫,就能夠執行了?大概原理以下:

  其實重點在於小括號上,小括號()表明了執行,而()前放什麼才能夠執行呢?答案是表達式。 那表達式是什麼呢?權威指南上關於表達式有這樣一句話‘表達式是JavaScript的一個短語,JavaScript解釋器會將其計算出一個結果’,簡單來講只要是某一個類型的值,就能夠看做表達式,固然此處關於表達式的理解是不全面的,但對於當即執行函數的當即來講是足夠的了。因此函數執行就能夠解釋了,函數名是該函數的引用,爲一個函數表達式,因此加上()後也就能夠執行了。下面根據此理論來解釋當即執行函數:

  凡是()圈起來的都是表達式,因此(function (){})爲一個表達式,而後再加上一個()則能夠執行,因此實參放入後面那個括號中,形參放於function後的括號中也就合理了;而(function() {} ())此種形式能夠算做當即執行函數的一個標準寫法,省去了function (){}轉換爲表達式的時間,執行能更快一些。

  除了經常使用的一些正規寫法外,利用其原理,還有一些其餘寫法,一樣能實現當即執行函數的效果。只要()前是表達式便可。以下:

// 成功的demo
        var demo1 = function func() {console.log('demo1')}(); //demo1,  demo1被賦值爲實名函數表達式

        var demo2 = function () {console.log('demo2')}; // demo2, demo2被複製爲匿名函數表達式

        +function (){console.log('demo3')}; // demo3, 經過+運算符的隱士類型轉換將+後面的轉換爲number原始表達式,因此也就能夠執行了
        console.log(typeof +function (){}); //number

        -function (){console.log('demo4')}; // demo4, 經過-運算符的隱士類型轉換將-後面的轉換爲number原始表達式,因此也就能夠執行了
         console.log(typeof -function (){}); //number

        !function (){console.log('demo5')}; // demo5, 經過+運算符的隱士類型轉換將+後面的轉換爲boolean原始表達式,因此也就能夠執行了
        console.log(typeof !function (){}); //boolean

        1 && function (){console.log('demo6')}(); // demo6, 經過&&運算符的隱士類型轉換將&&後面的轉換爲boolean原始表達式,因此也就能夠執行了,但此處須要注意一點,
                                &&運算符僅僅判斷是將值隱士轉換爲boolean類型的,但返回的是原值,由於都斷定爲true因此返回&&後面的原始值,不然返回&&前面的原始值
0 || function () {console.log('demo7')}(); // demo7, ||隱士類型轉換爲boolean原始表達式,因此能夠執行,0 斷定失敗, 看||後面的,||後面的斷定爲true,返回該函數因此執行 // 失敗的須要注意的demo *function (){console.log('demo1')}(); // 報錯,覺得*沒法隱士類型轉換 1 || function (){console.log('demo2')}(); // 報錯,由於1斷定爲true,因此不看||後面的,直接返回1,因此沒法執行 0 && function (){console.log('demo3')}(); // 報錯,由於0斷定爲false,因此不看&&後面的,直接返回0,因此沒法執行

 

 

 7、預編譯:

   一、js運行三部曲:

    (1)、語法分析(通篇分析查找是否存在低級語法錯誤,低級語法錯誤是直接能夠看出的,不用執行就能夠找出的)

  *低級語法錯誤:

  

  console.log('aa');
         console.log('a';  //低級語法錯誤,不用執行就能夠直接看出的 |* 影響上面代碼的執行,也影響下面代碼的執行
        //  執行結果: missing ) after argument list

  *邏輯語法錯誤:

    

 console.log('aa'); 
         console.log(a);  //邏輯語法錯誤,沒法直接看出,須要執行瀏覽器執行才能找出 |*不影響上面代碼的執行,但影響下面代碼的執行
         //執行結果: 
         //aa
         //a is not defined(…) 

 

    (2)、預編譯

  *預編譯前奏:

    i、imply global 暗示全局變量:即任何變量,若是變量未經聲明就賦值,此變量就爲全局對象全部

  

        function test() {
            var a = b = 10;
        }
        test();
        console.log(b); // 10
        console.log(a); //  a is not defined(…)
        // 此demo存在一個坑, 就是連續賦值 var a = b = 10;
        // 該賦值能夠分解爲兩條語句,分別爲b = 10; var a = b;
        // 因此b歸全局對象全部,而a歸test執行時的執行期上下文全部,
        //外部全局對象window訪問不了test內部的a變量,而且test執行結束後a會隨着test的執行期上下文銷燬而消失

     ii、一切聲明的全局變量,全是window的屬性。

  

        var a = 10;
        console.log(window.a); // 10

 (題外話:關於此處有關於對象的一個知識點,關聯一下, delete操做符能夠刪除對象的屬性,因此未經聲明就賦值的變量爲window對象的屬性,能夠用delete操做符刪除,但在全局環境中用var聲明的變量雖然能夠用window.name訪問,但沒法用delete操做符刪除)(能夠這樣理解,不用var聲明直接爲變量賦值的行爲是爲全局對象的屬性賦值的操做,而用var聲明的變量是真正的咱們所說的變量,而不是某個對象的屬性,固然該變量也是當前環境下this的屬性)

 

  *預編譯四部曲:

  i、建立AO(Activation Object)對象

  ii、找形參和變量聲明,將變量和形參名做爲AO屬性名,值爲undefined

  iii、將實參值和形參統一

  iiii、在函數體裏面找函數聲明,值賦予函數體

  var a = 20;
       function test () {
           console.log(a);
        //    此處一個小坑
           if(false) {
               var a = 10;
           }
       }
       test();  // 20 or undefined? answer is undefined
    //    預編譯時與if判斷的正誤沒有關係,只有在解釋執行時纔會在意if判斷的正誤

 

    (3)、解釋執行

 

 

 

 

------------------------------end

相關文章
相關標籤/搜索