谈到作用域,我们就能想到全局变量、局部变量等名词,接下来,我讲谈谈我对作用域的理解。
ES5中的作用域
ES5中,有两种作用域:函数作用域和全局作用域。
第一段代码:
<script type="text/javascript">
var a = 0;
func();
function func()
{
var b = 1;
console.log(a); //0 函数作用域中访问全局变量
console.log(b); //1
}
console.log(b); //报错 全局作用域中访问func函数作用域中的局部变量
</script>
上面代码中,有两种作用域。全局变量a存在于全局作用域中,而变量b则存在于函数func()所在的函数作用域中,也就是局部变量。
1. 当我们在函数作用域中访问变量a时,在func函数这个作用域中找不到变量a时,会继续往全局作用域找,因此在函数作用域中访问变量a时实际上是访问的全局变量a,所以console的结果是0。
2. 如果在函数func中访问不存在的变量c,在func这个函数作用域中找不到,然后继续往全局作用域找,还是找不到变量c,然后就报错。
3. 当我们在全局作用域访问func函数中的局部变量b时,由于已经处在全局这个大作用域中,再全局作用域中找不到变量b,而全局作用域又叫顶级作用域,因此无法继续往上查找,结果就是直接报错。
我们可以将全局作用域和函数作用域之间的关系理解为父与子,func函数作用存在于全局作用域中,相当于儿子。当儿子没有某个东西的时候,就会找父亲要,如果父亲也没有,那就报错,而父亲是不会找儿子要东西的,这个东西就是js中的变量。js在查找变量时,都是从下级作用域往上级作用域查找的,如果一直到顶级作用域(全局作用域)都没找到,则报错。上面代码中的作用域,可以通过下图来加深理解:
第二段代码:
<script type="text/javascript">
var i = 1;
function fn1()
{
var i = 5;
var j = 20
function fn2()
{
var i = 10;
function fn3()
{
var j = 15;
console.log(i); //10
}
fn3();
console.log(i); //10
console.log(j); //20
}
fn2();
}
fn1();
console.log(i); //1
</script>
首先,我们先看下上面的代码有多少个作用域:全局作用域、fn1函数作用域、fn2函数作用域、fn3函数作用域。
然后,我们再来看一下每一次console获取的值是怎么查找的。这里我就直接用图来表示了,如果不懂可以看前面的文字描述。
我们再在控制台中印证一下上面代码的运行结果:
ES6在ES5的基础上新增了块级作用域
前面提到,es5中只有两种作用域:函数作用域和全局作用域,而es6又新增了一种作用域:块级作用域,即{ }括号形成的作用域。
es5代码片段:
var a = 0;
if(a < 10)
{
a++;
var b = a;
}
console.log(b); //1 b是全局变量。处于全局作用域,会成为全局对象window对象的属性
以上代码,虽然b是在if代码块中定义的,但由于ES5只有全局作用域和函数作用域,没有块级作用域,而b变量不是在函数中定义的,所以b只能是全局变量。
es6代码片段:
let a = 0; //注意:使用'let声明的全局变量不会成为window对象的属性
if(a < 10)
{
a++;
let b = a;
}
console.log(b); //报错 b是if代码块中的变量,只在'if'代码块{}中生效。处于块级作用域。
ES6中{ }会形成一个块级作用域,所以上面代码的b处于if这个块作用域中,不属于全局作用域。
———————————————————— 分割线 ————————————————————————
在实际开发中,我们经常会遇到这样的问题:为多个元素绑定同一个事件,这时我们可以通过for循环来绑定。
代码如下:
<div class="wrap>
<div class="item">div01</div>
<div class="item">div02</div>
<div class="item">div03</div>
<div class="item">div04</div>
<div class="item">div05</div>
<div class="item">div06</div>
</div>
<script>
var list = document.getElementsByClassName(".item");
for(var i = 0, len = list.length; i < len; i ++)
{
list[i].onclick = function(){
console.log(i); //10
}
}
</script>
执行发现,我们实际不管点击列表中的哪一个div,上面的js代码console的结果都是6。
这是因为变量 i 是一个全局变量,一直存在全局作用域中,而我们为每一个div都绑定的是一个点击事件,需要点击才能触发该事件,而这段代码执行完成往往只需要短短的几毫秒,当我们去点击div时,for循环早就执行完成,这个时候,全局变量i已经是6了,所以不管点击哪一个div控制台输出的都是6。
如果我们将上面的js代码功能用es6去写:
<script>
let list = document.getElementsByClassName(".item");
for(let i = 0, len = list.length; i < len; i ++)
{
list[i].onclick = function(){
console.log(i);
}
}
</script>
这个时候就不一样了,会发现我们不管点击哪一个div都输出的是该div对应的 i 值。为什么呢?
这是因为,使用let,{}会形成一个块作用域,声明的变量仅在块级作用域内有效。也就是说,每次循环都会重新声明变量 i ,且只在本轮循环有效。所以每一次为div绑定事件的时候,i 都是当前声明的变量 i 的值。
那每一次重新声明变量 i 是怎么知道当前的 i 的值呢?因为js引擎会记住上一轮循环的值,所以每次循环重新声明变量 i 时,是在上一轮循环的基础上进行计算的。