JavaScript高級程序設計——第7章:函數表達式

定義函數的方式有兩種:一種是函數聲明,一種就是函數表達式。javascript

關於函數聲明,它的重要特徵:函數聲明提高(function declaration hosting)html

<!DOCTYPE html>
<html>
    <head>
        <title>Function Name Example</title>
    </head>
    <body>
        <script type="text/javascript">
            //函數聲明
            function functionName(){
                //noop
            }
            
            //works only in Firefox, Safari, Chrome, and Opera
            alert(functionName.name); //"functionName"
           
           //函數聲明提高
            sayHi();
            function sayHi(){
                alert("Hi!");
            }
           
           var fname=function(arg0,arg1){
           //函數體 這種狀況下建立的函數叫作匿名函數(有時候也叫拉姆達函數),由於function後面沒有標識符。匿名函數的name屬性是空字符串。
           }
        </script>
     
    </body>
</html>

如下代碼在ECMAScript中屬於無效語法,若是是使用函數表達式就沒有問題。java

<!DOCTYPE html>
<html>
<head>
    <title>Function Declaration Error Example</title>
</head>
<body>
    <script type="text/javascript">
    
    var condition = true;
    
    //never do this!
    if(condition){
        function sayHi(){
            alert("Hi!");
        }
    } else {
        function sayHi(){
            alert("Yo!");
        }
    }

    sayHi();
    </script>
 
</body>
</html>

 把函數當成值來使用的狀況下,均可以使用匿名函數。c#

7.1 遞歸 數組

經典的遞歸階乘函數,雖然表面上看沒有問題,但下面的代碼可能致使它出錯。閉包

<!DOCTYPE html>
<html>
    <head>
        <title>Recursion Example</title>
    </head>
    <body>
        <script type="text/javascript">
        
            function factorial(num){
                if (num <= 1){
                    return 1;
                } else {
                    return num * factorial(num-1);
                }
            }

            var anotherFactorial = factorial;
            factorial = null;
            alert(anotherFactorial(4));  //error!


        </script>
     
    </body>
</html>

在嚴格模式下不能經過腳本訪問 arguments.callee,訪問這個屬性會致使錯誤。app

<!DOCTYPE html>
<html>
    <head>
        <title>Recursion Example</title>
    </head>
    <body>
        <script type="text/javascript">
        
            function factorial(num){
                if (num <= 1){
                    return 1;
                } else {
                    return num * arguments.callee(num-1);
                }
            }

            var anotherFactorial = factorial;
            factorial = null;
            alert(anotherFactorial(4));  //24


        </script>
     
    </body>
</html>

以下方式在嚴格模式和非嚴格模式都行得通函數

<!DOCTYPE html>
<html>
    <head>
        <title>Recursion Example</title>
    </head>
    <body>
        <script type="text/javascript">
        
           factorial=(function f(num){
                if (num <= 1){
                    return 1;
                } else {
                    return num * f(num-1);
                }
            })

            var anotherFactorial = factorial;
            factorial = null;
            alert(anotherFactorial(4));  //24


        </script>
     
    </body>
</html>

 7.2 閉包oop

如何建立做用域鏈以及做用域鏈有什麼做用的細節,對完全理解閉包相當重要。當某個函數第一次被調用時,會建立一個執行環境(execution context)及相應的做用域鏈,並把做用域鏈賦值給一個特殊的內部屬性(即[[Scope]])。而後,使用this、argument和其它命名參數的值來初始化函數的活動對象(activetion object)。但在做用域鏈中,外部函數的活動對象始終處於第二位,外部函數的外部函數的活動對象處於第三位,......直至做爲做用域鏈終點的全局執行環境。後臺的每一個執行環境都有一個表示變量的對象---變量對象。全局的變量對象始終存在。而局部環境的變量對象,則只在函數執行的過程當中存在。做用域鏈本質上是一個指向變量對象的指針列表,它只引用但不實際包含變量對象。this

閉包是指有權訪問另外一個函數做用域中的變量的函數(函數調用返回後一個沒有釋放資源的棧區),建立閉包的常見方式,就是在一個函數內部建立另外一個函數。

