搞前端的小码农们应该都知道,Js不同于其他语言的块级作用域,它使用的是函数作用。那么到底什么是块级作用域,什么又是函数作用域呢:
- 块级作用域:以一对{}为界限形成一个代码块,代码块内声明的变量对于代码块外不可见。
- 函数作用域:以function(){}为界限,函数内声明的变量对函数外不可见。
怎么样,到这是不是觉得“楼主你在侮辱我的智商吧,这么简单的问题还用你说”,稍安勿躁,就这么一个看似简单的作用域问题,里面可是有很多小坑的呢,下面让我们一一来举例说明
1.在JavaScript中没有用var声明的变量都是全局变量
t = 10;//全局变量
function foo() {
t = 3;//全局变量,和上面的t是同一个变量
console.log(t);
}
console.log(t);
foo();
console.log(t);
复制代码
这段代码的输出结果是
10
3
3
复制代码
这个函数内部的t因为并不是用var声明的变量,所以它虽然在函数内但它的作用域并不是这个函数而是全局,那么此时他和外部的t是在同一个作用域内,那么当函数执行时后面的全局变量t的值就会覆盖前面的全局变量t的值,导致全局作用域内的t的值更改为3.
好,那么我们现在稍微的来修改一下这个代码
var t = 10;
function foo() {
t = 3;
console.log(t);
}
console.log(t);
foo();
console.log(t);
复制代码
看出我修改了什么地方吗,对,把函数外的t用var声明了一下,那么你觉得修改后的代码和上面的输出会一样吗?答案是一样的。虽然我们用var声明了这个变量,但由于它是在函数外,所以它的作用域还是全局,因此并没有丝毫影响.
再让我们修改一下
var t = 10;
function foo() {
var t = 3;//用var声明的,非全局变量,作用域为此函数内
console.log(t);
}
console.log(t);
foo();
console.log(t);
复制代码
这一次,会发现控制台输出的值变为了
10
3
10
复制代码
函数内的t用var声明形成了函数作用域,所以即使是函数执行完毕最后一个console.log(t)输出的还是作用于全局的那个变量t。
2.变量声明的提升
继续修改上面的代码
var t = 10;
function foo() {
console.log(t);
var t = 3;
}
console.log(t);
foo();
console.log(t);
复制代码
控制台的输出为:
10
undefined
10
复制代码
咦,中间输出的怎么会是undefined?这就涉及到变量声明的提升的问题了,在foo函数内 var t = 3;虽然在console.log(t);之后,但是var声明的变量会提升到当前作用域的最前面,注意这里仅仅是声明的提升,而值却是不提升的。
3.作用域链
作用域链:由于js的变量都是对象的属性,而该对象可能又是其它对象的属性,而所有的对象都是window对象的属性,所以这些对象的关系可以看作是一条链,链头就是变量所处的对象,链尾就是window对象
光说不练假把式,上代码
var t = 10;
function foo() {
console.log(t);
t = 3;
}
console.log(t);
foo();
console.log(t);
复制代码
控制台输出为
10
10
3
复制代码
foo中的t不是用var声明的,所以不存在变量提升的问题,故此时在当前函数作用域内找不到声明在console.log(t)的t,那么这时候怎么办呢,继续往上层作用域找啊,发现上一层的全局作用域中声明了var t = 10,找到啦,所以第二个输出的是10。这就是传说中的作用域链啦,对于console.log(t)来说,他所处的作用域链就是foo-->window。
4.Js没有块级作用域
其实写这个标题的时候我是有点方的,因为自从es6的let出现以后,Js也有块级作用域了。不过没关系,过于let我们一会再说。
先上代码
if(true){
var m=5;
console.log(m);
}
console.log(m);
复制代码
控制台输出为
5
5
复制代码
function foo (){
var m=5;
console.log(m);
}
foo();
console.log(m);
复制代码
控制台输出为
5
Uncaught ReferenceError: m is not defined
复制代码
怎么样,是不是很清晰很明了。Js中作用域以以function(){}为界限,不以{}为界限。
5.Es6中let形成的块级作用域
ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。也就是是说如果在以{}为界限的代码块中用let声明了一个变量,那么这个变量的作用域就是这个代码块,对于代码块外是不可见的。
if(true){
let m=5;
console.log(m);
}
console.log(m);
复制代码
控制台输出:
5
Uncaught ReferenceError: m is not defined
复制代码
let的出现解决了变量污染的问题,可以代替闭包解决一些问题,后面我也会专门写一篇关于闭包的文章。