漫谈ES5之前的块级作用域

1,773 阅读2分钟

ES6引用了let和const,这实际上带来了块级作用域,不过在ES5之前只存在全局作用域和函数作用域,相信小伙伴对着句话应该不陌生吧,不过下面就来聊聊一个ES5的块级作用域

思考下面一个例子代码输出值为多少

for (var i = 0; i < 10; i++) {
  setTimeout(function () {
    console.log(i);
  }, 100);
}

这里会输出10个10,因为只存在一个全局变量i,我们可以通过立即执行函数来解决这个问题

for (var i = 0; i < 10; i++) {
  (function(i) {
    setTimeout(function () {
      console.log(i);
    }, 100);
  })(i);
}

如果在ES6环境下可以简单使用let命令,它会在每次循环的时候创建一个作用域,至于为什么能记住上一次的值,那是因为js引擎会记住上一次循环的值。

for (let i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i);
  }, 100);
}

上面的例子只是简单回顾一下没有块级作用域的痛苦之一,实际上我们写项目的时候可能早早的就使用了babel,这里也是后面需要说的,它会通过怎么样的形式将letconst转化为支持ES5的环境。

铺垫了这么久,下面就来说说这个特殊的作用域,看下面一个例子

try {
  throw 5;
}catch(e) {
  console.log(e);
}
console.log(e); //error e is not defined

这里try会创建一个块级作用域,在全局环境下a不存在,所以报错了。 聪明的小伙伴可能已经想到了,可以使用try来创建块级作用域,理论上是可行的,不过实际上有两点问题

  1. 性能太慢
  2. 语法丑陋,要显示的报错

我们来看下babel怎么处理上面的例子

"use strict";

var _loop = function _loop(i) {
  setTimeout(function() {
    console.log(i);
  }, 100);
};

for (var i = 0; i < 10; i++) {
  _loop(i);
}

可以看到与立即执行函数基本相同,不过使用立即执行函数创建块级作用域需要注意一点this的指向 例如这个例子

const obj = {
  foo() {
    for (let i = 0; i < 5; i += 1) {
      setTimeout(() => {
        console.log(this.arr[i]);
      }, 1000);
    }
  },
  arr: [1, 2, 3, 4, 5],
};

箭头函数没有自己的this对象,是定义时的对象而不是运行时的对象,在这个例子中指向的是obj; 如果要把上面的代码转化为ES5环境支持的代码,我们可以通过作用域的规则来实现

(function() {
  var _this = this;
  setTimeout(function() {
    console.log(_this.arr[i]);
  }, 1000);  
})();

上面就实现了所需的功能