因爲閉包會攜帶包含它的函數的做用域,所以比其餘函數佔用更多的內存。建議只在絕對必要時考慮使用閉包。

閉包的做用:一是能夠讀取函數內部的變量;二是讓這些變量的值始終保持在內存中。

7.2.1 閉包與變量

做用域鏈的這種配置機制引出了一個值得注意的反作用,即閉包只能取得包含函數中任何變量的最後一個值。別忘了閉包所保存的是整個變量對象,而不是某個特殊的變量,下例能夠清淅的說明問題:

<!DOCTYPE html>
<html>
    <head>
        <title>Closure Example</title>
    </head>
    <body>
        <script type="text/javascript">
        
            function createFunctions(){
                var result = new Array();                
                for (var i=0; i < 10; i++){
                    result[i] = function(){
                        return i;
                    };
                }
                
                return result;
            }
            
            var funcs = createFunctions();//此時變量i的值是10
            
            //every function outputs 10
            for (var i=0; i < funcs.length; i++){
                document.write(funcs[i]() + "<br />");
            }

        </script>
        表面上看好像每一個函數都應該返本身的索引值,但實際上每一個函數都返回10,由於第個函數的做用域鏈中都保存着createFunctions()函數的活動對象,因此它們都引用的同一個變量i,當createFunctions()函數返回後,變量i的值是10,此時每一個函數都引用着保存變量i的同一個變量對象,因此每一個函數的內部i的值都是10。
</body> </html>
<!DOCTYPE html>
<html>
    <head>
        <title>Closure Example 2</title>
    </head>
    <body>
        <script type="text/javascript">
        
            function createFunctions(){
                var result = new Array();
                
                for (var i=0; i < 10; i++){
                    result[i] = function(num){
                        return function(){
                            return num;
                        };
                    }(i);//這裏的匿名函數有一個參數num,也就是最終的函數要返回的值。在調用每一個匿名函數時,咱們傳入了變量i,因爲函數參數是按值傳遞的,因此就會將變量i的當前值複製給參數num。而這個匿名函數內部又建立和並返回了一個訪問num的閉包。這樣一來result數組中的每一個函數都有本身num變量的一個副本,所以就能夠返回各自不一樣的數值了。
                }
                
                return result;
            }
            
            var funcs = createFunctions();
            
            //every function outputs 10
            for (var i=0; i < funcs.length; i++){
                document.write(funcs[i]() + "<br />");
            }

        </script>
     
</body> </html>

 7.2.2 關於this對象

每一個函數在被調用時,其活動對象都會自動取得兩個特殊變量:this和arguments。內部函數在搜索這兩個變量時,只會搜索到其活動對象爲止,所以永遠不可能直接訪問外部函數中的這兩個變量。arguments和this同樣,也存在一樣的問題。若是想訪問做用域中的this(arguments)對象,必須將對該對象的引用保存到另外一個閉包可以訪問的對象裏。匿名函數的執行環境具備全局性,所以其this對象一般指向window。

<!DOCTYPE html>
<html>
<head>
    <title>This Object Example</title>
</head>
<body>
    <script type="text/javascript">
        var name = "The Window";
        
        var object = {
            name : "My Object",
        
            getNameFunc : function(){
                return function(){
                    return this.name;
                };
            }
        };
        
        alert(object.getNameFunc()());  //"The Window"


    </script>
 
</body>
</html>
<!DOCTYPE html>
<html>
    <head>
        <title>This Object Example 2</title>
    </head>
    <body>
        <script type="text/javascript">
        
            var name = "The Window";
            
            var object = {
                name : "My Object",
            
                getNameFunc : function(){
                    var that = this;// 把外部做用域中的this對象保存在一個閉包可以訪問到的變量裏,就可讓閉包訪問該對象了。
                    return function(){
                        return that.name;
                    };
                }
            };
            
            alert(object.getNameFunc()());  //"MyObject"


        </script>
     
    </body>
