es6 快速入门 系列 —— 模块
其他章节请看:
模块
es6 以前,每个 javascript 都共享这一个全局作用域,随着代码量的增加,容易引发一些问题,比如命名冲突。
其他语言有包这样的概念来定义作用域,es6 的一个目标是解决作用域问题,也为了使 javascript 应用程序显得有序,于是引入了模块。
Tip:模块化开发规范有amd、commonjs等,而 es6 module 属于官方出品
准备环境
笔者提供了一个环境(来自”初步认识 webpack“一文),方便对下面介绍的语法进行测试、验证和学习。
项目结构如下:
es6-module
- src // 项目源码
- index.html // 页面模板
- index.js // 入口
- package.json // 存放了项目依赖的包
- webpack.config.js // webpack配置文件
src中的代码如下:
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=`, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<p>请查看控制台</p>
</body>
</html>
// index.js
console.log('我是入口')
package.json:
{
"name": "webpack-example2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"dev": "webpack-dev-server"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"html-webpack-plugin": "^4.5.2",
"webpack": "^4.46.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.2"
}
}
webpack.config.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
})
],
mode: 'development',
devServer: {
open: true,
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 9000,
},
};
在 es6-module 目录下运行项目:
// 安装项目依赖的包
> npm i
// 启动服务
> npm run dev
启动服务器后,浏览器会自动打开页面,如果看到”请查看控制台“,说明环境已准备就绪。
什么是模块
模块是自动运行在严格模式下并且没有办法退出运行的 javascript
代码。在模块顶部创建的变量不会自动被添加到全局作用域,仅在这个模块的顶级作用域中。
模块如果需要提供一些接口给其他模块使用,则可以通过 export
关键字导出;其他模块则可以通过 import
关键字导入其他模块
模块的真正魔力是仅导出和导入你需要的绑定,而不是将所用的东西都放到一个文件。只有很好的理解导出和导入才能理解模块与脚本的区别
Tip: 脚本,就是任何不是模块的 javascript
代码;模块顶部的 this
的值是 undefined
。
导出的基本语法
可以使用 export
关键字将一部分代码暴露给其他模块,最简单的方法,可以将 export
放在任何变量、函数或类声明的前面,就像这样:
// 导出数据
export var name = 'ph'
export let age = 18
// 导出函数
export function sum(v1, v2){
return v1 + v2
}
// 导出类
export class Dog{
constructor(name, color, age){
this.name = name;
this.color = color;
this.age = age;
}
toString(){
return `name=${this.name} color=${this.color} age=${this.age}`
}
}
// 这个函数是模块私有的
function subtract(v1, v2){
return v1 - v2;
}
除了 export 关键字,每一个声明与脚本中的一模一样。因为导出的函数、类等声明都需要一个名称,除非用 default
关键字,否则不能匿名导出函数或类。
// 正确。使用 default 匿名导出
export default function(v1, v2){
return v1 * v2
}
// 正确
export default 1
// 错误
export 1
导入的基本语法
从模块中导出的功能可以通过 import
关键字在另一个模块中访问,import
语句中的两个部分是:要导入的标识符和标识符应当从哪个模块中导入。基本形式是:
import {name, age} from './x.js'
当从模块中导入一个绑定,就好像使用 const
定义的一样。结果是你无法重新定义另一个同名变量:
import moduleX from './x.js'
// 报错
moduleX = 4
// 报错
let moduleX = 4
导入整个模块:
// 模块(z.js)
export var name = 'ph'
export let age = 18
export default 'man'
// 导入整个模块
import * as moduleZ from './z.js'
// [["name","ph"],["age",18],["default","man"]]
console.log(JSON.stringify(Object.entries(moduleZ)))
不管在 import
语句中把一个模块写了多少次,该模块将只执行一次:
// 模块(z.js)
export var name = 'ph'
export let age = 18
console.log('module z')
export default 'man'
// 重复导入模块c
import {name} from './z.js'
import {age} from './z.js'
console.log([name,age])
控制台只会输出一次module z
。
export
和 import
必须在其他语句和函数之外使用:
if(flag){
import {name} from './z.js' // 语法错误
}
模块语法存在的一个原因是要让 javascript
引擎静态的确定哪些可以导出,因此,只能在模块顶部使用 export
和 import
。
导入绑定的一个微妙怪异之处:import
为变量、函数、类创建的是只读绑定,而不是像正常变量一样简单地引用原始绑定。
// 模块(x.js)
export let name = 'ph'
export function setName(newName){
name = newName
}
// 模块(y.js)
import {name, setName} from './x.js'
// ph
console.log(name)
// 此更改会自动在导入的 name 绑定上体现
// 原因是 name 是导出的 name 标识符的本地名称
setName('lj')
// lj
console.log(name)
// webpack 中没有抛出错误
name = 3
// 3
console.log(name)
导出和导入时重命名
如果要使用不同的名字导入一个函数,可以使用 as
关键字,代码示意如下:
// 解构导出并重命名
export const { age, sum: add } = o;
import {sum as add} from './x.js';
import {default as DefaultExport} from './x.js'
模块的默认值
模块的默认值指的是通过 default
关键字指定的单个变量、函数或类。就像这样:
export default function(v1, v2){
return v1 + v2
}
只能为每个模块设置一个默认的导出值:
// 语法错误
export default 1
export default 2
如果想通过一条语句同时指定多个导出,包括默认导出,下面这个语法非常有用:
const age = 18
function sum(v1, v2){
return v1 + v2
}
export {sum as default, age}
可以通过一条语句导入所有导出的绑定,包括默认值:
import sum, {age} from './x.js'
用逗号将默认的本地名称与大括号包裹的非默认值分割开。默认值必须排在非默认值前面:
// 报错
import {age}, sum from './x.js'
与导出默认值一样,也可以在导入默认值时使用重命名语法:
import {default as sum, age} from './x.js'
这段代码中,默认值被重命名为 sum,并且还导入了 age。
重新导出一个绑定
在一个模块中导入 sum,又重新导出 sum,就像这样:
import {sum} from './x.js'
export {sum}
只用一条语句同样可以完成上面的工作:
export {sum} from './x.js'
这种形式的 export
在指定的模块中查找 sum 声明,然后将其导出。理解了这个语法后,我们在这种形式下,可以重命名或者导出一切。就像这样:
// 重命名导出
export {sum as add} from './x.js'
// 导出一切
export * from './x.js'
注:测试发现此语法(export * from './x.js'
)没有导出默认值
无绑定导入
有些模块可能不导出任何东西。可能只修改全局作用域中的对象。
虽然模块中顶层的变量、函数和类不会自动出现在全局作用域中,但这并不意味着模块无法访问全局作用域,比如我在某模块中给数组添加一个变量,其他模块没有引入该模块,也是可以访问到数组新添加的变量,请看示例:
// x.js
Array.prototype.myFlag = 'aaron'
// y.js
console.log(Array.prototype.myFlag)
// index.js
// 由于不导出任何东西,因此可以使用简化的导入操作
import './x.js'
// 模块 y 输出:aaron
import './y.js'
Tip: 无绑定导入最有可能用于 polyfill 和 shim
综合测试
我们将上文介绍的知识点综合测试和验证一下。
入口文件(src/index.js):
// 无绑定导入
import './a.js'
// 重复导入。只会输出一次”i am moduleC“
import './c.js'
import './c.js'
import './d.js'
其他文件内容如下:
a.js:
// 用逗号将默认的本地名称与大括号包裹的非默认值分割开。默认值必须排在非默认值前面
import multiplication, {name,age,sum,address,tel,Dog,SEX,HOUSE} from './b.js'
// console.table,将数据以表格的形式显示
console.table({
// name 和 age:导出数据
name,
age,
// 导出函数
'sum(3,3)': sum(3, 3),
// address 和 tel:一次导出多个
address,
tel,
"new Dog('乐乐', '黑色', 4)": new Dog('乐乐', '黑色', 4).toString(),
// 默认导出
'multiplication(3, 3)': multiplication(3, 3),
// 重命名导出
SEX,
// 解构导出
HOUSE
})
b.js:
// 导出数据
export var name = 'ph'
export let age = 18
const sex = 'man'
// 解构导出
export const { house: HOUSE} = {house:'别墅'}
// 一次导出多个
export let address='长沙', tel='123456789'
// 导出函数
export function sum(v1, v2){
return v1 + v2
}
// 导出类
class Dog{
constructor(name, color, age){
this.name = name;
this.color = color;
this.age = age;
}
toString(){
return `name=${this.name} color=${this.color} age=${this.age}`
}
}
// 这个函数是模块私有的
function subtract(v1, v2){
return v1 - v2;
}
// 定义一个函数
function multiplication(v1, v2){
return v1 * v2
}
// 导出模块集合
export {Dog}
// 重命名导出
export {sex as SEX}
// 默认导出
export default multiplication
// 报错
// export multiplication
c.js:
// 导入整个模块
import * as moduleB from './b.js'
const {name, HOUSE, default: multiplication} = moduleB;
console.log('i am moduleC')
// ["ph", "别墅", 9]
console.log([name, HOUSE, multiplication(3,3)])
d.js:
import * as moduleE from './e.js'
console.log(`moduleE.default=${moduleE.default}`)
e.js:
// 此语法没有导出模块b的默认值
export * from './b.js'
export default 'moduleE default value'
其他章节请看:
es6 快速入门 系列 —— 模块的更多相关文章
- es6 快速入门 系列 —— 变量声明:let和const
其他章节请看: es6 快速入门 系列 变量声明:let和const 试图解决的问题 经典的 var 声明让人迷惑 function demo1(v){ if(v){ var color='red' ...
- 前端学习 node 快速入门 系列 —— 模块(module)
其他章节请看: 前端学习 node 快速入门 系列 模块(module) 模块的导入 核心模块 在 初步认识 node 这篇文章中,我们在读文件的例子中用到了 require('fs'),在写最简单的 ...
- es6 快速入门 系列 —— promise
其他章节请看: es6 快速入门 系列 Promise Promise 是一种异步编程的选择 初步认识Promise 用 Promise 来实现这样一个功能:发送一个 ajax,返回后输出 json ...
- es6快速入门 系列 - async
其他章节请看: es6 快速入门 系列 async 前文我们已经知道 promise 是一种异步编程的选择.而 async 是一种用于执行异步任务更简单的语法. Tip:建议学完 Promise 在看 ...
- es6 快速入门 系列 —— 类 (class)
其他章节请看: es6 快速入门 系列 类 类(class)是 javascript 新特性的一个重要组成部分,这一特性提供了一种更简洁的语法和更好的功能,可以让你通过一个安全.一致的方式来自定义对象 ...
- es6 快速入门 系列 —— 对象
其他章节请看: es6 快速入门 系列 对象 试图解决的问题 写法繁杂 属性初始值需要重复写 function createPeople(name, age){ // name 和 age 都写了 2 ...
- es6 快速入门 系列
es6 快速入门(未完结,持续更新中...) 前言 为什么要学习es6 es6对于所有javaScript开发者来说,非常重要 未来,es6将构成javaScript应用程序的基础 es6中很多特性, ...
- es6 快速入门 —— 函数
其他章节请看: es6 快速入门 系列 函数 函数是所有编程语言的重要组成部分,es6之前函数语法一直没什么变化,遗留了许多问题,javaScript开发者多年来不断抱怨,es6终于决定大力度更新函数 ...
- python 全栈开发,Day88(csrf_exempt,ES6 快速入门,Vue)
BBS项目内容回顾 1. 登陆页面 1. 验证码 1. PIL(Pillow) 2. io 2. ORM 1. 增删改查 3. AJAX $.ajax({ url: '', type: '', dat ...
- vue 快速入门 系列 —— vue loader 上
其他章节请看: vue 快速入门 系列 vue loader 上 通过前面"webpack 系列"的学习,我们知道如何用 webpack 实现一个不成熟的脚手架,比如提供开发环境和 ...
随机推荐
- 【荐】开源Winform控件库:花木兰控件库
微信好友推荐,挺好看的Winfrom控件库,下面来看看. 介绍 基于 C#(语言) 4.0 . VS2019 . Net Framework 4.0(不包括Net Framework 4.0 Clie ...
- JavaScript合并多个数组
工作中经常会对数组进行合并,稍微总结一下常用的方法: concat JavaScript原生自带的函数,用法如下: let arr1 = [3, 5, 7]; let arr2 = [4, 78, 7 ...
- 【转】C语言表驱动法编程实践
来源:C语言表驱动法编程实践(精华帖,建议收藏并实践) (qq.com) 数据压倒一切.如果选择了正确的数据结构并把一切组织的井井有条,正确的算法就不言自明.编程的核心是数据结构,而不是算法. --R ...
- 【TouchGFX】屏幕概念
Screen构成 界面布局 View, 逻辑处理 Presenter Currently Active Screen 因TouchGFX分配内存的的方式是,按照最大的Screen分配,即最大View+ ...
- PHPCMS V9安装出现DNS解析失败的解决方法-不支持采集和保存远程图片
目前因为phpcms官网停止解析后,很多人安装phpcms v9出现如下错误: 这是因为检测dns解析的域名是phpcms官网的域名,官网域名停止解析后肯定检测失败.解决方法如下: 打开/ ...
- WebApi的输出结果重写 OnActionExecuted
public override void OnActionExecuted(ActionExecutedContext context) { if (context.Exception != null ...
- 百度网盘(百度云)SVIP超级会员共享账号每日更新(2023.12.18)
一.百度网盘SVIP超级会员共享账号 可能很多人不懂这个共享账号是什么意思,小编在这里给大家做一下解答. 我们多知道百度网盘很大的用处就是类似U盘,不同的人把文件上传到百度网盘,别人可以直接下载,避免 ...
- 单元测试中如何Mock HttpContext
最近团队有小伙伴问在单元测试中如何Mock HttpContext. 这是一个好问题,整理了一个实现方案分享给大家. 在C#中,尤其是在单元测试场景下,模拟(Mocking)HttpContext 是 ...
- [转帖]探索惊群 ④ - nginx - accept_mutex
https://wenfh2020.com/2021/10/10/nginx-thundering-herd-accept-mutex/ 由主进程创建的 listen socket,要被 fork ...
- 【图】苹果Safari 6.0停止支持Windows PC (转载)
[图]果Safari 6.0停止支持Windows PC (转载) http://bbs.tianya.cn/post-414-41510-1.shtml 2012年之后 苹果就不在开发 window ...