2013年6月28日 星期五

JavaScript function scope

以下是測試 JavaScript 變數、函數不是在 scope 一開始就宣告的情況,
會有 hoisting 的特性。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var#var_hoisting

測試範例的輸出,是使用 chrome。

範例1:variable
第一個 console.log(x),輸出 undefined ,表示此時 x 不是全域變數了。
var x = 1;
function bar() {
    console.log(x); // undefined
    var x = 2;
    console.log(x); // 2
}
bar();
因為 JavaScript 解譯器,猶如會默默的將變數名稱宣告,移置 scope 最上面。但變數值的定義,沒有移置最上面。
效果與下相同:
var x = 1;
function bar() {
    var x;
    console.log(x); // undefined
    x = 2;
    console.log(x); // 2
}
bar();



範例2:variable
console.log(x),都輸出 undefined ,表示 x 不是全域變數了。
var x = 1;
function bar() {
    console.log(x); // undefined
    if(false){
        var x = 2;
    }
    console.log(x); // undefined
}
bar();
因為 JavaScript 解譯器,猶如會默默的將變數名稱宣告,移置 scope 最上面。但變數值的定義,沒有移置最上面。
就算原本區域變數宣告是在不會執行的區塊,亦不影響。
效果與下相同:
var x = 1;
function bar() {
    var x;
    console.log(x); // undefined
    if(false){
        x = 2;
    }
    console.log(x); // undefined
}
bar();



範例3:function declaration
以下程式碼,可以正常執行。foo() 輸出 123,表示 foo() 已經定義了。
function test() {
    foo(); // 會執行
    function foo() {
        console.log("123"); // 123
    };
}
test();
因為 JavaScript 解譯器,猶如會默默的將 function declaration,整個移到 scope 最上面。
效果與下相同:
function test() {
    function foo() {
        console.log("123"); // 123
    };
    foo(); // 會執行
}
test();



範例4:function expression
以下程式碼,會發生錯誤,顯示 foo() 未定義,不是一個函式
function test() {
    foo(); // Uncaught TypeError: undefined is not a function 
    var foo = function () {
        console.log("123");
    };
}
test();
因為 JavaScript 解譯器,猶如會默默的將 function expression 的函數名稱,移到 scope 最上面。但 function body 沒有移至最上面。
效果與下相同:
function test() {
    var foo;
    foo(); // Uncaught TypeError: undefined is not a function 
    foo = function () {
        console.log("123");
    };
}
test();



範例5:function declaration
以下程式碼,可以正常執行,表示 foo() 已經定義了。
function test() {
    foo(); // 會執行
    if(false)
    {
        function foo() {
            console.log("123"); // 123
        };
    }
}
test();
因為 JavaScript 解譯器,猶如會默默的將 function declaration,整個移到 scope 最上面。
就算原本 function declaration 是在不會執行的區塊,亦不影響
效果與下相同:
function test() {
    function foo() {
        console.log("123"); // 123
    };
    foo(); // 會執行
    if(false)
    {
    }
}
test();



範例6:function declaration
在 bar() 裡面,使用 function declaration 方式,加一個跟全域變數 a 同名稱的 function a(),不管放在哪個位置,均會使 bar() 裡面的 a 變成區域變數。
var a = 1;
function bar() {
    /*
    firebug 的 console.log 只會顯示 a(),
    用 alert(a),才會顯示 function a() {var y = 123;}
    chrome、IE10 的 console.log 則都會顯示 function a() {var y = 123;}
    */
    console.log(a); // function a() {var y = 123;}
    console.log(typeof a); // function 
    a = 10;
    console.log(a); // 10
    console.log(typeof a); // number
    return;
    function a() {var y = 123;}
}
bar();
console.log(a); // 1
console.log(typeof a); // number
因為 JavaScript 解譯器,猶如會默默的將 function declaration,整個移到 scope 最上面。
因此在 function bar() 裡面的 a,變成區域變數。
效果與下相同:
var a = 1;
function bar() {
    function a() {var y = 123;}
    console.log(a); // function a() {var y = 123;}
    console.log(typeof a); // function 
    a = 10;
    console.log(a); // 10
    console.log(typeof a); // number
    return;
}
bar();
console.log(a); // 1
console.log(typeof a); // number



範例7:function expression
註: http://stackoverflow.com/a/338053
這邊說 var baz = function spam() {console.log(123);}; 這種寫法
spam() 在 IE 還是可以執行,其他瀏覽器則是未定義
我測試在 IE10 測試 spam() 也是未定義,
但用開發者工具切換到 IE7、IE8、IE9,spam()則是有定義可以執行

baz(); //Uncaught TypeError: Property 'baz' of object [object Object] is not a function
spam(); //Uncaught ReferenceError: spam is not defined 
var baz = function spam() {console.log(123);};
baz(); // 123
spam(); //Uncaught ReferenceError: spam is not defined 
效果與下相同:
var baz;
baz(); //Uncaught TypeError: Property 'baz' of object [object Object] is not a function
spam(); //Uncaught ReferenceError: spam is not defined 
baz = function spam() {console.log(123);};
baz(); // 123
spam(); //Uncaught ReferenceError: spam is not defined 



範例8:function declaration
以下程式碼,foo() 會回傳 b
if(true){
   function foo(){ return 'a'}
}else{
   function foo(){ return 'b'}
}
console.log(foo());// b
因為 JavaScript 解譯器,猶如會默默的將 function declaration,整個移到 scope 最上面。

效果與下相同:
function foo(){ return 'a'}
function foo(){ return 'b'}
if(true){
}else{
}
console.log(foo());// b



範例9:function expression
以下程式碼,foo() 會回傳 a
if(true){
   var foo = function(){ return 'a'};
}else{
   var foo = function(){ return 'b'};
}
console.log(foo());// a
因為 JavaScript 解譯器,猶如會默默的將 function expression 的函數名稱,移到 scope 最上面。但 function body 沒有移至最上面

效果與下相同:
var foo;
var foo;
if(true){
   foo = function(){ return 'a'};
}else{
   foo = function(){ return 'b'};
}
console.log(foo());// a



結論:
  • 變數:
    var x=1
    效果如同 var x; 移至最上面,但 x=1 留在原處。
  • function declaration:
    function foo(){......}
    效果如同 function foo(){ .....} 整個移至最上面。
  • function expression:
    var foo = function(){......};
    效果如同 var foo;移至最上面,但 foo = function(){......}; 留在原處。
  • 不管程式邏輯是否會執行到該程式碼,不會影響以上三點。
  • function expression:
    var foo = function abc(){......};
    abc() 在 firefox、chrome、IE10 會無定義,IE7、IE8、IE9 有定義。


其他:
以下,跟 C 語言不同,if 裡面再宣告一次同名稱的變數,該變數 scope 跟外面是相通的。
var x = 1;
console.log(x); // 1
if (true) {
    var x = 2;
    console.log(x); // 2
}
console.log(x); // 2
如果想在 if {...} 裡面,生成一個暫時的 x 區域變數,可用以下方法。
var x = 1;
console.log(x); // 1
if (true) {
    (function () {
        var x = 2;
        console.log(x); // 2
    }());
}
console.log(x); // 1
參考:
http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope#Function_constructor_vs._function_declaration_vs._function_expression
http://docstore.mik.ua/orelly/webprog/jscript/ch04_03.htm
http://www.ptt.cc/bbs/Web_Design/M.1371040928.A.C95.html
http://javascriptweblog.wordpress.com/2010/07/06/function-declarations-vs-function-expressions/

沒有留言:

張貼留言