热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

探究JS中的模塊化

原文鏈接:http:yanjiie.me有時的一個周末溫習了一下JS的模塊範例,革新了一下對JS模塊化的明白。從最先Coding以來,總會周期性地突發奇想舉行CodeReview。

原文鏈接: http://yanjiie.me

有時的一個周末溫習了一下 JS 的模塊範例,革新了一下對 JS 模塊化的明白。

從最先 Coding 以來,總會周期性地突發奇想舉行 Code Review。既是對一段時代的代碼舉行總結,也是對那一段時刻的思念。

間隔上一次 Review 已過去近兩個月,此次居然把兩年前在源續寫的代碼翻了出來,代碼亂七八糟的水平就像當時越發急躁的本身,讓人慨嘆時刻流逝之快。

話不多說,直接上碼。

當時在做的是一個境外電商項目(越南天寶商城),作為非 CS 的新手遞次員,打仗 Coding 時刻不長和工程化看法不強,在當時的項目中湧現了如許的代碼:

import.js:
《探究 JS 中的模塊化》

這段代碼看起來就是不斷地從 DOM 中插進 CSS 和 JS,雖然寫得很爛,然則很能反應之前的 Web 開闢體式格局。

在 Web 開闢中,有一個準繩叫“關注點星散(separation of concerns)“,意義是種種手藝只擔任本身的範疇,不相互耦合夾雜在一起,所以催生出了 HTML、CSS 和 Javascript。

个中,在 Web 中擔任邏輯和交互 的 Javascript,是一門只用 10 天設想出來的言語,雖然自創了許多優異靜態和動態言語的長處,但卻一向沒有模塊 ( module ) 系統。這致使了它將一個大遞次拆分紅相互依靠的小文件,再用簡樸的要領拼裝起來。其他言語都有這項功用,比方 RubyrequirePythonimport,以至就連 CSS 都有 @import,然則 Javascript 任何這方面的支撐都沒有。而且 JS 是一種加載即運轉的手藝,在頁面中插進去劇本時還須要斟酌庫的依靠,JS 在這方面的瑕玷,對開闢大型的、龐雜的項目形成了龐大停滯。

生長進程

雖然 JS 本身並不支撐模塊化,然則這並不能阻撓 JS 走向模塊化的途徑。既然本身不支撐,那末就從代碼層面處理題目。活潑的社區最先制訂了一些模塊計劃,个中最主要的是 CommonJS 和 AMD,ES6 範例出台以後,以一種更簡樸的情勢制訂了 JS 的模塊範例 (ES Module),並融會了 CommonJS 和 AMD 的長處。

大抵的生長進程:

CommonJS(服務端) => AMD (瀏覽器端) => CMD / UMD => ES Module

CommonJS 範例

2009年,Node.js 橫空出世,JS 得以離開瀏覽器運轉,我們能夠運用 JS 來編寫服務端的代碼了。關於服務端的 JS,沒有模塊化簡直是不能忍。

CommonJs (前 ServerJS) 在這個階段應運而生,制訂了 Module/1.0 範例,定義了初版模塊範例。

範例內容:

  1. 模塊經由進程變量 exports 來向外暴露 API,exports 只能是一個對象,暴露的 API 須作為此對象的屬性。
  2. 定義全局函數 require,經由進程傳入模塊標識來引入其他模塊,實行的結果即為別的模塊暴露出來的 API。
  3. 假如被 require 函數引入的模塊中也包括依靠,那末順次加載這些依靠。

特性:

  1. 模塊能夠屢次加載,初次加載的結果將會被緩存,想讓模塊從新運轉須要消滅緩存。
  2. 模塊的加載是一項壅塞操縱,也就是同步加載。

它的語法看起來是如許的:

// a.js
module.exports = {
moduleFunc: function() {
return true;
};
}
// 或
exports.moduleFunc = function() {
return true;
};
// 在 b.js 中援用
var moduleA = require('a.js');
// 或
var moduleFunc = require('a.js').moduleFunc;
console.log(moduleA.moduleFunc());
console.log(moduleFunc())

AMD 範例(Asynchromous Module Definition)

CommonJS 範例湧現后,在 Node 開闢中產生了異常優越的結果,開闢者願望自創這個履歷來處理瀏覽器端 JS 的模塊化。

