1. 程式人生 > >Js中的函式型別及宣告和表示式

Js中的函式型別及宣告和表示式

Js中比較有趣的恐怕就是函式型別了:

function value(){
return value;
}
alert(typeof value);    //“function”

typeof是一個獲得運算元型別的操作符,列印結果是function型別,這和傳統的c和c++之類的程式語言不一樣,在Js中,function是一個型別。並且,實際上也是一個物件,因為,每一個函式實際上也是Object物件的繼承。所以,函式也有自己的屬性,最典型的就是this和argument屬性,this實際上也是一個物件。對於函式而言,函式名只是一個指標,所以,當你要引用函式而不是執行函式時,就可以去掉後面的括號,比如將一個函式賦值給一個變數:

var sum=function(num1,num2){
return num1+num2;
};

注意,函式沒有函式名,因為函式名在這裡只是一個指標,而該函式已經賦給了sum,並且,變數也是一個指標,所以,sum可以代替函式,並且,就像初始化語句一樣,在這個匿名函式末尾也有分號

函式不能過載,這和C++是截然不同的,但是ECMAscript允許使用者定義同名函式,就像剛剛說的,函式名只是指標,就像常規指標一樣,後定義的函式會覆蓋前面的同名函式,就像指標賦值一樣,並且由於Js的垃圾回收機制,當前面的函式已經沒有指標呼叫的時候,他的執行環境也就結束了,會被自動回收掉,所以不用擔心記憶體問題。

Js對於函式是十分寬容的,js直譯器會在程式碼執行前把所有函式宣告放到原始碼樹的頂端,所以:

alert(sum(10,10));
function sum(num1,num2){
return num1+num2;
}

這種寫法僅適用於函式宣告,如果是函式表示式將會報錯。
這樣的語句是可以執行的,因為在執行程式碼開始,或者說還沒有執行的時候,Js直譯器會先把所有的函式宣告放到程式碼的最前端,所以,alert是認識sum的
也可以在一個函式內部寫一個匿名函式作為返回值。

即然函式是一種物件,那麼他也和其他物件一樣有屬性和方法每一個函式物件的例項都有length和prototype屬性,並且有兩個內建物件,this和argument,argument又有一個callee屬性,argument物件中儲存了這個函式的引數,他的callee屬性是一個指標,指向擁有該argument物件的函式,在函式內部,為了降低函式內語句和函式名的耦合,通常用argument.callee來代替函式名。this 是一個物件,this引用的是當前函資料以執行的環境物件,如果該全域性作用域函式沒有呼叫物件,那麼預設是window,如果有,則是呼叫物件。

如果是在另一個作用於內定義的函式預設是該環境物件
PS:Js中,while,for,if,不享有單獨作用域,分割作用域的只有函式。

執行環境是Js中一個很重要的概念,執行環境定義了變數或函式有權訪問的其他資料,決定了他們各自的行為。每個執行環境都有一個與之關聯的隱藏的僅後臺可見的變數物件。或者說執行環境是一個巨集觀概念,生成的臨時的變數物件是具體的實現。

全域性執行環境是最外圍的環境,在web瀏覽器中體現為Window物件。因此,全域性變數和函式都是作為window物件的屬性和方法建立的。某個執行環境中的所有程式碼執行完後,執行環境會被銷燬,儲存其中的變數和函式也會被銷燬。

每個函式都有自己的執行環境,當執行流進入一個函式中時,函式的環境就會被推入一個環境棧,本質上是將變數物件指標壓入棧,並且在變數物件中建立作用域鏈,作用域鏈也是一個指標列表,作用於鏈的前端永遠是當前變數物件的引用,最後端是全域性作用域,由內到外一層層包裹,每次this查詢都會從前端開始,所以保證了this永遠引用活動物件

window.color="red";
var o={color:"blue"};
function sayColor(){
    alert(this.color);
}

sayColor();  //red
o.sayColor=sayColor;
o.sayColor();  //blue

閉包
閉包是指有權訪問另一個函式作用域的變數的函式,建立閉包的常見方式是在函式內部建立一個函式。
下面來看一個閉包的例子:

