Расширенный синтаксис JavaScript (модульный), который нельзя пропустить

внешний интерфейс JavaScript

Как мы все знаем, статус js во фронтенд-разработке. Очень важно хорошо этому научиться.

В следующей статье представлена ​​модульность.

Что такое модульность?

Что такое модульность и модульная разработка?

  • На самом деле конечной целью модульной разработки является разделение программы на небольшие структуры.
  • Эта структура пишет свой собственный логический код, имеет свою область видимости и не будет влиять на другие структуры.
  • Эта структура может экспортировать переменные, функции, объекты и т. д., которые она хочет предоставить своей структуре для использования.
  • Вы также можете каким-то образом импортировать переменные, функции, объекты и т. д. в другие структуры.

Вышеупомянутая структура является модулем, а процесс разделения программы разработки в соответствии с этой структурой является процессом модульной разработки.

Модульная история

На заре веб-разработки Брендан Эйх разработал JavaScript только как язык сценариев для простой проверки формы или реализации анимации и т. д. В то время код был еще очень маленьким:

  • В настоящее время нам нужно только написать код JavaScript для
  • Нет необходимости писать в нескольких файлах.

Но с быстрым развитием интерфейса и JavaScript код JavaScript становится все более и более сложным:

  • Появление ajax, разделение front-end и back-end разработки, означает, что после того, как back-end вернет данные, нам нужно отрисовать front-end страницу через JavaScript.
  • С появлением SPA внешние страницы стали более сложными: ряд сложных требований, включая внешнюю маршрутизацию, управление состоянием и т. д., необходимо реализовать с помощью JavaScript.
  • Включая реализацию Node, написание сложных серверных программ на JavaScript, отсутствие модульности — фатальный недостаток.

Поэтому модульность уже является очень острой потребностью в JavaScript. Так ES6 (2015) запустил собственное модульное решение.

До этого для поддержки модуляризации в JavaScript появилось множество различных спецификаций модульности: AMD, CMD, CommonJS и т. д.

Нет проблем с модульностью

  • Например, конфликты имен.

Вышеупомянутая проблема решается выражением немедленного вызова функции (IIFE). Поскольку функции имеют свою собственную область видимости, конфликтов имен между разными файлами не будет.

    // 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

Однако мы фактически принесли новую проблему:

  • Я должен помнить имя возвращаемого объекта в каждом модуле, чтобы правильно использовать его в других модулях.
  • Код сложно писать, и код в каждом файле должен быть написан в анонимной функции.
  • В отсутствие надлежащей спецификации все и каждая компания может называть что-то произвольно или даже иметь одно и то же имя модуля.

Поэтому мы обнаружим, что, хотя модульность достигнута, наша реализация слишком проста и не имеет спецификации.

Нам нужно сформулировать некую спецификацию, чтобы заставить всех писать модульный код в соответствии с этой спецификацией. Эта спецификация должна включать основную функциональность: сам модуль может экспортировать открытые свойства, а модуль может импортировать необходимые ему свойства. Чтобы решить вышеупомянутые проблемы, сообщество JavaScript разработало ряд полезных спецификаций, а затем мы изучим некоторые репрезентативные спецификации.

Спецификация CommonJS и узел

Нам необходимо знать, что CommonJS — это спецификация. Первоначально она была предложена для использования вне браузера и в то время называлась ServerJS. Позже, чтобы отразить ее широту, она была изменена на CommonJS, которую мы также будем называть CJS для короткая.

  • Node — репрезентативная реализация CommonJS на стороне сервера.
  • Browserify — это реализация CommonJS в браузере.
  • Сборщик веб-пакетов имеет поддержку и преобразования для CommonJS.

Таким образом, CommonJS поддерживается и внедряется в Node, что позволяет нам выполнять модульную разработку во время разработки Node.

  • Каждый файл js в Node — это отдельный модуль.
  • Этот модуль включает основные переменные спецификации CommonJS: exports, module.exports и require.
  • Мы можем использовать эти переменные для облегчения модульной разработки.

Ранее мы упоминали, что ядром модульности является экспорт и импорт, реализованный в Node:

  • exports и module.exports могут отвечать за экспорт содержимого модуля.
  • Функция require может помочь нам импортировать контент из других модулей (пользовательские модули, системные модули, модули сторонних библиотек).

Модульность Node.js

CommonJS поддерживается и внедряется в Node, так что мы можем легко выполнять модульную разработку в процессе разработки узла:

  • Каждый файл js в Node — это отдельный модуль.
  • Этот модуль включает основные переменные спецификации 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, то есть модулем.
  • Так что на самом деле для экспорта в Node используется вовсе не экспорт, а module.exports.
  • Потому что модуль является реальным реализатором экспорта.
  • и будет внутреннеexportsназначить наmodule.exports.

Импорт и экспорт этого метода имеет следующие характеристики:

  • Все файлы в Node запускаются внутри функции. можно распечатать поconsole.log(arguments.callee + "")проверять.

