DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> JavaScript基礎知識 >> 深入學習JavaScript(二)
深入學習JavaScript(二)
編輯:JavaScript基礎知識     

函數表達式和函數聲明

函數聲明

 function 函數名(參數){函數體}


函數表達式

function 函數名(可選)(參數){函數體}


示例:

function foo(){} // 聲明,因為它是程序的一部分
var bar = function foo(){}; // 表達式,因為它是賦值表達式的一部分
new function bar(){}; // 表達式,因為它是new表達式

(function(){
function bar(){} // 聲明,因為它是函數體的一部分
  })();


另外一種不太常見的函數表達式

function foo(){}//函數聲明
(function foo(){});//函數表達式,因為在外面加上了一個分組操作符,分組操作符有一個作用是將括號中的函數聲明轉化成為函數表達式
//驗證分組操作符內是否一定要是函數聲明
try{
    (var x=5);  //var x=5是一個語句,不是函數聲明
}catch(err){
    alert(err);
}


函數表達式與函數聲明各自的作用?

函數聲明會在函數表達式被解析和求和之前先被解析,如果是函數表達式在函數聲明之前那麼函數聲明也會在其之前解析

示例:

alert(foo());
function foo(){
    return "Hello World";
}


提示:函數聲明最好不要在判斷語句中使用,因為在判斷語句中函數聲明是沒有被標准化的,在不同的浏覽器環境中可能會有不同的效果

函數聲明只能出現在程序或函數體內。從句法上講,它們 不能出現在Block(塊)({ ... })中,例如不能出現在 if、while 或 for 語句中。因為 Block(塊) 中只能包含Statement語句, 而不能包含函數聲明這樣的源元素。另一方面,仔細看一看規則也會發現,唯一可能讓表達式出現在Block(塊)中情形,就是讓它作為表達式語句的一部分。但是,規范明確規定了表達式語句不能以關鍵字function開頭。而這實際上就是說,函數表達式同樣也不能出現在Statement語句或Block(塊)中(因為Block(塊)就是由Statement語句構成的)。


函數表達式的名字只在新定義的函數作用域內有效,因為規范規定了標示符不能在外圍的作用域內有效:

示例:

var t=text(){
    return typeof text; //text在內部作用域有效
};
typeof text;  //undefined
t();  //undefined


如果是只對方法的內部有限的話,話句話說也就是外部沒法引用,那麼我們為什麼不用匿名函數呢?原因是因為顯示函數有利於我們的調試,因為調試的過程中我們可以很清楚的知道調試棧中的對象是那個,相比於匿名函數比較方便

webkit的displayName屬性

WebKit引入了一個“特殊的”displayName屬性(本質上是一個字符串),如果開發人員為函數的這個屬性賦值,則該屬性的值將在調試器或性能分析器中被顯示在函數“名稱”的位置上

JavaScript的Module模式

基本用法

//相當於是一個類
var module=function(eq){
    //定義變量
    var VALUE="6",
        my={};
    //定義屬性
    my.Name="xiecanyong";
    my.money=function(data){
        text(data);//調用方法
    }
    //定義函數
    function text(data){
        console.log(data);
    }
    //暴露公開成員
    /*return {
        add:function(x,y){
            var count=x+y;
        }
    }*/
    return my;//暴露公開的成員活或者是屬性方法
}
var MO=new module(); //創建對象
var NAME=MO.Name;  //獲取屬性
var MONEY=MO.money(1);//給屬性值傳參


另外由於我們可以在方法中聲明一個全局變量,為了保證內部環境不對外部環境造成污染,所以我們可以通過以下兩種方法來實現:

1、引入全局變量

2、匿名閉包

引入全局變量和匿名函數

最好的例子就是jquery的源碼了,簡單的了解一下

(function($){
    //jquery源碼
}(jquery));


Module模式高級用法

拓展

我們一般使用閉包都是將相關的代碼寫在同一個文件下面,但是如果是項目工程比較大的話,需要多人合作共同完成,那麼怎樣將一個文件分離,達到多人協作呢?

var blogModule = (function (my) {
my.AddPhoto = function () {
    //添加內部代碼  
};
return my;
} (blogModule));



上面的這一段代碼的運行原理:通過創建一個blogModule全局變量,然後每次調用都會將blogModule中的方法添加到blogModule全局變量中,這樣待到全部的文件加載完成後,全局變量中也就有了加載的全部方法和變量

但是這一段代碼雖然語法上是正確的,但是運行確實會報錯,這裡面的原因是:因為第一次運行的時候blogMudule是一個已定義但未初始化的變量,所以向其中添加方法會發生異常

所以我們應該這樣處理,加上一個判斷時候對象未初始化,這樣就可以解決問題了

var blogModule = (function (my) {
my.AddPhoto = function () {
    //添加內部代碼  
};
return my;
} (blogModule||{}));


如果是所有的調用都是采用這種形式的話,也就意味著不論哪個文件先加載最後的blogModule全局變量都是一樣的,這樣的過程我們稱為松耦合拓展

緊耦合拓展

既然有松耦合拓展那麼對應的就有緊耦合拓展,緊耦合拓展顧名思義就是不能打亂文件的加載順序但是這樣比起松耦合拓展來說會使得使用起來更為的不方便,但是卻卻可以實現方法重載的目的

var blogModule = (function (my) {
    my.oldAddPhotoMethod = my.AddPhoto;
    //將之前調用的方法傳遞給oldAddPhotoMethod,然後這樣我們再重載AddPhoto的時候就不會將原來的函數覆蓋掉
    my.AddPhoto = function () {
        // 重載方法,依然可通過oldAddPhotoMethod調用舊的方法
    };  
    return my;
} (blogModule));


