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并不支持nomodule属性 (issue)。但在Firefox nightly版里已经修复了这个问题。
  • 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头信息。

浏览器问题

  • Firefox没有成功的加载演示页面 (issue).
  • Edge浏览器加载了没有 CORS headers 的 module scripts (issue)。

没有凭证信息(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上,则使用importrequire

require/exports 是 CommonJS/AMD 中为了解决模块化语法而引入的
import/export 是ES6引入的新规范,因为浏览器引擎兼容问题,需要在node中用babel将ES6语法编译成ES5语法
关于import在浏览器中被支持的情况如下

$ 调用时间

require 是运行时调用,所以理论上可以运作在代码的任何地方
import 是编译时调用,所以必须放在文件的开头

$ 本质

require 是赋值过程,其实require的结果就是对象、数字、字符串、函数等,再把结果赋值给某个变量。它是普通的值拷贝传递。
import 是解构过程。使用import导入模块的属性或者方法是引用传递。且importread-only的,值是单向传递的。default是ES6 模块化所独有的关键字,export default {} 输出默认的接口对象,如果没有命名,则在import时可以自定义一个名称用来关联这个对象

$ 语法用法展示

require的基本语法

  在导出的文件中使用module.exports对模块中的数据导出,内容类型可以是字符串,变量,对象,方法等不予限定。使用require()引入到需要的文件中即可

  在模块中,将所要导出的数据存放在moduleexport属性中,在经过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的是一个对象,即使导出的是基础数据类型

部分转载自:https://www.jianshu.com/p/f1e54dde30c8

Last modification:April 20, 2022
如果觉得我的文章对你有用,请随意赞赏