但大部分人以為瀏覽器和服務器環境差異太大,畢竟瀏覽器端 JS 是經由進程收集動態順次加載的,而不是像服務端 JS 存在當地磁盤中。因而,瀏覽器須要完成的是異步模塊,模塊在定義的時刻就必須先指明它所須要依靠的模塊,然後把本模塊的代碼寫在回調函數中去實行,終究衍生出了 AMD 範例。

AMD 的主要頭腦是異步模塊,主邏輯在回調函數中實行,這和瀏覽器前端所習氣的開闢體式格局不約而同,RequireJS 應運而生。

範例內容:

  1. 用全局函數 define 來定義模塊,用法為:define(id?, dependencies?, factory);
  2. id 為模塊標識,順從 CommonJS Module Identifiers 範例
  3. dependencies 為依靠的模塊數組,在 factory 中需傳入形介入之一一對應,假如 dependencies 省略不寫,則默以為 ["require", "exports", "module"] ,factory 中也會默許傳入 require, exports, module,與 ComminJS 中的完成保持一致
  4. 假如 factory 為函數,模塊對外暴露 API 的要領有三種:return 恣意範例的數據、exports.xxx = xxx 或 module.exports = xxx
  5. 假如 factory 為對象,則該對象即為模塊的返回值

特性:

  1. 前置依靠,異步加載
  2. 便於治理模塊之間的依靠性,有利於代碼的編寫和保護。

它的用法看起來是如許的:

// a.js
define(function (require, exports, module) {
console.log('a.js');
exports.name = 'Jack';
});
// b.js
define(function (require, exports, module) {
console.log('b.js');
exports.desc = 'Hello World';
});
// main.js
require(['a', 'b'], function (moduleA, moduleB) {
console.log('main.js');
console.log(moduleA.name + ', ' + moduleB.desc);
});
// 實行遞次:
// a.js
// b.js
// main.js

人無完人,AMD/RequireJS 也存在飽受詬病的瑕玷。根據 AMD 的範例,在定義模塊的時刻須要把一切依靠模塊都排列一遍(前置依靠),而且在運用時還須要在 factory 中作為形參傳進去。

define(['a', 'b', 'c', 'd', 'e', 'f', 'g'], function(a, b, c, d, e, f, g){ ..... });

看起來稍微不爽 …

RequireJS 模塊化的遞次是如許的:模塊預加載 => 悉數模塊預實行 => 主邏輯中挪用模塊,所以本質是依靠加載完成后還會預先一一將模塊實行一遍,這類體式格局會使得遞次效力有點低。

所以 RequireJS 也供應了就近依靠,會在實行至 require 要領才會去舉行依靠加載和實行,但這類體式格局的用戶體驗不是很好,用戶的操縱會有顯著的耽誤(下載依靠進程),雖然能夠經由進程種種 loading 去處理。

// 就近依靠
define(function () {
setTimeout(function () {
require(['a'], function (moduleA) {
console.log(moduleA.name);
});
}, 1000);
});

CMD 範例(Common Module Definition)

AMD/RequireJS 的 JS 模塊完成上有許多不文雅的處所,長期以來在開闢者中廣受詬病,緣由重如果不能以一種更好的治理模塊的依靠加載和實行,雖然有不足的處所,但它提出的頭腦在當時是異常先進的。

既然優瑕玷那末必定有人出來圓滿它,SeaJS 在這個時刻湧現。

SeaJS 遵照的是 CMD 範例,CMD 是在 AMD 基礎上革新的一種範例,處理了 AMD 對依靠模塊的實行機遇處理題目。

SeaJS 模塊化的遞次是如許的:模塊預加載 => 主邏輯挪用模塊前才實行模塊中的代碼,經由進程依靠的耽誤實行,很好處理了 RequireJS 被詬病的瑕玷。

SeaJS 用法和 AMD 基礎雷同,而且融會了 CommonJS 的寫法:

// a.js
define(function (require, exports, module) {
console.log('a.js');
exports.name = 'Jack';
});
// main.js
define(function (require, exports, module) {
console.log('main.js');
var moduleA = require('a');
console.log(moduleA.name);
});
// 實行遞次
// main.js
// a.js

除此之外,SeaJS 還供應了 async API,完成依靠的耽誤加載。

// main.js
define(function (require, exports, module) {
var moduleA = require.async('a');
console.log(moduleA.name);
});

SeaJS 的湧現,貌似以一種比較圓滿的情勢處理了 JS 模塊化的題目,是 CommonJS 在瀏覽器端的踐行者,並吸收了 RequestJS 的長處。

ES Module

ES Module 是如今 web 開闢中運用率最高的模塊化範例。