image.png

  • Импорт и экспорт является ссылкой на значение.Если экспорт является значением базового типа данных, то файл экспорта изменяет значение, а затем значение переменной в файле импорта не изменяется.
    // 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
  • он через требованиефункцияимпортировать, только ввыполнить js-кодзнать зависимости модулей.
  • Код выполняется синхронно.
  • Модули, импортированные несколько раз, будут загружены только один раз. Внутри каждого модуля будет загружено, чтобы определить, был ли он загружен.
  • Когда вводится цикл кода, модуль загружается в глубину. Потом сначала в ширину.

Ниже приводится подробное введение вrequireИмпорт сведений о

Теперь мы знаем, что require — это функция, которая помогает нам импортировать объекты, экспортированные в файл (модуль).

Итак, каковы правила поиска для require?

Подробные правила поиска смотрите здесь

Здесь я резюмирую наиболее распространенные правила поиска: формат импорта следующий: require(X)

  • X — это основной модуль Node, такой как path, http.
    • Вернитесь прямо к основному модулю и прекратите поиск.
  • Х это./или../или/(корневая директория).
    • Шаг 1: Найдите файл X в соответствующем каталоге.

      • Если есть имя суффикса, найдите соответствующий файл в соответствии с форматом имени суффикса.
      • Если имя суффикса отсутствует, оно будет в следующем порядке:
        • Найти файл X напрямую
        • Найти файлы X.js
        • Найдите файл X.json
        • Найдите файл X.node
    • Шаг 2: Если соответствующий файл не найден, используйте X в качестве каталога.

      • Найдите файл индекса в каталоге
        • Найдите файл X/index.js
        • Найдите файл X/index.json.
        • Найдите файл X/index.node
    • Если не найдено, то сообщается об ошибке: не найдено

  • является X напрямую (без пути), и X не является основным модулем. Затем он будет искать файл node_modules в верхнем каталоге. пока не будет найден корневой каталог. Мы можем распечатать эти каталоги поиска через module.paths. Если ни один из вышеуказанных путей не найден, сообщается об ошибке: не найдено.

Сведения о загрузке модуля

  • Когда модуль впервые представлен, код js в модуле будет запущен один раз.
  • Когда модуль импортируется несколько раз, он кэшируется и в конечном итоге загружается (запускается) только один раз.

Почему только один раз загрузить и запустить?

Это связано с тем, что каждый объектный модуль модуля имеет атрибут: загружен. Если оно ложно, это означает, что оно не было загружено, а если оно истинно, то оно было загружено.

  • Если введены циклы, каков порядок загрузки?

image.pngКак показано выше, Node используеталгоритм поиска в глубину: main -> aaa -> ccc -> ddd -> eee -> bbb

Недостатки спецификации CommonJS

  • Модули загрузки CommonJS синхронны:
    • Синхронный означает, что содержимое текущего модуля не может быть выполнено до тех пор, пока соответствующий модуль не будет загружен.
    • Это не будет проблемой на сервере, потому что файлы js, загружаемые сервером, являются локальными файлами, а скорость загрузки очень высока.

Что, если он был применен к браузеру?

  • Когда браузер загружает файл js, ему необходимо сначала загрузить файл с сервера, а затем загрузить и запустить его.

  • Затем использование синхронного означает, что последующий код js не может нормально работать, даже некоторые простые операции DOM. Поэтому в браузерах мы обычно не используем спецификацию CommonJS. Конечно, использование CommonJS с webpack — это отдельная история. Потому что он превращает наш код в код, который браузер может выполнять напрямую.

Технические характеристики AMD

В первые дни обычно использовались AMD или CMD, чтобы иметь возможность использовать модульность в браузере. Однако, с одной стороны, современные браузеры уже поддерживают ES-модули, а с другой стороны, с помощью таких инструментов, как webpack, можно реализовать преобразование кода CommonJS или ES-модуля. AMD и CMD использовались очень мало, поэтому здесь мы проводим простое пошаговое руководство.

AMD — это в основном модульная спецификация, применяемая к браузерам:

  • AMD — это аббревиатура от определения асинхронного модуля. Он использует асинхронную загрузку модулей.
  • Мы упоминали, что спецификация определяет только то, как должен быть написан код, и может применяться только при наличии конкретной реализации.
  • Наиболее часто используемые библиотеки, реализованные AMD, — это require.js и curl.js.

Использование require.js

  • Загрузите исходный код:Github.com/require js / день ...Найдите в нем файл require.js.
  • Тег script, определяющий HTML, вводит 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 также является модульной спецификацией, применяемой к браузерам:

  • CMD — это аббревиатура от Common Module Definition. Он также использует асинхронную загрузку модулей, но поглощает преимущества CommonJS.
  • Наиболее часто используемой библиотекой, реализованной AMD, является SeaJS.

Использование SeaJS

    // 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

Есть некоторые различия между модулем ES и модульностью CommonJS:

  • С одной стороны, он использует ключевые слова import и export для достижения модульности.
  • С другой стороны, он использует статический анализ во время компиляции, а также добавляет динамические ссылки.
  • export отвечает за экспорт содержимого модуля.
  • import отвечает за импорт содержимого из других модулей.
  • Принятие модуля ES автоматически примет строгий режим: используйте строгий.

