下面两段代码输出什么:
1 var foo = 1; 2 function bar() { 3 if (!foo) { 4 var foo = 10; 5 } // end if 6 alert(foo); 7 } // end bar() 8 9 bar(); // 10
你可以再这里查看结果:http://jsfiddle.net/pMfNk/
1 var a = 1; 2 function b() { 3 a = 10; 4 return; 5 function a() {} 6 } // end b() 7 8 b(); 9 alert(a); // 1
可以再这里查看结果: http://jsfiddle.net/g8NZ4/
这里牵涉到javascript变量作用域和变量声明提前。
javascript是函数作用域。不是块作用域。也就是说通过函数定义新的作用域。这一特性通常用来定义命名空间、闭包等。
一个变量名有四种方法进入作用域:
- 语言定义:所有作用域默认都定义了this和arguments
- 参数名称:函数声明时定义的参数列表
- 函数声明:通过这种格式声明的函数:function foo() {}
- 变量声明:通过这种形式声明的变量: var foo;
规则1: 所有函数声明和变量声明都将被提前到它们作用域的顶端。在这里保存着函数参数和语言定义的变量。也就是说不管声明变量的语句被执行到,它们都会被声明。
1 function foo() { 2 bar(); 3 var x = 1; 4 } 5 6 // 实际上会被这样解析 7 8 function foo() { 9 var x; 10 bar(); 11 x = 1; 12 } 13 14 // 下面两个函数时一样的效果 15 function foo() { 16 if (false) { 17 var x = 1; 18 } 19 return; 20 var y = 1; 21 } 22 23 function foo() { 24 var x, y; 25 if (false) { 26 x = 1; 27 } 28 return; 29 y = 1; 30 }
规则2:变量声明部分被提前,但是赋值部分不会提前。当函数声明时需要注意,函数声明有两种方法:变量赋值法和直接声明法。变量赋值与普通变量声明相同。直接声明法将函数体也会提前
1 function test() { 2 // foo(); // typeerror "foo is not a function" 3 bar(); // this will run 4 5 var foo = function () { 6 alert("this won't run"); 7 } 8 function bar() { 9 alert("this will run"); 10 } 11 } 12 13 test();
到这里查看结果: http://jsfiddle.net/8gUHM/
规则3:名字解析顺序,名字解析顺序将按照前面所列顺序进行,当一个名字已经定义了,它不会被后续同名属性定义。也就意味着函数声明优先级高于变量名声明。声明不会覆盖,但是当函数运行到具体赋值处时仍然会覆盖。当多个参数使用同一个名字时。后出现的将获得更高优先级(赋值顺序导致内容覆盖?)
规则4:命名函数表达式,当将命名的普通函数定义表达式赋值给一个变量时。函数名将不会被声明,函数体也不会被提前,(这里实际上成为了变量声明,函数名也不见了)
1 function test() { 2 // foo(); // typeError "foo is not a function" 3 bar(); // valid 4 // baz(); // typeerror "baz is not a function " 5 // spam(); // ReferenceError "spam is not defined" 6 7 var foo = function () {}; // anonymous function expression (foo gets hoisted) 8 function bar() {} // function declaration (bar and the function body get hoisted) 9 var baz = function spam() {}; // named function expression (only baz gets hoisted, spam is never declared) 10 11 foo(); // valid 12 bar(); // valid 13 baz(); // valid 14 // spam(); // referenceerror 15 } 16 17 test();
这里是测试结果: http://jsfiddle.net/3VYTG/
知道这些之后应该怎么做?
- 总是使用var声明变量
- 将变量声明放到函数顶端
- 只用一个var,用JSLint onevar选项检查代码
出处:http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html