緊耦合拓展在使用的時候要注意的是,全局變量中的var一定要加上,否則會出錯,這個原因後面將會說明。通過對這個緊耦合拓展的分析我們可以知道,在調用者段代碼之前,必須要有另外一段AddPhoto的方法,這裡如果是加載方法的時候與前面的方法發生了沖突,那麼我們就將前面的方法賦值給另外一個變量,這樣就可以實現重載(這裡的重載可能與強語言的重載有一些不一樣),還有一點也是要注意的,雖然緊耦合的傳入參數不需要判斷是否為空,但是在緊耦合拓展的文件加載上,第一個加載的文件的傳入參數也是要blogModule||{}


跨文件共享私有對象

通過上面的拓展的介紹,你一定是對JS的團隊開發有了一定的了解,但是如果是團隊之間要共享一些方法,那麼我們應該怎樣去實現呢?這就需要我們能夠對代碼裡面的變量進行共享

var blogModule = (function (my) {
var _private = my._private = my._private || {},
    //應用聲明周期內,調用開鎖
    _unseal = my._unseal = my._unseal || function () {
        my._private = _private;
        my._seal = _seal;
        my._unseal = _unseal;
    };
    //加載完成後上鎖,阻止外部訪問
    _seal = my._seal = my._seal || function () {
        delete my._private;
        delete my._seal;
        delete my._unseal;
        
    },
    
    return my;
} (blogModule || {}));


子模塊

我們在上面討論的是一個單模塊的應用,但是在實際的項目中,我們有可能會因為工程過大,所以我們需要再單模塊的基礎上去創建多個子模塊

示例:

blogModule.test=(function (){
    
}());



值得一提的是方法後面的自執行的括號是可以去掉的,這個在正常的開發中比較少見,所以在此一提

自執行

一般我們的自執行是這樣定義的

var foo=function(){/*代碼*/};


這一段代碼是可以正常運行的,但是不是所有的代碼都可以加個()就自執行

function (){/*代碼*/}()


這段代碼執行會報錯,因為解析器在解析全局function或者是function內部function關鍵字的時候,默認解析為函數聲明,但是由前面的知識我們可以知道,函數聲明是必須要命名的,這樣一來,解析器就解析成為未命名的function,這樣就出錯了

但是如果你將代碼寫為如下的這種形式,那這樣是否就能正常運行呢?

function foo(){/*代碼*/}()


答案是不能正常運行,從這一段代碼上來看,是好像一段帶自執行的函數聲明,但是函數聲明後面的括號不想函數表達式是自執行的意思,而是分組操作符,分組操作符是不允許為空的, 所以這段代碼出錯

function foo(){ /* code */ }( 1 );


上面的這一段代碼就不會發生錯誤,但是也不會執行

為了解決上面的問題,我們應該在其外面加上括號,這個的原理在上面我們已經有提過了,就是因為分組操作符會將裡面的函數聲明解析成為函數表達式,而函數表達式後面的括號指的是自執行

(function foo(){/*代碼*/}())


下面我們就來通過例子了解什麼是自執行和立即調用

// 這是一個自執行的函數,函數內部執行自身,遞歸
    function foo() { foo(); }

// 這是一個自執行的匿名函數,因為沒有標示名稱
// 必須使用arguments.callee屬性來執行自己
    var foo = function () { arguments.callee(); };

// 這可能也是一個自執行的匿名函數,僅僅是foo標示名稱引用它自身
// 如果你將foo改變成其它的,你將得到一個used-to-self-execute匿名函數
    var foo = function () { foo(); };

// 有些人叫這個是自執行的匿名函數(即便它不是),因為它沒有調用自身,它只是立即執行而已。
    (function () { /* code */ } ());

// 為函數表達式添加一個標示名稱,可以方便Debug
// 但一定命名了,這個函數就不再是匿名的了
    (function foo() { /* code */ } ());

// 立即調用的函數表達式(IIFE)也可以自執行,不過可能不常用罷了
    (function () { arguments.callee(); } ());
    (function foo() { foo(); } ());

// 另外,下面的代碼在黑莓5裡執行會出錯,因為在一個命名的函數表達式裡,他的名稱是undefined
// 呵呵,奇怪
    (function foo() { foo(); } ());


這裡我們出現了arguments.callee()之類的方法,下面我們就來介紹一下這些是什麼?

aruguments對象代表正在執行的函數和調用它的函數的參數,calle方法返回正在執行的對象,其他的方法參數和用法詳見

js的隱含參數(arguments,callee,caller)使用方法

js中的caller和callee屬性

最後我們就貼出一個Module模型的例子來分享一下

// 創建一個立即調用的匿名函數表達式
// return一個變量,其中這個變量裡包含你要暴露的東西
// 返回的這個變量將賦值給counter,而不是外面聲明的function自身

var counter = (function () {
var i = 0;

return {
    get: function () {
        return i;
    },
    set: function (val) {
        i = val;
    },
    increment: function () {
        return ++i;
    }
};
} ());

// counter是一個帶有多個屬性的對象,上面的代碼對於屬性的體現其實是方法

counter.get(); // 0
counter.set(3);
counter.increment(); // 4
counter.increment(); // 5

counter.i; // undefined 因為i不是返回對象的屬性
i; // 引用錯誤: i 沒有定義(因為i只存在於閉包)


好的關於這篇文章的內容在此就結束了,如果喜歡的小伙伴請點個贊,你的前進的動力

Copyright © DIV+CSS佈局教程網 All Rights Reserved