Quiz |
下面Javascript代碼為什么能運行?
hello(); function hello(){ alert("Hello, world!"); }
但對於C,這么寫會報錯:
#include "stdio.h" void main(){ hello(); } void hello(){ printf("Hello, world\n"); }
由於hello沒有被預先聲明,代碼“hello()”被認為是隱式聲明,而隱式聲明返回類型是int,所以提示hello類型錯誤。
通過預先聲明或者將main函數放在hello函數的后面可以很容易解決這個問題。
那么對於Javascript卻能運行,這代表了什么呢?
被提升的聲明
Javascript引擎會先對代碼解釋,將聲明提升,然后再執行。例如為了判斷一個變量定義與否,如果我們如此寫是會出引用錯誤的:
if(someVar === undefined){ alert("someVar未定義"); }
但如果這樣卻不會出錯:
if(someVar === undefined){ var someVar = 1; alert("someVar未定義"); }
可見聲明被提升了,但只有聲明被提升了,因為someVar依然等於undefined,而不是1。
值得一提的是,這樣子寫也會報錯:
if(someVar === undefined){ someVar = 1; alert("someVar未定義"); }
這證明在解釋階段,隱式聲明是沒有作用的!而且為了讓代碼邏輯清晰,還是用顯式聲明吧!
chrome瀏覽器的詭異現象
chrome瀏覽器(至少在本文簽寫時的最新版本22.0.1229.94 m依然是如此)有個詭異的現象:
var name = 20; alert(typeof(name)); //string name += 12; alert(name); //2012
所以請不要把name定義為除字符串的其他類型,當然可以的話也盡量避免使用這個全局變量吧。
函數的兩種創建方式
函數申明:
function 函數名 (參數可選){ 函數體 }
函數表達式:
function 函數名可選 (參數可選){ 函數體 }
Syntax
FunctionDeclaration :
function Identifier ( FormalParameterListopt ) { FunctionBody }
FunctionExpression :
function Identifieropt ( FormalParameterListopt ) { FunctionBody }——Standard ECMA-262 ECMAScript Language Specification . ECMA
為什么函數要分聲明(FunctionDeclaration)和表達式(FunctionExpression)呢?
譯注:實際上FunctionDeclaration和FunctionExpression都是函數產生的語法,這和一般編程語言中函數聲明只是定義接受參數數量及類型與返回值類型不太相同。或者說,這根本不是字面意義上的函數聲明。我們可以理解對於Javascript的函數聲明(FunctionDeclaration)其實是聲明式(即需要提升的)函數產生語法,而函數表達式(FunctionExpression)則是表達式式(即執行時產生)函數產生語法。
主要為了區分函數的創建是否需要被提升,如果是函數聲明就需要被提升創建,但如果是表達式,那么可以在執行時再創建。這樣是很必要的,比如我們可以利用表達式動態定義函數:
var foo; if(Condition){ foo = function(){ //do something } }else{ foo = function(){ //do anything else } }
再比如對於匿名函數:
(function(){ //do something })();
聲明提升也沒什么意義,因為它不會在別的地方被引用。
如何判斷函數聲明與函數表達式
- 匿名函數必然是函數表達式
- 如果有名字的函數作為賦值表達式的一部分那么他也是一個表達式
- 如果有名字的函數被括號“()”括住,那么他也是一個表達式
本文不准備深入命名函數表達式(named function expressions),具體可參見參考文獻,不過一般應當避免命名函數表達式的使用,因為大部分功能都可以用匿名函數找到替代方案,或者說實際使用中不必考慮命名函數表達式。
匿名函數立刻執行
我們經常希望匿名函數定義好后立刻執行,但這么寫會拋出語法錯誤:
function(){ alert(1); }();
正確寫法如下。
- 聲明一個函數對象,然后執行它:
(function(){ alert(1); })();
- 用括號強制執行:
(function(){ alert(1); }());
- 使用void操作符:
void function(){ alert(1); }();
總結
- Javascript中聲明會被提升;
- 對於變量顯式聲明提升的僅僅是聲明,賦值並未被提升;
- 對於函數聲明由於其賦值和聲明是一體的,所以提升的是整個函數的定義;
- 變量隱式聲明和函數表達式不會被提升。
思考題
1. 如果我們用函數表達式來創建函數,而不是用函數聲明來創建,剛開始的題目會如何呢?
hello(); var hello = function(){ alert("Hello, world!"); }
2. 下面是一個Button類,並創建了一個他的實例,我們可以在瀏覽器中看到一個按鈕,但是為什么單機按鈕時alert出來的數值不是13,而是空的呢?如何能alert出我們設置的數值?
function Button(clickFunction) { this.button = document.createElement("button"); this.button.appendChild(document.createTextNode("Test")); document.body.appendChild(this.button); this.button.onclick = function(){alert(this.value);} } var bt = new Button(13); //單擊這個button的時候alert出空
參考資料 |
Standard ECMA-262 ECMAScript Language Specification . ECMA . June 2011
Named function expressions demystified . Juriy "kangax" Zaytsev . June 17, 2009
函數式JavaScript編程指南 . ShiningRay(譯) . 2008/01/02