一、js没有块级作用域
在强类型语言中都有块级作用域,例如,有如下C#代码
for(int i = 0; i < 10; i++) { //do something } Console.WriteLine(i);
这样的代码是无法通过编译的,因为循环变量i是一个局部变量,它的作用域范围只在for循环的大括号{}之内,出了这个大括号就不是i的作用域了,所以Console.WriteLine(i); 这条语句就不能编译通过了。
但如果这样类似的代码是js写的就另当别论了。比如:
for(var i = 0; i < 10; i++){ //do something; } console.log(i);//输出10
这个代码是没有问题的,运行结果是10 。这说明了js没有传统意义上的块级作用域。
但这不代表js没有局部作用域。js的作用域只有全局作用域和函数作用域,js也只有函数能产生局部作用域。
一、全局作用域
只要是在一对<script>标签之内直接声明的对象,它的作用域就是全局的。
如果一个对象是在function内部声明的,那么它的作用域就是局部的,函数内部的作用域。
比如:
<script> var a = 123; console.log(a); //输出123 function f1(){ console.log(a); } f1();//输出123 </script>
此例中,a就是直接声明在了一对<script>标签内的,不是声明在某一个函数之内的,那么它是个全局作用域,所以不管是在f1函数内部,还是在外部,都可以访问到变量a
二、函数作用域
把上边的例子稍微改改
<script> var a = 123; console.log(a); //输出123 function f1(){ var num = 10; console.log(a); } f1();//输出123 console.log(num); // 报错,无法访问到num </script>
这个代码会报错,因为num是在f1函数内部声明的,它的作用域仅限于f1函数声明的内部,也就是f1那个大括号{}内部,所以在函数声明的外部访问num肯定是不可以的。
我们可能会看到一个词,说js中的作用域是:词法作用域。那么怎么理解这个所谓的“词法作用域”呢?
看这个例子
<script> var a = 123; console.log(a); //输出123 function f1(){ console.log(a); } function f2(){ var a = 456; f1(a); } f2();//输出123 </script>
这个例子就很值得思考了。
1.有一个全局作用域的变量a
2.f1函数调用了a
3.f2函数先声明了一个局部变量a,并且与全局变量同名
4.f2函数声明了这个同名的局部变量a之后,又调用了f1函数
这个程序执行完之后的结果是 : 123 , 而不是456。原因是js中不存在块级作用域,只存在函数作用域。
而函数作用域又被称之为词法作用域,说白了就是:你的函数时在哪儿声明的,那它就属于哪儿,也就意味着它只能访问到这个区间的成员。
在这个例子中,f1函数是声明在全局作用域下的,那么它就属于全局作用域,也就意味着它(函数体内部)只能访问到全局作用域下声明的对象。显然,当前全局作用域下的a是等于123的,而不是f2内部声明的那个等于456的a,所以,不管是在何时调用f1这个函数,它访问到的a必然是那个值等于123的全局变量a。
那究竟该如何清晰的描述出对象的作用域范围?这就要使用一个叫做“作用域链”的东西了。
三、作用域链
看这个例子就明白什么是作用域链了。
<script> var num = 1; function f1(){ var num = 2; function f2(){ var num = 3; function f3(){ console.log(num); } f3(); } f2(); } f1(); //输出 3 // f2();//报错, // f3();//报错 </script>
这个例子就是函数嵌套声明,并且每个函数内部还都又声明了一个跟全局变量num同名的局部变量。相信通过刚才的解释,大家已经能清晰的得出程序执行结果了。
简单说明一下:
1.在全局作用域下,不能直接调用f2和f3函数,因为它们都是在f1内部嵌套声明的,它们都是局部作用域声明的对象,所以不能在全局作用域中访问。
2.在局部作用域中,如果出现于更高级的作用域同名的对象,那么优先访问本作用域范围之内的对象
3.如果在本作用域之内找不到,那么就往“上一级”作用域中去找,知道全局作用域,如果还没有就报错。
那么这里所谓的“上一级”作用域,可以使用作用域链清晰的描绘出来。这个例子的作用域链我们可以用图画出来。
首先这里的全局作用域我们称之为 0 级作用域,f1函数时在全局作用域下声明的,所以f1自己形成一个小的作用域,称之为 1 级。以此类推。
当我们在外部调用f2和f3是不可能的。因为从图上我们清晰的看到,在0级作用域的横线上根本没有声明一个叫f2或者f3的对象。
当我们在全局(0级)作用域下,调用f1函数,然后逐级的调用到f3的时候,f3内部有一条console.log(num)。此时,程序会沿着我们画的线,现在f3的作用域,也就是3级作用下找有没有声明一个叫num的对象,如果有就直接拿来打印,如果没有,则沿着这个作用域链往上递推,首先推到离他最近的上一级作用域,也就是f2的作用域,它是2级作用域,这里声明了一个叫做num的变量,并且赋了初值为3,于是,这条打印语句,就立刻把这个=3的num拿来使用,而不再理会更高级的作用域了。因为它已经找到了一个可以访问到的num。
我们把这个图描绘的作用域之间的关系,就形象的称之为作用域链。
在作用域链中搜索对象,是只能逐级往上查找的,而不能往低级的作用域中查找。