首先你们看一下下面的代码,猜猜会输出什么结果?
express
var foo = 1;function bar() { if (!foo) { var foo = 10; } alert(foo); } bar();
答案是10!
你是否会疑惑条件语句if(!foo)并不会执行,为何foo会被赋值为10闭包
再来看第二个例子ide
var a = 1;function b() { a = 10; return; function a() {} } b(); alert(a);
答案仍是10吗?显然不是,alert输出了1函数
若是你仍然对上面两个输出结果摸不着头脑,那么请认真阅读这篇文章!this
Javascript的做用域已是老生常谈的问题了,可是不必定每一个人都能准确理解。
咱们先来看一下C语言的一个例子:spa
#include <stdio.h>int main() { int x = 1; printf("%d, ", x); // 1 if (1) { int x = 2; printf("%d, ", x); // 2 } printf("%d\n", x); // 1}
程序依次输出了1,2,1
为何第三个输出了1而不是2呢?由于在C语言中,咱们有块级做用域(block-level scope)。在一个代码块的中变量并不会覆盖掉代码块外面的变量。咱们不妨试一下Javascript中的表现.net
var x = 1; console.log(x); // 1 if (true) { var x = 2; console.log(x); // 2 } console.log(x); // 2
输出的结果为1,2,2 if代码块中的变量覆盖了全局变量。那是由于JavaScript是一种函数级做用域(function-level scope)因此if中并无独立维护一个scope,变量x影响到了全局变量xcode
C,C++,C#和Java都是块级做用域语言,那么在Javascript中,咱们怎么实现一种相似块级做用域的效果呢?答案是闭包。对象
function foo() { var x = 1; if (x) { (function () {var x = 2;// some other code }()); } // x is still 1. }
上面代码在if条件块中建立了一个闭包,它是一个当即执行函数,因此至关于咱们又建立了一个函数做用域,因此内部的x并不会对外部产生影响。函数级做用域指的是,只要建立一个新的函数,就能够将变量分隔开,他们之间相互独立。C语言的块级做用域则是值,一对{ }括号就能够将变量分隔开,使{ }内外的数据相互独立。ip
在Javascript中,变量进入一个做用域能够经过下面四种方式:
语言自定义变量:全部的做用域中都存在this和arguments这两个默认变量
函数形参:函数的形参存在函数做用域中
函数声明:function foo() {}
变量定义:var foo
其中,___在代码运行前,函数声明和变量定义一般会被解释器移动到其所在做用域的最顶部___,如何理解这句话呢?
function foo() { bar(); var x = 1; }
上面这段在吗,被代码解释器编译完后,将变成下面的形式:
function foo() { var x; bar(); x = 1; }
咱们注意到,x变量的定义被移动到函数的最顶部。而后在bar()后,再对其进行赋值。
再来看一个例子,下面两段代码实际上是等价的:
function foo() { if (false) { var x = 1; } return; var y = 1; }
function foo() { var x, y; if (false) { x = 1; } return; y = 1; }
因此变量的上升(Hoisting)只是其定义上升,而变量的赋值并不会上升。
咱们都知道,建立一个函数的方法有两种,一种是经过函数声明function foo(){}
另外一种是经过定义一个变量var foo = function(){} 那这两种在代码执行上有什么区别呢?
来看下面的例子:
function test() { foo(); // TypeError "foo is not a function" bar(); // "this will run!" var foo = function () { // function expression assigned to local variable 'foo' alert("this won't run!"); } function bar() { // function declaration, given the name 'bar' alert("this will run!"); } } test();
在这个例子中,foo()调用的时候报错了,而bar可以正常调用
咱们前面说过变量会上升,因此var foo首先会上升到函数体顶部,然而此时的foo为undefined,因此执行报错。而对于函数bar, 函数自己也是一种变量,因此也存在变量上升的现象,可是它是上升了整个函数,因此bar()才可以顺利执行。
再回到一开始咱们提出的两个例子,能理解其输出原理了吗?
var foo = 1; function bar() { if (!foo) { var foo = 10; } alert(foo); } bar();
其实就是:
var foo = 1; function bar() { var foo; if (!foo) { foo = 10; } alert(foo); } bar();
var a = 1; function b() { a = 10; return; function a() {}////这里创建了一个匿名函数对象,typeof(a)=function } b(); alert(a);
其实就是:
var a = 1; function b() { function a() {}//这里创建了一个匿名函数对象,typeof(a)=function a = 10; return; } b(); alert(a);
实验一下:
var a =1; function b() { function a(){a=100;} /*a =10;*/ alert(a); alert(typeof(a)); return; } b(); alert(a);
会弹出
function
1
这就是为何,咱们写代码的时候,变量定义总要写在最前面。
在ES6中,存在let关键字,它声明的变量一样存在块级做用域。
并且函数自己的做用域,只存在其所在的块级做用域以内,例如:
function f() { console.log('I am outside!'); } if(true) { // 重复声明一次函数f function f() { console.log('I am inside!'); } } f();
上面这段代码在ES5中的输出结果为I am inside!由于f被条件语句中的f上升覆盖了。在ES6中的输出是I am outside!块级中定义的函数不会影响外部。