新手关于import/export的理解

前言

从事前端工作已经两年多了,技术上从最开始的jq,到后来的angular,然后react,vue,都一一学习过来并且应用到了实践中。自从有了es6,node,有了脚手架,感觉写代码体验如飞。直到有一天用原生开发,出现了这样一个问题。我需要在原生的页面上引入另一个文件,我习惯性的import 了那个文件,可是没有效果…

Uncaught SyntaxError: Unexpected string
瞬间懵逼。我明明用的js啊,我在Vue,React,Angular中都这么用的啊,为什么写到原生就不行了呢?我知道babel,可是没想到import也需要被babel转译。瞬间感觉离开babel就好像不会写es6。可见我的认知被以前很方便的框架给局限了,而且es6中的import/export我也是理解很久,因为它们相互交错较难理解。下面我来归纳我所理解的import/export的几种常见用法,因为这也是属于es6的基础。

export

export 就是对外暴露方法、类或者数据供另外的js使用。

1
2
3
4
5
6
7
8
9
10
export const A = 1; // 对外暴露模块内变量A
export const Fn = ()=>{}; // 对外暴露模块内方法Fn.其中Fn可以使用模块内的方法。
// 上面的export方式,等价于
const A= 1;
const Fn = () =>{};
export {A,Fn};
// 其中可以对导出的内容起别名
export {A as B ,Fn as Fm};
// 默认导出.一个文件中只能有一个默认导出
export default class extends react.Components{}

import

我们在原生js中要Import看来是难以实现了,或者说,现在浏览器对js直接使用es6语法的支持还不够。但是在拥有babel的环境下(比如使用框架)使用import 还是很必须的。

1
2
3
4
5
6
7
import './config.js'
// 这里的import, 表示引入了整个config.js文件,并且运行了这个js文件除了export以外的所有代码。
import vue from 'vue'
import aa from '../config.js'
// from顾名思义就是来自某个文件。其中没有相对路径的(比如Vue)就是取自node_modules内的对应文件夹内的index.js。
// import 后面则是来自文件内的默认导出的模块,引入后等同于在引入文件中添加了对应的模块。默认导入可以用as 起别名
import {Fn,A} from 'a.js'; // 引入js文件a中的导出的方法Fn和常量A

import/export的es5实现

es6的相关语法,其实也就是es5的相关的语法糖。我通过babel还原了一下使用import/export的场景。首先是export的部分代码。

1
2
3
4
5
6
7
8
9
10
11
12
// index.js
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var Fn = exports.Fn = function Fn() {

};
exports.default = {
a: 1
};
var A = exports.A = 1;

首先就是代码头部声明严格模式,这很重要。然后我们看到导出部分代码是通过 exports 对象来输出模块内的引用,这类似于CommonJs的模式,即有要输出的对象统统挂载在 module.exports 上,然后暴露给外界。但是相比较于CommonJs,Babel解析出来的代码多了一个export.default,即默认导出。同时对export对象添加属性”exports.__esModul =true”,这是为什么呢?让我们再看一下导入解析出来的代码。

1
2
3
4
5
6
7
8
// import.js
'use strict';
var _index = require('./index');
var _index2 = _interopRequireDefault(_index);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
(0, _index.Fn)();
console.log(_index.A);
console.log(_index2.default);

这里看到导入文件是使用的require(),那么可以确定babel对模块是参照CommonJs进行解析的。然后可以看到导入文件中有一个_interopRequireDefault函数,这里就解释了”exports.__esModul =true”的用法,原来是方便检测导入模块是否通过了babel解析,这样 Babel 就可以放心地从加载的模块中调用”exports.default” 这个导出的对象了。如果加载的模块没有检测到,则可能是第三方的复合CommonJs的模块,可能不存在default导出,此时就将default导出指向模块本身。继续看代码发现”(0, _index.Fn)();”,我从事前端两年了也没有见过这种写法,有点类似立即执行函数。查阅资料才知道,如果执行 (“(0, _index.Fn)();”,这个逗号表达式等价于执行 _index.Fn(),但是执行时的上下文环境会被绑定到全局对象身上,所以实际上真正等价于执行 foo.bar.call(GLOBAL_OBJECT)。类的导入导出和函数类似,解析出的代码有点长这里就不多做概述了。

结语

模块化对前端的意义是巨大的,es6的出现对前端的影响也是空前绝后。
在当今前端界三大框架盛行的情况下,我们写着业务代码的时候,不知是否有思考这个方法的原理?es6以前,js模块化需要用到复杂的commonjs、seajs、requirejs中的一个或者几个,这也是需要对基础有较高的要求。框架方便开发,但是基础才是一个人真正实力的体现。以后前端的局势可能会变动,但是基础知识的牢固是突破未来的利剑。我以前就是太依赖于框架,认为会了框架就会了前端,看山都是山。以至于脱离框架,就开始看山不识山。只有将基础打磨好,才可以一览纵山小。


作者简介: 张栓,人和未来大数据前端工程师,专注于html/css/js的学习与开发。