export default ,require,import
import和js的发展息息相关,历史上js没有模块体系,无法将一个大程序拆分成相互依赖的肖文杰,再用简单方法拼装起来.在es6之前,社区制定了一些模块加载方案.ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。也就是我们常见的 require 方法。比如 `let { stat, exists, readFile } = require('fs');ES6 在语言标准的层面上,实现了模块功能。ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
import的用法解释
- import后面的from指定模块文件的位置,可以使相对路径,也可以是绝对路径,.js后缀可以省略.如果只是模块名,不带有路径,那么必须有配置文件,告诉js该引擎模块的位置.某些打包工具可以允许或要求使用扩展名
- 使用as关键字,可以指定import引入的模块别名
- import * from 'xx'将导入整个模块的内容,而import default
- import {export1,export2}将导入export的某个对象或值(导入俩个模块)
- 直接import 'xxx'将允许模块中的全部代码,而且不导入任何值
- import的形式需要export的支持,比如import 'xxx' from 'xxx'将导出在modules.js中export的对象或值
export也是es6中的内容和import是一对.export的
默认导出,export命名导出需要export名字和import名字严格一致.而export default为模块指定默认输出命令.为模块指定默认输出,在import的时候可以随意命名.一个模块只能有一个默认输出,也就是说export default一个模块只能用一次
//输出一个默认函数
export default function add(x,y){return x+y;}
//输出一个变量
let name="String"
export default name
//输出一个类
export default class {...}
//输出一个值
export default 1;
export和import混合使用(模块重定向)也就是在一个模块之中,先输入后输出同一个模块
//命名导出引入的命名导入
export { xxx1,xxx2 } from 'xxxx'
错误
index.html
<!DOCTYPE html>
<html lang="en">
<script type="text/javascript" src="./1.js" ></script>
<!-- <script type="text/javascript" src="./2.js" type="module"></script> -->
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
1.js
import {firstName} from './2.js'
console.log(firstName);
2.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};
但如果直接在浏览器中这样写可能会遇到一个报错
Uncaught SyntaxError: Unexpected token {
但是谷歌浏览器已经支持es6的语法了,为什么不能直接使用呢
正确的写法应该是这样
<!DOCTYPE html>
<html lang="en">
<!-- <script type="text/javascript" src="./1.js" type="module"></script> -->
<!-- <script type="text/javascript" src="./2.js" type="module"></script> -->
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="module">
import {firstName} from './2.js'
console.log(firstName);
</script>
</body>
</html>
下面引用一下https://jakearchibald.com/2017/es-modules-in-browsers/文章的说法
您所需要的是脚本元素上的type=module,浏览器将把内联或外部脚本作为ECMAScript模块处理。
已经有一些关于模块的优秀文章,但是我想分享一些我在测试和阅读规范时学到的关于浏览器的东西:
目前不支持“裸”导入说明符
// 支持
import {foo} from 'https://jakearchibald.com/utils/bar.mjs';
import {foo} from '/utils/bar.mjs';
import {foo} from './bar.mjs';
import {foo} from '../bar.mjs';
// 不支持
import {foo} from 'bar.mjs';
import {foo} from 'utils/bar.mjs';
有效的模块说明符必须匹配下列之一:
一个完整的非相对URL。同样,当通过新URL(moduleSpecifier)输入时,它不会抛出错误。
开始/.
开始./.
从../开始。
其他的说明符是为将来使用而保留的,比如导入内置模块。
nomodule 用于向后兼容
<script type="module" src="module.js"></script>
<script nomodule src="fallback.js"></script>
能够认识type=module
语法的浏览器会忽略具有nomodule
属性的scripts。也就是说,我们可以使用一些脚本服务于支持module语法的浏览器,同时提供一个nomodule
的脚本用于哪些不支持module语法的浏览器,作为补救。
浏览器问题
Firefox并不支持。但在Firefox nightly版里已经修复了这个问题。nomodule
属性 (issue)- Edge并不支持
nomodule
属性。 (issue). - Safari 10.1 并不支持
nomodule
属性,但在最新的技术预览版中修复了这个问题。对于10.1版, 有一个很聪明的变通技巧。
缺省设置为Defer
<!-- 这个脚本的执行将会晚于… -->
<script type="module" src="1.js"></script>
<!-- …这个脚本… -->
<script src="2.js"></script>
<!-- …但是会先于这个脚本. -->
<script defer src="3.js"></script>
执行的顺序将会是2.js
, 1.js
, 3.js
.
如果script代码块阻止HTML分析器下载其他代码,这是非常糟糕的事情,通常我们会使用 defer
属性来防止这种解析阻塞,但同时这样也会延迟script脚本的执行——直到真个文档解析完成。而且还要参考其它deferred script脚本的执行顺序。Module scripts缺省行为状态很像 defer
属性的作用 – 一个 module script 不会妨碍HTML分析器下载其它资源。
Module scripts队列的执行顺序跟使用了defer
属性的普通脚本队列的执行顺序一样。
Inline scripts同样是deferred
<!-- 这个脚本的执行将会晚于… -->
<script type="module">
addTextToBody("Inline module executed");
</script>
<!-- …这个脚本… -->
<script src="1.js"></script>
<!-- …和这个脚本… -->
<script defer>
addTextToBody("Inline script executed");
</script>
<!-- …但会先于这个脚本。-->
<script defer src="2.js"></script>
执行的顺序是 1.js
, inline script, inline module, 2.js
.
普通的inline scripts会忽略defer
属性,而 inline module scripts 永远是deferred的,不管它是否有 import 行为。
外部 & inline modules script脚本上的 Async 属性
<!-- 这个脚本将会在imports完成后立即执行 -->
<script async type="module">
import {addTextToBody} from './utils.js';
addTextToBody('Inline module executed.');
</script>
<!-- 这个脚本将会在脚本加载和imports完成后立即执行 -->
<script async type="module" src="1.js"></script>
这个快速下载script会率先执行。
跟普通的scripts一样, async
属性能让script加载的同时并不阻碍HTML解析器的工作,而且在加载完成后立即执行。跟普通的scripts不同的是, async
属性在inline modules脚本上也有效。
因为永远都是async
, 所以这些scripts的执行顺序也许并不会像它们出现在DOM里的顺序。
浏览器问题
- Firefox并不支持inline module scripts上的
async
特性。(issue).
Modules只执行一次
<!-- 1.js 只执行一次 -->
<script type="module" src="1.js"></script>
<script type="module" src="1.js"></script>
<script type="module">
import "./1.js";
</script>
<!-- 而普通的脚本会执行多次 -->
<script src="2.js"></script>
<script src="2.js"></script>
如果你知道 ES modules,你就应该知道,modules可以import多次,但只会执行一次。这种原则同样适用于HTML里的script modules – 一个确定的URL上的module script在一个页面上只会执行一次。
浏览器问题
- Edge浏览器会多次执行 modules (issue).
CORS 跨域资源共享限制
<!-- 这个脚本不会执行,因为跨域资源共享限制 -->
<script type="module" src="https://….now.sh/no-cors"></script>
<!-- 这个脚本不会执行,因为跨域资源共享限制-->
<script type="module">
import 'https://….now.sh/no-cors';
addTextToBody("This will not execute.");
</script>
<!-- 这个没问题 -->
<script type="module" src="https://….now.sh/cors"></script>
跟普通的scripts不同, module scripts (以及它们的 imports 行为) 受 CORS 跨域资源共享限制。也就是说,跨域的 module scripts 必须返回带有有效 Access-Control-Allow-Origin: *
的CORS头信息。
浏览器问题
没有凭证信息(credentials)
<!-- 有凭证信息 (cookies等) -->
<script src="1.js"></script>
<!-- 没有凭证信息 -->
<script type="module" src="1.js"></script>
<!-- 有凭证信息 -->
<script type="module" crossorigin src="1.js?"></script>
<!-- 没有凭证信息 -->
<script type="module" crossorigin src="https://other-origin/1.js"></script>
<!-- 有凭证信息-->
<script type="module" crossorigin="use-credentials" src="https://other-origin/1.js?"></script>
当请求在同一安全域下,大多数的CORS-based APIs都会发送凭证信息 (cookies 等),但fetch()
和 module scripts 例外 – 它们并不发送凭证信息,除非我们要求它们。
我们可以通过添加crossorigin
属性来让同源module脚本携带凭证信息,如果你也想让非同源module脚本也携带凭证信息,使用 crossorigin="use-credentials"
属性。需要注意的是,非同源脚本需要具有 Access-Control-Allow-Credentials: true
头信息。
同样,“Modules只执行一次”的规则也会影响到这一特征。URL是Modules的唯一标志,如果你先请求的Modules没有携带凭证信息,然后你第二次请求希望携带凭证信息,你仍然会得到一个没有凭证信息的module。这就是为什么上面的有个URL上我使用了一个?
号,是让URL变的不同。
浏览器问题
- 谷歌浏览器在请求同源 modules 时带有 credentials (issue).
- Safari 请求同源 modules 时不带有 credentials,即使你使用了
crossorigin
属性标志。 (issue). - Edge浏览器完全相反。它缺省状态下对同源请求 modules 时带有 credentials 而当你指定了
crossorigin
属性后反倒没了。 (issue).
火狐浏览器是唯一做的正确的浏览器,干的漂亮!
JS中的「import」和「require 」
模块化是一种将系统分离成独立功能部分的方法,一个模块是为完成一个功能的一段程序或子程序。"模块"是系统中功能单一且可替换的部分。
模块化思想是从java上衍生过来的,他将所需要的功能封装成一个类,哪里需要就在哪里调用,JS中没有类的说法,但它引入了这种思想,在js中用对象或构造函数来模拟类的封装实现模块化,而在html上,则使用import
和require
require/exports
是 CommonJS/AMD 中为了解决模块化语法而引入的import/export
是ES6引入的新规范,因为浏览器引擎兼容问题,需要在node中用babel
将ES6语法编译成ES5语法
关于import在浏览器中被支持的情况如下
$ 调用时间
require
是运行时调用,所以理论上可以运作在代码的任何地方import
是编译时调用,所以必须放在文件的开头
$ 本质
require
是赋值过程,其实require
的结果就是对象、数字、字符串、函数等,再把结果赋值给某个变量。它是普通的值拷贝传递。import
是解构过程。使用import
导入模块的属性或者方法是引用传递。且import
是read-only
的,值是单向传递的。default
是ES6 模块化所独有的关键字,export default {}
输出默认的接口对象,如果没有命名,则在import
时可以自定义一个名称用来关联这个对象
$ 语法用法展示
require的基本语法
在导出的文件中使用module.exports
对模块中的数据导出,内容类型可以是字符串,变量,对象,方法等不予限定。使用require()
引入到需要的文件中即可
在模块中,将所要导出的数据存放在module
的export
属性中,在经过CommonJs/AMD规范的处理,在需要的页面中使用require
指定到该模块,即可导出模块中的export
属性并执行赋值操作(值拷贝)
// module.js
module.exports = {
a: function() {
console.log('exports from module');
}
}
// sample.js
var obj = require('./module.js');
obj.a() // exports from module
// module.js
function test(str) {
console.log(str);
}
module.exports = {
test
}
// sample.js
let { test } = require('./module.js');
test ('this is a test');
import 的基本语法
使用import
导出的值与模块中的值始终保持一致,即引用拷贝,采用ES6中解构赋值的语法,import
配合export
结合使用
// module.js
export function test(args) {
console.log(args);
}
// 定义一个默认导出文件, 一个文件只能定义一次
export default {
a: function() {
console.log('export from module');
}
}
export const name = 'gzc'
// 使用_导出export default的内容
import _, { test, name } from './a.js'
test(`my name is ${name}`) // 模板字符串中使用${}加入变量
$ 写法形式
| require/exports 方式的写法比较统一
// require
const module = require('module')
// exports
export.fs = fs
module.exports = fs
// import
import fs from 'fs';
import { newFs as fs } from 'fs'; // ES6语法, 将fs重命名为newFs, 命名冲突时常用
import { part } from fs;
import fs, { part } from fs;
// export
export default fs;
export const fs;
export function part;
export { part1, part2 };
export * from 'fs';
$ 要点总结
- 通过
require
引入基础数据类型时,属于复制该变量 - 通过
require
引入复杂数据类型时, 属于浅拷贝该对象 - 出现模块之间循环引用时, 会输出已执行的模块, 未执行模块不会输出
- CommonJS规范默认
export
的是一个对象,即使导出的是基础数据类型