основное использование

    // 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-файл загружает файл ввода, вам нужно указать тип как модуль.
  • При открытии html-файла вам нужно открыть локальную службу, а не напрямую открывать и запускать ее в браузере.

image.png Это объясняется на MDN:

  • Вы должны быть осторожны с локальным тестированием — если вы загружаете HTML-файл локально (например, файл с путем file://), вы получите ошибки CORS, как того требует безопасность модуля Javascript.
  • Вам нужно передать сервер для тестирования.

ключевое слово экспорта

Ключевое слово export экспортирует переменные, функции, классы и т. д. в модуль.

Мы хотим экспортировать все остальное содержимое следующими способами:

  • Способ 1. Добавьте ключевое слово экспорта непосредственно перед объявлением инструкции.
    export const name = "zh"
    export const age = 22
  • Способ 2. Поместите все идентификаторы, которые необходимо экспортировать, в конец экспорта.{}середина.Примечание. {} здесь не является расширенной нотацией литералов объектов ES6, а {} не представляет объект. Итак: экспорт {имя: имя} неверен.
    const name = "zh"
    const age = 22
    function foo() {
      console.log("foo function")
    }

    export {
      name,
      age,
      foo
    }
  • Способ 3: Дайте идентификатору псевдоним при экспорте. (В основном бесполезен, обычно используется псевдоним в файле импорта). Затем в файле импорта вы можете использовать только псевдоним, чтобы получить его.
    export {
      name as fName,
      age as fAge,
      foo as fFoo
    }

ключевое слово импорта

Ключевое слово import отвечает за импорт контента из другого модуля.

Существует также несколько способов импорта контента:

  • Способ 1: импортировать {список идентификаторов} из «модуля».Примечание: {} здесь не является объектом, он только хранит содержимое импортированного списка идентификаторов.
    import { name, age } from "./foo.js"
  • Способ 2: Псевдоним идентификатора при импорте.
    import { name as fName, age as fAge } from './foo.js'
  • Способ 3: пройти*Поместите функцию модуля в объект функции модуля (объект модуля). Затем используйте его с псевдонимом.
    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'

почему ты хочешь сделать это?

При разработке и инкапсуляции функциональной библиотеки мы обычно хотим поместить все открытые интерфейсы в один файл.Это позволяет легко задавать унифицированную спецификацию интерфейса и читать ее. В настоящее время мы можем использовать комбинацию экспорта и импорта.

использование по умолчанию

Все функции экспорта, которые мы изучили ранее, называются экспортами:

  • Имя было указано при экспорте.
  • Необходимо знать конкретное имя при импорте import.

Существует также экспорт, называемый экспортом по умолчанию (экспорт по умолчанию).

    // 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
  • При экспорте по умолчанию имя указывать не нужно.
  • Вам не нужно использовать {} при импорте, и вы можете указать имя самостоятельно.
  • Это также облегчает нашу совместимость с существующими спецификациями, такими как CommonJS.

Примечание. В модуле может быть только один экспорт по умолчанию.

функция импорта

Загрузка модуля через импорт не может быть размещена в логическом коде, например:

    if(true) {
        import foo from './foo.js'
    }

Почему это происходит?

  • Это связано с тем, что модуль ES должен знать свои зависимости, когда он анализируется механизмом JS.
  • Поскольку код js в настоящее время не выполняется, невозможно выполнить аналогичную оценку if в соответствии с выполнением кода.

Но в некоторых случаях нам действительно нужно динамически загружать модуль:

  • Если в соответствии с различными условиями, динамически выберите путь для загрузки модуля.

В настоящее время нам нужно использовать функцию import() для динамической загрузки. Результатом, возвращаемым функцией импорта, является обещание.

    import("./foo.js").then(res => {
      console.log("res:", res.name)
    })

es11 добавил новое свойство. Само мета-свойство также является объектом: { url: "путь, где находится текущий модуль" }

    console.log(import.meta)

Процесс анализа модуля ES

Как модуль ES анализируется браузерами и позволяет модулям ссылаться друг на друга?

Пожалуйста, обратитесь к этой статье для деталей

Процесс парсинга модуля ES можно разделить на три этапа:

  • Этап 1: Создание, найдите файл js по адресу, загрузите его и разберите в запись модуля.
  • Этап 2: создание экземпляра, создание экземпляра записи модуля, выделение памяти, анализ операторов импорта и экспорта модуля и указание модулю соответствующего адреса памяти.
  • Фаза 3: Оценка, запуск кода, вычисление значения и заполнение адреса памяти значением.

image.pngПервый этап:image.pngВторой и третий этапы:image.png Таким образом, из приведенного выше видно, что изменение значения переменной в файле экспорта повлияет на значение в файле импорта. Кроме того, файлу импорта запрещено изменять значения файла экспорта.

Разница между модулем es6 и common.js