</html>
<!DOCTYPE html>
<html>
    <head>
        <title>This Object Example 3</title>
    </head>
    <body>
        <script type="text/javascript">
        
            var name = "The Window";
            
            var object = {
                name : "My Object",
            
                getName: function(){
                    return this.name;
                }
            };
            
            alert(object.getName());     //"My Object"
            alert((object.getName)());   //"My Object"
            alert((object.getName = object.getName)());   //"The Window" in non-strict mode

        </script>
     
    </body>
</html>

7.3 模仿塊級做用域

用做塊級做用域(一般稱爲私有做用域)的匿名函數的語法以下所示(這種技術常常在全局做用域中被用在函數外部,從而限制在全局做用域中添加過多的變量和函數):

(fuction(){

//這裏是塊級做用域

})()

定義並當即調用了一個匿名函數

fuction(){

//這裏是塊級做用域

}()//出錯!致使語法錯誤的緣由是由於javascript把fuction看成函數聲明的開始,而函數聲明後面不能跟圓括號。

不管在什麼地方,只要臨時須要一些變量,就可使用私有做用域,例如:

<!DOCTYPE html>
<html>
    <head>
        <title>Block Scope Example</title>
    </head>
    <body>
        <script type="text/javascript">
        
            function outputNumbers(count){
            
                (function () {
                    for (var i=0; i < count; i++){
                        alert(i);
                    }
                })();
                
                alert(i);   //causes an error 在匿名函數中定義的任何變量,都會在函數執行結束時被銷燬
            }

            outputNumbers(5);
        </script>
     
    </body>
</html>

通常來講咱們應該儘可能少向全局做用域中添加變量和函數,在一個由不少開發人員共同參與的大型應用程序中,過多的全局變量和函數很容易致使命名衝突。而經過建立私有做用域,開發人員既可使用本身的變量,又不怕搞亂全局做用域。

(function(){

     var now=new Date();

     if(now.getMonth()==0&&now.getDate==1){

         alert("happy new year!");

     }

  })(); 

這種做法能夠減小閉包占用的內存問題,由於沒有指向匿名函數的引用。只要函數執行完畢,就能夠當即銷燬其做用域鏈了。

 7.4 私有變量

 嚴格來說,javascript中沒有私有成員的概念,全部對象屬性都是公有的,不過卻是有一個私有變量的概念。私有變量包括:函數的參數、局部變量、在函數內部定義的其它函數(構造函數裏定義的function,即爲私有方法)。利用閉包能夠建立用於訪問私有變量的公有方法,把有權訪問私有變量和私有函數的公有方法稱爲特權方法,有兩種建立特權方法的方式: 

var Person = function(name,sex){
    this.name = name;
    this.sex = sex;     
    var _privateVariable = "";//私有變量    
    //構造器中定義的方法,即爲私有方法
    function privateMethod(){   
        _privateVariable = "private value";
        alert("私有方法被調用!私有成員值:" + _privateVariable);             
    }
    privateMethod(); //構造器內部能夠調用私有方法            
}
 
Person.prototype.sayHello = function(){
    alert("姓名:" + this.name + ",性別:" + this.sex);
}
 
var p = new Person("Nicholas",""); 
p.sayHello();

//p.privateMethod();//這裏將報錯,私成方法沒法被實例調用
alert(p._privateVariable);//顯示: undefined

