Вы действительно понимаете модульность?
Углубитесь в учебу, займите центральное место, скорее прекрасное, чем разное, скорее особенное, чем слишком много. —— Чжоу Эньлай
Краткая история модулей
- В первые дни JavaScript часто писался как язык сценариев, встроенный в HTML-страницы для управления анимацией и простым взаимодействием с пользователем.
<!--html-->
<script type="application/javascript">
// module1 code
// module2 code
</script>
-
Все объекты JavaScript, встроенные в веб-страницы, используют глобальный
window
объект для хранения неиспользуемыхvar
определенная переменная. Это приводит к проблеме, заключающейся в том, что последняя вызываемая функция или переменная зависит от порядка, в котором мы ее ввели. -
模块化时代
.随着单页应用与富客户端的流行,不断增长的代码库也急需合理的代码分割与依赖管理的解决方案,这也就是我们在软件工程领域所熟悉的模块化(Modularity)
. -
Непосредственно определенные зависимости, шаблон пространства имен, шаблон модуля, определения отдельных зависимостей, песочница, внедрение зависимостей, CommonJS, AMD, UMD, помеченные модули, YModules, модули ES 2015. Это продукты модульной эпохи.
-
Вот проблема,
过度
Фрагментированные модули также приведут к потере производительности и увеличению размера пакета, включая загрузку модуля и анализ модуля, потому что Webpack и другие инструменты упаковки слишком сильно оборачивают модуль.IIFE
Сбои оптимизации движка JavaScript, вызванные функциями и т. д.
Так что же такое модульность?
В двух словах, модульность — это разделение большой функции на куски, каждый из которых
独立
Вам не нужно беспокоиться污染
глобальная переменная с именем冲突
Какой.
выгода
- Функция пакета
- охватывающая область
- Может решить проблемы с зависимостями
- Более высокая производительность, облегчение реконструкции
- разрешать конфликты имен
- ...
Есть ли у js модульность?
- JS не имеет модульной системы и не поддерживает
封闭
Управление областью действия и зависимостями - нет стандартной библиотеки, нет файловой системы и
IO流
API - нет системы управления пакетами
Как реализовать модульность JS?
- Спецификация CommonJS, узел находится в
v8引擎
Среда выполнения javascript на сервере, как сервер, не может иметь функцию модульности, поэтому была создана спецификация CommonJS, а текущий узел использует CommonJS2. Различия между CommonJS2 и CommonJS1 также приведены ниже. принадлежать动态同步加载
.
// CommonJS2也可以通过这种方式导出
module.exports = {
a: 1
}
// CommonJS1只能通过这种方式
exports.a = 1
// b.js
var module = require('./a.js')
module.a // -> log 1
- AMD && CMD. AMD это
RequireJS
предлагается, в основном依赖前置
. CMD этоSeaJS
提出的,主要是就近依赖(只要用到才会导入),两者用法接近。 принадлежать异步加载
.
// file lib/greeting.js
define(function() {
var helloInLang = {
en: 'Hello world!',
es: '¡Hola mundo!',
ru: 'Привет мир!'
};
return {
sayHello: function (lang) {
return helloInLang[lang];
}
};
});
// file hello.js
define(['./lib/greeting'], function(greeting) {
var phrase = greeting.sayHello('en');
document.write(phrase);
});
- УМД. Поскольку CommonJS нельзя использовать в AMD, был разработан UMD, который может использовать как AMD, так и CommonJS в UMD.
(function(define) {
define(function () {
var helloInLang = 'hello';
return {
sayHello: function (lang) {
return helloInLang[lang];
}
};
});
}(
typeof module === 'object' && module.exports && typeof define !== 'function' ?
function (factory) { module.exports = factory(); } :
define
));
Реализация CommonJS
- Прежде всего, CommonJS, о котором мы здесь говорим, — это CommonJS2, и нам нужно понять его характеристики.
- будет найден при ссылке на модуль
绝对路径
- После загрузки модуля появится
缓存
, возьмите имя файла в качестве ключа и модуль в качестве значения - узел является модульным дополнением замыкания, и от реализации этого
闭包(runInThisContext)
- Когда модуль загружается,
同步
действовать - суффикс будет добавлен по умолчанию
js
,json
,... - Переменные в разных модулях не будут взаимодействовать друг с другом
冲突
Реализация замыкания (по сути, каждый модуль в CommonJS является замыканием, поэтому переменные внутри не влияют друг на друга)
- Мы можем создать проект arguments.js в vscode
//arguments就是参数列表
console.log(arguments)
- На данный момент, когда файл выполняется в среде узла, выход будет следующим образом
{ '0': {},
'1':
{ [Function: require]
resolve: { [Function: resolve] paths: [Function: paths] },
main:
Module {
id: '.',
exports: {},
parent: null,
filename: '/Users/chenxufeng/Desktop/笔记/node/arguments.js',
loaded: false,
children: [],
paths: [Array] },
extensions: { '.js': [Function], '.json': [Function], '.node': [Function] },
cache: { '/Users/chenxufeng/Desktop/笔记/node/arguments.js': [Object] } },
'2':
Module {
id: '.',
exports: {},
parent: null,
filename: '/Users/chenxufeng/Desktop/笔记/node/arguments.js',
loaded: false,
children: [],
paths:
[ '/Users/chenxufeng/Desktop/笔记/node/node_modules',
'/Users/chenxufeng/Desktop/笔记/node_modules',
'/Users/chenxufeng/Desktop/node_modules',
'/Users/chenxufeng/node_modules',
'/Users/node_modules',
'/node_modules' ] },
'3': '/Users/chenxufeng/Desktop/笔记/node/arguments.js',
'4': '/Users/chenxufeng/Desktop/笔记/node' }
- На самом деле такой слой замыкания обернут снаружи каждого модуля, поэтому значение module.exports может быть получено только внешним запросом
//exports内存中指向的就是module.exports指向的那块空间
//require一个方法
//Module模块类
//__filename该文件绝对路径
//__dirname该文件父文件夹的绝对路径
(function(exports,require,Module,__filename,__dirname){
module.exports = exports = this = {}
//文件中的所有代码
//不能改变exports指向,因为返回的是module.exports,所以是个{}
return module.exports
})
Поэтому, когда мы требуем, это фактически эквивалентно выполнению такого закрытия, и тогда возвращается наш модуль.
Что такое требование?
- Каждый модуль будет иметь требуемый метод
- Динамическая загрузка (v8 не будет загружать этот модуль до этого шага)
- Разные категории модулей имеют разные методы загрузки.Как правило, есть три общих суффикса.
- Файлы сценариев JavaScript с суффиксом .js должны быть считаны в память перед запуском.
- Файл JSON с суффиксом .json, считанный в память с помощью fs и преобразованный в объект JSON.
- Скомпилированный двоичный файл модуля расширения C/C++ с суффиксом .node можно использовать напрямую.
- Найти сторонние модули
- Если функция require указывает только имя, считается, что файл загружается из узла node_modules, так что вы можете перемещать модуль, не изменяя указанный путь к модулю.
- Пути запросов для сторонних модулей включают module.paths и глобальный каталог.
блок-схема
Код
Ниже я объясню шаг за шагом
require
реализация всего
Узнать, есть ли кеш на основе пути
//require方法
function req(moduleId){
//解析绝对路径的方法,返回一个绝对路径
let p = Module._resolveFileName(moduleId)
//查看是否有缓存
if(Module._catcheModule[p]){
//有缓存直接返回对应模块的exports
return Module._catcheModule[p].exports
}
//没有缓存就生成一个
let module = new Module(p)
//把他放入缓存中
Module._catcheModule[p] = module
//加载模块
module.exports = module.load(p)
return module.exports
}
Выше много способов, не торопитесь, будем реализовывать потихоньку
СоздайтеModule类
, и добавить_resolveFileName
а также_catcheModule
//node原生的模块,用来读写文件(fileSystem)
let fs = require('fs')
//node原生的模块,用来解析文件路径
let path = require('path')
//Module类,就相当于我们的模块(因为node环境不支持es6的class,这里用function)
function Module(p){
//当前模块的标识
this.id = p
//没个模块都有一个exports属性
this.exports = {}
//这个模块默认没有加载完
this.loaded = false
//模块加载方法(这个我们到时候再实现)
this.load = function(filepath){
//判断文件是json还是 node还是js
let ext = path.extname(filepath)
//返回一个exports
return Module._extensions[ext](this)
}
}
//以绝对路径为key存储一个module
Module._catcheModule = {}
// 解析绝对路径的方法,返回一个绝对路径
Module._resolveFileName = function(moduleId){
//获取moduleId的绝对路径
let p = path.resolve(moduleId)
try{
//同步地测试 path 指定的文件或目录的用户权限
fs.accessSync(p)
return p
}catch(e){
console.log(e)
}
}
В этот момент будет проблема, если мы не передаем суффикс файла, он не будет читаться
Добавить один в модуль加载策略
, И в_resolveFileName
добавить что-то к этому
//所有的加载策略
Module._extensions = {
'.js': function(module){
//每个文件的加载逻辑不一样,这个我们后面再写
},
'.json': function(module){
},
'.node': 'xxx',
}
Module._resolveFileName = function(moduleId){
//对象中所有的key做成一个数组[]
let arr = Object.keys(Module._extensions)
for(let i=0;i<arr.length;i++){
let file = p+arr[i]
//因为整个模块读取是个同步过程,所以得用sync,这里判断有没有这个文件存在
try{
fs.accessSync(file)
return p
}catch(e){
console.log(e)
}
}
}
На этом этапе мы можем найти абсолютный путь к файлу и передать его экземпляру модуля на
load
метод
реализация метода загрузки
//node原生的模块,用来读写文件(fileSystem)
let fs = require('fs')
//node原生的模块,用来解析文件路径
let path = require('path')
//提供了一系列 API 用于在 V8 虚拟机环境中编译和运行代码。
let vm = require('vm')
//Module类,就相当于我们的模块(因为node环境不支持es6的class,这里用function)
function Module(p){
//当前模块的标识
this.id = p
//没个模块都有一个exports属性
this.exports = {}
//这个模块默认没有加载完
this.loaded = false
//模块加载方法
this.load = function(filepath){
//判断文件后缀是json还是 node还是js
let ext = path.extname(filepath)
return Module._extensions[ext](this)
}
}
//js文件加载的包装类
Module._wrapper = ['(function(exports,require,module,__dirname,__filename){','\n})']
//所有的加载策略
Module._extensions = {
//这里的module参数是就是Module的实例
'.js': function(module){
let fn = Module._wrapper[0] + fs.readFileSync(module.id,'utf8') + Module._wrapper[1]
//执行包装后的方法 把js文件中的导出引入module的exports中
//模块中的this === module.exports === {} exports也只是module.exports的别名
//runInThisContext:虚拟机会产生一个干净的作用域来跑其中的代码,类似于沙箱sandbox
vm.runInThisContext(fn).call(module.exports,module.exports,req,module)
return module.exports
},
'.json': function(module){
//同步读取文件中的内容并把它转为JSON对象
return JSON.parse(fs.readFileSync(module.id,'utf8'))
},
'.node': 'xxx',
}
На этом наш код готов.
- Давайте попробуем случайный файл.Конечно, если он находится под vscode, параметр пути req должен быть в корневом каталоге, который является ямой.
- Если это vscode, вы можете перейти к следующему плагину
Code Runner
, вы можете запустить файл js напрямую, щелкнув правой кнопкой мыши vscode в среде узла. - Давайте поэкспериментируем с предыдущими arguments.js
- Успешный выход! !
полный код
//node原生的模块,用来读写文件(fileSystem)
let fs = require('fs')
//node原生的模块,用来解析文件路径
let path = require('path')
//提供了一系列 API 用于在 V8 虚拟机环境中编译和运行代码。
let vm = require('vm')
//Module类,就相当于我们的模块(因为node环境不支持es6的class,这里用function)
function Module(p){
//当前模块的标识
this.id = p
//没个模块都有一个exports属性
this.exports = {}
//这个模块默认没有加载完
this.loaded = false
//模块加载方法
this.load = function(filepath){
//判断文件是json还是 node还是js
let ext = path.extname(filepath)
return Module._extensions[ext](this)
}
}
//js文件加载的包装类
Module._wrapper = ['(function(exports,require,module,__dirname,__filename){','\n})']
//所有的加载策略
Module._extensions = {
'.js': function(module){
let fn = Module._wrapper[0] + fs.readFileSync(module.id,'utf8') + Module._wrapper[1]
//执行包装后的方法 把js文件中的导出引入module的exports中
//模块中的this === module.exports === {} exports也只是module.exports的别名
vm.runInThisContext(fn).call(module.exports,module.exports,req,module)
return module.exports
},
'.json': function(module){
return JSON.parse(fs.readFileSync(module.id,'utf8'))
},
'.node': 'xxx',
}
//以绝对路径为key存储一个module
Module._catcheModule = {}
// 解析绝对路径的方法,返回一个绝对路径
Module._resolveFileName = function(moduleId){
let p = path.resolve(moduleId)
try{
fs.accessSync(p)
return p
}catch(e){
console.log(e)
}
//对象中所有的key做成一个数组[]
let arr = Object.keys(Module._extensions)
for(let i=0;i<arr.length;i++){
let file = p+arr[i]
//因为整个模块读取是个同步过程,所以得用sync,这里判断有没有这个文件存在
try{
fs.accessSync(file)
return file
}catch(e){
console.log(e)
}
}
}
//require方法
function req(moduleId){
let p = Module._resolveFileName(moduleId)
if(Module._catcheModule[p]){
//模块已存在
return Module._catcheModule[p].exports
}
//没有缓存就生成一个
let module = new Module(p)
Module._catcheModule[p] = module
//加载模块
module.exports = module.load(p)
return module.exports
}