Heim >Web-Frontend >js-Tutorial >Eine kurze Einführung in die JavaScript-Modularität
Vorwort
In Bezug auf die Modularisierung sind die von uns geschriebenen „Require“- und „Import“-Schlüsselwörter der direkteste Ausdruck Wenn Sie relevante Informationen nachschlagen, werden Sie auf jeden Fall auf Begriffe wie CommonJS und CMD AMD stoßen, aber auch auf unbekannte Frameworks wie RequireJS und SeaJS. Die offizielle Website von SeaJS beschreibt sich beispielsweise so: „Einfache und benutzerfreundliche Moduldefinitionsspezifikation, Sea.js folgt der CMD-Spezifikation. Natürliche und intuitive Codeorganisation, automatisches Laden von Abhängigkeiten …“
Wie Ich bin ehrlich gesagt ein Front-End-Neuling. Er sah verwirrt aus und konnte es nicht verstehen. Gemäß meinem üblichen Stil muss ich, bevor ich etwas vorstelle, immer erklären, warum es notwendig ist.
JavaScript-Grundlagen
Studenten, die am Client arbeiten, sind mit OC vertraut Sie sollten mit #import "classname", den Modul- und Dateimodifikatoren von Swift und dem Importpaket+Klassenmodus von Java vertraut sein. Wir sind an das Muster gewöhnt, dass der Verweis auf eine Datei auf eine Klasse verweist. In einer dynamischen Sprache wie JavaScript haben sich die Dinge jedoch geändert:
<html> <head> <script type="text/javascript" src="index.js"></script> </head> <body> <p id="hello"> Hello Wrold </p> <input type="button" onclick="onPress()" value="Click me" /> </body> </html>
// index.js function onPress() { var p = document.getElementById('hello'); p.innerHTML = 'Hello bestswifter'; }
Das 3f1c4e4b6b16bbbd69b2ee476dc4f83a-Tag kann als Import verstanden werden, sodass das Onclick-Ereignis der Schaltfläche Sie können die in index.js definierte onPress-Funktion aufrufen.
Angenommen, wir stellen im Verlauf des Projekts fest, dass der angeklickte Text dynamisch generiert und von anderen JS-Dateien generiert werden muss, dann ist einfaches index.js nicht in der Lage, diese Aufgabe zu erledigen. Wir gehen davon aus, dass der generierte Inhalt in der Datei math.js definiert ist:
// math.js function add(a, b) { return a + b; }
Nach Ansicht des Kunden sollte die Datei index.js zu diesem Zeitpunkt wie folgt geschrieben sein:
// index.js import "math.js" function onPress() { var p = document.getElementById('hello'); p.innerHTML = add(1, 2); }
Leider unterstützt JavaScript die Importmethode nicht, was bedeutet, dass Methoden in anderen JS-Dateien nicht in einer JS-Datei referenziert werden können. Die richtige Lösung besteht darin, die Add-Methode direkt in index.js aufzurufen und gleichzeitig auf math.js in index.html zu verweisen:
<html> <head> <script type="text/javascript" src="index.js"></script> <script type="text/javascript" src="math.js"></script> </head> <body> <p id="hello"> Hello Wrold </p> <input type="button" onclick="onPress()" value="Click me" /> </body> </html>
// index.js function onPress() { var p = document.getElementById('hello'); p.innerHTML = add(1, 2); }
Sie können sehen, dass dies bei dieser Schreibmethode nicht der Fall ist elegant, index.js hat keine Kontrolle über den Inhalt in anderen JS-Dateien. Ob Sie die Add-Methode aufrufen können, hängt ausschließlich davon ab, ob Ihre eigene HTML-Datei korrekt auf andere JS-Dateien verwiesen hat.
Vorläufige Modularisierung
Die gerade erwähnten Schwachstellen Tatsächlich kann es in zwei Typen unterteilt werden:
1.index.js kann nicht importiert werden und basiert auf HTML-Referenzen
2.index.js kann die Quelle der Add-Methode nicht erkennen und fehlt ein Namespace Das Konzept von
Die erste Frage wird später beantwortet. Lassen Sie uns beispielsweise zuerst die Funktion in ein Objekt einfügen, damit wir ein Objekt verfügbar machen und den Benutzer dieses aufrufen lassen können Objekt. Mehrere Methoden:
//index.js function onPress() { var p = document.getElementById('hello'); p.innerHTML = math.add(1, 2); } //math.js var math = { base: 0, add: function(a, b) { return a + b + base; }, };
Sie können sehen, dass in index.js eine vereinfachte Version des Namespace (d. h. math) angegeben werden kann. Es gibt jedoch noch ein kleines Problem: Das Basisattribut wird beispielsweise der Außenwelt zugänglich gemacht und kann auch geändert werden. Eine bessere Möglichkeit besteht also darin, Mathematik in einem Abschluss zu definieren, um die internen Eigenschaften auszublenden:
// math.js var math = (function() { var base = 0; return { add: function(a, b) { return a + b + base; }, }; })();
Bisher haben wir die Definition und Verwendung von Modulen implementiert. Ein Kern der Modularisierung liegt jedoch im Namensraum, was bedeutet, dass wir hoffen, dass unser Mathematikmodul nicht global ist, sondern bei Bedarf importiert wird. Selbst wenn mehrere Dateien Objekte mit demselben Namen verfügbar machen, gibt es keine Problem. Genau wie in node.js definiert das Modul, das verfügbar gemacht werden muss, seinen eigenen Exportinhalt, und dann verwendet der Aufrufer die Methode require.
Tatsächlich können Sie einfach den Arbeitsmodus von node.js simulieren und ihn lösen, indem Sie eine Zwischenschicht hinzufügen: Definieren Sie zunächst eine globale Variable:
// global.js var module = { exports: {}, // 用来存储所有暴露的内容 };
und legen Sie dann das Objekt in frei math.js:
var math = (function() { var base = 0; return { add: function(a, b) { return a + b + base; }, }; })(); module.exports.math = math;
Benutzer index.js sollte jetzt sein:
var math = module.exports.math; function onPress() { var p = document.getElementById('hello'); // math p.innerHTML = math.add(1, 2); }
Bestehende modulare Lösung
Der obige einfache modulare Ansatz weist einige kleinere Probleme auf. Zunächst muss sich index.js strikt auf die Ausführung von math.js verlassen, da sich math.js erst nach der Ausführung beim globalen module.export registriert. Dies erfordert, dass Entwickler die Ladereihenfolge von JS-Dateien manuell verwalten. Mit zunehmender Projektgröße wird die Pflege von Abhängigkeiten immer komplexer.
Zweitens benötigen wir auch ein asynchrones On-Demand-Laden der JS-Datei, da der Browser beim Laden der JS-Datei das Rendern der Webseite stoppt.
Das letzte Problem besteht darin, dass die zuvor angegebene vereinfachte Modularisierungslösung den Modul-Namespace nicht löst. Derselbe Export ersetzt weiterhin den vorherigen Inhalt und die Lösung besteht darin, einen „Dateipfad< ;--> Export“ beizubehalten Inhaltstabelle und laden Sie sie basierend auf dem Dateipfad.
Basierend auf den oben genannten Anforderungen sind viele Sätze modularer Lösungen auf dem Markt erschienen. Warum es mehrere Standards gibt, liegt tatsächlich an den Eigenschaften des Frontends. Aufgrund des Fehlens eines einheitlichen Standards verlässt sich in vielen Fällen jeder auf Konventionen, wenn es um Dinge geht, wie zum Beispiel den oben genannten Export und Require. Wenn der Anbieter des Codes den Exportinhalt in module.exports speichert und der Benutzer module.export liest, ist das natürlich vergeblich. Darüber hinaus sind auch die Implementierungsmethoden und Nutzungsszenarien jeder Spezifikation unterschiedlich.
CommonJS
比较知名的规范有 CommonJS、AMD 和 CMD。而知名框架 Node.js、RequireJS 和 Seajs 分别实现了上述规范。
最早的规范是 CommonJS,Node.js 使用了这一规范。这一规范和我们之前的做法比较类似,是同步加载 JS 脚本。这么做在服务端毫无问题,因为文件都存在磁盘上,然而浏览器的特性决定了 JS 脚本需要异步加载,否则就会失去响应,因此 CommonJS 规范无法直接在浏览器中使用。
AMD
浏览器端著名的模块管理工具 Require.js 的做法是异步加载,通过 Webworker 的 importScripts(url); 函数加载 JS 脚本,然后执行当初注册的回调。Require.js 的写法是:
require(['myModule1', 'myModule2'], function (m1, m2){ // 主回调逻辑 m1.printName(); m2.printName(); });
由于这两个模块是异步下载,因此哪个模块先被下载、执行并不确定,但可以肯定的是主回调一定在所有依赖都被加载完成后才执行。
Require.js 的这种写法也被称为前置加载,在写主逻辑之前必须指定所有的依赖,同时这些依赖也会立刻被异步加载。
由 Require.js 引申出来的规范被称为 AMD(Asynchronous Module Definition)。
CMD
另一种优秀的模块管理工具是 Sea.js,它的写法是:
define(function(require, exports, module) { var foo = require('foo'); // 同步 foo.add(1, 2); ... require.async('math', function(math) { // 异步 math.add(1, 2); }); });
Sea.js 也被称为就近加载,从它的写法上可以很明显的看到和 Require.js 的不同。我们可以在需要用到依赖的时候才申明。
Sea.js 遇到依赖后只会去下载 JS 文件,并不会执行,而是等到所有被依赖的 JS 脚本都下载完以后,才从头开始执行主逻辑。因此被依赖模块的执行顺序和书写顺序完全一致。
由 Sea.js 引申出来的规范被称为 CMD(Common Module Definition)。
ES 6 模块化
在 ES6 中,我们使用 export 关键字来导出模块,使用 import 关键字引用模块。需要说明的是,ES 6 的这套标准和目前的标准没有直接关系,目前也很少有 JS 引擎能直接支持。因此 Babel 的做法实际上是将不被支持的 import 翻译成目前已被支持的 require。
尽管目前使用 import 和 require 的区别不大(本质上是一回事),但依然强烈推荐使用 import 关键字,因为一旦 JS 引擎能够解析 ES 6 的 import 关键字,整个实现方式就会和目前发生比较大的变化。如果目前就开始使用 import 关键字,将来代码的改动会非常小。