說明:類的構造函數裏定義的function,即爲私有方法;而在構造函數裏用var聲明的變量,也至關因而私有變量。(不過類比於c#這類強類型語言中的私有成員概念仍是有區別的,好比沒法在非構造函數之外的其它方法中調用) 

相似的,咱們還能實現相似set,get屬性的封裝

<!DOCTYPE html>
<html>
    <head>
        <title>Privileged Method Example</title>
    </head>
    <body>
        <script type="text/javascript">
        
            function Person(name){
            
                this.getName = function(){
                    return name;
                };
            
                this.setName = function (value) {
                    name = value;
                };
            }
            
            var person = new Person("Nicholas");
            alert(person.getName());   //"Nicholas"
            person.setName("Greg");
            alert(person.getName());   //"Greg"


        </script>
     
    </body>
</html>

在構造函數中定義特權方法也有一個缺點,那就是必須使用構造函數模式來達到這個目的。使用靜態私有變量特權方法就能解決這個問題。

7.4.1 靜態私有變量 

<!DOCTYPE html>
<html>
    <head>
        <title>Privileged Method Example 2</title>
    </head>
    <body>
        <script type="text/javascript">
        
            (function(){
            
                var name = "";
                
                Person = function(value){                
                    name = value;                
                };
                
                Person.prototype.getName = function(){
                    return name;
                };
                
                Person.prototype.setName = function (value){
                    name = value;
                };
            })();
            
            var person1 = new Person("Nicholas");
            alert(person1.getName());   //"Nicholas"
            person1.setName("Greg");
            alert(person1.getName());   //"Greg"
                               
            var person2 = new Person("Michael");
            alert(person1.getName());   //"Michael"
            alert(person2.getName());   //"Michael"

        </script>
     
    </body>
</html>

 這個例子裏name變成了一個靜態的、由全部實例共享的屬性。以這種方式建立靜態私有變量會由於使用原型增進代碼利用,但每一個實例都沒有本身的私有變量。究竟是使用實例變量,仍是靜態私有變量,最終視具體需求而定。

7.4.2 模塊模式

前面的模式是用於爲自定義類型建立私有變量和特權方法的。模塊模式則是爲單例建立私有變量和特權方法。所謂單例,就是隻有一個實例的對象。

<!DOCTYPE html>
<html>
    <head>
        <title>Module Pattern Example</title>
    </head>
    <body>
        <script type="text/javascript">
        
            function BaseComponent(){
            }
            
            function OtherComponent(){
            }
        
            var application = function(){
            
                //private variables and functions
                var components = new Array();
            
                //initialization
                components.push(new BaseComponent());
            
                //public interface
                return {
                    getComponentCount : function(){
                        return components.length;
                    },//返回已註冊的組件數目
            
                    registerComponent : function(component){
                        if (typeof component == "object"){
                            components.push(component);
                        }
                    }//後者用於註冊新組件
                };
            }();

            application.registerComponent(new OtherComponent());
            alert(application.getComponentCount());  //2
        </script>
     
    </body>
</html>

在Web應用程序中,常常須要使用一個單例來管理應用程序級的信息。這個簡單的例子建立了一個用於管理組件的application對象。簡言之,若是必需要建立一個對象並以某些數據對其進行初始化,同時還要公開一些可以訪問這些私有數據的方法,那麼就可使用模塊模式。以這種模式建立的每一個單例都是Object的實例,由於最終要經過一個對象字面量來表示它。事實上這也沒什麼,畢竟單例一般都是做爲全局對象存在的,咱們不會將它傳遞給一個函數,所以也沒有必要用instansof操做符來檢查它的對象類型。

7.4.3 加強的模塊模式

<!DOCTYPE html>
<html>
    <head>
        <title>Module Pattern Example</title>
    </head>
    <body>
        <script type="text/javascript">
        
            function BaseComponent(){
            }
            
            function OtherComponent(){
            }
        
            var application = function(){
            
                //private variables and functions
                var components = new Array();
            
                //initialization
                components.push(new BaseComponent());
            
                //create a local copy of application
                var app = new BaseComponent();
            
                //public interface
                app.getComponentCount = function(){
                    return components.length;
                };
            
                app.registerComponent = function(component){
                    if (typeof component == "object"){
                        components.push(component);
                    }
                };
            
                //return it
                return app;
            }();

            alert(application instanceof BaseComponent);
            application.registerComponent(new OtherComponent());
            alert(application.getComponentCount());  //2
        </script>
     
    </body>
</html>

這種模式適合那些單例必須是某種類型的實例,同時必須添加某些屬性和(或)方法對其加以加強。

7.5 小結

javascript的函數表達式和閉包都是極其有用的特性,利用它們能夠實現不少功能。

相關文章
相關標籤/搜索