Как разработчик внешнего интерфейса, вы когда-нибудь задумывались, почему вы можете использовать его прямо в своем коде?require
Метод загружает модуль, почему Node знает, какой файл выбрать в качестве записи при загрузке стороннего пакета, и часто спрашивают, почему модуль ES6 имеет эффект [тип ссылки] при экспорте базовых типов данных?
С этими вопросами и любопытством, я надеюсь, что чтение этой статьи может развеять ваши сомнения.
Спецификация CommonJS
До ES6 ECMAScript не предоставлял возможности организации кода. В то время он обычно основывался на IIFE для достижения «модульности». С другой стороны, исходная спецификация модуля на стороне браузера не подходила для крупномасштабных приложений. Так раноСпецификация CommonJS, целью которого является предоставление общего способа организации модулей для определения модулей.
Определение и использование модуля
В Commonjs файл — это модуль. Определите модуль для экспорта черезexports
илиmodule.exports
Просто смонтируйте его.
exports.count = 1;
Импорт модуля также прост,require
Получить соответствующий модульexports
объект.
const counter = require('./counter');
console.log(counter.count);
CommonJS
Модули в основном состоят из нативных модулейmodule
Для этого некоторые свойства этого класса очень полезны для понимания механизма модуля.
Module {
id: '.', // 如果是 mainModule id 固定为 '.',如果不是则为模块绝对路径
exports: {}, // 模块最终 exports
filename: '/absolute/path/to/entry.js', // 当前模块的绝对路径
loaded: false, // 模块是否已加载完毕
children: [], // 被该模块引用的模块
parent: '', // 第一个引用该模块的模块
paths: [ // 模块的搜索路径
'/absolute/path/to/node_modules',
'/absolute/path/node_modules',
'/absolute/node_modules',
'/node_modules'
]
}
Требуется прийти от?
При написании модулей CommonJS мы используемrequire
чтобы загрузить модуль, используйтеexports
сделать вывод модуля, а такжеmodule
,__filename
, __dirname
Эти переменные, почему их не нужно вводить?
vm.runInThisContext
преобразовать строку вFunction
Сформируйте область, чтобы избежать глобального загрязнения.
let wrap = function(script) {
return Module.wrapper[0] + script + Module.wrapper[1];
};
const wrapper = [
'(function (exports, require, module, __filename, __dirname) { ',
'\n});'
];
Поэтому в модуле CommonJS вы можете напрямую обращаться к этим методам и переменным, не требуя их.
в параметреmodule
текущего модуляmodule
Пример (хотя код модуля на данный момент не скомпилирован и не выполнен),exports
даmodule.exports
псевдонимы, которые в итогеrequire
когда выходmodule.exports
ценность .require
Последний звонок такжеModule._load
метод.__filename
,__dirname
Это абсолютный путь и путь к текущей папке текущего модуля в системе соответственно.
процесс поиска модуля
Разработчикам очень просто использовать require, но на самом деле для того, чтобы учесть разные способы написания, разные типы модулей,node_modules
Процесс поиска таких модулей, как пакеты, немного громоздкий.
Во-первых, при создании объекта модуля будет свойство paths, значение которого вычисляется из текущего пути к файлу, из текущего каталога в корневой каталог системы.node_modules
. можно распечатать в модулеmodule.paths
посмотри.
[
'/Users/evan/Desktop/demo/node_modules',
'/Users/evan/Desktop/node_modules',
'/Users/evan/node_modules',
'/Users/node_modules',
'/node_modules'
]
В дополнение к этому также просматривается глобальный путь (если он существует)
[
execPath/../../lib/node_modules, // 当前 node 执行文件相对路径下的 lib/node_modules
NODE_PATH, // 全局变量 NODE_PATH
HOME/.node_modules, // HOME 目录下的 .node_module
HOME/.node_libraries' // HOME 目录下的 .node-libraries
]
Согласно официальной документацииНайти процессЭто уже достаточно подробно, здесь дан только общий процесс.
从 Y 路径运行 require(X)
1. 如果 X 是内置模块(比如 require('http'))
a. 返回该模块。
b. 不再继续执行。
2. 如果 X 是以 '/' 开头、
a. 设置 Y 为 '/'
3. 如果 X 是以 './' 或 '/' 或 '../' 开头
a. 依次尝试加载文件,如果找到则不再执行
- (Y + X)
- (Y + X).js
- (Y + X).json
- (Y + X).node
b. 依次尝试加载目录,如果找到则不再执行
- (Y + X + package.json 中的 main 字段).js
- (Y + X + package.json 中的 main 字段).json
- (Y + X + package.json 中的 main 字段).node
c. 抛出 "not found"
4. 遍历 module paths 查找,如果找到则不再执行
5. 抛出 "not found"
Процесс поиска модуля заменит программную ссылку реальным путем в системе, например.lib/foo/node_moduels/bar
мягкая ссылка наlib/bar
,bar
в упаковкеrequire('quux')
, и, наконец, запуститьfoo
модуль,require('quux')
Путь поискаlib/bar/node_moduels/quux
вместоlib/foo/node_moduels/quux
.
Связано с загрузкой модуля
MainModule
при бегеnode index.js
, Node вызывает статический метод класса Module_load(process.argv[1])
Загрузите этот модуль, отметьте его как основной модуль и назначьте егоprocess.mainModule
а такжеrequire.main
, вы можете использовать эти два поля, чтобы определить, является ли текущий модуль основным илиrequire
Войдите.
CommonJS
Нормой является загрузка модулей синхронно и блокно во время выполнения кода, встречаяrequire(X)
Он остановится и будет ждать загрузки нового модуля, прежде чем продолжить выполнение следующего кода.
Несмотря на то, что он является синхронным обструктивным, этот шаг на самом деле очень быстрый, и браузер блокируется, анализируется, выполняется.js
Файлы — это не класс, и чтение файлов с жесткого диска происходит намного быстрее, чем сетевые запросы.
Кэширование и циклические ссылки
Поиск файлового модуля занимает много времени. Если вам нужно каждый раз заново проходить поиск по папке, производительность будет низкой, а в фактической разработке модуль может содержатьпобочный эффекткод, например, выполняемый на верхнем уровне модуляaddEventListener
, могут возникнуть проблемы, если процесс требуется, повторяется несколько раз.
CommonJS
Кэширование может решить проблему повторного поиска и повторного выполнения. В процессе загрузки модуля абсолютный путь модуля будет использоваться какkey
, module
объектvalue
записыватьcache
. При чтении модуля приоритет будет определять, находится ли он уже в кеше, если да, возвращаться напрямуюmodule.exports
; Если нет, то войдет в процесс поиска модуля, а потом напишет после нахождения модуляcache
.
// a.js
module.exports = {
foo: 1,
};
// main.js
const a1 = require('./a.js');
a1.foo = 2;
const a2 = require('./a.js');
console.log(a2.foo); // 2
console.log(a1 === a2); // true
В приведенном выше примере,require a.js
и изменитьfoo
свойства, затем сноваrequire a.js
можно увидеть дваждыrequire
Результат тот же.
Кэш модуля можно распечататьrequire.cache
смотреть.
{
'/Users/evan/Desktop/demo/main.js':
Module {
id: '.',
exports: {},
parent: null,
filename: '/Users/evan/Desktop/demo/main.js',
loaded: false,
children: [ [Object] ],
paths:
[ '/Users/evan/Desktop/demo/node_modules',
'/Users/evan/Desktop/node_modules',
'/Users/evan/node_modules',
'/Users/node_modules',
'/node_modules'
]
},
'/Users/evan/Desktop/demo/a.js':
Module {
id: '/Users/evan/Desktop/demo/a.js',
exports: { foo: 1 },
parent:
Module {
id: '.',
exports: {},
parent: null,
filename: '/Users/evan/Desktop/demo/main.js',
loaded: false,
children: [Array],
paths: [Array] },
filename: '/Users/evan/Desktop/demo/a.js',
loaded: true,
children: [],
paths:
[ '/Users/evan/Desktop/demo/node_modules',
'/Users/evan/Desktop/node_modules',
'/Users/evan/node_modules',
'/Users/node_modules',
'/node_modules' ] } }
Кэширование также решает проблему циклических ссылок. Например, для модуля а теперь требуется модуль b, а для модуля b требуется модуль а.
// main.js
const a = require('./a');
console.log('in main, a.a1 = %j, a.a2 = %j', a.a1, a.a2);
// a.js
exports.a1 = true;
const b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.a2 = true;
// b.js
const a = require('./a.js');
console.log('in b, a.a1 = %j, a.a2 = %j', a.a1, a.a2);
Результат выполнения программы следующий:
in b, a.a1 = true, a.a2 = undefined
in main, a.a1 = true, a.a2 = true
На самом деле экземпляр Module был создан и записан в кеш до выполнения кода модуля а. В это время код еще не выполнен, а экспортыпустой объект.
'/Users/evan/Desktop/module/a.js':
Module {
exports: {},
//...
}
}
кодexports.a1 = true;
отредактированоmodule.exports
Вверхa1
дляtrue
, В этот моментa2
Код еще не выполнен.
'/Users/evan/Desktop/module/a.js':
Module {
exports: {
a1: true
}
//...
}
}
Войтиb
модуль,require a.js
Когда обнаруживается, что он уже существует в кэше, получаемa
на модулеexports
. Распечататьa1, a2
соответственноtrue
,а такжеundefined
.
закончитьсяb
модуль, продолжитьa
оставшийся код модуля,exports.a2 = true;
еще разexports
объект добавленa2
свойства, в настоящее времяmodule a
изexport
объектa1, a2
обеtrue
.
exports: {
a1: true,
a2: true
}
назад сноваmain
модуль, из-заrequire('./a.js')
получить этоmodule a
export
Ссылка на объект, на этот раз печатаетa1, a2
все дляtrue
.
резюме:
CommonJS
Процесс загрузки модуля представляет собой синхронную загрузку с блокировкой, которая записывается до запуска кода модуля.cache
, один и тот же модуль вызывается несколько разrequire
будет выполняться только один раз, повторяетсяrequire
получить то же самоеexports
Цитировать.
Стоит отметить: cache key
Используется абсолютное положение модуля в системе, т.к.Разница в том, где вызывается модуль, идентичныйrequire('foo')
Код не гарантирует, что будет возвращена единственная ссылка на объект. Я случайно сталкивался с этим раньше,require('яйцо-ядро') дважды, но они не равны.
Модули ES6
ES6
Модули — более привычный способ для студентов, изучающих фронтенд-разработку.import
, export
Ключевые слова для ввода и вывода модуля.ES6
Вместо использования замыканий и инкапсуляции функций для модуляризации модульность обеспечивается на синтаксическом уровне.
ES6
Модуль не существуетrequire
, module.exports
, __filename
и другие переменные,CommonJS
также нельзя использовать вimport
. Две спецификации несовместимы, обычно пишутся в будние дни.ES6
Код модуля в конечном итоге пройдетBabel
, Typescript
инструменты перерабатываются вCommonJS
код.
использоватьNode
РоднойES6
модуль долженjs
Суффикс файла изменен наmjs
,илиpackage.json
Поле «тип» изменено на «модуль», что сообщаетNode
использоватьES Module
Загрузите модуль в виде .
Процесс загрузки модуля ES6
Процесс загрузки модулей ES6 делится на три этапа:
1. Найдите, загрузите, проанализируйте и соберите все экземпляры модуля.
Модули ES6 найдут все модули в соответствии с отношением модулей до запуска программы, сгенерируют ациклический граф отношений и создадут все экземпляры модуля.Этот метод, естественно, позволяет избежать проблемы циклических ссылок.Конечно, существуют также кеши загрузки модулей.Если вы повторно импортировать один и тот же модуль, код будет выполнен только один раз.
2. Освободите место в памяти для экспортируемого содержимого (значение экспорта еще не записано). Затем сделайте так, чтобы импорт и экспорт указывали на эти пространства в памяти, этот процесс также называется связыванием.
Работа, проделанная на этом шаге, равнаliving binding import export
, с помощью следующих примеров, чтобы помочь понять.
// counter.js
let count = 1;
function increment () {
count++;
}
module.exports = {
count,
increment
}
// main.js
const counter = require('counter.cjs');
counter.increment();
console.log(counter.count); // 1
надCommonJS
Результаты выполнения примера хорошо понятны, модифицируйтеcount++
Модификация — это базовая переменная типа данных в модуле, которая не изменится.exports.count
, поэтому результат печати считает, что1
.
// counter.mjs
export let count = 1;
export function increment () {
count++;
}
// main.mjs
import { increment, count } from './counter.mjs'
increment();
console.log(count); // 2
Используйте результатыES6
написание модуля, когдаexport
Когда переменная изменяется, это повлияетimport
результат. Реализация этой функции естьliving binding
, вы можете временно игнорировать то, как конкретная спецификация реализована внизу, но знайтеliving binding
Лучше понять, чем то, что онлайн-статья описывает как «модули ES6 выводят ссылки на значения».
БлижеES6
модульныйCommonJS
Код может быть следующим:
exports.counter = 1;
exports.increment = function () {
exports.counter++;
}
3. Запустите код модуля, чтобы заполнить фактическое значение переменной в пространстве, сгенерированном на втором шаге.
На третьем шаге будет заполнено значение обхода в глубину на основе ациклического графа, сгенерированного на первом шаге, если процесс еще не посетил пространство, инициализация завершена, он выдаст исключение.
// a.mjs
export const a1 = true;
import * as b from './b.mjs';
export const a2 = true;
// b.mjs
import { a1, a2 } from './a.mjs'
console.log(a1, a2);
Приведенный выше пример вызовет исключение во время выполнения.ReferenceError: Cannot access 'a1' before initialization
. Если изменить наimport * as a from 'a.mjs'
можно увидетьa
модульexport
// b.mjs
import * as a from './a.mjs'
console.log(a);
Выход{ a1: <uninitialized>, a2: <uninitialized> }
Видно, что модуль ES6 зарезервировал место для экспортируемой переменной, но еще не присвоил ей значение. здесь иCommonJS
Разные,CommonJS
вот знатьa1
дляtrue
, a2
дляundefined
В дополнение к этому мы также можем получить некоторые модули ES6 иCommonJS
Точка разницы:
-
CommonJS
Переменные могут использоваться для запроса во время выполнения, например.require(path.join('xxxx', 'xxx.js'))
, в то время как статическийimport
грамматика (и动态 import
,вернутьPromise
) не будет работать, потому что модули ES6 разрешают все модули перед выполнением кода.
-
require
завершитexports
импорт объекта,import
можно толькоimport
Часть необходимого контента, поэтому используйтеTree Shaking
Вы должны использовать нотацию модуля ES6. -
import
другого модуля нетexport
переменная, перед выполнением кода будет сообщено об ошибке, иCommonJS
Ошибка сообщается, когда модуль работает.
Почему я могу смешивать и писать в обычном развитии?
упомянутый ранееES6
модули иCommonJS
Модули очень разные и не могут быть смешаны напрямую. Это отличается от производительности в процессе разработки, потому что модули ES6, написанные в процессе разработки, в конечном итоге будут обработаны инструментами упаковки вCommonJS
модули, чтобы быть совместимыми с большим количеством сред, а также с текущим общим сообществомCommonJS
Слияние модулей.
В процессе преобразования может возникнуть некоторая путаница, например:
__esModule
что это такое? Для чего это?
При использовании инструментов преобразования для обработки модулей ES6 вы часто видите, что они появляются после упаковки.__esModule
свойство, буквально обозначая его какES6 Module
. Эта переменная предназначена для облегчения обработки при обращении к модулям.
Например, в модулях ES6export default
в превращении вCommonJS
будет установлен наexports['default']
Горит во время работыrequire('./a.js')
Когда вы не можете читать напрямуюdefault
на значение, чтобы и ES6import a from './a.js'
поведение последовательное, основанное на__esModule
Обработка приговора.
// a.js
export default 1;
// main.js
import a from './a';
console.log(a);
после преобразования
// a.js
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = 1;
// main.js
'use strict';
var _a = require('./a');
var _a2 = _interopRequireDefault(_a);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
console.log(_a2.default);
a
модульexport defualt
будет преобразован вexports.default = 1;
, который также обычно используется при разработке фронтенд-проектов.require
Зачем тебе еще нужно.default
чтобы получить целевое значение.
Затем при бегеimport a from './a.js'
час,es module
Ожидается возвращениеexport
Содержание. Инструмент преобразует код в_interopRequireDefault
пакет, по которому можно судить о том,esModule
, если да, вернуться напрямую, если даcommonjs
Если модуль завернут в слой{default: obj}
, когда значение a будет окончательно получено, оно также будет заменено на_a1.default
.