单页——前端模块化

  ⼀个⾜够复杂的⼯程,需要尽量将功能解耦。什么叫解耦?简单来说,需要将不同的功能分开到不同的⽂件中,或不同的⽬录结构中,形成⼀个个模块,模块之间通过有限的接⼝交互,模块内部的数据变化对外部隐藏。

  在 Node.js 上,这⼀点表现的⾮常不错。Node.js 实现了 CommonJS 规范,每个 JS ⽂件就是⼀个模块,模块中的所有数据对外隐藏,仅通过 Module.exports 暴露模块内的内容。于是,模块化开发在 Node.js 环境中已不成问题。

  然⽽,JavaScript 语⾔可以运⾏的不仅仅是 Node.js 环境,它还有⼀个⾮常⾮常重要,也很常⻅的运⾏环境:客户端浏览器。JavaScript 代码量的剧增,给前端开发带来了前所未有的压⼒。在浏览器⼀侧,我们并没有像Node.js 环境那样的模块化开发能⼒,于是,不可避免会遇到下列问题:
  1. 如何将代码分离?
  2. 代码分离后,浏览器引⽤ JavaScript 的时候,如何处理它们之间的依赖关系?
  3. 代码分离后,浏览器引⽤ JavaScript 会导致额外的请求发⽣,如何解决效率问题?
 
第三⽅模块化规范
一、CommonJS
二、AMD 规范
三、CMD 规范
  然⽽,这⼀切都在慢慢的成为历史,当 ECMAScript 6 出世之后,模块化的规范,终于有了官⽅的标准。
 
ECMAScript 6 的模块化
它的⼤致规则如下:

1. ⼀个 JavaScript ⽂件,即⼀个模块。
2. 模块中所有的数据,除⾮导出,否则都是私有数据,仅能在模块中访问。
3. 使⽤ export 关键字导出模块。
4. 使⽤ import 关键字导⼊模块,导⼊时使⽤解构表达式得到导出的数据,否则,将使⽤导出的默认
数据。
5. ⼀个模块可以有多个导出。
6. 导⼊的模块会被缓存。
7. 在⻚⾯中加载⼊⼝模块使⽤ <script type="module" src="⼊⼝模块">

一、模块导出

1、导出数据,定义一个变量,将要导出的数据赋值给变量,通过export导出

// 导出数据
export var color = "red";
export let name = "Nicholas";
export const magicNumber = 7;

2、导出函数,在函数前加上export导出

// 导出函数
export function sum(num1, num2) { 
 return num1 + num2;
}

3、导出类,在类class前加上export导出

// 导出类
export class Rectangle {
 constructor(length, width) { 
 this.length = length; 
 this.width = width;
 }
}

4、也可将定义的私有函数导出,通过export对象导出

// 定义⼀个函数
function multiply(num1, num2) { 
 return num1 * num2;
}
// 稍后将其导出
export {
 multiply
};

以上方法最终能够得到的结果为:

{
 color: "red",
 name: "Nicholas",
 magicNumber: 7,
 sum: function,
 Rectangle: class,
 multiply: function
}

二、模块导⼊

  ⼀旦你有了包含导出的模块,就能在其他模块内使⽤ import 关键字来访问已被导出的功能。import 语句有 2 个部分:⼀是需要导⼊的标识符,⼆是需导⼊的标识符的来源模块。
import { color, name, multiply } from "./mymodule.js";

  这是导入模块的基本形式。

  也可使用通配符(*)导入模块中的所有东西:

import * as all from "./mymodule.js"; // all 可以是任意的名称
// 导⼊后,all 是⼀个对象,包含模块中导出的所有内容

  注意,export 与 import 都有⼀个重要的限制,那就是它们必须被⽤在其他语句或表达式的外部。

if (flag) {
 export flag; // 语法错误
}
function tryImport() {
 import flag from "./example.js"; // 语法错误
}

以上的导出导入方法就是错误的!

三、导⼊导出默认值

  为了简化导⼊导出语法,还提供了⼀种导⼊导出的⽅式:默认值。使⽤这种⽅式导⼊导出会更加简单,但要注意的是,每个模块只能导出⼀个默认值。

export default function(num1, num2) { 
 return num1 + num2;
}
在浏览器中使⽤⼊⼝模块,需要在html文档中引用JS程序,我们使用以下方式引用:
<script type="module" src="./app.js"></script>
只需要把 type 值从 text/javascript 改为 module 即可,但要注意的是,只有那些⽀持 ECMAScript6 的浏览器才可能兼容这种写法。
  下面我们来看看实例:
比如 ,我需要使用继承类的方式书写单页面,那么在主js之外新建一个base文档:

 

在base文档中书写以下代码:

export default class { //登录页面的视图
    constructor({
        el
    }) { //element
        this.el = el;
        this.init();
    }
    init() {
        this.render();
        this.mounted();
        this.handle();
    }
    render() { //负责页面渲染
       
    }
    mounted() { //负责挂载其他操作

    }
    handle() { //负责事件监听的
       
    }
}

使用导出默认值的方式导出。

之后再主info.js中导入:

import Base from "./base.js";

之后就可以在info.js中使用继承了:

 

 页面的渲染,事件监听都做好后就可以将其整体再次默认导出,在路由router文档中建立url接口,导入浏览器需要显示的info.js:

import info from "./view/info.js";
import addBooks from "./view/books/addBooks.js";
import updateBooks from "./view/books/updateBooks.js";

var routes = {
    '/info': {
        on: function () {
            new info({
                el: "#root"
            })
        },
        '/addBooks': function () {
            new addBooks({
                el: "#root"
            })
        },
        '/updateBooks': function () {
            new updateBooks({
                el: "#root"
            })
        }
    }
};

var router = Router(routes);

export default {
    init() {
        router.init(); //初始化路由
        location.hash = location.hash || "/info"; //有锚点就不会回到第一个锚点
    }
};

在进行new的时候,最好使用与导出的标识符相同的单词,这样import会自动出现,避免书写麻烦,或错误导致无法联通。

  总的来说,浏览器的模块化开发就是将页面渲染、事件监听进行包裹,整体的导出导入,达到我们一开始说的“形成⼀个个模块,模块之间通过有限的接⼝交互,模块内部的数据变化对外部隐藏。”,虽然前期的学习这种方式容易使人混乱,但是让我们的代码实现了工程化,每一个文档都有自己独立的目的,让我们的代码维护更加方便。

 
posted @ 2020-02-02 16:43  袁钧钧  阅读(406)  评论(0)    收藏  举报