跟着 JS 模塊化開闢的呼聲越來越高,作為 JS 言語範例的官方構造 ECMA 也最先將 JS 模塊化歸入 TC39 提案中,並在 ECMAScript 6.0 中獲得實踐。

ES Module 吸收了其他計劃的長處並以更文雅的情勢完成模塊化,它的頭腦是只管的靜態化,即在編譯時就肯定一切模塊的依靠關聯,以及輸入和輸出的變量,和 CommonJS 和 AMD/CMD 這些範例差別的是,它們都是在運轉時才肯定須要依靠哪一些模塊而且實行它。ES Module 使得靜態剖析成為可能。有了它,就可以進一步拓寬 Javascript 的語法,完成一些只能靠靜態剖析完成的功用(比方引入宏(macro)和範例磨練(type system)。

範例內容:

  1. 模塊功用主要由兩個敕令組成:exportimportexport 敕令用於劃定模塊的對外接口,import 敕令用於輸入其他模塊供應的功用。
  2. 經由進程 export 敕令定義了模塊的對外接口,其他 JS 文件就可以夠經由進程 import 敕令加載這個模塊。

ES Module 能夠有多種用法:

模塊的定義:

/**
* export 只支撐對象情勢導出,不支撐值的導出,export default 敕令用於指定模塊的默許輸出,
* 只支撐值導出,然則只能指定一個,本質上它就是輸出一個叫做 default 的變量或要領
*/
// 寫法 1
export var m = 1;
// 寫法 2
var m = 1;
export { m };
// 寫法 3
var n = 1;
export { n as m };
// 寫法 4
var n = 1;
export default n;

模塊的引入:

// 解構引入
import { firstName, lastName, year } from 'a-module';
// 為輸入的變量從新命名
import { lastName as surname } from 'a-module';
// 引出模塊對象(引入一切)
import * as ModuleA from 'a-module';

在運用 ES Module 值得注意的是:importexport 敕令只能在模塊的頂層,在代碼塊中將會報錯,這是由於 ES Module 須要在編譯時代舉行模塊靜態優化,importexport 敕令會被 Javascript 引擎靜態剖析,先於模塊內的其他語句實行,這類設想有利於編譯器進步效力,但也致使沒法在運轉時加載模塊(動態加載)。

關於這個瑕玷,TC39 有了一個新的提案 — Dynamic Import,提案的內容是發起引入 import() 要領,完成模塊動態加載。

// specifier: 指定所要加載的模塊的位置
import(specifier)

import() 要領返回的是一個 Promise 對象。

import('b-module')
.then(module => {
module.helloWorld();
})
.catch(err => {
console.log(err.message);
});

import() 函數能夠用在任何處所,不僅僅是模塊,非模塊的劇本也能夠運用。它是運轉時實行,也就是說,什麼時刻運轉到這一句,就會加載指定的模塊。別的,import() 函數與所加載的模塊沒有靜態銜接關聯,這點也是與 import 語句不雷同。import() 類似於 Node 的 require 要領,區分重如果前者是異步加載,後者是同步加載。

經由進程 importexport 敕令以及 import() 要領,ES Module 險些完成了 CommonJS/AMD/CMD 計劃的一切功用,更主要的是它是作為 ECMAScript 範例湧現的,帶有正統基因,這也是它在如今 Web 開闢中廣泛運用的緣由之一。

但 ES Module 是在 ECMAScript 6.0 範例中的,而如今絕大多數的瀏覽器並直接支撐 ES6 語法,ES Module 並不能直接運用在瀏覽器上,所以須要 Babel 先舉行轉碼,將 import 和 export 敕令轉譯成 ES2015 語法才被瀏覽器剖析。

總結

JS 模塊化的湧現使得前端工程化水平越來越高,讓運用 JS 開闢大型運用成為觸手可及的實際(VScode)。縱觀 JS 模塊化的生長,个中許多頭腦都自創了其他優異的動態言語(Python),然後連繫 JS 運轉環境的特性,衍生出相符本身的範例。但其實在本質上,瀏覽器端的 JS 仍沒有真正意義上的支撐模塊化,只能經由進程東西庫(RequireJS、SeaJS)或許語法糖(ES Module)去 Hack 完成模塊化。跟着 Node 前端工程化東西的繁華生長(Grunt/Gulp/webpack),使我們能夠不關注模塊化的完成進程,直接享用 JS 模塊化編程的快感。

在溫習 JS 模塊化的進程當中,對 Webpack 等東西的模塊化語法糖轉碼產生了新的興緻,願望有時刻能夠去剖析一下模塊化的打包機制和轉譯代碼,然後整理出來加深一下本身對模塊化完成道理的熟悉和明白。

期待下一篇。

參考文章:

  • ECMAScript 6 入門 — 阮一峰
  • JS 模塊化進程

推荐阅读
  • 本文介绍了Sencha Touch的学习使用心得,主要包括搭建项目框架的过程。作者强调了使用MVC模式的重要性,并提供了一个干净的引用示例。文章还介绍了Index.html页面的作用,以及如何通过链接样式表来改变全局风格。 ... [详细]
  • 本文介绍了前端人员必须知道的三个问题,即前端都做哪些事、前端都需要哪些技术,以及前端的发展阶段。初级阶段包括HTML、CSS、JavaScript和jQuery的基础知识。进阶阶段涵盖了面向对象编程、响应式设计、Ajax、HTML5等新兴技术。高级阶段包括架构基础、模块化开发、预编译和前沿规范等内容。此外,还介绍了一些后端服务,如Node.js。 ... [详细]
  • JavaScript和HTML之间的交互是经由过程事宜完成的。事宜:文档或浏览器窗口中发作的一些特定的交互霎时。能够运用侦听器(或处置惩罚递次来预订事宜),以便事宜发作时实行相应的 ... [详细]
  • 从零基础到精通的前台学习路线
    随着互联网的发展,前台开发工程师成为市场上非常抢手的人才。本文介绍了从零基础到精通前台开发的学习路线,包括学习HTML、CSS、JavaScript等基础知识和常用工具的使用。通过循序渐进的学习,可以掌握前台开发的基本技能,并有能力找到一份月薪8000以上的工作。 ... [详细]
  • 在springmvc框架中,前台ajax调用方法,对图片批量下载,如何弹出提示保存位置选框?Controller方法 ... [详细]
  • 本文记录了在vue cli 3.x中移除console的一些采坑经验,通过使用uglifyjs-webpack-plugin插件,在vue.config.js中进行相关配置,包括设置minimizer、UglifyJsPlugin和compress等参数,最终成功移除了console。同时,还包括了一些可能出现的报错情况和解决方法。 ... [详细]
  • 本文介绍了绕过WAF的XSS检测机制的方法,包括确定payload结构、测试和混淆。同时提出了一种构建XSS payload的方法,该payload与安全机制使用的正则表达式不匹配。通过清理用户输入、转义输出、使用文档对象模型(DOM)接收器和源、实施适当的跨域资源共享(CORS)策略和其他安全策略,可以有效阻止XSS漏洞。但是,WAF或自定义过滤器仍然被广泛使用来增加安全性。本文的方法可以绕过这种安全机制,构建与正则表达式不匹配的XSS payload。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • 安卓select模态框样式改变_微软Office风格的多端(Web、安卓、iOS)组件库——Fabric UI...
    介绍FabricUI是微软开源的一套Office风格的多端组件库,共有三套针对性的组件,分别适用于web、android以及iOS,Fab ... [详细]
  • position属性absolute与relative的区别和用法详解
    本文详细解读了CSS中的position属性absolute和relative的区别和用法。通过解释绝对定位和相对定位的含义,以及配合TOP、RIGHT、BOTTOM、LEFT进行定位的方式,说明了它们的特性和能够实现的效果。同时指出了在网页居中时使用Absolute可能会出错的原因,即以浏览器左上角为原始点进行定位,不会随着分辨率的变化而变化位置。最后总结了一些使用这两个属性的技巧。 ... [详细]
  • React基础篇一 - JSX语法扩展与使用
    本文介绍了React基础篇一中的JSX语法扩展与使用。JSX是一种JavaScript的语法扩展,用于描述React中的用户界面。文章详细介绍了在JSX中使用表达式的方法,并给出了一个示例代码。最后,提到了JSX在编译后会被转化为普通的JavaScript对象。 ... [详细]
  • Android实战——jsoup实现网络爬虫,糗事百科项目的起步
    本文介绍了Android实战中使用jsoup实现网络爬虫的方法,以糗事百科项目为例。对于初学者来说,数据源的缺乏是做项目的最大烦恼之一。本文讲述了如何使用网络爬虫获取数据,并以糗事百科作为练手项目。同时,提到了使用jsoup需要结合前端基础知识,以及如果学过JS的话可以更轻松地使用该框架。 ... [详细]
author-avatar
mobiledu2502898253
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有