javascript遍历对象 js在for循环中使用java代码

背景大力教育的在线教室中台提供封装了核心能力的教室 SDK,业务方基于教室 SDK 开发面向用户的在线教室 App 。最近对教室 SDK 做一次比较大的改动时,我遇到了一个懵逼的问题 。这个问题耗费了我 3 天左右时间,让我压力一度大到全身发热 。当时虽然解决了问题,但并没有很理解原因 。直到一个多月后,才有时间做一些更深入的分析,并写下这篇文章 。
当时的情况是,业务方 App 工程能通过 TypeScript 编译,但在运行时会报错 。就不同的使用教室 SDK 的方式,报错有两种 。图 1 为在业务方 App 工程里正常安装教室 SDK 后进行调试时的报错;图 2 为在业务方 App 工程里 yarn link 教室 SDK 后进行调试时的报错 。

javascript遍历对象 js在for循环中使用java代码

文章插图
javascript遍历对象 js在for循环中使用java代码

文章插图
javascript遍历对象 js在for循环中使用java代码

文章插图
javascript遍历对象 js在for循环中使用java代码

文章插图
javascript遍历对象 js在for循环中使用java代码

文章插图
javascript遍历对象 js在for循环中使用java代码

文章插图
javascript遍历对象 js在for循环中使用java代码

文章插图
javascript遍历对象 js在for循环中使用java代码

文章插图
javascript遍历对象 js在for循环中使用java代码

文章插图
javascript遍历对象 js在for循环中使用java代码

文章插图
使用不当的问题由于子模块先于父模块被执行,子模块直接执行从父模块导入的变量会导致 JS 错误 。
// 文件 parent.jsimport {} from './child.js';export const parent = 'parent';// 文件 child.jsimport { parent } from './parent.js';console.log(parent); // 报错代码 2
如代码 2 所示,child.js 中的导入变量 parent 被绑定为 parent.js 的导出变量 parent,当执行 child.js 的最后一行代码时,parent.js 还没有被执行,parent.js 的导出变量 parent 未被初始化,所以 child.js 中的导入变量 parent 也就没有被初始化,会导致 JS 错误 。注意,本文说的变量是统称,包含 var、let、const、function 等关键字声明的变量 。
console.log(parent)^ReferenceError: Cannot access 'parent' before initialization如果是异步执行,则没问题,因为异步执行的时候父模块已经被执行了 。例如,代码 3 是能正常运行的 。
// parent.jsimport {} from './child.js';export const parent = 'parent';// child.jsimport { parent } from './parent.js';setTimeout(() => {console.log(parent) // 输出 'parent'}, 0);代码 3
纠正教程观点《ECMAScript 6 入门教程》一书说的三个重大差异如下:
  1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用 。
  2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口 。
  3. CommonJS 模块的 require() 是同步加载模块,ES6 模块的 import 命令是异步加载,有一个独立的模块依赖的解析阶段 。
对于第 1 点,CommonJS 和 ES6 模块输出的都是变量,变量都是值的引用 。该章节的评论中也有人质疑这个点 。对于第 2 点,前半句基本正确,后半句基本错误 。CommonJS 模块在执行阶段加载子模块文件,ES6 模块在预处理阶段加载子模块文件,当然 ES6 模块在执行阶段也会加载子模块文件,不过会使用预处理阶段的缓存 。从形式上看,CommonJS 模块整体导出一个包含若干个变量的对象,ES6 模块分开导出单个变量,如果只看父模块,ES6 模块的父模块确实在预处理阶段就绑定了子模块的导出变量,但是预处理阶段的子模块的导出变量是还没有被赋最终值的,所以并不能算真正输出 。对于第 3 点,CommonJS 模块同步加载并执行模块文件,ES6 模块提前加载并执行模块文件 。异步通常被理解为延后一个时间节点执行,所以说成异步加载是错误的 。
分析问题对 JS 模块机制有了更深刻的理解后,我们回来分析我遇到的问题 。
问题一首先分析图 1 的报错 。业务方 App 的工程代码用 webpack 打包,所以实际运行的是 CommonJS 模块 。上面讲过 CommonJS 模块循环引用使用不当一般不会导致 JS 错误,为啥这里会出现 JS 报错呢?这是因为,循环引用使用不当导致变量的值为 undefined,我们的代码使用了 extends[21],而 extends 不支持 undefined 。由于使用了 Bable[22] 进行转码,所以由垫片 _inherits[23] 报错 。另外一个典型的不支持的 undefined 的 case 是 Object.create(undefined) 。