跳到主要內容
黯羽輕揚每天積累一點點

JS 學習筆記 3_函數表達式

免費2015-04-07#JS#js函数表达式

說到函數表達式能扯的東西就多了:閉包、this、作用域鏈、私有屬性。。還可以再往裡邊扯:context,變量對象,活動對象,內部屬性。。。

##1. 函數表達式與函數聲明的區別##

函數聲明有「提升」(hoisting)的特性,而函數表達式沒有。也就是說,函數聲明會在加載代碼時被預先加載到 context 中,而函數表達式只有在執行表達式語句時才會被加載

##2. 閉包##

有權訪問另一個函數作用域中的變量的函數。閉包可以訪問另一個作用域中的變量,因此閉包得到的變量值是最終值,而不是該變量在某一時刻的值,有一個很經典的例子:

function createFuns(){
  var result = new Array();
  for(var i = 0;i < 10;i++){
    result[i] = function(){
      return i;
    };
    
    /*
    result[i] = function(arg){
      return function(){
        return arg;
      }
    }(i);//此處匿名函數立即執行的 () 可以省略,因為 function 在等號右邊出現,不存在歧義(一般形式是 (function(){})())
    */
  }
  
  return result;
}

createFuns 函數返回一組函數,這些函數執行結果都是 10(閉包得到的變量值是最終值),但註釋中的方式返回函數的執行結果就是 i 的當前值,因為是值傳遞

##3. 函數表達式中的 this##

內部函數無法直接訪問外部函數中的 this 和 arguments 對象,因為內部函數在搜索這兩個變量時,只會搜索到其活動對象為止

P.S. 活動對象是作用域鏈的實體,作用域鏈是抽象概念,而活動對象就是這個概念的具體實現。其實作用域鏈映射到代碼裡就是變量對象鏈條,又扯出了變量對象,不用怕,一點都不複雜:

執行環境(context)中定義的所有變量和函數都保存在變量對象中,如果執行環境是一個函數,那就把函數的活動對象當作變量對象,並作為變量對象鏈的一環,也就是作用域鏈的一環。

一開始函數的活動對象只有一個屬性——arguments 對象,每在函數內部聲明一個自定義屬性,就給該函數的活動對象添加一個屬性。。。

嗯,扯了這麼多,其實就一句話:this 就是活動對象/變量對象的引用

如果還不大明白,請參考 前輩的博文,順便推薦這位前輩的其它博文,關於 js 的都很不錯

##4. 重複聲明變量##

不會引起語法錯誤,會自動忽略多餘的聲明,但聲明同時的初始化操作被執行,例如:

var x = 1;
var x = 2;//var 聲明被忽略,所以不報錯,但賦值會被執行
alert(x);//2

##5. 實現塊級作用域的思路##

聲明一個匿名函數並立即調用,那麼匿名函數內部就是塊級作用域。具體實現:

(function(){/*塊級作用域*/})();

注意:需要用圓括號來消除函數表達式與函數聲明的歧義(從解釋器的角度看就是這樣),因為函數聲明後面不能直接跟圓括號,而函數表達式可以。

P.S. 示例中的代碼只是一種實現 IIFE 的方式,還有幾種,具體請參考 [javascript]IIFE 立即執行的函數表達式,這篇博文給出了詳細的對比

##6. 私有變量##

函數內部用 var 或者 function 聲明的變量是私有變量。(實例無法直接訪問,可以通過定義公有函數來提供訪問接口)

而用 this.attr = value; 方式聲明的變量是公有變量。(實例可以直接訪問)

##7. 閉包、匿名函數、內部函數、內部匿名函數的區別##

  • 閉包:有權訪問另一個函數作用域中的變量的函數

  • 匿名函數:沒有名字的函數表達式

  • 內部函數:在函數內部聲明的函數,也就是閉包

  • 內部匿名函數:在函數內部聲明的匿名函數,當然,也是閉包

P.S. 濫用閉包可能會佔用大量記憶體,閉包之所以能夠訪問外層函數作用域中的變量,是因為閉包的活動對象持有外層函數活動對象的引用

只有在閉包被銷毀後,外層函數中的變量才能被銷毀,不能及時銷毀,所以可能佔用大量記憶體

##8. 執行函數的整個過程##

  1. 創建執行環境 context,context 有內部屬性 [[Scope]]

  2. 更新作用域鏈 ScopeChain

  3. 創建活動對象

  4. 初始化活動對象的 this, arguments, 形參等各個屬性

  5. 執行函數體

  6. 銷毀活動對象(存在閉包時無法銷毀)

  7. 更新作用域鏈

##9.js 單例模式與模組模式##

  • 單例模式:創建只有一個實例的對象的一種模式,說白了,模式就是方法,設計模式就是前輩總結出來的好方法。js 中實現單例模式尤其簡單:

     var singleton = {attr1: value1, attr2: value2};
    

沒錯,就是對象字面量,其實就是創建了匿名對象,不知道構造函數的名字,當然無法創建第 2 個實例了

  • 模組模式:道格拉斯提出的用來增強單例的方法,可以給單例添加私有屬性和公有屬性(有時候也叫特權屬性,表示有權修改私有屬性的屬性),例如:

     var singleton = function(){
       //私有屬性
       var privateStr = 'secret';
       function addPrefix(){
         privateStr = 'this is my ' + privateStr;
       }
       
       //公有屬性(特權屬性)
       return{//返回一個匿名對象
         getStr : function(){
           addPrefix();
           return privateStr;
         }
       };
     }();//又是匿名函數立即執行
     
     alert(singleton.getStr());
     
    

P.S. 如果不需要單例,只要保護私有屬性的話,可以這樣做:

function Cat(){
  //私有屬性
  var privateStr = 'secret';
  function addPrefix(){
    privateStr = 'this is my ' + privateStr;
  }
  
  //公有屬性(特權屬性)
  this.getStr = function(){
    addPrefix();
    return privateStr;
  }
}
var obj = new Cat();
alert(obj.getStr());

參考資料###

評論

暫無評論,快來發表你的看法吧

提交評論