JavaScript 模块封装
JavaScript 模块封装
前言介绍
在最早的时候JavaScript这门语言其实是并没有模块这一概念,但是随着时间的推移与技术的发展将一些复用性较强的代码封装成模块变成了必要的趋势。
在这篇文章中主要介绍原生的 JavaScript封装的几种手段以及新增的 ES6 Module的语法,来实现模块封装。
并且会简单的使用Webpack让Es6代码向后兼容。
引入问题
以下有两个Js文件,如果不采取任何封装手段直接导入会导致window环境污染。
并且,如果文件中有相同名字的变量或函数会发生命名冲突,因为它们都是放在全局作用域window对象中的。
<script src="./js_m1.js"></script>
<script src="./js_m2.js"></script>
<script>
"use strict";
// 这是由于js_m2后引入,所以js_m1的同名变量以及函数都被覆盖掉了。
console.log(module_name); // js_m2
show(); // js_m2.show
</script>
var module_name = "js_m1";
function show(){
console.log("js_m1.show");
}
var module_name = "js_m2";
function show(){
console.log("js_m2.show");
}

简单解决
IIFE封装
针对上述问题,采取函数的闭包及作用域特性我们为每个模块封装一个作用域。
第一步:进行自执行函数包裹代码封装出局部作用域
第二步:向外部暴露接口,为
window对象添加新的对象
<script src="./js_m1.js"></script>
<script src="./js_m2.js"></script>
<script>
"use strict";
console.log(js_m1.module_name); // js_m1
js_m1.show(); // js_m1.show
console.log(js_m2.module_name); // js_m2
js_m2.show(); // js_m2.show
</script>
(function () {
var module_name = "js_m1";
function show() {
console.log("js_m1.show");
}
window.js_m1 = { module_name: module_name, show: show };
// 在es6中,可简写为 { module_name , show }
}())
(function () {
var module_name = "js_m2";
function show() {
console.log("js_m2.show");
}
window.js_m2 = { module_name: module_name, show: show };
// 在es6中,可简写为 { module_name , show }
}())

Es6块级封装
在Es6之前,由于没有出现块级作用域的概念,那时候大家都使用上面的方式进行封装。
在当Es6的块级作用域出现之后,又诞生出了新的封装方式即块级作用域封装。
和
IIFE封装相同,都是利用作用域的特性进行封装。注意一点,块级作用域只对
let或const声明有效。
<script src="./js_m1.js"></script>
<script src="./js_m2.js"></script>
<script>
"use strict";
console.log(js_m1.module_name); // js_m1
js_m1.show(); // js_m1.show
console.log(js_m2.module_name); // js_m2
js_m2.show(); // js_m2.show
</script>
{
let module_name = "js_m1";
let show = function () {
console.log("js_m1.show");
}
window.js_m1 = { module_name, show };
}
{
let module_name = "js_m2";
let show = function () {
console.log("js_m2.show");
}
window.js_m2 = { module_name, show };
}

Es6 module 语法
上面的两种方式虽然都能达到模块封装的效果,但是我们依然有更好的选择。
下面将介绍极力推荐的Es6 module语法进行导入。
学习Es6 module从以下三个方面来入手:
1.模块标签及其特性
2.导出
3.导入
模块标签
要想使用Es6 module语法导入模块,必须使用模块标签来引入Js文件。
模块标签与普通的<script>标签具有一些不太一样的地方,下面会从各个方面逐一进行介绍。
声明标签
将<script>标签添加上type="module"的属性。
<script type="module"></script>
导入路径
在浏览器中引用模块必须添加路径如./ ,但在打包工具如webpack中则不需要,因为他们有自己的存放方式。
总而言之,即使是在当前目录也要添加上./,不可以进行省略。
这也是推荐的一种引入文件方式,不管是何种语言中都推荐引入文件时不进行路径省略。
正确的导入路径
<script type="module" src="./js_m1.js"></script>
<script type="module" src="./js_m2.js"></script>
错误的导入路径
<script type="module" src="js_m1.js"></script> // 不可省略!省略就会抛出异常
<script type="module" src="js_m2.js"></script>
延迟解析
所谓延迟解析是指在模块标签中的代码会提到HTML代码以及嵌入式的<script>标签后才进行执行。
注意看下面的示例,编码时模块标签在普通的<script>之上,但是结果却相反。
<script type="module">
console.log("<script type='module'> code run...");
</script>
<script>
"use strict";
console.log("<script> code run...");
</script>