function createFunctions(){
    var result=new Array();
    for(var i=0;i<10;i++){
        result[i]=function (){
            return i;
        }
    }
    return result;

}

在這個函式中,我們首先新建了一個數組物件result,然後寫了一個for迴圈,注意,JS中for迴圈不享有塊級作用域,所以for內宣告的i可以在for外,只要在函式內就行。然後我們在for內寫了一個匿名函式返回i,又在for外返回i那麼一般來看,似乎result每一個索引對應的元素儲存的函式所返回的值應該是隨索引不同而不同的,也就是說,result[0]返回0,result[1]返回1,以此類推,但是實際上每一個函式都返回10,這是因為,我們每在一個函式內部呼叫一個新函式,前面說過,會將環境棧壓入並且建立變數物件,對於函式來說就是活動物件,活動物件一個重要屬性就是作用域鏈,作用域鏈儲存了從全域性作用域到當前活動物件的所有引用,所以,對於閉包來說,只要最內層的函式沒有消亡,那麼,即便外部函式已經退出了,但是內部函式還是儲存了他的引用,所以不會被回收掉,外部函式仍然存在,外部變數仍然存在,並且,活動物件儲存的是變數本身而不是值,換句話說,變數總是保持最新的值,那麼變數物件儲存的變數的引用所得到的值也是最新的也就是10.那麼我們怎麼做才能讓他正常工作呢?
下面是改進版:

function createFunction(){
    var result=new Array();
    for(var i=0;i<10;i++){
        result[i]=function(num){
            return function(){
                return num;
            }
        }(i);
    }
    return result;
}

這裡首先講一下,一般情況下函式是先聲明後呼叫,如果想讓函式在宣告時就執行,可以用下面的語法:

(function funName(){}());
(function funName(){})();

上面的語法任意一種都可以,在最後一個括號中傳入引數(如果有的話)。
我們在看上面的程式碼,改進之處在於,在賦值語句右邊的匿名函式內部有建立了一個匿名函式,並且每次迴圈都立即執行,所以每次都返回一個最新的i值。注意到,內部匿名函式能夠訪問外部函式的變數,這是因為,首先,匿名函式因為沒有呼叫物件,所以被看作是全域性作用域(通過變數呼叫另說)。並且,內部函式被呼叫時建立的活動物件會包含整條作用域鏈,因此儲存了外部函式的活動物件,也就儲存了外部函式的變數的引用,所以可以訪問。

每次執行for迴圈,都會執行賦值語句並且執行匿名函式,在匿名函式內部返回一個以當前i值的副本為引數的函式並儲存在result[]陣列的相應元素中,換句話說,每次立即執行都會生成被賦值為當前i值得num的副本儲存在函式中,並付給result。

閉包的另一個作用是構建私有變數。

嚴格來說,Js中沒有私有概念,但是,函式享有塊級作用域,所以,在函式內部建立一個變數,外部是不可知的,然後通過閉包等技術,可以實現包含私有變數的物件的建立。

(function(){
    var name="1";
    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關鍵字時,預設是全域性作用域,(在嚴格模式下會報錯)。所以Person是可以在外部呼叫,建立物件,並且包含了兩個公有方法訪問私有屬性。

對於一些用於管理程式級的資訊的物件,一般是可以單例,即,只建立一個該物件的例項,此時:

var singletion=(function(){
    var privateVariable=10;
    function privateFunction(){
        return false;
    }
    return {
        publicProperty:true,
        publicMethod:function(){
            privateVariable++;
            return privateFunction();
        }
    }
})();

或者也可以在函式內建立一個Object物件並返回。

var singletion=(function(){
    var privateVariable=10;
    function privateFunction(){
        return false;
    }
    var obj=new CustomType();
    obj.publicProperty=true;
    obj.publicMethod=function(){
        privateVariable++;
        return privateFunction();       
    }
    return obj;

})();

最後,我們可以想到,多查詢作用域鏈的一個層次,就會多耗費一定的時間和記憶體資源,所以,閉包和私有變數使用應當謹慎。