JavaScript 模块体系

摘要:
export命令export命令用于规定模块的对外接口。functionfoo(){exportdefault'bar'//SyntaxError}foo()import命令import命令用于输入其他模块提供的功能。六、CommonJS模块的require命令和ES6模块的import命令最好不要写在同一个模块里目前阶段,通过Babel转码,CommonJS模块的require命令和ES6模块的import命令,可以写在同一个模块里面,但是最好不要这样做。因为import在静态解析阶段执行,所以它是一个模块之中最早执行的。



CommonJS

用于服务器


AMD

用于浏览器


ES6 Module 的语法

太长不看版:ES6 Module export命令 和 import命令

ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";

ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this


export 命令

export命令用于规定模块的对外接口。

写法一:

// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

写法二:

// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export { firstName, lastName, year };

写法三:

function v1() { ... }
function v2() { ... }

export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion // 重命名后,v2可以用不同的名字输出两次
};

错误写法:

// export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。

// 报错
export 1; // 没有提供对外的接口。直接输出 1
// 正确
export var m = 1;

// 报错
var m = 1;
export m; // 没有提供对外的接口。通过变量m,还是直接输出 1
// 正确
var m = 1;
export {m};

// 报错
function f() {}
export f;
// 正确
export function f() {};
// 正确
function f() {}
export {f};

*注意:

一、动态绑定关系

export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。

export var foo = 'bar';
setTimeout(() => foo = 'hello', 500);

上面代码输出变量foo,值为bar,500 毫秒之后变成hello

这一点与 CommonJS 规范完全不同。CommonJS 模块输出的是值的缓存,不存在动态更新,详见《Module 的加载实现》一节。

二、处于模块顶层

export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,下一节的import命令也是如此。这是因为处于条件代码块之中,就没法做静态优化了,违背了 ES6 模块的设计初衷。

function foo() {
  export default 'bar' // SyntaxError
}
foo()

import 命令

import命令用于输入其他模块提供的功能。

写法一:

import { lastName } from './profile.js';

写法二:重命名

import { lastName as surname } from './profile.js';

写法三:整体加载

// circle.js

export function area(radius) {
  return Math.PI * radius * radius;
}

export function circumference(radius) {
  return 2 * Math.PI * radius;
}

现在,加载这个模块。

import * as circle from './circle';

console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));

注意,模块整体加载所在的那个对象(上例是circle),应该是可以静态分析的,所以不允许运行时改变。下面的写法都是不允许的。

import * as circle from './circle';

circle.foo = 'hello'; // 不允许
circle.area = function () {};  // 不允许

*注意:

一、只读特点

import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。

import {a} from './xxx.js'

a = {}; // Syntax Error : 'a' is read-only;

但是,如果a是一个对象,改写a的属性是允许的。

import {a} from './xxx.js'

a.foo = 'hello'; // 合法操作

上面代码中,a的属性可以成功改写,并且其他模块也可以读到改写后的值。不过,这种写法很难查错,建议凡是输入的变量,都当作完全只读,不要轻易改变它的属性。

二、提升效果

foo();

import { foo } from 'my_module';

上面的代码不会报错,因为import的执行早于foo的调用。这种行为的本质是,import命令是编译阶段执行的,在代码运行之前。

三、静态执行

由于import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。

// 报错
import { 'f' + 'oo' } from 'my_module';

// 报错
let module = 'my_module';
import { foo } from module;

// 报错
if (x === 1) {
  import { foo } from 'module1';
} else {
  import { foo } from 'module2';
}

上面三种写法都会报错,因为它们用到了表达式、变量和if结构。在静态分析阶段,这些语法都是没法得到值的。

四、import语句会执行所加载的模块,因此可以有下面的写法

import 'lodash';

五、多次重复执行同一句import语句,那么只会执行一次,而不会执行多次。

import 'lodash';
import 'lodash';

上面代码加载了两次lodash,但是只会执行一次。

import { foo } from 'my_module';
import { bar } from 'my_module';

// 等同于
import { foo, bar } from 'my_module';

上面代码中,虽然foobar在两个语句中加载,但是它们对应的是同一个my_module模块。也就是说,import语句是 Singleton 模式。

