原生js之理解作用域

谈到作用域,我们就能想到全局变量、局部变量等名词,接下来,我讲谈谈我对作用域的理解。


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 时,是在上一轮循环的基础上进行计算的。


这里写图片描述
这里写图片描述
这里写图片描述

猜你喜欢

转载自blog.csdn.net/qq_33036599/article/details/80769238