首页 > 代码库 > 神奇的函数作用域
神奇的函数作用域
今天发现了两个关于函数作用域的神奇例子,这里和大家分享分享:
var a = 1; function foo() { if (!a) { var a = 2; } alert(a); }; foo();
上面这段代码在运行时会产生什么结果?
我们来分析一下:
1.创建了全局变量 a,定义其值为 1
2.创建了函数 foo
3.在 foo 的函数体内,if 语句将不会执行,因为 !a 会将变量 a 转变成布尔的假值,也就是 false
4.跳过条件分支,alert 变量 a,最终的结果应该是输出 1
看起来无懈可击的分析,但是实际上,结果错误。答案竟然是 2!为什么?
什么叫申明?
是指你声称某样东西的存在,比如一个变量或一个函数;但你没有说明这样东西到底是什么,仅仅是告诉解释器这样东西存在而已;
什么叫定义?
是指你指明了某样东西的具体实现,比如一个变量的值是多少,一个函数的函数体是什么,确切的表达了这样东西的意义。
所以上面的代码实际上可以写成这样:
var a; a = 1; function foo() { var a; // 关键在这里 if (!a) { a = 2; } alert(a); // 此时的 a 并非函数体外的那个全局变量 } foo();
然后又有人会问,不是有个if吗?if不成立哪就不会为a赋值为2。
因为 JavaScript 没有块级作用域(Block Scoping),只有函数作用域(Function Scoping),所以说不是看见一对花括号 {} 就代表产生了新的作用域,和 C 不一样!
当解析器读到 if 语句的时候,它发现此处有一个变量声明和赋值,于是解析器会将其声明提升至当前作用域的顶部(这是默认行为,并且无法更改),这个行为就叫做 Hoisting。
怎样能够alert出那个a=1?
let a; a = 1; function foo() { let a; // 关键在这里 if (!a) { a = 2; } alert(a); // 此时的 a 并非函数体外的那个全局变量 } foo();
es6的语法,javascript是有块级作用域的。
还可以通过闭包的方式实现:
var a = 1; function foo() { if (!a) { (function() { var a = 2; }()); }; alert(a); }; foo();
第二个例子:
var a = 1; function test() { foo(); var foo = function() { alert(a); } } test();
这个运行的结果是什么?初略一看,alert(1),但是实际上报错。
Uncaught TypeError: foo is not a function。
为什么会这样?
提升的仅仅是变量名 foo,至于它的定义依然停留在原处。因此在执行 foo() 之前,作用域只知道 foo 的命名,不知道它到底是什么,所以执行会报错(通常会是:undefined is not a function)。这叫做函数表达式(Function Expression),函数表达式只有命名会被提升,定义的函数体则不会。
var a = 1; function test() { var foo; foo(); // 这个时候函数foo,只声明,未赋值。 foo = function() { alert(a); } } test();
怎么改?
var a = 1; function test() { var foo = function() { alert(a); } foo(); } test(); // 1
这个例子也展示了函数声明与函数表达式的差别,函数申明会放到作用域的顶部,函数表达式则不会。
最后引用很多书中的一句话:“请始终保持作用域内所有变量的声明放置在作用域的顶部”,相信现在的你对这句话应该有一个认识了。
神奇的函数作用域