Heim > Artikel > Web-Frontend > Modularisierung der erweiterten JavaScript-Syntax (empfohlene Sammlung)
Dieser Artikel vermittelt Ihnen relevantes Wissen über die Modularisierung in JavaScript. Ich hoffe, er wird Ihnen hilfreich sein.
Wie wir alle wissen, ist der Status von js in der Front-End-Entwicklung. Es ist wirklich wichtig, es gut zu lernen.
Der folgende Artikel stellt die Modularität vor.
Was genau ist Modularisierung und modulare Entwicklung?
Tatsächlich besteht das ultimative Ziel der modularen Entwicklung darin, das Programm in kleine Strukturen zu unterteilen.
Schreiben Sie Ihren eigenen Logikcode in diese Struktur, der seinen eigenen Geltungsbereich hat und keine Auswirkungen auf andere Strukturen hat.
Diese Struktur kann die Variablen, Funktionen, Objekte usw. exportieren, die Sie zur Verwendung in ihrer Struktur verfügbar machen möchten.
Sie können Variablen, Funktionen, Objekte usw. auch auf irgendeine Weise in andere Strukturen importieren.
Die oben erwähnte Struktur ist das Modul; der Prozess der Aufteilung des Entwicklungsprogramms gemäß dieser Struktur ist der Prozess der modularen Entwicklung.
In den Anfängen der Webentwicklung entwickelte Brendan Eich JavaScript nur als Skriptsprache, um eine einfache Formularvalidierung oder Animationsimplementierung durchzuführen. Damals war der Code noch sehr klein:
Zu diesem Zeitpunkt müssen wir nur den JavaScript-Code in
schreiben und es ist nicht erforderlich, ihn in mehrere Dateien zu schreiben.
Aber mit der rasanten Entwicklung von Front-End und JavaScript ist der JavaScript-Code immer komplexer geworden:
Das Aufkommen von Ajax und die Trennung von Front-End- und Back-End-Entwicklung bedeuten, dass nach hinten -end gibt Daten zurück, wir müssen dies durch JavaScript-Rendering der Front-End-Seiten tun.
Mit dem Aufkommen von SPA sind Front-End-Seiten komplexer geworden: Eine Reihe komplexer Anforderungen, darunter Front-End-Routing, Statusverwaltung usw., müssen über JavaScript implementiert werden.
Einschließlich der Implementierung von Node und dem Schreiben komplexer Back-End-Programme in JavaScript ist der Mangel an Modularität ein fataler Fehler.
Modularisierung ist also bereits ein sehr dringender Bedarf für JavaScript. Aus diesem Grund hat ES6 (2015) eine eigene modulare Lösung auf den Markt gebracht.
Davor wurden viele verschiedene Modularisierungsspezifikationen entwickelt, damit JavaScript die Modularisierung unterstützen kann: AMD, CMD, CommonJS usw.
wie Namenskonflikte.
Lösen Sie das obige Problem durch den sofortigen Funktionsaufrufausdruck (IIFE). Da die Funktion über einen eigenen Gültigkeitsbereich verfügt, kommt es nicht zu Namenskonflikten zwischen verschiedenen Dateien.
// a.js var moduleA = (function() { var name = "llm" var age = 22 var isFlag = true return { name: name, isFlag: isFlag } })()
// b.js var moduleB = (function() { var name = "zh" var isFlag = false return { name: name, isFlag: isFlag } })()
// 使用 moduleA.name moduleB.name
Wir haben jedoch tatsächlich neue Probleme mit sich gebracht:
Ich muss mir den Namen des zurückgegebenen Objekts in jedem Modul merken, damit es bei Verwendung anderer Module korrekt verwendet werden kann.
Das Schreiben des Codes ist verwirrend und der Code in jeder Datei muss in eine anonyme Funktion eingeschlossen werden.
In Ermangelung entsprechender Spezifikationen kann jeder und jedes Unternehmen Module beliebig benennen oder sogar den gleichen Modulnamen haben.
Wir werden also feststellen, dass unsere Implementierung trotz der erreichten Modularisierung zu einfach und nicht standardisiert ist.
Wir müssen bestimmte Spezifikationen formulieren, um jeden dazu zu zwingen, modularen Code gemäß dieser Spezifikation zu schreiben. Diese Spezifikation sollte Kernfunktionen umfassen: Das Modul selbst kann offengelegte Eigenschaften exportieren und das Modul kann die benötigten Eigenschaften importieren. Um die oben genannten Probleme zu lösen, hat die JavaScript-Community eine Reihe nützlicher Spezifikationen entwickelt. Als Nächstes lernen wir einige repräsentative Spezifikationen kennen.
Wir müssen wissen, dass CommonJS eine Spezifikation ist, die ursprünglich für die Verwendung außerhalb von Browsern vorgeschlagen wurde und später in CommonJS We umbenannt wurde Normalerweise wird es auch kurz CJS genannt.
Node ist eine repräsentative Implementierung von CommonJS auf der Serverseite.
Browserify ist eine Implementierung von CommonJS im Browser.
Das Webpack-Paketierungstool bietet Unterstützung und Konvertierung für CommonJS.
CommonJS wird also in Node unterstützt und implementiert, sodass wir während der Entwicklung von Node problemlos eine modulare Entwicklung durchführen können.
Jede js-Datei in Node ist ein separates Modul.
Dieses Modul enthält die Kernvariablen der CommonJS-Spezifikation: exports, module.exports und require.
Wir können diese Variablen verwenden, um die modulare Entwicklung zu erleichtern.
Wir haben bereits erwähnt, dass der Kern der Modularisierung der Export und Import ist, der in Node implementiert ist:
exports und module.exports können für den Export des Inhalts im Modul verantwortlich sein. Die Funktion „require“ kann uns dabei helfen, Inhalte aus anderen Modulen (benutzerdefinierte Module, Systemmodule, Bibliotheksmodule von Drittanbietern) zu importieren.
Modularisierung von Node.js
在Node中每一个js文件都是一个单独的模块。
这个模块中包括CommonJS规范的核心变量:exports、module.exports、require。
exports和module.exports可以负责对模块中的内容进行导出。
require函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容。
下面我们将来介绍exports、module.exports、require的使用。
exports是一个对象,我们可以在这个对象中添加很多个属性,添加的属性会导出。
我们也可以通过module.exports直接导出一个对象。
我们通过require()函数导入一个文件。并且该文件导出的变量。
下面来详细介绍一个module.exports。
CommonJS中是没有module.exports的概念的。
但是为了实现模块的导出,Node中使用的是Module的类,每一个模块都是Module的一个实例,也就是module。
所以在Node中真正用于导出的其实根本不是exports,而是module.exports。
因为module才是导出的真正实现者。
并且内部将exports赋值给module.exports。
该方式的导入导出有以下特点:
Node中的文件都运行在一个函数中。可以通过打印console.log(arguments.callee + "")来验证。
导入导出是值的引用,如果导出的是一个基本数据类型值,那么导出文件改变该值,然后导入文件该变量的值也不会变。
// a.js const obj = require("./b.js") console.log(obj) setTimeout(() => { obj.name = "llm" }, 1000)
// b.js const info = { name: "zh", age: 22, foo: function() { console.log("foo函数~") } } setTimeout(() => { console.log(info.name) // llm }, 2000) module.exports = info
他是通过require 函数来导入的,只有在执行js代码才会知道模块的依赖关系。
代码是同步执行的。
模块多次引入,只会加载一次。每个module内部会存在一个loaded来确定是否被加载过。
代码循环引入的时候,深度优先来加载模块。然后再广度优先。
我们现在已经知道,require是一个函数,可以帮助我们引入一个文件(模块)中导出的对象。
那么,require的查找规则是怎么样的呢?
详细查找规则,请访问这里
这里我总结比较常见的查找规则:导入格式如下:require(X)
模块在被第一次引入时,模块中的js代码会被运行一次
模块被多次引入时,会缓存,最终只加载(运行)一次
为什么只会加载运行一次呢?
这是因为每个模块对象module都有一个属性:loaded。为false表示还没有加载,为true表示已经加载。
如果有循环引入,那么加载顺序是什么?
如上图,Node采用的是深度优先算法:main -> aaa -> ccc -> ddd -> eee ->bbb
CommonJS加载模块是同步的:
同步的意味着只有等到对应的模块加载完毕,当前模块中的内容才能被运行。
这个在服务器不会有什么问题,因为服务器加载的js文件都是本地文件,加载速度非常快。
如果将它应用于浏览器呢?
浏览器加载js文件需要先从服务器将文件下载下来,之后再加载运行。
那么采用同步的就意味着后续的js代码都无法正常运行,即使是一些简单的DOM操作。所以在浏览器中,我们通常不使用CommonJS规范。当然在webpack中使用CommonJS是另外一回事。因为它会将我们的代码转成浏览器可以直接执行的代码。
在早期为了可以在浏览器中使用模块化,通常会采用AMD或CMD。但是目前一方面现代的浏览器已经支持ES Modules,另一方面借助于webpack等工具可以实现对CommonJS或者ES Module代码的转换。AMD和CMD已经使用非常少了,所以这里我们进行简单的演练。
AMD主要是应用于浏览器的一种模块化规范:
AMD是Asynchronous Module Definition(异步模块定义)的缩写。它采用的是异步加载模块。
我们提到过,规范只是定义代码的应该如何去编写,只有有了具体的实现才能被应用。
AMD实现的比较常用的库是require.js和curl.js。
require.js的使用
定义HTML的script标签引入require.js和定义入口文件。data-main属性的作用是在加载完src的文件后会加载执行该文件
// index.html <script src="./require.js" data-main="./index.js"></script>
//main.js require.config({ baseUrl: '', // 默认是main.js的文件夹路径 paths: { foo: "./foo" } }) require(["foo"], function(foo) { console.log("main:", foo) })
// foo.js define(function() { const name = "zh" const age = 22 function sum(num1, num2) { return num1 + num2 } return { name, age, sum } })
CMD规范也是应用于浏览器的一种模块化规范:
CMD 是Common Module Definition(通用模块定义)的缩写。它也采用了异步加载模块,但是它将CommonJS的优点吸收了过来。
AMD实现的比较常用的库是SeaJS。
SeaJS的使用
引入sea.js和使用主入口文件。
// index.html <script src="./sea.js"></script> <script> seajs.use("./main.js") </script>
//main.js define(function(require, exports, module) { const foo = require("./foo") console.log("main:", foo) })
// foo.js define(function(require, exports, module) { const name = "zh" const age = 22 function sum(num1, num2) { return num1 + num2 } // exports.name = name // exports.age = age module.exports = { name, age, sum } });
ES Module和CommonJS的模块化有一些不同之处:
一方面它使用了import和export关键字来实现模块化。
另一方面它采用编译期的静态分析,并且也加入了动态引用的方式。
export负责将模块内的内容导出。
import负责从其他模块导入内容。
采用ES Module将自动采用严格模式:use strict。
基本使用
// index.html <script src="./main.js" type="module"></script>
// foo.js let obj = { name: "zh", age: 22 } export default sum
// main.js import foo from './foo.js' console.log(foo)
在html文件加载入口文件的时候,需要指定type为module。
在打开html文件时,需要开启本地服务,而不能直接打开运行在浏览器上。
这个在MDN上面有给出解释:
你需要注意本地测试 — 如果你通过本地加载Html 文件 (比如一个 file:// 路径的文件), 你将会遇到 CORS 错误,因为Javascript 模块安全性需要。
你需要通过一个服务器来测试。
export关键字将一个模块中的变量、函数、类等导出。
我们希望将其他中内容全部导出,它可以有如下的方式:
方式一:在语句声明的前面直接加上export关键字。
export const name = "zh" export const age = 22
方式二:将所有需要导出的标识符,放到export后面的 {} 中。注意:这里的 {}里面不是ES6的对象字面量的增强写法,{}也不是表示一个对象的。所以: export {name: name},是错误的写法。
const name = "zh" const age = 22 function foo() { console.log("foo function") } export { name, age, foo }
方式三:导出时给标识符起一个别名。(基本没用,一般在导入文件中起别名)。然后在导入文件中就只能使用别名来获取。
export { name as fName, age as fAge, foo as fFoo }
import关键字负责从另外一个模块中导入内容。
导入内容的方式也有多种:
方式一:import {标识符列表} from '模块'。注意:这里的{}也不是一个对象,里面只是存放导入的标识符列表内容。
import { name, age } from "./foo.js"
方式二:导入时给标识符起别名。
import { name as fName, age as fAge } from './foo.js'
方式三:通过 * 将模块功能放到一个模块功能对象(a module object)上。然后通过起别名来使用。
import * as foo from './foo.js'
表示导入导出。
import { add, sub } from './math.js' import {otherProperty} from './other.js' export { add, sub, otherProperty }
等价于
// 导入的所有文件会统一被导出 export { add, sub } from './math.js' export {otherProperty} from './other.js'
等价于
export * from './math.js' export * from './other.js'
为什么要这样做呢?
在开发和封装一个功能库时,通常我们希望将暴露的所有接口放到一个文件中。 这样方便指定统一的接口规范,也方便阅读。这个时候,我们就可以使用export和import结合使用。
前面我们学习的导出功能都是有名字的导出(named exports):
在导出export时指定了名字。
在导入import时需要知道具体的名字。
还有一种导出叫做默认导出(default export)
// foo.js const name = "zh" cconst age = 22 export { name, // 或者这样的默认导出 // age as default } export default age
// 导入语句: 导入的默认的导出 import foo, {name} from './foo.js' console.log(foo, name) // 22 zh
默认导出export时可以不需要指定名字。
在导入时不需要使用 {},并且可以自己来指定名字。
它也方便我们和现有的CommonJS等规范相互操作。
注意:在一个模块中,只能有一个默认导出(default export)。
通过import加载一个模块,是不可以在其放到逻辑代码中的,比如:
if(true) { import foo from './foo.js' }
为什么会出现这个情况呢?
这是因为ES Module在被JS引擎解析时,就必须知道它的依赖关系。
由于这个时候js代码没有任何的运行,所以无法在进行类似于if判断中根据代码的执行情况。
但是某些情况下,我们确确实实希望动态的来加载某一个模块:
如果根据不同的条件,动态来选择加载模块的路径。
这个时候我们需要使用 import() 函数来动态加载。import函数返回的结果是一个Promise。
import("./foo.js").then(res => { console.log("res:", res.name) })
es11新增了一个属性。meta属性本身也是一个对象: { url: "当前模块所在的路径" }
console.log(import.meta)
ES Module是如何被浏览器解析并且让模块之间可以相互引用的呢?
ES Module的解析过程可以划分为三个阶段:
阶段一:构建(Construction),根据地址查找js文件,并且下载,将其解析成模块记录(Module Record)。
阶段二:实例化(Instantiation),对模块记录进行实例化,并且分配内存空间,解析模块的导入和导出语句,把模块指向对应的内存地址。
阶段三:运行(Evaluation),运行代码,计算值,并且将值填充到内存地址中。
Phase 1:
Phase 2 und 3:
Wie oben zu sehen ist, wirkt sich eine Änderung des Werts der Variablen in der Exportdatei auf den Wert in der Importdatei aus . Und die importierte Datei darf den Wert der exportierten Datei nicht ändern.
Verwandte Empfehlungen: Javascript-Lern-Tutorial
Das obige ist der detaillierte Inhalt vonModularisierung der erweiterten JavaScript-Syntax (empfohlene Sammlung). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!