严格模式
模块标签中的所有代码都是按严格模式运行的,请注意变量名的声明以及this指向问题,同时还有解构赋值等等。
<script type="module">
username = "云崖"; // 抛出异常,未声明
</script>
<script type="module">
let obj = {
show() {
console.log(this); // {show: ƒ}
(function () { console.log(this); }()) // undefined 严格模式下为undefined ,普通模式下为window对象
}
};
obj.show();
</script>
作用域
每个模块标签中的代码都会为其创建一个专属的作用域,禁止相互之间进行访问。
而普通的<script>标签中的代码全部在全局作用域下执行。
<script>
let m1 = "m1...";
</script>
<script>
console.log(m1); // m1...
</script>
<script type="module">
let m1 = "m1...";
</script>
<script type="module">
console.log(m1); // Uncaught ReferenceError: m1 is not defined
</script>
预解析
模块在导入时只执行一次解析,之后的导入不会再执行模块代码,而使用第一次解析结果,并共享数据。
可以在首次导入时完成一些初始化工作
如果模块内有后台请求,也只执行一次即可
<script type="module" src="./js_m3.js"></script>
<script type="module" src="./js_m3.js"></script>
<script type="module" src="./js_m3.js"></script> <!-- 导入多次,也只执行一次代码 -->
<!-- 打印结果如下:import m3... --> <!-- js_m3内容如下: console.log("import m3..."); -->
导出模块
ES6使用基于文件的模块,即一个文件一个模块。
可以使用export来将模块中的接口进行导出,导出方式分为以下几种:
1.单个导出
2.默认导出
3.多个导出
4.混合导出
5.别名导出
另外,ES6的导出是是以引用方式导出,无论是标量还是对象,即模块内部变量发生变化将影响已经导入的变量。
单个导出
下面将使用export来将模块中的接口进行单个单个的导出。
export let module_name = "js_m3.js";
export function test(){
console.log("测试功能");
}
export class User{
constructor(username){
this.username = username;
}
show(){
console.log(this.username);
}
}
默认导出
一个模块中,只能默认导出一个接口。
如果默认导出的是一个类,那么该类就可以不用起类名,此外函数同理。
export let module_name = "js_m3.js";
export function test(){
console.log("测试功能");
}
export default class{ // 默认导出
constructor(username){
this.username = username;
}
show(){
console.log(this.username);
}
}
多个导出
可以使用exprot与{}的形式进行接口的批量多个导出。
let module_name = "js_m3.js";
function test() {
console.log("测试功能");
}
class User {
constructor(username) {
this.username = username;
}
show() {
console.log(this.username);
}
}
export { module_name, test, User };
混合导出
使用export default 导出默认接口,使用 export {} 批量导入普通接口
let module_name = "js_m3.js";
function test() {
console.log("测试功能");
}
export default class {
constructor(username) {
this.username = username;
}
show() {
console.log(this.username);
}
}
export { module_name, test };
同时也可以使用as来为一个导出的接口取别名,如果该接口别名为default则将该接口当做默认导出。
let module_name = "js_m3.js";
function test() {
console.log("测试功能");
}
class User {
constructor(username) {
this.username = username;
}
show() {
console.log(this.username);
}
}
export { module_name, test, User as default };
别名导出
使用as来为导出的export {}中的导出接口起一个别名,当导入时也应该使用导出接口的别名进行接收。
当一个接口的别名为default时,该接口将当做默认导出。
let module_name = "js_m3.js";
function test() {
console.log("测试功能");
}
class User {
constructor(username) {
this.username = username;
}
show() {
console.log(this.username);
}
}
export { module_name as m_name, test as m_tst, User as default };
导入模块
使用import与from进行静态的模块的导入,注意导入时必须将导入语句放在顶层。
模块的导入分为以下几部分:
1.具名导入
2.批量导入
3.默认导入
4.混合导入
5.别名导入
6.动态导入
具名导入
具名导入应该注意与导出的接口名一致。
下面是模块导出的代码:
let module_name = "js_m3.js";
function test() {
console.log("测试功能");
}
class User {
constructor(username) {
this.username = username;
}
show() {
console.log(this.username);
}
}
export { module_name, test, User };
使用具名导入:
<script type="module">
import { module_name, test, User} from "./js_m3.js";
console.log(module_name); // js_m3.js
test(); // 测试功能
let u1 = new User("云崖");
u1.show(); // 云崖
</script>
批量导入
如果导入的内容过多,可使用*进行批量导入,注意批量导入后应该使用as来取一个别名方便调用。
下面是模块导出的代码:
let module_name = "js_m3.js";
function test() {
console.log("测试功能");
}
class User {
constructor(username) {
this.username = username;
}
show() {
console.log(this.username);
}
}
export { module_name, test, User };
使用批量导入:
<script type="module">
import * as m3 from "./js_m3.js"; // 别名为m3,下面使用都要以m3开头
console.log(m3.module_name); // js_m3.js
m3.test(); // 测试功能
let u1 = new m3.User("云崖");
u1.show(); // 云崖
</script>
默认导入
使用默认导入时不需要用{}进行接收,并且可以使用任意名字来接收默认导出的接口。
下面是模块导出的代码:
let module_name = "js_m3.js";
function test() {
console.log("测试功能");
}
class User {
constructor(username) {
this.username = username;
}
show() {
console.log(this.username);
}
}
export { module_name, test, User as default };
使用默认导入,我们只导入默认导出的接口,可以随便取一个名字。
<script type="module">
import m3U from "./js_m3.js";
let u1 = new m3U("云崖");
u1.show(); // 云崖
</script>
混合导入
当一个模块中导出的又有默认导出的接口,又有其他的导出接口时,我们可以使用混合导入。
使用{}来接收其他的导出接口,对于默认导出的接口而言只需要取一个名字即可。
下面是模块导出的代码:
let module_name = "js_m3.js";
function test() {
console.log("测试功能");
}
class User {
constructor(username) {
this.username = username;
}
show() {
console.log(this.username);
}
}
export { module_name, test, User as default };
使用混合导入:
<script type="module">
import m3U, { module_name, test } from "./js_m3.js";
console.log(module_name); // js_m3.js
test(); // 测试功能
let u1 = new m3U("云崖");
u1.show(); // 云崖
</script>
别名导入
为了防止多个模块下接口名相同,我们可以使用as别名导入,再使用时也应该按照别名进行使用。
下面是m1模块导出的代码:
let module_name = "js_m1";
let show = function () {
console.log("js_m1.show");
}
export { module_name, show };
下面是m2模块导出的代码:
let module_name = "js_m2";
let show = function () {
console.log("js_m2.show");
}
export { module_name, show };
下面是使用别名导入这两个模块的接口并进行使用:
<script type="module">
import { module_name as m1_name, show as m1_show } from "./js_m1.js";
import { module_name as m2_name, show as m2_show } from "./js_m2.js";
console.log(m1_name); // js_m1
console.log(m2_name); // js_m2
m1_show(); // js_m1.show
m2_show(); // js_m2.show
</script>
动态导入
使用import与from的导入方式属于静态导入,必须将导入语句放在最顶层,如果不是则抛出异常。
这是模块中的导出接口:
export function test() {
console.log("测试功能");
}
如果我们想在某种特定条件下才导入并调用改接口,使用import与from的方式会抛出异常。
<script type="module">
if (true) {
import { test } from "./js_m3.js"; // Error
test(); // 想在特定条件下执行模块中的测试功能
}
</script>
这个时候就需要用到动态导入,使用 import() 函数可以动态导入,实现按需加载,它返回一个 promise 对象。
<script type="module">
if (true) {
let m3 = import("./js_m3.js");
m3.then((module)=> module.test()); // 测试功能
}
</script>
我们可以使用解构语法来将模块中的接口一个一个全部拿出来。
<script type="module">
if (true) {
let m3 = import("./js_m3.js");
m3.then(({ test, }) => test()); // 拿出test接口
}
</script>
合并使用
如果有多个模块都需要被使用,我们可以先定义一个Js文件将这些需要用到的模块中的接口做一个合并,然后再将该文件导出即可。
合并导出请将export与from结合使用。
// js_m1
export default class{ // 默认导出
static register(){
console.log("注册功能");
}
}
// js_m2
export class Login{
static login(){
console.log("登录功能");
}
}
export function test(){
console.log("js_m2测试功能");
}
// index.js // 合并导出 import js_m1 from "./js_m1.js"; // js_m1中有接口是默认导出,因此我们需要不同的导出方式 , 注意这里就导出了一个接口,即js_m1的注册类
export {default as js_m1_register} from "./js_m1.js"; // 导出js_m2中的接口,共导出两个接口。登录类和测试函数。
export * as js_m2 from "./js_m2.js";
导入与使用:
<script type="module">
import * as index from "./index.js";
index.js_m1_register.register(); // 注册功能
index.js_m2.Login.login(); // 登录功能
index.js_m2.test(); // js_m2测试功能
</script>
指令总结
| 表达式 | 说明 |
|---|---|
| export function show(){} | 导出函数 |
| export const name="Yunya" | 导出变量 |
| export class User{} | 导出类 |
| export default show | 默认导出 |
|
const name = "Yunya" export {name} |
导出已经存在变量 |
| export {name as m1_name} | 别名导出 |
| import m1_default from './m1_js.js' | 导入默认导出 |
| import {name,show} from '/m1_js.js' | 导入命名导出 |
| Import {name as m1_name,show} from 'm1_js.js' | 别名导入 |
| Import * as m1 from '/m1_js.js' | 导入全部接口 |
编译打包
由于module语法是Es6推出的,所以对老旧的浏览器兼容不太友好,这个时候就需要用到打包工具进行打包处理使其能让老旧的浏览器上进行兼容。
首先登录 https://nodejs.org/en/ 官网下载安装Node.js,我们将使用其他的npm命令,npm用来安装第三方类库。
在命令行输入 node -v 显示版本信息表示安装成功。
安装配置
cd到你的项目路径,并使用以下命令生成配置文件 package.json
npm init -y
修改package.json添加打包命令
...
"main": "index.js",
"scripts": {
"dev": "webpack --mode development --watch" // 添加这一句
},
...
安装webpack工具包,如果安装慢可以使用淘宝 cnpm 命令
npm i webpack webpack-cli --save-dev
目录结构
index.html
--dist #压缩打包后的文件
--src
----index.js #合并入口
----style.js //模块
index.html内容如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<script src="dist/main.js"></script>
</body>
</html>
index.js内容如下
import style from "./style";
new style().init();
style.js
export default class User {
constructor() {}
init() {
document.body.style.backgroundColor = "green";
}
}
执行打包
运行以下命令将生成打包文件到 dist目录,因为在命令中添加了 --watch参数,所以源文件编辑后自动生成打包文件。
npm run dev
JavaScript 模块封装的更多相关文章
- 简易的highcharts公共绘图模块封装--基于.net mvc
运行效果: 之所以选择这个图表插件,是因为它较其他同类插件轻量且中文文档详细完整,Demo丰富,配置使用简单.具体内容请登录中文官网:http://www.hcharts.cn/ 项目详细: 项目环境 ...
- 转: javascript模块加载框架seajs详解
javascript模块加载框架seajs详解 SeaJS是一个遵循commonJS规范的javascript模块加载框架,可以实现javascript的模块化开发和模块化加载(模块可按需加载或全部加 ...
- javascript模块加载框架seajs详解
SeaJS是一个遵循commonJS规范的javascript模块加载框架,可以实现javascript的模块化开发和模块化加载(模块可按需加载或全部加载).SeaJS可以和jQuery完美集成,使用 ...
- Openlayer3之C++接口在javaScript的封装使用
0.写在前面: 1)涉及的关键词定义: 传入:JavaScript向CAPI传值 传出:CAPI向JavaScript传值 2)关于类和结构体的封装,需要严格执行内存对齐,以防止读取越界,但是避免不了 ...
- React-Native开发之原生模块封装(Android)升级版
本文主题:如何实现原生代码的复用,即如何将原生模块封装. (尊重劳动成果,转载请注明出处:http://blog.csdn.net/qq_25827845/article/details/52862 ...
- 【JavaScript框架封装】使用原生js封装的类似于JQuery的框架及核心源码分享(多文件版本)
这个版本的JQuery是对上一个版本的JQuery,使用了require.js进行了二次封装,基本上把前面的每一个框架封装成为一个单独的模块,最终的目录结构如下: 由于代码量和目录比较多,这个封装好的 ...
- 使用ECMAScript 6 模块封装代码
JavaScript 用"共享一切"的方法加载代码,这是该语言中最容易出错且最容易让人感到困惑的地方.其他语言使用诸如包这样的概念来定义代码作用域,但在 ECMAScript 6 ...
- 2016/11/17 周四 <javascript的封装简单示例>
这是一个简单的javascript代码封装的示例以及封装后的调用方法: var ticker={ n:0, add:function() { this.n++; }, show:function() ...
- 关于javascript模块加载技术的一些思考
前不久有个网友问我在前端使用requireJs和seajs的问题,我当时问他你们公司以前有没有自己编写的javascript库,或者javascript框架,他的回答是什么都没有,他只是听说像requ ...
随机推荐
- Newbe.Claptrap 框架中为什么用 Claptrap 和 Minion 两个词?
Newbe.Claptrap 框架中为什么用 Claptrap 和 Minion 两个词?最近整理了一下项目的术语表.今天就谈谈为什么起了 Claptrap 和 Minion 两个名字. Claptr ...
- 网络编程-HTTPS
明文: 对称加密: 非对称:(公钥:pk 私钥:sk) 对称+非对称: 先用非对称方式发送num1给server,server用私钥得出key(由num1算出来),自此,约定C.S以此key(num1 ...
- bzoj3381[Usaco2004 Open]Cave Cows 2 洞穴里的牛之二*
bzoj3381[Usaco2004 Open]Cave Cows 2 洞穴里的牛之二 题意: RMQ问题.序列长度≤25000,问题数≤25000. 题解: 倍增. 代码: #include < ...
- CodeChef Sum of distances(分治)
CodeChef Sum of distances(分治) 题目大意 有一排点,每个点 i 向 \(i + 1, i + 2, i + 3\) 分别连价值为 \(a_i,b_i,c_i\) 的有向边, ...
- Mysql UDF提权方法
0x01 UDF UDF(user defined function)用户自定义函数,是mysql的一个拓展接口.用户可以通过自定义函数实现在mysql中无法方便实现的功能,其添加的新函数都可以在sq ...
- CentOS7 64位下MySQL区分大小写
在使用centos系统时,安装完MySQL数据库,创建完表之后,发现查询表操作时,是区分大小写的, 说以说在创建表之前,需要查看一下数据库是否区分大小写: 查看办法: lower_case_table ...
- Burp Suite Spider Module - 网络爬虫模块
Web application spdiering 和scanning 可以结合使用. Burp Suite 的Spider Module - Options 主要包含:Crawler Setting ...
- Ethical Hacking - NETWORK PENETRATION TESTING(1)
Pre--Connection-Attacks that can be done before connecting to the network. Gaining Access - How to b ...
- 题解 洛谷 P3734 【[HAOI2017]方案数】
可以先考虑没有障碍物的情况,设计状态\(f_{i,j,k}\),表示到达坐标 \((x,y,z)\)二进制下,\(x\)有\(i\)位,\(y\)有\(j\)位,\(z\)有\(k\)位的方案数. 得 ...
- vue怎么自定义组件
我们在搭建好的手脚架中 进行使用 一.在src =>components => 创建XXbtn文件夹用来存放你的组件 =>在创建一个vue的文件 . 二.在src => com ...