Вы действительно понимаете модульность? Научить вас реализации CommonJS

Node.js внешний интерфейс JavaScript V8
Вы действительно понимаете модульность? Научить вас реализации CommonJS

Вы действительно понимаете модульность?

Углубитесь в учебу, займите центральное место, скорее прекрасное, чем разное, скорее особенное, чем слишком много. —— Чжоу Эньлай

Краткая история модулей

  • В первые дни 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
}