1 function log(str) { 2 // 本篇文章所有的打印都将调用此方法 3 console.log(str); 4 }
函数声明和变量声明总是会被解释器悄悄地被“提升”到方法体的最顶部
变量声明、命名、提升
在JS中, 变量有4种基本方式进入作用域:
- 语言内置: 所有的作用域里都有this和arguments;(需要注意的是arguments在全局作用域是不可见的)
- 形式参数: 函数的形式参数会作为函数体作用域的一部分;
- 函数声明: 像这种形式: function foo() {};
- 变量声明: 像这样: var foo;
变量提升
function test1() { a = 5; log(a); log(window.a); var a = 10; log(a); } test1();
依次会输出 5 、undefined 、10 因为在解析时候是等价于
1 var a; 2 a=5; 3 log(a); 4 log(window.a); 5 a=10; 6 log(a);
接着看另外一个例子:
1 function test2() { 2 var a = 1; 3 var b = 2; 4 var c = 3; 5 } 6 /*test2中的语句,是这样被执行的 这个时候就把变量提升了 7 8 function test2(){ 9 var a,b,c; 10 var a = 1; 11 var b = 2; 12 var c = 3; 13 } 14 */
只有函数级作用域,if语句不会有:test3():
function test3(){ var a = 1; log(a); // 1 if (true) { var a = 2; log(a); //2 } log(a); // 2 }
函数的提升
我们写JS的时候,通常会有两种写法:
- 函数表达式 var fn=function fn(){}
- 函数声明方式 function fn(){}
我们需要重点注意的是,只有函数声明形式才能被提升。
变量赋值并没有被提升,只是声明被提升了。但是,函数的声明有点不一样,函数体也会一同被提升
1 function test3() { 2 fn(); 3 function fn() { 4 log("我来自fn"); 5 } 6 } 7 test3(); 8 function test4() { 9 fn(); // fn is not a function 10 var fn = function fn() { 11 alert("我来自 fn test4"); 12 } 13 } 14 test4();
函数表达式需要注意的
- 在function内部,fn完全等于fn1
- 在function外面,fn1则是 not defined
1 function test5() { 2 var fn = function fn1() { 3 log(fn === fn1); // true 4 log(fn == fn1); // true 5 } 6 fn(); 7 log(fn === fn1); // fn1 is not defined 8 log(fn == fn1); // fn1 is not defined 9 } 10 test5();
!兼容
// b();
// var a = function b() {alert(‘this is b‘)};
// 则ie下是可以执行b的. 说明不同浏览器在处理函数表达式细节上是有差别的.
补充一点函数表达式
定义里面的指定的函数名是不是被提升的
1 function text7() { 2 a(); // TypeError "a is not a function" 3 b(); 4 c(); // TypeError "c is not a function" 5 d(); // ReferenceError "d is not defined" 6 7 var a = function() {}; // a指向匿名函数 8 function b() {}; // 函数声明 9 var c = function d() {}; // 命名函数,只有c被提升,d不会被提升。 10 11 a(); 13 b(); 14 c(); 15 d(); // ReferenceError "d is not defined" 16 } 17 text7();
大家先看下面一段代码test6,思考一下会打印什么?
1 function text6() { 2 var a = 1; 3 function b() { 4 a = 10; 5 return; 6 function a() {} 7 } 8 b(); 9 log(a); // ? 10 } 11 text6();
||
||
||
|| 输出在下面
||
||
||
||
||
||
what? 什么鬼?为什么是1?
这里需要注意的是,在function b()中,
var = a // function 类型的
a=10; // 重新把10复制给a, 此时的a是function b()中的内部变量
return;
function a() {} // 不会被执行
所以,外面输出的a 依旧是最开始定义的全局变量
函数的声明比变量的声明的优先级要高
1 function text6() { 2 function a() {} 3 var a; 4 log(a); //打印出a的函数体 5 6 var b; 7 function b() {} 8 log(b); //打印出b的函数体 9 10 // !注意看,一旦变量被赋值后,将会输出变量 11 var c = 12 12 function c() {} 13 log(c); //12 14 15 function d() {} 16 var d = 12 17 log(d); //12 18 } 19 text6();
变量解析的顺序
一般情况下,会按照最开始说的四种方式依次解析
- 语言内置:
- 形式参数:
- 函数声明:
- 变量声明:
也有例外:
- 内置的名称arguments表现得很奇怪,看起来应该是声明在形参之后,但是却在声明之前。这是说,如果形参里面有arguments,它会比内置的那个优先级高。所以尽可能不要在形参里面使用arguments;
- 在任何地方定义this变量都会出语法错误
- 如果多个形式参数拥有相同的名称,最后的那个优先级高,即便是实际运行的时候它的值是undefined;
CAO!这么多坑,以后肿么写代码?
用var定义变量。对于一个名称,在一个作用域里面永远只有一次var声明。这样就不会遇到作用域和变量提升问题。
ECMAScript参考文档关于作用域和变量提升的部分:
如果变量在函数体类声明,则它是函数作用域。否则,它是全局作用域(作为global的属性)。变量将会在执行进入作用域的时候被创建。块(比如if(){})不会定义新的作用域,只有函数声明和全局性质的代码(单个JS文件)才会创造新的作用域。变量在创建的时候会被初始化为undefined。如果变量声明语句里面带有赋值操作,则赋值操作只有被执行到的时候才会发生,而不是创建的时候。