Babel 不是万能的, 慎用 for..of

38 阅读4分钟

最近被一个for..of的问题耽误好久,发现是for..of的问题,哈哈,真的很苦逼啊,有时候能过,有时候过不了,看babel 的心情啦!

在这里插入图片描述

什么是Babel

给大家讲个故事

传说古时候一群人想建一个通天塔,去天上看看上帝在干什么。上帝于是挥挥手,让这群人说不同的语言,于是他们再也不能顺畅沟通,塔也就建不起来了。

在JavaScript中,不同版本的浏览器对JavaScript的版本各有要求,而Babel的出现是的开发者几乎不用考虑浏览器的支持情况,尽情享受最新语法的舒适。

Babel是一个编译器,能将ES2015+的代码转换正ES2015。(Babel is a JavaScript compiler.)

ES6结构语法
let [a,,b] = [1,2,3]
转换成ES5
var _ref = [1,2,3];
	a = _ref [0];
	b = _ref [2];

Babel做了什么?

在这里插入图片描述

解析 (Parsing)

通过词法分析和语法分析,将JavaScript源代码转换成抽象语法树。

转换 (transforming)

对语法树(ast)进行转换操作,把ES2015+的部分转换为ES5。

再建 (generation)

将转换之后的语法树重新生成代码。

Babel做什么,不做什么

Babel只对不属于ES5的语法做了转换,如:

for-of

箭头函数

结构

块级作用域

let/const

但下面情况不作处理:

一些内置的全局变量,如:Promise、Symbol、Set、WeakMap

对一些Object新增的方法,如: Array.includes、Object.is、generactor 函数。

接下来给大家贴下我遇到的我问题!突然间运行不了啦!(for-of),编译报错

在这里插入图片描述

可能babel 有时候识别不了,或者配置没有配置正确,为了最少的改动 直接of 改成in

在这里插入图片描述

我们观察到 Babel 转换后的代码里第 9 行仍然出现了 ES6 的特性——Symbol.iterator,这是为什么呢?我们先来探究一下 for…of 的实现原理。
for…of 在对数据结构进行循环时,背后实际上是调用了该数据结构的 Iterator 接口。一种数据结构只要具有 Iterator 接口,我们就可以认为该数据结构是“可遍历的”(iterable)。原生数据结构中具有“可遍历”属性的包括数组、Set、Map、以及字符串之类的类数组对象等。具体到 Iterator 接口上,ES6 规定,默认的 Iterator 接口部署在该数据结构的 Symbol.iterator 属性上(Symbol 是 ES6 新增的原始数据类型,表示独一无二的值,具体参见 ES6 文档),该属性本身是一个函数,执行该函数会返回一个指针对象。该指针对象称为遍历器,其必须包含一个 next 方法,不断调用 next 方法可以使指针从数据结构的第一个成员一直指向最后一个成员,即调用 next 方法会返回数据结构当前成员的信息,该信息为一个对象,包含 value 和 done 两个属性。value 是当前成员的值,done 是一个布尔值,表示遍历是否结束。以上的理论有点抽象,我们来模拟一个“可遍历”的数据结构:
const iterableData = {
  data: ['paul','jordan','griffin','redick','rivers'],
  dataIndex: 0,
  //Symbol 类型的值作为对象属性时必须使用方括号结构
  [Symbol.iterator]: function () {
    var self = this;
    return {
      next: function () {
	    return {
		 value: self.data[self.dataIndex++],
          done: self.dataIndex < self.data.length? false: true	
	    };
      }
    };
  }
};


for (let item of iterableData) {
  console.log(item);
}
// paul, jordan, griffin, redick, rivers
可以看到,只要一个数据结构具有符合要求的 Symbol.iterator 属性,就可以通过 for…of 遍历(事实上,解构赋值、扩展运算符、yield* 等 ES6 特性也是调用该属性接口)。
现在,我们回过头来看 Babel 转换 for…of 循环的代码,其本质上还是通过调用 Iterator 接口(注意第 9 行),将 for…of 转换为传统的 for 循环,并在每次循环中调用遍历器的 next 方法来吐出数组中的值。如果在循环调用过程中出现错误,遍历器中如含有预定义的 return 函数(参见 ES6 文档中遍历器对象的规范 ),则调用之,否则直接抛出错误。
所以,问题就出现了,即使调用 Babel 对 for…of 循环进行转码,我们实际上还是无法完全摆脱 ES6 的特性——在不支持 Symbol 的环境下,代码仍然会报错。因为 Babel 默认只转换新的 JavaScript 句法(syntax),而不转换 Proxy、Set、Promise、Symbol 等新的 API。所以,在对兼容性要求较高时,确实要慎重使用 for…of 语法,即使我们有 Babel 这件神兵利器。
实际上,要想完全抹平 ES6 特性带来的新 API 也是可行的,只要在项目中引入 babel-polyfill 并配置好即可,但是这样带来的另一个问题就是因为 babel-polyfill 本身的体积,我们的代码也会变庞大不少。所以此举有利有弊,需要根据实际情况进行权衡。

babel