ES6 借鉴了其他编程语言的特性,为 JavaScript 带来了 for…of 循环语法,用于遍历数组等数据结构。当然,由于是 ES6 的特性,我们使用 for…of 的时候,依然要借助 Babel 进行转码。我们来看看 Babel 是如何处理 for…of 代码的。

ES6 原生代码如下。为避免干扰,这里不使用 ES6 其他特性。

var names = ['paul', 'jordan', 'griffin'];
for (var name of names) {
  console.log(name);
}

Babel 转码后结果如下:

'use strict';

var names = ['paul', 'jordan', 'griffin'];
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;

try {
  for (var _iterator = names[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
    var name = _step.value;

    console.log(name);
  }
} catch (err) {
  _didIteratorError = true;
  _iteratorError = err;
} finally {
  try {
    if (!_iteratorNormalCompletion && _iterator.return) {
      _iterator.return();
    }
  } finally {
    if (_didIteratorError) {
      throw _iteratorError;
    }
  }
}

我们观察到 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 本身的体积,我们的代码也会变庞大不少。所以此举有利有弊,需要根据实际情况进行权衡。

支付宝扫码打赏 微信打赏

坚持原创技术分享,您的支持将鼓励我继续创作!

扫描二维码,分享此文章

逆葵's Picture
逆葵

花名逆葵,刚刚毕业的北邮 CS 土著一枚。现在淘宝 FED 搬砖。《Vue.js 权威指南》作者之一。学习、思考、沉淀中,向成为顶级 JavaScript 技术栈开发者努力。