ES6块级作用域及相关高频笔试题解析

1. 概述

1.1 变量的作用域

  • ES6之前,变量的作用域只有两种:
    • 局部作用域:只能在当前函数内部使用
    • 全局作用域:可以在任意函数内使用——是全局对象的成员
  • ES6中,增加了一种变量作用域:
    • 块级作用域:只能在当前块内使用

1.2 为什么要用块级作用域

  • 变量提升,内层变量覆盖外层变量
var i = 10;
function func() {
    
    
  console.log(i);	
  if (true) {
    
    
	 var i = 5;
  }	
}
func(); // undefined

为什么是undefined?

var i = 10;
function func() {
    
    
  var i;  //变量提升
  console.log(i);	// undefined
  if (true) {
    
    
	 var i = 5;
  }	
}
func(); 
  • 用来计数的循环变量污染了全局。下例变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。
for(var i=0;i<10;i++){
    
    
  console.log(i);
}
console.log(i); //10

1.3 ES6块级作用域的使用

  • let为JS新增了块级作用域
{
    
    
  let i=10;
}
console.log(i); //i is not defined
  • 块级作用域可以任意嵌套,外层作用域无法读取内层作用域的变量
{
    
    {
    
    {
    
    
  {
    
    
    {
    
    
      let i=10
    }
  console.log(i); // 报错
  }
}}};
  • 内层作用域可以定义外层作用域的同名变量
{
    
    {
    
    {
    
    
  {
    
    
    {
    
    
      let i=10;
      console.log(i); //10
    }
  let i=1;
  console.log(i); //1
  }
}}};
  • ES6允许在块级作用域中声明函数
function f() {
    
    
	console.log('I am outside!');
}
(function () {
    
    
	if (false) {
    
     
		function f() {
    
    
			console.log('I am inside!');
		}
	}
	f();
}());

    在ES5中,会得到 I am inside!,因为存在函数提升(整个函数都会被提升)会将整个函数提升到匿名函数作用域顶部,外部的f()函数被覆盖,所以f()的结果是I am inside!
    在ES6中,我们分两种情况看:

  • 严格模式下,得到的结果是I am outside! ,因为内部的f()函数在if的块级作用域内,无法影响外部全局的f()函数
  • 非严格模式下,会报错: Uncaught TypeError: f is not a function。

2. 一道高频笔试题解析

我们先看看下面这段代码:

<body>
  <button>click me</button>
  <button>click me</button>
  <button>click me</button>
  <script>
    var btns=document.getElementsByTagName("button");
    for(var i=0;i<btns.length;i++){
     
     
        btns[i].onclick=function(){
     
     
          alert(`我是第${
       
       i}个按钮`)
        }   
    }
  </script>
</body>

    上述代码想实现:一个网页上有三个完全相同按钮,我们希望点第一个按钮时,弹出“我是第0个按钮”,点第二按钮弹出“我是第1个按钮”,点第三个按钮弹出“我是第2个按钮”,但是运行结果却不是我们预期的——无论我们点击哪个按钮,结果都是“我是第3个按钮”,为什么呢?
    首先,我们要知道:

  • 循环结束时,i的值为3;
  • οnclick=function(){alert(我是第${i}个按钮)} 是绑定事件处理函数,只定义函数并不执行,只有当用户点击了一个按钮后才会执行。

    我们现在点击一个按钮,触发事件开始执行onclick函数。在onclick函数的作用域链对象中,按照从局部到全局的顺序引用对象。局部对象指向了onclick函数临时创建的函数作用域对象(活动对象),活动对象中保存着onclick函数可用的局部变量。现在我们思考,onclick函数中是否有局部变量?
    i是局部变量吗???绝对不是!!!,i只是一个变量而已,所以我们在活动对象中没有找到任何局部变量。
    按照js引擎的执行顺序,局部找不到,就在全局window中寻找。控制循环的变量i执行完泄露到全局中,我们在全局中找到了i=3,于是就输出了“我是第3个按钮”!
    怎么解决这个问题呢?看下面代码

<body>
  <button>click me</button>
  <button>click me</button>
  <button>click me</button>
  <script>
    var btns=document.getElementsByTagName("button");
    for(let i=0;i<btns.length;i++){
    
    
        btns[i].onclick=function(){
    
    
          alert(`我是第${
      
      i}个按钮`)
        }   
    }
  </script>

    为什么用let就可以了呢?让我们看看let的底层代码

<body>
  <button>click me</button>
  <button>click me</button>
  <button>click me</button>
  <script>
    var btns=document.getElementsByTagName("button");
    for(var i=0;i<btns.length;i++){
    
    
      (function(i){
    
    
        btns[i].onclick=function(){
    
    
          alert(`我是第${
      
      i}个按钮`)
        }
      })(i)    
    }
  </script>

    有什么不同呢?

  • for循环内部使用了一个匿名函数自调
  • 并且将当前的变量值i通过传参的方式传进去

    当我们点击第一个按钮时,开始执行匿名函数,同时创建了匿名函数的活动对象,里面保存着匿名函数可用的局部变量。这时i作为匿名函数的参数,一定是保存在活动对象中的,此时,i=0。
    接下来我们执行内部的onclick函数。因为onclick函数在匿名函数内部,所以它的作用域链分为三层,第一层是为自己预留的,第二层是外层匿名函数的,第三层全局window的。js引擎同样按照从局部到全局的顺序寻找i。果然在第二层外层匿名函数中找到了i=0,所以结果显示“我是第0个按钮”,第二个第三个按钮亦是如此。
    以上也解释了为什么let和for一起使用能形成闭包的效果。

    本文主要讲述了ES6的作用域,创作过程中查找了有关函数提升,以及作用域链的知识,后期也会具体的做一个总结。

猜你喜欢

转载自blog.csdn.net/weixin_44410783/article/details/110350935
今日推荐