Модульность интерфейса является краеугольным камнем разработки интерфейса. В настоящее время использование модулей в эпоху большого интерфейса повсеместно.
Что такое модуль? И посмотрите на определение в webpack:
В модульном программировании разработчики разбивают программу на отдельные функциональные блоки, называемые модулями. Каждый модуль имеет меньшую контактную поверхность, чем полная программа, что упрощает проверку, отладку и тестирование. Хорошо написанные модули обеспечивают прочные границы абстракции и инкапсуляции, так что каждый модуль в приложении имеет согласованную структуру и четкое назначение.
Модули должны быть дискретными функциональными блоками с единой ответственностью, независимыми друг от друга, мало связанными, в высокой степени связанными и заменяемыми.
Что такое модульность?
Модульность — это способ справиться с декомпозицией сложных систем на более управляемые модули. Он может разделить системный код на ряд модулей с единой ответственностью, сильно развязанных и заменяемых модулей. Как изменения в одной части системы повлияют на другие модули. Детали становятся очевидными, а ремонтопригодность системы намного проще и доступнее.
Модульность — это идея «разделяй и властвуй».Разбивая сложные системы на независимые модули для достижения детального и детального управления, это очень полезно для обслуживания и управления сложными системами. Модульность также является краеугольным камнем компонентизации, предпосылкой для красочного внешнего мира.
Зачем нужна модульность
Основное различие между front-end разработкой и другими разработками заключается в том, что front-end основан на многоязычном, многоуровневом кодировании и организации, а доставка front-end продуктов основана на браузерах, и эти ресурсы запускать в браузер с помощью инкрементной загрузки. , Как организовать эти фрагментированные коды и ресурсы в среде разработки и обеспечить их быструю и изящную загрузку и обновление на стороне браузера, требует модульной системы. Эта идеальная модульная система — то, что нужно инженеры работают над этой головоломкой уже много лет.
В частности, нынешний фронтенд уже не тот, что был раньше.В бесконечном потоке появляются различные фронтенд-фреймворки и технологии.От предыдущей веб-разработки до разработки систем и приложений код становится все более сложным, и передняя часть несет все больше и больше обязанностей. Для таких проблем, как организация и сопровождение кода, а также повторное использование функций, срочно необходимо решение, основанное на инженерном мышлении.
Зачем нам модульность?Конечно, самое главное, что у нас есть потребности, но их на самом деле нет. Сам JavaScript не предоставляет такого решения из-за исторических проблем или проблем с позиционированием, но Java, имеющая значительное происхождение, имеет пакетный механизм, который организует структуру кода посредством пакетов и классов.
Конечно, теперь у нас есть свои и различные модульные реализации.В этой статье в основном исследуется механизм CommonJS, основанный на реализации в Node.
Краткая история модульности
- Самый простой и грубый способ
function fn1(){
// ...
}
function fn2(){
// ...
}
Импортируйте файлы с помощью тегов сценария и вызывайте соответствующие функции. Таким образом, необходимо вручную управлять порядком зависимостей, что легко может вызвать конфликты имен, загрязнить всю ситуацию и увеличить стоимость обслуживания по мере увеличения сложности проекта.
- Эмулировать пространства имен с помощью объектов
var output = {
_count: 0,
fn1: function(){
// ...
}
}
Это может решить вышеупомянутую проблему глобального загрязнения.Это имеет значение пространства имен, но по мере увеличения сложности проекта необходимо поддерживать все больше и больше таких объектов.Если ничего другого, именование является проблемой. Самое главное, что внутренние свойства могут быть напрямую доступны и изменены.
- Закрытие
Наиболее широко используетсяIIFE.
var module = (function(){
var _count = 0;
var fn1 = function (){
// ...
}
var fn2 = function fn2(){
// ...
}
return {
fn1: fn1,
fn2: fn2
}
})()
module.fn1();
module._count; // undefined
Таким образом, он имеет независимую лексическую область, и в памяти существует только одна копия. Это не только предотвращает доступ внешнего мира к этомуIIFEпеременные в , не загрязняя глобальную область, путемreturnОткройте общедоступный интерфейс для внешнего мира. На самом деле это основа современных модульных реализаций.
- Более
Существуют также различные способы слабосвязанного расширения, тесно связанного расширения, наследования, подмодулей, совместного использования частных объектов между файлами и новой конструкции, основанной на реализации закрытия.Этот метод больше не является элегантным, пожалуйста, обратитесь к цитате в конце , не буду вдаваться в подробности.
// 松耦合拓展
// 这种方式使得可以在不同的文件中以相同结构共同实现一个功能块,且不用考虑在引入这些文件时候的顺序问题。
// 缺点是没办法重写你的一些属性或者函数,也不能在初始化的时候就是用module的属性。
var module = (function(my){
// ...
return my
})(module || {})
// 紧耦合拓展(没有传默认参数)
// 加载顺序不再自由,但是可以重载
var module = (function(my){
var old = my.someOldFunc
my.someOldFunc = function(){
// 重载方法,依然可通过old调用旧的方法...
}
return my
})(module)
CommonJS
CommonJS — это проект, созданный с целью создания экосистемы JavaScript вне среды браузера, например, в среде сервера и рабочего стола.
Отправной точкой является устранение болевых точек JavaScript:
- Бесмодульная система (ES6 решает эту проблему)
- управление пакетами
- Слишком маленькая стандартная библиотека
- ...
Модули CommonJS характеризуются следующим образом:
- Весь код выполняется в области модуля и не загрязняет глобальную область.
- Модуль может быть загружен несколько раз, но только когда первая загрузка загружается один раз, затем результат кэшируется, а затем загружается, непосредственно считывается результат кэша. Чтобы модуль снова запустился, необходимо очистить кеш.
- Порядок загрузки модулей, порядок их появления в коде.
- В модульной системе Node.js каждый файл рассматривается как отдельный модуль.
Сама спецификация CommonJS охватывает модули, двоичные файлы, буферы, файловые системы, управление пакетами и т. д., в то время как Node позаимствовал модульную систему из спецификации CommonJS и реализовал очень простую в использовании модульную систему.
Определение модулей в CommonJS можно разделить на три части: ссылки на модули (require), определение модуля (exports,module.exports), идентификатор модуля (requireпараметр).
CommonJS использования не в этом повторе.
Поскольку мы изучаем модульное программирование через Node, мы должны сначала понять модули в Node.
Типы модулей в узле
Следующий контент должен постоянно находить соответствующую логику всего процесса загрузки модуля в исходном коде.исходный кодчитать.
- основной модуль
- встроенные модули: модули C/CPP в каталоге src.
- Собственный модуль: модуль в каталоге lib.Некоторые нативные модули вызывают встроенные модули в нижней части, например буферные модули, выделение памяти которых реализовано в модулях C/CPP.
-
Сторонние модули: сохранить в
node_modulesВстроенные модули, отличные от Node, в каталоге -
файловый модуль: например.
require('./utils'), который характеризуется путем к файлу с абсолютным или относительным путем
Украденное фото:
воплощать в жизньnode index.js
Вероятно, поток выполнения/src/node_main.cc --> /src/node.cc--> выполнитьnode::LoadEnvironment()
// Bootstrap internal loaders
loader_exports = ExecuteBootstrapper(env, "internal/bootstrap/loaders", &loaders_params, &loaders_args);
if (loader_exports.IsEmpty()) {
return;
}
if (ExecuteBootstrapper(env, "internal/bootstrap/node", &node_params, &node_args).IsEmpty()) {
return;
}
появился здесьinternal/bootstrap/loaders. Давайте посмотрим на комментарии заголовка файла:
// This file creates the internal module & binding loaders used by built-in
// modules. In contrast, user land modules are loaded using
// lib/internal/modules/cjs/loader.js (CommonJS Modules) or
// lib/internal/modules/esm/* (ES Modules).
//
// This file is compiled and run by node.cc before bootstrap/node.js
// was called, therefore the loaders are bootstraped before we start to
// actually bootstrap Node.js. It creates the following objects:
//
// C++ binding loaders:
// - process.binding(): the legacy C++ binding loader, accessible from user land
// because it is an object attached to the global process object.
// These C++ bindings are created using NODE_BUILTIN_MODULE_CONTEXT_AWARE()
// and have their nm_flags set to NM_F_BUILTIN. We do not make any guarantees
// about the stability of these bindings, but still have to take care of
// compatibility issues caused by them from time to time.
// - process._linkedBinding(): intended to be used by embedders to add
// additional C++ bindings in their applications. These C++ bindings
// can be created using NODE_MODULE_CONTEXT_AWARE_CPP() with the flag
// NM_F_LINKED.
// - internalBinding(): the private internal C++ binding loader, inaccessible
// from user land because they are only available from NativeModule.require().
// These C++ bindings are created using NODE_MODULE_CONTEXT_AWARE_INTERNAL()
// and have their nm_flags set to NM_F_INTERNAL.
//
// Internal JavaScript module loader:
// - NativeModule: a minimal module system used to load the JavaScript core
// modules found in lib/**/*.js and deps/**/*.js. All core modules are
// compiled into the node binary via node_javascript.cc generated by js2c.py,
// so they can be loaded faster without the cost of I/O. This class makes the
// lib/internal/*, deps/internal/* modules and internalBinding() available by
// default to core modules, and lets the core modules require itself via
// require('internal/bootstrap/loaders') even when this file is not written in
// CommonJS style.
//
// Other objects:
// - process.moduleLoadList: an array recording the bindings and the modules
// loaded in the process and the order in which they are loaded.
Комментарии в этом файле указывают на то, что файл используется для создания привязки процессов для загрузки модулей C ++ во время инициализации, и что NativeModule используется для загрузки встроенных модулей (lib/**/*.jsа такжеdeps/**/*.js).
Встроенные модули компилируются в узел в двоичной форме, поэтому они загружаются быстро и не требуют дополнительных операций ввода-вывода. NativeModule здесь представляет собой мини-версию реализации модульной системы (CommonJS).
Также упоминается, что загрузка файлов для невстроенных модулей определяется вlib/internal/modules/cjs/loader.js (CommonJS Modules)илиlib/internal/modules/esm/* (ES Modules).
Поскольку среда загружается первой при запуске узла, поэтому
internal/bootstrap/loadersСначала выполнит, создаст процесс и NativeModule, поэтому вlib/internal/modules/cjs/loader.jsЗаголовок файла можно использовать напрямуюrequire()Причина, то есть здесь использованиеNativeModule.requireдля загрузки встроенных модулей.
Module.runMain()
оглядыватьсяinternal/bootstrap/nodeСодержание:
Поток выполнения функции:startup() --> startExecution() --> executeUserCode() --> CJSModule.runMain();
здесьCJSModuleизlib/internal/modules/cjs/loader.jsпройти черезNativeModule.requireимпортированныйModuleобъект. Давайте посмотрим, что определено внутриrunMain()метод:
Module.runMain()-- исходный код нажмите здесь
// internal/bootstrap/node.js
const CJSModule = NativeModule.require('internal/modules/cjs/loader');
// ...
CJSModule.runMain();
// internal/modules/cjs/loader
// bootstrap main module.
// 就是执行入口模块(主模块)
Module.runMain = function() {
// 加载主模块 - 命令行参数.
if (experimentalModules) {
// 懒加载 ESM
if (asyncESM === undefined) lazyLoadESM();
asyncESM.loaderPromise.then((loader) => {
return loader.import(pathToFileURL(process.argv[1]).pathname);
})
.catch((e) => {
decorateErrorStack(e);
console.error(e);
process.exit(1);
});
} else {
Module._load(process.argv[1], null, true);
}
// 处理第一个 tick 中添加的任何 nextTicks
process._tickCallback();
};
Сосредоточимся на этом предложении исполняемого кода:Module._load(process.argv[1], null, true);
здесьprocess.argv[1]это наш титулindex.js, то есть выполнитьnode index.jsПроцесс документирования, его суть заключается вModule._load(index.js)процесс этого документа.
Затем исходим изModule._load()Начинать!
Module._load()
Прежде чем мы будем следовать этой строке выполнения, нам нужно знать, как определить объект модуля:
Module-- исходный код нажмите здесь
// Module 定义(类)
function Module(id, parent) {
this.id = id; // 模块的识别符,通常是带有绝对路径的模块文件名
this.exports = {}; // 表示模块对外输出的值。
this.parent = parent; // 返回一个对象,表示调用该模块的模块。
updateChildren(parent, this, false); // 更新函数
this.filename = null; // 模块的文件名,带有绝对路径。
this.loaded = false; // 返回一个布尔值,表示模块是否已经完成加载。
this.children = []; // 返回一个数组,表示该模块要用到的其他模块。
}
👌, затем продолжайте вводить_loadметод:
Module._load()-- исходный код нажмите здесь
// 检查对请求文件的缓存.
// 1. 如果缓存了该模块: 直接返回 exports 对象.
// 2. 如果是 native 模块: 调用并返回 `NativeModule.require()`.
// 3. 否则就创建一个新的 module,缓存起来,并返回其 exports.
// 参数说明:分别是 *模块名称*, *父级模块(调用这个模块的模块)*, *是不是主入口文件(node index.js 中的 index.js 就是主入口文件, require('./index.js') 就不是)*
Module._load = function(request, parent, isMain) {
if (parent) {
debug('Module._load REQUEST %s parent: %s', request, parent.id);
}
// * 解析文件的路径
var filename = Module._resolveFilename(request, parent, isMain);
var cachedModule = Module._cache[filename];
if (cachedModule) {
updateChildren(parent, cachedModule, true);
return cachedModule.exports;
}
if (NativeModule.nonInternalExists(filename)) {
debug('load native module %s', request);
return NativeModule.require(filename);
}
// Don't call updateChildren(), Module constructor already does.
var module = new Module(filename, parent);
if (isMain) {
process.mainModule = module;
module.id = '.';
}
Module._cache[filename] = module;
// * 尝试加载该模块
tryModuleLoad(module, filename);
return module.exports;
};
Внедрение модуля состоит из трех процессов:
- разрешение пути
- местоположение файла
- Скомпилировать и выполнить
Итак, вModule._load()Нам нужно обратить внимание на два важных вызова методов в функции:Module._resolveFilename(request, parent, isMain),tryModuleLoad(module, filename)
Module._resolveFilename()
Эта функция соответствует упомянутому выше процессу разбора и позиционирования пути к файлу.
Module._resolveFilename()-- исходный код
// 省略部分代码
// 过程
// 1. 自带模块里面有的话 返回文件名
// 2. 算出所有这个文件可能的路径放进数组(_resolveLookupPaths)
// 3. 在可能路径中找出真正的路径并返回(_findPath)
Module._resolveFilename = function(request, parent, isMain, options) {
if (NativeModule.nonInternalExists(request)) {
return request;
}
var paths;
if (typeof options === 'object' && options !== null &&
Array.isArray(options.paths)) {
const fakeParent = new Module('', null);
paths = [];
for (var i = 0; i < options.paths.length; i++) {
const path = options.paths[i];
fakeParent.paths = Module._nodeModulePaths(path);
const lookupPaths = Module._resolveLookupPaths(request, fakeParent, true);
for (var j = 0; j < lookupPaths.length; j++) {
if (!paths.includes(lookupPaths[j]))
paths.push(lookupPaths[j]);
}
}
} else {
paths = Module._resolveLookupPaths(request, parent, true);
}
// look up the filename first, since that's the cache key.
var filename = Module._findPath(request, paths, isMain);
if (!filename) {
// eslint-disable-next-line no-restricted-syntax
var err = new Error(`Cannot find module '${request}'`);
err.code = 'MODULE_NOT_FOUND';
throw err;
}
return filename;
};
Здесь нужно сосредоточиться на двух функциях:
-
Module._resolveLookupPaths(request, parent, true): получить все возможные пути к файлу -
Module._findPath(request, paths, isMain): найти абсолютный путь к файлу в соответствии с возможным путем к файлу, включая завершение суффикса (.js, .json, .node) и т. д., которые выполняются в этом методе, и, наконец, вернуть абсолютный путь к файлу.
Фактически, чтобы найти все возможные пути, нужно размышлять в нескольких ситуациях и, наконец, возвращать результирующий набор возможных путей.
- Путь не является относительным путем, это может быть модуль, поставляемый с Node.
- Путь не является относительным путем, это может быть глобально установленный пакет, т.е.
npm i webpack -g - Если вызывающей стороны нет, это может быть пакет в node_module проекта.
- В противном случае абсолютный путь вычисляется на основе пути вызывающего объекта (родителя).
По сути, этот процесс анализа состоит в том, чтобы попробовать каждую ситуацию один раз, весь процесс выглядит следующим образом (пиратская картинка):
tryModuleLoad()
Эта функция соответствует процессу выполнения компиляции выше, мы причесываем:
// 通过 module.load 函数加载模块,失败就删除该模块的缓存。
function tryModuleLoad(module, filename) {
var threw = true;
try {
module.load(filename);
threw = false;
} finally {
if (threw) {
delete Module._cache[filename];
}
}
}
здесь черезModule.prototype.loadЗагрузив модуль, продолжим смотреть на его реализацию:
// 省略部分代码
Module.prototype.load = function(filename) {
debug('load %j for module %j', filename, this.id);
assert(!this.loaded);
this.filename = filename;
this.paths = Module._nodeModulePaths(path.dirname(filename));
var extension = findLongestRegisteredExtension(filename);
Module._extensions[extension](this, filename);
this.loaded = true;
// ...
};
здесьextensionНа самом деле это суффикс файла.native extensionВключать.js, .json, .node. Порядок его определения означает, что это также поиск..js -> .json -> .nodeЗаказ.
通过对象查找表的方式分发不同后缀文件的处理方式也利于后续的可拓展性。我们接着看:
// Native extension for .js
Module._extensions['.js'] = function(module, filename) {
var content = fs.readFileSync(filename, 'utf8');
module._compile(stripBOM(content), filename);
};
// Native extension for .json
Module._extensions['.json'] = function(module, filename) {
var content = fs.readFileSync(filename, 'utf8');
try {
module.exports = JSON.parse(stripBOM(content));
} catch (err) {
err.message = filename + ': ' + err.message;
throw err;
}
};
// Native extension for .node
Module._extensions['.node'] = function(module, filename) {
return process.dlopen(module, path.toNamespacedPath(filename));
};
в.jsonТип метода загрузки файла является самым простым, непосредственное чтение содержимого файла, а затемJSON.parseЗатем верните объект.
Давайте еще раз взглянем на загрузку сторонних модулей C/C++ (суффикс .node). Интуитивно это очень просто, просто позвонитеprocess.dlopenметод.
Мы ориентируемся на.jsОбработка файлов:
казненmodule._compile()мы вводим эту функцию:
Module.prototype._compile() -- 源码
Module.wrap = function(script) {
return Module.wrapper[0] + script + Module.wrapper[1];
};
Module.wrapper = [
'(function (exports, require, module, __filename, __dirname) { ',
'\n});'
];
// 省略部分代码
Module.prototype._compile = function(content, filename) {
// ...
// 把模块的内容用一个 IIFE 包起来从而有独立的词法作用域,传入了 exports, require, module 参数
// 这也就是我们在模块中可以直接使用 exports, require, module 的原因。
var wrapper = Module.wrap(content);
// 生成 require 函数
var require = makeRequireFunction(this);
// V8 处理字符串源码,相当于 eval
var compiledWrapper = vm.runInThisContext(wrapper, {
filename: filename,
lineOffset: 0,
displayErrors: true,
importModuleDynamically: experimentalModules ? async (specifier) => {
if (asyncESM === undefined) lazyLoadESM();
const loader = await asyncESM.loaderPromise;
return loader.import(specifier, normalizeReferrerURL(filename));
} : undefined,
});
//...
// 直接调用包装好的函数,传入需要的参数。
result = compiledWrapper.call(this.exports, this.exports, require, this, filename, dirname);
return result;
}
// makeRequireFunction 定义在 lib/internal/modules/cjs/helpers.js
function makeRequireFunction(mod) {
const Module = mod.constructor;
// 深度机制
function require(path) {
try {
exports.requireDepth += 1;
return mod.require(path);
} finally {
exports.requireDepth -= 1;
}
}
function resolve(request, options) {
validateString(request, 'request');
return Module._resolveFilename(request, mod, false, options);
}
require.resolve = resolve;
function paths(request) {
validateString(request, 'request');
return Module._resolveLookupPaths(request, mod, true);
}
resolve.paths = paths;
require.main = process.mainModule;
// 支持拓展.
require.extensions = Module._extensions;
require.cache = Module._cache;
return require;
}
На этом процесс компиляции и выполнения завершен.На самом деле процесс загрузки файлового модуля, который мы показали выше, относится к процессу загрузки встроенного модуля.Процесс загрузки встроенного модуля в целом похожи.NativeModuleИсходный код определения модуля можно увидеть один или два.
require()
Мы проходим вышеrequireФабричная функция может знать, что вrequire('./index'), на самом деле звонокModule.prototype.require
Module.prototype.require = function(id) {
validateString(id, 'id');
if (id === '') {
throw new ERR_INVALID_ARG_VALUE('id', id, 'must be a non-empty string');
}
return Module._load(id, this, /* isMain */ false);
};
Итак, каждый раз, когда мы выполняемrequireВозвращаемое значение, полученное после этого, на самом деле является тем, что возвращается после выполнения компиляции и загрузки.module.exports.
В течение всего процесса мы прошли через реализацию CommonJS в Node и украли картинку:
Рукописный CommonJS
После ознакомления со всем описанным выше процессом загрузки у нас есть общее представление о реализации CommonJS в Node, поэтому мы можем легко написать упрощенную версию CommonJS:
const path = require('path')
const fs = require('fs')
const vm = require('vm')
// 定义Module
function Module(id){
this.id = id
this.filename = id
this.exports = {}
this.loaded = false
}
// 定义拓展与解析规则
Module._extensions = Object.create(null)
Module._extensions['.json'] = function(module){
return Module.exports = JSON.parse(fs.readFileSync(module.filename, 'utf8'))
}
Module._extensions['.js'] = function(module){
Module._compile(moudle)
}
// 包装函数
Module.wrap = function(script) {
return Module.wrapper[0] + script + Module.wrapper[1];
};
Module.wrapper = [
'(function (exports, require, module, __filename, __dirname) { ',
'\n});'
];
// 编译执行
Module._compile = function(module){
const content = fs.readFileSync(module.filename, 'utf8'), filename = module.filename;
const wrapper = Module.wrap(content)
const compiledWrapper = vm.runInThisContext(wrapper, {
filename: filename,
lineOffset: 0,
displayErrors: true,
})
const result = compiledWrapper.call(module.exports, module.exports, require, module, filename, dirname);
return result
}
// 缓存
Module._cache = Object.create(null)
Module.prototype.load = function(filename){
let extname = path.extname(filename)
Module._extensions[extname](this);
this.loaded = true;
}
// 加载
Module._load = function(filename) {
const cacheModule = Module._cache[filename]
if(cacheModule){
return cacheModule.exports
}
let module = new Module(filename)
Module._cache[filename] = module
module.load(filename)
return module.exports
}
// 简单的路径解析
Module._resolveFilename = function(path) {
let p = path.resolve(path)
if(!/\.\w+$/.test(p)){
let arr = Object.keys(Module._extensions)
arr.forEach(item => {
let file = `${p}${item}`
try{
fs.accessSync(file)
return file
}catch(e){
// ...
}
})
}else{
return p
}
}
// require 函数
function require(path){
const filename = Module._resolveFilename(path)
return Module._load(filename)
}
Ссылаться на
3. История модульного развития JS
4. Процесс модульной веб-интерфейсной разработки
5. Краткая история модульности
6. Определение модуляризации и компонентизации клиентской разработки и взаимосвязь между ними?
7. JavaScript Modular Programming Master (2009-2016)
11. Документация по узлу -- Модули