六、CommonJS 模块的require命令和 ES6 模块的import命令最好不要写在同一个模块里

目前阶段,通过 Babel 转码,CommonJS 模块的require命令和 ES6 模块的import命令,可以写在同一个模块里面,但是最好不要这样做。因为import在静态解析阶段执行,所以它是一个模块之中最早执行的。下面的代码可能不会得到预期结果。

require('core-js/modules/es6.symbol');
require('core-js/modules/es6.promise');
import React from 'React';

export default 命令

使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。

为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。

写法:

// export-default.js
// 写法一、匿名函数
export default function () {
  console.log('foo');
}

// 写法二、非匿名直接导出
export default function foo() {
  console.log('foo');
}

// 写法三、非匿名变量导出
function foo() {
  console.log('foo');
}

export default foo;
// import-default.js
import customName from './export-default';
customName(); // 'foo'
  • import命令可以为该匿名函数指定任意名字。

  • import命令后面,不使用大括号。

  • 一个模块只能有一个默认输出,因此export default命令只能使用一次、也因此命令后面才不用加大括号。

    export default命令本质(传送门:https://es6.ruanyifeng.com/#docs/module#export-default-命令)


区别

  1. ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。

参考

https://es6.ruanyifeng.com/#docs/module

免责声明:文章转载自《JavaScript 模块体系》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇【转载】Perl异常处理方法总结SSM-CRUD下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

PgSql备份pg_dump与还原手记pg_restore(转)

真没有想到,以前一直是PostgreSQL使用者,突然需要库移植又成了头一招了!原来它与mysql命令行操作区别还挺大。不用怕,但绝对要细心,因为数据库操作是网站的核心,一旦出现损坏或丢失,后果就非常严重了。我先写了步骤,然后按计划进行,虽然也出现了错误,但最终还是安全移植了。这里记录在案,以备后用。备份还原方法:pg_dump和pg_restore,先仔...

GPG(pgp)加解密

一、介绍 我们都知道,互联网是不安全的,但其上所使用的大部分应用,如Web、Email等一般都只提供明文传输方式(用https、smtps等例外)。所以,当我们需要传输重要文件时,应该对当中的信息加密。非对称密码系统是其中一种常见的加密手段。而在基于PGP方式加密的中文介绍少之又少,所以萌生了写一个完整教程的想法,当然本文部分资料是我搜遍网络整理出来的,并...

ssh使用密钥进行认证

生成私钥与公钥 比如,张三平常使用密码连接到服务器A的root账户,现在可以利用公钥,免密码连接到服务器A的root账户,首先,张三要生成一对密钥,私钥与公钥,私钥是自己保留的,一定不要泄露给它人,公钥是给别人用的,张三把公钥发给自己的朋友,朋友们就能用张三的公钥加密信息或者验证身份,当张三准备好了私钥与公钥,只要把公钥交给"服务器A的root账户",当张...

Linux(Centos7)下redis5缓存服务集群分布式搭建

注意:可以查看Redis官网查看集群搭建方式,连接如下 https://redis.io/topics/cluster-tutorial 集群中应该至少有三个节点,每个节点有一备份节点。需要6台服务器。 如果条件有限,可以搭建伪分布式,以下步骤是在一台 Linux 服务器上搭建有6个节点的 Redis集群。 准备工作:安装依赖包 [root@localho...

Linux、Windows如何进行性能监控与调优

【本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究。若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!】 1.Linux命令行工具 推荐:CentOS 7 1.1 top命令 top命令的输出如下: top命令的输出可以分为两部分:前半部分是系统统计信息,后半部分是进程信息。在统计信息中, 第1行...

在Apache Struts中利用OGNL注入

前言 本文简要介绍了Apache Struts的OGNL注入缺陷,文章中介绍使用简单的应用程序复现OGNL注入。深入研究针对公共漏洞,并理解这类漏洞。 内容 安装Apache Tomcat服务器(入门) 熟悉Java应用程序在服务器上的工作方式(Web服务器基础知识) Struts应用程序示例(Struts应用程序示例) 表达语言注入(表达式语言注入) 了...