Краткое изложение основ JavaScript

JavaScript
Краткое изложение основ JavaScript

1. Введение в JavaScript

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

  • Сегодня JavaScript является наиболее широко используемым языком браузера, полностью интегрированным с HTML/CSS.

  • Есть много других языков, которые можно «скомпилировать» в JavaScript, и эти языки предлагают еще больше функциональных возможностей. Рекомендуется хорошо понимать эти языки, по крайней мере, после того, как вы освоите JavaScript.

2. Переменные

Мы можем объявлять переменные, используя var, let или const для хранения данных.

  • let — Современный способ объявления переменных.

  • var — Старый способ объявления переменных. В общем, больше не пользуемся. Тем не менее, мы рассмотрим нюансы var и добавим старую главу о var на случай, если она вам понадобится.

  • const — Аналогично let, но значение переменной нельзя изменить.

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

3. Типы данных

В JavaScript существует восемь основных типов данных.

  • число используется для любого типа числа: целое число или число с плавающей запятой, целое число в диапазоне ±(253-1).

  • bigint используется для целых чисел произвольной длины.

  • строка для строк: строка может содержать 0 или более символов, поэтому отдельного односимвольного типа не существует.

  • логическое значение для истинного и ложного.

  • null используется для неизвестных значений — независимых типов только с одним нулевым значением.

  • undefined используется для неопределенных значений — независимый тип только с одним неопределенным значением.

  • символ используется для уникальных идентификаторов.

  • Объект используется для более сложных структур данных.

Мы можем увидеть тип данных, хранящихся в переменной, с помощью оператора typeof.

  • Две формы: typeof x или typeof(x).

  • Возвращает имя типа в виде строки, например "string".

  • typeof null возвращает «объект» — ошибка в языке программирования JavaScript, который на самом деле не является объектом.

4. Преобразование типов

Существует три распространенных преобразования типов: преобразование в строковый тип, преобразование в числовой тип и преобразование в логический тип.

Преобразования строк — преобразования происходят при выводе контента, или явные преобразования могут быть выполнены с помощью String(value) . Преобразования строковых типов значений примитивного типа обычно очевидны.

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

Числовые преобразования следуют этим правилам:

Булевы преобразования подчиняются следующим правилам: Булевы преобразования — преобразования происходят при выполнении логических операций, а также могут быть явно преобразованы через логическое значение (значение).

Числовое преобразование результатов undefined в NaN, а не в 0. Большинство приведенных выше правил легко понять и запомнить. Известные примеры распространенных ошибок, которые допускают люди, следующие:

  • Вывод верен для логических преобразований "0" и строк только с пробелами (например: " ").

5. Сравнение значений

  • Операторы сравнения всегда возвращают логические значения.

  • Сравнение строк сравнивает размер символ за символом в «лексикографическом» порядке.

  • При сравнении значений разных типов они конвертируются в числа (исключая строгие проверки на равенство) перед сравнением.

  • При нестрогом равенстве == значения null и undefined равны, и каждое из них не равно никакому другому значению.

  • При использовании > или

6. Нулевой оператор объединения '??'

  • Оператор консолидации null обеспечивает простой способ выбора первого «определенного» значения из списка.

Он используется для присвоения значений по умолчанию переменным:

// 当 height 的值为 null 或 undefined 时,将 height 的值设置为 100
height = height ?? 100;
  • У оператора ?? очень низкий приоритет, лишь немного выше, чем у ? и =, поэтому рассмотрите возможность добавления круглых скобок при его использовании в выражениях.

  • Его нельзя использовать с || или && без явного добавления круглых скобок.

7. Циклы: while и for

Мы изучили три вида петель:

  • while – проверка условия перед каждой итерацией.

  • do..while — Всегда проверяем условие после итерации.

  • for (;;) - условие проверяется перед каждой итерацией, можно использовать другие настройки.

Обычно while(true) используется для построения «бесконечных» циклов. Такие циклы, как и другие циклы, могут быть завершены командой break.

Если мы не хотим ничего делать в текущей итерации и хотим перейти к следующей итерации, мы можем использовать инструкцию continue.

break/continue поддерживает метки перед циклами. Метки - единственный способ прервать/продолжить выход из вложенного цикла, чтобы выйти наружу.

8. Функции

Объявление функции выглядит следующим образом:

function name(parameters, delimited, by, comma) {
  /* code */
}
  • Значения, переданные в качестве параметров функции, копируются в локальные переменные функции.

  • Функции могут обращаться к внешним переменным. Но это работает только изнутри наружу. Код вне функции не может видеть локальные переменные внутри функции.

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

Чтобы код оставался кратким и простым для понимания, рекомендуется в основном использовать в функциях локальные переменные и параметры, а не внешние переменные.

Функцию, которая принимает параметр, использует параметр и возвращает результат, легче понять, чем функцию, которая не принимает параметров, но изменяет внешнюю переменную в качестве побочного эффекта.

Название функции:

  • Имя функции должно четко описывать, что она делает. Когда мы видим в коде вызов функции, удачное имя функции позволяет нам сразу узнать, что делает функция и что она возвращает.

  • Функция — это действие, поэтому имена функций обычно представляют собой глаголы.

  • Есть много превосходных префиксов имен функций, таких как create..., show..., get..., check... и так далее. Используйте их, чтобы намекнуть на то, что делает функция.

9. Функциональные выражения

  • Функции — это значения. Их можно назначать, копировать или объявлять в любом месте кода.

  • Если функция объявлена ​​как отдельная инструкция в основном потоке кода, она называется «объявлением функции».

  • Если функция создается как часть выражения, она называется «выражением функции».

  • Внутренний алгоритм обрабатывает объявление функции перед выполнением блока кода. Таким образом, объявления функций видны в любом месте блока кода, в котором они объявлены.

  • Функциональные выражения создаются при поступлении потока выполнения.

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

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

10. Стрелочные функции, основы

Стрелочные функции очень удобны для однострочных функций. Он имеет два конкретных типа:

  1. Без фигурных скобок: (...args) => выражение — правая часть является выражением: функция вычисляет выражение и возвращает его результат.

  2. С фигурными скобками: (...args) => {тело} — фигурные скобки позволяют нам написать несколько операторов в функции, но нам нужно что-то явно вернуть, чтобы что-то вернуть.

11. Объекты

Объекты представляют собой ассоциативные массивы с некоторыми особыми свойствами.

Они хранят свойства (пары ключ-значение), где:

  • Ключи свойств должны быть строками или символами (обычно строками).

  • Значения могут быть любого типа.

Мы можем получить доступ к свойствам следующими способами:

  • Точечная запись: obj.property.

  • Квадратные скобки obj["свойство"], квадратные скобки позволяют получить ключ из переменной, например obj[varWithKey].

Другие операции:

  • Удалить свойство: удалить obj.prop.

  • Проверьте, есть ли свойство для данного ключа: «key» в obj.

  • Перебор объектов: цикл for(let key in obj).

То, что мы изучаем в этой главе, называется «простой объект» или просто объект.

В JavaScript есть много других типов объектов:

  • Массив используется для хранения упорядоченных коллекций данных,

  • Дата используется для хранения времени и даты,

  • Ошибка используется для хранения информации об ошибке.

  • ……так далее.

У них есть свои особенности, о которых мы узнаем позже. Иногда люди говорят «Тип массива» или «Тип даты», но это не их собственные типы, а тип объекта, называемый «объект». Они расширяют «объект» по-разному.

12. Ссылки на объекты и репликация

Объекты назначаются и копируются по ссылке. Другими словами, переменная хранит не «значение объекта», а «ссылку» (адрес памяти) на значение. Поэтому, когда вы копируете такую ​​переменную или передаете ее в качестве параметра функции, копируется ссылка, а не сам объект.

Все операции (такие как добавление и удаление свойств) через скопированную ссылку выполняются над одним и тем же объектом.

Чтобы создать «настоящую копию» (клон), мы можем использовать Object.assign для так называемой «поверхностной копии» (вложенные объекты копируются по ссылке) или использовать функцию «глубокой копии», например._.cloneDeep(obj).

13. Метод объекта, "это"

  • Функции, хранящиеся в свойствах объекта, называются «методами».

  • Методы позволяют объектам выполнять «манипуляции», такие как object.doSomething() .

  • Методы могут ссылаться на объекты следующим образом.

  • Значение this получается при запуске программы.

  • Функция может использовать this при объявлении, но this имеет значение только при вызове функции.

  • Функции можно копировать между объектами.

  • При вызове функции с синтаксисом «метод»: object.method() значением this во время вызова является объект.

Обратите внимание, что стрелочные функции особенные: у них нет этого. Доступ к this внутри функции стрелки получается извне.

14. Необязательная цепочка "?".

Необязательный синтаксис цепочки ?. имеет три формы:

  1. obj?.prop — возвращает obj.prop, если obj существует, в противном случае возвращает значение undefined.

  2. obj?.[prop] — возвращает obj[prop], если obj существует, в противном случае — undefined.

  3. obj.method?.() – вызвать obj.method(), если obj.method существует, иначе вернуть undefined.

Как мы видели, эти синтаксические формы просты в использовании. ?. Проверьте, является ли левая часть нулевой/неопределенной, если нет, продолжите операцию.

Цепочка ?. позволяет нам безопасно обращаться к вложенным свойствам.

Однако мы должны использовать ?. экономно, только когда ничего страшного, если левая часть не существует. Для того, чтобы в коде были программные ошибки, от нас их не скроют.

15. Тип символа

Символ — это базовый тип для уникальных идентификаторов.

Символы создаются с помощью вызова Symbol() с необязательным описанием (именем).

Символы всегда имеют разные значения, даже если они имеют одинаковое имя. Если мы хотим, чтобы символы с одинаковым именем были равны, то мы должны использовать глобальный реестр: Symbol.for(key) возвращает (при необходимости создает) глобальный символ с ключом в качестве имени. При использовании Symbol.for для многократного вызова Symbol с одним и тем же ключом возвращается один и тот же Symbol.

Символы имеют два основных сценария использования:

  1. «Скрытые» объектные свойства. Если мы хотим добавить свойство на объект, который «принадлежит» другому скрипту или библиотеке, мы можем создать символ и использовать это в качестве ключа свойства. Свойство Symbol не отображается для ... как, поэтому он не случайно обрабатывается с другими свойствами. Кроме того, это не будет доступен напрямую, потому что другой скрипт не имеет нашего символа. Следовательно, собственность будет защищена от случайного использования или переоценки.

Следовательно, мы можем использовать свойства символов «тайно», чтобы быть что-то скрытые объекты, которые нам нужны, но не видите его в другом месте.

  1. JavaScript использует ряд системных символов, которые доступны как Symbol.* . Мы можем использовать их, чтобы изменить некоторые встроенные модели поведения. Например, позже в этом руководстве мы будем использовать Symbol.iterator для выполненияповторятьОперация, используйте Symbol.toPrimitive для установкиПреобразование значений примитивов объектаи т.п.

Технически символы не скрыты на 100%. Есть встроенный методObject.getOwnPropertySymbols(obj)Позволяет нам получить все символы. Существует такжеReflect.ownKeys(obj)Методы могут возвращать все ключи объекта, включая символы. Так что на самом деле они не скрыты. Но большинство библиотек, встроенных методов и синтаксических конструкций не используют эти методы.

16. Числовые типы

Чтобы записать числа со многими нулями:

  • Добавьте «e» и количество нулей к числу. Например: 123e6 — это то же самое, что 123, за которым следуют 6 нулей.

  • Отрицательное число после «e» разделит число на 1, за которым следует заданное количество нулей. Например, 123e-6 означает 0,000123 (одна миллионная от 123).

Для разных систем счисления:

  • Числа можно записывать непосредственно в шестнадцатеричной (0x), восьмеричной (0o) и двоичной (0b) системах.

  • parseInt(str, base) анализирует строку str как целое число в заданной базовой системе счисления, 2 ≤ основание ≤ 36.

  • num.toString(base) преобразует число в строку в заданной базовой системе счисления.

Чтобы преобразовать такие значения, как 12pt и 100px, в числа:

  • Используйте parseInt/parseFloat для «мягкого» преобразования, которое считывает число из строки и возвращает значение, которое можно было прочитать до возникновения ошибки.

Десятичная дробь:

  • Используйте Math.floor, Math.ceil, Math.trunc, Math.round или num.toFixed(precision) для округления.

  • Обязательно помните, что вы теряете точность при использовании десятичных дробей.

Дополнительные математические функции:

  • Пожалуйста, проверьте, если необходимоMathобъект. Эта библиотека небольшая, но достаточная для основных нужд.

17. Струны

  • Есть 3 типа кавычек. Обратные кавычки позволяют строкам занимать несколько строк, а выражения могут быть встроены в строки с помощью ${…}.

  • Строки в JavaScript кодируются в UTF-16.

  • Мы можем вставлять символы, используя специальные символы, такие как \n, или манипулируя их юникодом, используя \u... .

  • При получении символов используйте [].

  • Чтобы получить подстроку, используйте slice или substring.

  • Преобразование строк в верхний/нижний регистр, используйте: toLowerCase/toUpperCase.

  • Используйте indexOf или include/startsWith/endsWith для простых проверок при поиске подстрок.

  • Используйте localeCompare при сравнении строк по языку, в противном случае сравнивайте по коду символа.

Есть несколько других полезных строковых методов:

  • str.trim() - удаляет начальные и конечные пробелы ("обрезает") из строки.

  • str.repeat(n) – повторить строку n раз.

  • ...для более подробной информации см.руководство.

18. Массивы

Массив — это особый тип объекта, подходящий для хранения упорядоченных элементов данных и управления ими.

  • утверждение:

    // квадратные скобки (общее использование) Пусть rem = [item1, item2 ...];

    // новый массив (крайне редко) пусть arr = новый массив (элемент1, элемент2...);

Вызов new Array(number) создает массив заданной длины, но не содержит элементов.

  • Свойство length — это длина массива, точнее, это последнее числовое значение индекса массива плюс один. Он автоматически корректируется методом массива.

  • Если мы сократим длину вручную, массив будет усечен.

Мы можем использовать массивы в качестве двухсторонней очереди, выполнив следующие действия:

  • push(...items) добавляет элементы в конец.

  • pop() удаляет и возвращает элемент с конца.

  • shift() удаляет и возвращает элемент с самого начала.

  • unshift(...items) добавляет элементы с самого начала.

Перебрать элементы массива:

  • for (let i=0; i

  • for (let item of arr) — современный синтаксис, доступны только элементы.

  • for (let i in arr) — никогда не используйте это.

При сравнении массивов не используйте оператор == (и, конечно же, не используйте такие операторы, как > и

Однако мы можем использовать цикл for..of для сравнения массивов поэлементно.

19. Методы массива

Шпаргалка по методу массива:

  • Добавить/удалить элементы:

    • push(...items) - добавить элементы в конец,

    • pop() - извлекает элемент с конца,

    • shift() - извлекает элемент с самого начала,

    • unshift(...items) - добавить элементы в заголовок,

    • splice(pos, deleteCount, ...items) — удаляет элементы deleteCount из pos и вставляет элементы.

    • slice(start, end) — создает новый массив, копируя элементы из начала индекса в конец индекса (но не включая конец).

    • concat(...items) - Возвращает новый массив: копирует все элементы текущего массива и добавляет в него элементы. Если какой-либо элемент в items является массивом, возьмите его элемент.

  • Элемент поиска:

    • indexOf/lastIndexOf(item, pos) - начать поиск элемента с позиции index, вернуть индекс элемента, если он найден, иначе вернуть -1.

    • include(value) — возвращает true, если массив имеет значение, иначе false.

    • find/filter(func) — фильтровать элементы по func, возвращать первое значение/все значения, которые заставляют func возвращать true.

    • findIndex похож на find, но вместо значения возвращает индекс.

  • Итерация по элементам:

    • forEach(func) – вызывает func для каждого элемента, ничего не возвращая.
  • Преобразовать массив:

    • map(func) — создает новый массив на основе результата вызова func для каждого элемента.

    • sort(func) — сортирует массив на месте и возвращает его.

    • reverse () — переворачивает массив на месте, а затем возвращает его.

    • split/join - преобразовать строку в массив и обратно.

    • reduce/reduceRight(func, initial) — вычисляет одно значение в массиве, вызывая func для каждого элемента, передавая промежуточные результаты между вызовами.

  • разное:

    • Array.isArray(arr) проверяет, является ли arr массивом.

Обратите внимание, что методы sort, reverse и splice изменяют сам массив.

Это наиболее часто используемые методы, и они охватывают 99% вариантов использования. Но есть несколько других:

Подобно карте, функция fn вызывается для каждого элемента массива. Возвращает true, если какой-либо/все результаты верны, в противном случае возвращает false.

Эти два метода ведут себя как операторы || и &&: если fn возвращает истинное значение, arr.some() немедленно возвращает true и прекращает итерацию по оставшимся элементам массива; если fn возвращает ложное значение, arr.every() немедленно возвращает значение Возвращает false и прекращает перебор оставшихся элементов массива.

Мы можем использовать every для сравнения массивов:

function arraysEqual(arr1, arr2) {
  return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]);
}

alert( arraysEqual([1, 2], [1, 2])); // true
  • arr.fill(value, start, end)- От начала индекса до конца заполнить массив повторяющимися значениями.

  • arr.copyWithin(target, start, end)- Копирует все элементы от начала до конца в свою собственную целевую позицию (переписывая существующие элементы).

  • arr.flat(depth)/arr.flatMap(fn)Создает новый плоский массив из многомерного массива.

  • Array.of(element0[ element1[ …[ elementN]]])Создает новый экземпляр Array на основе переменного количества аргументов, независимо от количества или типов аргументов.

Полный список см.руководство.

20. Итерируемый объект

Объекты, к которым можно применить for..of, называются итерируемыми.

  • Технически итерации должны реализовывать метод Symbol.iterator.

    • Результат obj[Symbol.iterator]() называется итератором. Он обрабатывает дальнейшие итерации.

    • Итератор должен иметь метод next(), который возвращает объект {done: Boolean, value: any}, где done:true указывает на конец итерации, в противном случае value является следующим значением.

  • Метод Symbol.iterator автоматически вызывается for..of , но мы также можем вызвать его напрямую.

  • Встроенные итераторы, такие как строки и массивы, реализуют Symbol.iterator.

  • Строковые итераторы распознают суррогатные пары. (Аннотация: суррогатные пары — это расширенные символы UTF-16.)

Объекты со свойствами индекса и свойствами длины называются массивоподобными объектами. Такие объекты также могут иметь другие свойства и методы, но не встроенные методы для массивов.

Если мы внимательнее посмотрим на спецификацию, то большинство встроенных методов предполагают, что им нужно иметь дело с итерируемыми или массивоподобными объектами, а не с «настоящими» массивами, которые более абстрактны.

Array.from(obj[ mapFn, thisArg]) преобразует итерируемый объект или массивоподобный объект obj в реальный массив Array, а затем мы можем применить к нему методы массива. Необязательные параметры mapFn и thisArg позволяют нам применить функцию к каждому элементу.

21. Карта и набор

Карта — это ключевой набор элементов данных.

Методы и свойства следующие:

  • new Map([iterable]) — создает карту, необязательно с итерируемым (например, массивом) пар [ключ, значение] для инициализации.

  • map.set(key, value) — сохранить значение по ключу.

  • map.get(key) — возвращает значение по ключу или undefined, если соответствующий ключ не существует в карте.

  • map.has(key) — возвращает true, если ключ существует, иначе false.

  • map.delete(key) — удалить значение указанного ключа.

  • map.clear() - очищает карту.

  • map.size — возвращает текущее количество элементов.

Отличия от обычных объектов Объект:

  • В качестве ключа может использоваться любой ключ, объект.

  • Существуют и другие удобные методы, такие как свойство size.

Set - это набор уникальных значений.

Методы и свойства:

  • new Set([iterable]) — создает набор, необязательно с итерируемым объектом (например, массивом) для инициализации.

  • set.add(value) — добавляет значение (ничего не делает, если значение существует), возвращает сам набор.

  • set.delete (значение) - Удалить значение Возвращает true, если значение существует во время вызова метода, в противном случае false.

  • set.has(value) — возвращает true, если значение находится в наборе, иначе false.

  • set.clear() - очищает набор.

  • set.size — количество элементов.

Итерация в Картах и ​​Наборах всегда происходит в том порядке, в котором были вставлены значения, поэтому мы не можем сказать, что эти наборы неупорядочены, но мы не можем переупорядочивать элементы, и мы не можем напрямую получать элементы по их числа.

22. WeakMap и WeakSet

Weakmap похож на коллекцию Map, которая позволяет использовать объект в качестве ключа, и если к нему невозможно получить доступ другими способами, они будут удалены вместе со связанными с ними значениями.

Слабое задача - это наборная коллекция, которая просто хранит объекты и удаляет их, как только они станут недоступными другими способами.

Ни один из них не поддерживает методы и свойства, которые относятся ко всем ключам или их количеству. Допускается только одна операция.

WeakMap и WeakSet используются как «вторичные» структуры данных в дополнение к «первичному» хранилищу объектов. После удаления объекта из основной памяти он будет автоматически очищен, если объект используется только в качестве ключа в WeakMap или WeakSet.

23. Деструктивное назначение

  • Деструктурирующее присваивание может отображать объект или массив сразу в несколько переменных.

  • Полный синтаксис для деструктурирования объекта:

    let {prop : varName = default, ...rest} = object

Это означает, что свойство prop будет присвоено переменной varName, а если такого свойства нет, будет использоваться значение по умолчанию default.

Свойства объекта без соответствующего сопоставления копируются в оставшийся объект.

  • Полный синтаксис для деструктурирования массива:

    let [item1 = default, item2, ...rest] = array

Первый элемент массива присваивается item1, второй элемент присваивается item2, а все остальные элементы копируются в другой массив rest.

  • Также можно извлекать данные из вложенных массивов/объектов, и в этом случае левая часть знака равенства должна иметь ту же структуру, что и правая часть знака равенства.

24. Дата и время

  • В JavaScript дата и время используютсяDateобъект для представления. Мы не можем просто создать дату или только время, объект Date всегда создает и то, и другое.

  • Месяцы считаются с 0 (да, январь равен 0).

  • День недели getDay() также начинается с 0 (0 — воскресенье).

  • Дата автоматически калибруется, когда установлены компоненты, выходящие за пределы допустимого диапазона. Это полезно для сложения и вычитания дней/месяцев/часов.

  • Даты можно вычесть, чтобы получить разницу в миллисекундах. Потому что, когда Date преобразуется в число, объект Date преобразуется в метку времени.

  • Используйте Date.now(), чтобы быстрее получить метку времени для текущего времени.

В отличие от других систем временные метки в JavaScript указываются в миллисекундах, а не в секундах.

Иногда нам нужно более точное измерение времени. В самом JavaScript нет способа измерения микросекунд (миллионных долей секунды), но в большинстве сред выполнения они есть. Например: браузер имеетperformance.now()метод, чтобы указать количество микросекунд в миллисекундах с момента загрузки страницы (с точностью до трех знаков после запятой в миллисекундах):

alert(`Loading started ${performance.now()}ms ago`);
// 类似于 "Loading started 34731.26000000001ms ago"
// .26 表示的是微秒(260 微秒)
// 小数点后超过 3 位的数字是精度错误,只有前三位数字是正确的

Node.js имеет модуль микровремени, а также другие методы. Технически почти все устройства и среды позволяют получать более точные значения, но не через объекты Date.

25. Метод JSON, toJSON

  • JSON — это формат данных с собственным независимым стандартом и библиотеками для большинства языков программирования.

  • JSON поддерживает объект, массив, строку, число, логическое значение и нуль.

  • JavaScript предоставляет способ сериализации в JSONJSON.stringifyИ методы разброса JSONJSON.parse.

  • Оба метода поддерживают функции преобразования для интеллектуального чтения/записи.

  • Если у объекта есть toJSON, то он будет вызываться JSON.stringify.

26. Рекурсия и стек

период, термин:

  • Рекурсия — это термин программирования, означающий вызов функции из самой себя. Рекурсивные функции можно использовать для решения проблем более элегантным способом.

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

Например, связанный список можно определить как структуру данных, состоящую из объектов, ссылающихся на список (или null).

list = { value, next -> list }

Деревья HTML-элементов или дерево отделов в этой главе также рекурсивны по своей природе: у них есть ветви, а у ветвей могут быть другие ветви.

Их можно пройти с помощью рекурсивных функций, как мы видели в примере sumSalary.

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

27. Остальные параметры и синтаксис спреда

Когда мы видим "..." в коде, это либо остаточный параметр, либо расширенный синтаксис.

Есть простой способ отличить их друг от друга:

  • Если ... появляется в конце списка параметров функции, то это остаточный параметр, который собирает оставшиеся параметры в списке параметров в массив.

  • Если ... появляется в вызове функции или подобном выражении, это синтаксис расширения, который расширяет массив в список.

используемые сцены:

  • Остальные параметры используются для создания функций, которые принимают любое количество параметров.

  • Синтаксис расширения используется для передачи массива в функцию, которая обычно требует списка многих параметров.

Их присутствие помогает нам легко конвертировать туда и обратно между списками и массивами параметров.

Аргументы «старого стиля» (массивоподобные и итерируемые объекты) по-прежнему помогают нам получить все аргументы при вызове функции.

28. Глобальные объекты

  • Глобальный объект содержит переменные, которые должны быть видны везде.

  • К ним относятся встроенные методы JavaScript, такие как «Array», и специфичные для среды значения, такие как window.innerHeight — высота окна в браузере.

  • Глобальный объект имеет общее имя globalThis.

  • ... но чаще используются "старомодные" имена для конкретных сред, такие как окно (браузер) и глобальное (Node.js).

  • Значение должно храниться в глобальном объекте только в том случае, если оно действительно является глобальным для нашего проекта. и свести к минимуму.

  • в браузере, если только мы не используемmodules, в противном случае глобальные функции и переменные, объявленные с помощью var, становятся свойствами глобального объекта.

  • Чтобы сделать наш код ориентированным на будущее и более понятным, мы должны использовать прямой способ доступа к свойствам глобальных объектов, таких как window.x.

29. Функциональный объект, NFE

Функции — это объекты.

Введем некоторые их свойства:

  • name - имя функции. Обычно берется из определения функции, но если имя функции не указано при определении функции, JavaScript попытается угадать имя функции из контекста функции (например, взять присвоенное имя переменной в качестве имени функции).

  • length - количество входных параметров при определении функции. Остальные параметры в подсчете не участвуют.

Если функция объявлена ​​как выражение функции (не в основном потоке кода) с прикрепленным именем, то она называется выражением именованной функции. Это имя можно использовать для самовызовов внутри функции, таких как рекурсивные вызовы и т. д.

Кроме того, функции могут иметь дополнительные свойства. Многие известные библиотеки JavaScript в полной мере используют эту функцию.

Они создают «основную» функцию, а затем присоединяют к ней множество других «вспомогательных» функций. Например,jQueryБиблиотека создает функцию с именем $.lodashБиблиотека создает _ функцию, затем добавляет _.Add, _.keyby и другие атрибуты для него (для получения дополнительной, см.docs). На самом деле они делают это, чтобы уменьшить загрязнение глобального пространства, чтобы в библиотеке была только одна глобальная переменная. Это снижает вероятность конфликтов имен.

Итак, функция может выполнять полезную работу сама по себе, а также может нести множество других функций в своих собственных свойствах.

29. Синтаксис «новой функции»

грамматика:

let func = new Function ([arg1, arg2, ...argN], functionBody);

По историческим причинам аргументы также могут быть указаны в качестве разделителей-запятых.

Следующие три утверждения имеют одинаковый смысл:

new Function('a', 'b', 'return a + b'); // 基础语法
new Function('a,b', 'return a + b'); // 逗号分隔
new Function('a , b', 'return a + b'); // 逗号和空格分隔

Для функции, созданной с помощью new Function, ее [[Environment]] указывает на глобальное лексическое окружение, а не на внешнее лексическое окружение, в котором находится функция. Поэтому мы не можем использовать внешние переменные напрямую в новой функции. Но это хорошо, это помогает уменьшить вероятность того, что наш код пойдет не так. Кроме того, с точки зрения архитектуры кода, явное использование параметров по значению является лучшим подходом и позволяет избежать конфликтов с использованием компрессоров.

30. Планирование: setTimeout и setInterval

  • Методы setTimeout(func, delay, ...args) и setInterval(func, delay, ...args) позволяют нам запускать func один раз после задержки в миллисекундах или периодически при задержке в миллисекундах.

  • Чтобы отменить выполнение функции, мы должны вызвать clearInterval/clearTimeout и передать в качестве аргумента значение, возвращаемое setInterval/setTimeout.

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

  • Планирование с нулевой задержкой setTimeout(func, 0) (так же, как setTimeout(func)) используется для планирования вызовов, которые необходимо выполнить как можно скорее, но которые будут вызываться после завершения выполнения текущего скрипта.

  • Браузеры ограничивают минимальную задержку пяти или более вложенных вызовов setTimeout или setInterval (после пяти вызовов) до 4 мс. Это вопрос, оставшийся от истории.

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

Например, таймеры в браузере могут работать медленно по многим причинам:

  • ЦП перегружен.

  • Вкладки браузера находятся в фоновом режиме.

  • Ноутбуки работают от батареек.

Все эти факторы могут увеличить минимальное разрешение таймера (минимальную задержку) до 300 мс или даже 1000 мс, в зависимости от браузера и его настроек.

31. Шаблон декоратора и переадресация, звоните/применяйте

Декоратор — это оболочка для изменения поведения функции. Основная работа по-прежнему выполняется этой функцией.

Декораторы можно рассматривать как «функции» или «аспекты», которые можно добавлять к функциям. Мы можем добавить один или добавить более одного. И все это без изменения кода!

Для реализации cachingDecorator мы исследовали следующие методы:

Общая переадресация звонков обычно выполняется с помощью apply :

let wrapper = function() {
  return original.apply(this, arguments);
};

Мы также можем увидеть пример заимствования методов, когда мы берем метод из одного объекта и «вызываем» его в контексте другого объекта. Обычно берут методы массива и применяют их к аргументам параметра. Другой способ — использовать объект параметра Rest, который представляет собой настоящий массив.

32. Привязка функций

Метод func.bind(context, ...args) возвращает «связанный вариант» функции func, которая связывает контекст this и первый аргумент (если задан).

Обычно мы используем bind для привязки this методов объекта, чтобы мы могли передавать их для использования в другом месте. Например, передается в setTimeout.

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

Partial полезен, когда мы не хотим повторять одни и те же параметры снова и снова. Например, у нас есть функция отправки (от, кому), и from всегда должен быть одним и тем же для нашей задачи, тогда мы можем сделать партиал и использовать его.

33. Глубокое понимание стрелочных функций

Функция стрелки:

  • без этого

  • без аргументов

  • нельзя вызывать с новым

  • У них тоже нет супера, но мы его еще не изучили. Мы будемнаследование классовГлава, чтобы узнать это.

Это связано с тем, что стрелочные функции предназначены для шорткодов, которые не имеют собственного «контекста», но работают в текущем контексте. И стрелочные функции действительно блестят в этом сценарии использования.

34. Прототипное наследование

  • В JavaScript все объекты имеют скрытое свойство [[Prototype]], которое может быть либо другим объектом, либо нулевым.

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

  • Объекты, на которые ссылается [[Prototype]], называются «прототипами».

  • Если мы хотим прочитать свойство obj или вызвать метод, а оно не существует, JavaScript попытается найти его в прототипе.

  • Операции записи / удаления выполняются непосредственно на объекте, они не используют прототип (при условии, что это свойство данных, а не сеттера).

  • Если мы вызовем obj.method() и метод будет получен из прототипа, this все равно будет ссылаться на obj. Поэтому методы всегда используются с текущим объектом, даже если метод унаследован.

  • Цикл for..in повторяет сам себя и унаследованные свойства. Все остальные методы извлечения ключей/значений работают только с самим объектом.

35. F.prototype

Все просто, нужно только запомнить несколько ключевых моментов, чтобы четко их усвоить:

  • Свойство F.prototype (не путать с [[Prototype]] ) присваивает значение [[Prototype]] нового объекта при вызове нового F.

  • Значение F.prototype является либо объектом, либо нулевым значением: больше ничего не работает.

  • Свойство «прототип» имеет этот особый эффект только тогда, когда функция-конструктор установлена ​​и вызывается с помощью new .

На обычных объектах в прототипе нет ничего особенного:

let user = {
  name: "John",
  prototype: "Bla-bla" // 这里只是普通的属性
};

По умолчанию все функции имеют F.prototype = {constructor: F}, поэтому мы можем получить конструктор объекта, обратившись к его свойству «конструктор».

36. Нативные прототипы

  • Все встроенные объекты следуют одному и тому же шаблону:

    • Все методы хранятся в прототипе (Array.prototype, Object.prototype, Date.prototype и т. д.).

    • Сам объект только хранит данные (элементы массива, свойства объекта, даты).

  • Примитивные типы данных в методе хранения объекта-оболочки прототипа: Number.prototype, String.prototype и Boolean.prototype. Только неопределенный и нулевой объект-оболочка.

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

37. Метод-прототип, объект без __proto__

Современные способы настройки и прямого доступа к прототипам:

  • Object.create(proto, [descriptors])- Создает пустой объект с заданным прототипом как [[Prototype]] (который может быть нулевым) и необязательными описаниями свойств.

  • Object.getPrototypeOf(obj)-- Возвращает [[Prototype]] объекта obj (то же, что и геттер __proto__).

  • Object.setPrototypeOf(obj, proto)-- Установите [[Prototype]] объекта obj в proto (так же, как сеттер __proto__).

Если вы генерируемый пользователем ключ в объект, то встроенный __PROTO__ Getter / Setter небезопасна. Поскольку пользователь может ввести «__PROTO__» в качестве ключа, который вызывает ошибку, хотя мы надеемся, что этот вопрос не приведет к значительному воздействию, но обычно вызывает непредсказуемые последствия.

Поэтому мы можем использовать Object.create(null) для создания «очень простого» объекта без __proto__ или придерживаться объекта Map для таких сценариев.

Кроме того, Object.create предоставляет простой способ поверхностного копирования всех дескрипторов объекта:

let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));

Кроме того, мы ясно дали понять, что __proto__ является геттером/сеттером для [[Prototype]] , который находится в Object.prototype, как и другие методы.

Мы можем использовать Object.create(null) для создания объектов без прототипов. Такие объекты используются как "чистые словари", для которых не проблема использовать "__proto__" в качестве ключа.

Другие методы:

Все методы, которые возвращают свойства объекта (такие как Object.keys и другие) - все возвращают "собственные" свойства. Если мы хотим наследовать их, мы можем использовать for...in.

38. Базовая грамматика класса

Синтаксис базового класса выглядит следующим образом:

class MyClass {
  prop = value; // 属性

  constructor(...) { // 构造器
    // ...
  }

  method(...) {} // method

  get something(...) {} // getter 方法
  set something(...) {} // setter 方法

  [Symbol.iterator]() {} // 有计算名称(computed name)的方法(此处为 symbol)
  // ...
}

Технически MyClass — это функция (которую мы предоставляем в качестве конструктора), а методы, геттеры и сетторы записываются в MyClass.prototype.

39. Наследование классов

  1. Хотите расширить класс: класс Child расширяет Parent:
  • Это означает, что Child.prototype.__proto__ будет Parent.prototype, поэтому методы будут наследоваться.
  1. Переопределить конструктор:
  • Прежде чем использовать это, мы должны вызвать родительский конструктор как super() в дочернем конструкторе.
  1. Переопределить метод:
  • Мы можем использовать super.method() в дочернем методе для вызова родительского метода.
  1. внутренний:
  • Методы запоминают свой класс/объект во внутреннем свойстве [[HomeObject]]. Вот как super разрешает родительские методы.

  • Поэтому копировать метод с super из одного объекта в другой небезопасно.

Пополнить:

  • У стрелочных функций нет собственного this или super, поэтому они сливаются с непосредственным контекстом, как если бы они были прозрачными.

40. Статические свойства и статические методы

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

Например, метод сравнения Article.compare(article1, article2) или фабричный метод Article.createTodays().

В жизни класса все они отмечены ключевым словом static.

Статические свойства используются, когда мы хотим хранить данные на уровне класса, не привязываясь к экземпляру.

Синтаксис следующий:

class MyClass {
  static property = ...;

  static method() {
    ...
  }
}

Технически статическое объявление — это то же самое, что присваивание значения непосредственно самому классу:

MyClass.property = ...
MyClass.method = ...

Статические свойства и методы наследуются.

Поскольку класс B расширяет A, прототип класса B указывает на A: B.[[Prototype]] = A. Таким образом, если поле не найдено в B, оно продолжит поиск в A.

41. Частные и защищенные свойства и методы

В терминах объектно-ориентированного программирования (ООП) разделение внутренних и внешних интерфейсов называетсяупаковка.

Он имеет следующие преимущества:

Защитите пользователей, чтобы они случайно не поранились

Представьте группу разработчиков, использующих кофеварку. Данная кофеварка производства компании "Лучшая Кофеварка" работает нормально, но защитная крышка снята. Таким образом, внутренний интерфейс открыт.

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

Это точно не вина Джона, а вина того парня, который снял щит и позволил Джону сделать это.

Программирование такое же. Если пользователь класса хочет изменить что-то, что не предназначено для изменения извне — последствия непредсказуемы.

Поддержка

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

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

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

Для пользователей, когда выходит новая версия, внутренние компоненты приложения могут быть переработаны, но если внешний интерфейс остается прежним, его по-прежнему легко обновить.

скрыть сложность

Люди любят использовать простые вещи. По крайней мере снаружи. Что внутри - другой вопрос.

Программисты не исключение.

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

Чтобы скрыть внутренний интерфейс, мы используем защищенные или приватные свойства:

  • Защищенные поля начинаются с _. Это общеизвестное соглашение, не применяемое на уровне языка. Программист должен получать доступ к полям, начинающимся с _, только через его класс и классы, которые унаследованы от него.

  • Частные поля начинаются с # . JavaScript гарантирует, что мы можем получить к ним доступ только внутри класса.

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

42. Проверка класса: "instanceof"

Подытожим, что мы знаем о методах проверки типов:

Оператор instanceof действительно превосходен в сценариях, когда мы работаем с иерархией классов и хотим проверить этот класс, одновременно рассматривая наследование. Как мы видели, {}.toString технически является "более высоким" типом.

43. Режим миксина

Mixin — это общий термин объектно-ориентированного программирования: класс, который содержит методы из других классов.

Некоторые другие языки программирования допускают множественное наследование. JavaScript не поддерживает множественное наследование, но миксины можно реализовать, скопировав методы в прототип.

Мы можем использовать примеси как способ расширения класса, добавляя различные варианты поведения, такие как упомянутая выше обработка событий.

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

44. Обработка ошибок, "try..catch"

Структура TRY..CATCH позволяет нам обрабатывать ошибку, которая появляется во время выполнения. Из буквального литерала он позволяет «попробовать» запустить код и «захват», в котором возможные ошибки.

Синтаксис следующий:

try {
  // 执行此处代码
} catch(err) {
  // 如果发生错误,跳转至此处
  // err 是一个 error 对象
} finally {
  // 无论怎样都会在 try/catch 之后执行
}

Может не быть раздела catch или finally, поэтому доступны либо try..catch, либо try..finally.

Объекты ошибок содержат следующие свойства:

  • message — Человекочитаемое сообщение об ошибке.

  • name — Строка с названием ошибки (имя конструктора Error).

  • стек (нестандартный, но хорошо поддерживаемый) — стек вызовов при возникновении ошибки.

Если нам не нужен объект ошибки, мы можем опустить его, используя catch { вместо catch(err) { .

Мы также можем использовать оператор throw для создания пользовательских ошибок. Технически аргумент для throw может быть любым, но обычно это объект ошибки, который наследуется от встроенного класса Error. В следующей главе мы подробно рассмотрим расширение ошибок.

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

Даже если у нас нет try..catch, большинство сред выполнения позволяют нам настроить «глобальные» обработчики ошибок для перехвата «выпадающих» ошибок. В браузерах это window.onerror.

45. Настроить ошибку, расширить ошибку

  • Обычно мы можем наследовать от Error и других встроенных классов ошибок. Нам просто нужно обратить внимание на свойство name и не забыть вызвать super.

  • Мы можем использовать instanceof для проверки конкретных ошибок. Но иногда у нас есть объект ошибки из сторонней библиотеки, и нет простого способа получить здесь его класс. Атрибут имени затем можно использовать для этого типа проверки.

  • Обёртывание исключений — широко используемый метод: функции, которые обрабатывают низкоуровневые исключения и создают высокоуровневые ошибки вместо различных низкоуровневых ошибок. В приведенном выше примере низкоуровневое исключение иногда становится свойством объекта, например err.cause, но это не является строго обязательным.

46. ​​Цепочка обещаний

Если обработчик .then (или catch/finally работает) возвращает обещание, то остальная часть цепочки будет ждать, пока оно не будет выполнено. Когда он установлен, его результат (или ошибка) будет передан дальше.

Вот полная блок-схема:

47. Обработка ошибок с обещаниями

  • .catch обрабатывает все виды ошибок в промисах: в вызовах reject() или выброшенных ошибках в обработчиках.

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

  • Если нет возможности исправить ошибку, можно не использовать .catch.

  • В любом случае у нас должны быть обработчики событий unhandledrejection (для браузеров и эмуляции других сред), чтобы отслеживать необработанные ошибки и информировать пользователя (и, возможно, наш сервер) об информации, позволяющей нашим прикладным программам никогда не «умирать».

добавить

Получить пример обработки ошибок

Давайте улучшим обработку ошибок для примера с пользовательской загрузкой.

Когда запрос не может быть сделан,fetchreject возвращает обещание. Например, удаленный сервер недоступен или неверный URL-адрес. Но если удаленный сервер возвращает ошибку ответа 404 или даже ошибку 500, такие ответы считаются законными.

Что делать, если в строке (*) сервер возвращает не-JSON (не-JSON) страницу с ошибкой 500? Что, если GitHub вернет страницу с ошибкой 404 без этого пользователя?

fetch('no-such-user.json') // (*)
  .then(response => response.json())
  .then(user => fetch(`https://api.github.com/users/${user.name}`)) // (**)
  .then(response => response.json())
  .catch(alert); // SyntaxError: Unexpected token < in JSON at position 0
  // ...

Пока что код пытается загрузить данные ответа в формате JSON, но все равно завершается с ошибкой синтаксиса. Вы можете увидеть соответствующую информацию, выполнив приведенный выше пример, поскольку файл no-such-user.json не существует.

Это как бы плохо, потому что ошибка просто падает по цепочке, без подробностей о том, что и где не удалось.

Итак, мы добавляем еще один шаг: мы должны проверить свойство response.status с HTTP-статусом и выдать ошибку, если это не 200.

class HttpError extends Error { // (1)
  constructor(response) {
    super(`${response.status} for ${response.url}`);
    this.name = 'HttpError';
    this.response = response;
  }
}

function loadJson(url) { // (2)
  return fetch(url)
    .then(response => {
      if (response.status == 200) {
        return response.json();
      } else {
        throw new HttpError(response);
      }
    })
}

loadJson('no-such-user.json') // (3)
  .catch(alert); // HttpError: 404 for .../no-such-user.json
  1. Мы создаем собственный класс для ошибок HTTP, чтобы отличать ошибки HTTP от других типов ошибок. Кроме того, в новом классе есть конструктор, который берет объект ответа и сохраняет его в error. Следовательно, код обработки ошибок может получить данные ответа.

  2. Затем мы оборачиваем код запроса и обработки ошибок в функцию, которая извлекает URL-адрес и обрабатывает все коды состояния, кроме 200, как ошибки. Это удобно, потому что нам обычно нужна подобная логика.

  3. Теперь предупреждение отображает более полезную описательную информацию.

Преимущество наличия нашего собственного класса обработки ошибок заключается в том, что мы можем легко проверять наличие ошибок в нашем коде обработки ошибок, используя instanceof .

Например, мы можем создать запрос, и если мы получим 404, мы можем попросить пользователя изменить информацию.

Код ниже загружает пользователя с заданным именем из GitHub. Если такого пользователя нет, он предложит пользователю ввести правильное имя:

function demoGithubUser() {
  let name = prompt("Enter a name?", "iliakan");

  return loadJson(`https://api.github.com/users/${name}`)
    .then(user => {
      alert(`Full name: ${user.name}.`);
      return user;
    })
    .catch(err => {
      if (err instanceof HttpError && err.response.status == 404) {
        alert("No such user, please reenter.");
        return demoGithubUser();
      } else {
        throw err; // (*)
      }
    });
}

demoGithubUser();

Обратите внимание: здесь .catch будет ловить все ошибки, но он «знает, как обрабатывать» только HttpError 404. В данном конкретном случае это означает, что такого пользователя нет, и в этом случае .catch просто повторяет попытку.

Для других ошибок он не знает, что не так. Может быть ошибка программирования или другая ошибка. Таким образом, он просто снова выбрасывает строку (*).

разное

Если у нас есть индикация нагрузки,. Финально это хороший обработчик, чтобы остановить извлечение, когда она заканчивается:

function demoGithubUser() {
  let name = prompt("Enter a name?", "iliakan");

  document.body.style.opacity = 0.3; // (1) 开始指示(indication)

  return loadJson(`https://api.github.com/users/${name}`)
    .finally(() => { // (2) 停止指示(indication)
      document.body.style.opacity = '';
      return new Promise(resolve => setTimeout(resolve)); // (*)
    })
    .then(user => {
      alert(`Full name: ${user.name}.`);
      return user;
    })
    .catch(err => {
      if (err instanceof HttpError && err.response.status == 404) {
        alert("No such user, please reenter.");
        return demoGithubUser();
      } else {
        throw err;
      }
    });
}

demoGithubUser();

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

Когда обещание разрешено, выборка может быть либо успешной, либо ошибкой, в конечном итоге запускающей строку (2) и завершающей индикацию загрузки.

Существует трюк с браузером (*), чтобы вернуть обещание нулевого тайм-аута от finally. Это связано с тем, что некоторые браузеры (например, Chrome) требуют «немного» от обработчика обещаний, чтобы отобразить изменения в документе. Таким образом, обеспечивается визуальная остановка индикации перед переходом к следующему шагу в цепочке.

48. Promise API

Класс Promise имеет 5 статических методов:

  1. Promise.all(promises) — дождаться разрешения всех промисов, вернуть массив их результатов. Если какое-либо из данных промисов отклоняется, то это становится ошибкой Promise.all, а результаты всех других промисов игнорируются.

  2. Promise.allSettled(promises) (новое в ES2020) — ожидает выполнения всех промисов и возвращает их результаты в виде массива объектов, содержащих:

    • статус: "выполнен" или "отклонен"

    • значение (если выполнено) или причина (если отклонено).

  3. Promise.race(promises) - дождаться первого промиса об урегулировании и получить его результат/ошибку как результат.

  4. Promise.resolve(value) — создает разрешенное обещание с заданным значением.

  5. Promise.reject(ошибка) – создает отклоненное обещание с данной ошибкой.

Среди этих пяти методов Promise.all, вероятно, наиболее часто используется на практике.

49. Микрозадача

Обработка промисов всегда асинхронна, так как все промисы проходят через внутреннюю очередь «обещаний заданий», также известную как «очередь микрозадач» (терминология ES8).

Поэтому обработчики .then/catch/finally всегда вызываются после завершения текущего кода.

Если нам нужно убедиться, что фрагмент кода выполняется после .then/catch/finally, мы можем добавить его в цепочку .then.

В большинстве движков JavaScript (включая браузеры и Node.js) концепция микрозадач тесно связана с «циклами событий» и «макрозадачами».

50. Async/await

Ключевое слово async перед функцией имеет два эффекта:

  1. Сделайте так, чтобы эта функция всегда возвращала обещание.

  2. Разрешить ожидание внутри этой функции.

Ключевое слово await перед промисом заставляет движок JavaScript ожидать выполнения промиса, а затем:

  1. Если есть ошибка, то выбрасывается исключение — как будто там вызвали throw error.

  2. В противном случае вернуть результат.

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

С async/await нам почти никогда не нужно использовать promise.then/catch, но не забывайте, что они основаны на промисах, потому что иногда (например, в самой внешней области видимости) мы должны использовать эти методы. Кроме того, Promise.all полезен, когда нам нужно одновременно ждать выполнения задач.

51. Generator

  • Генераторы создаются с помощью функций генератора function* f(…) {…}.

  • Внутри генератора (только) есть операция yield.

  • Внешний код и генераторы могут обмениваться результатами через вызовы next/yield.

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

Кроме того, в следующей главе мы узнаем об асинхронных генераторах, которые используются для чтения асинхронно сгенерированных потоков данных (например, постраничных выборок по сети) в цикле for await ... of.

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

52. Асинхронная итерация и генераторы

Обычные итераторы и генераторы хорошо работают с данными, создание которых не требует времени.

Когда мы хотим получать данные асинхронно, с задержкой, мы можем использовать их асинхронные версии и использовать for await..of вместо for..of.

Синтаксические различия между асинхронными итераторами и обычными итераторами:

Синтаксические различия между асинхронными генераторами и обычными генераторами:

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

Мы можем использовать асинхронные генераторы для обработки таких данных. Стоит отметить, что в некоторых средах, таких как среды браузера, есть еще один API, называемый Streams, который предоставляет специальные интерфейсы для обработки таких потоков данных, преобразования данных и передачи данных из одного потока в другой поток данных (например, для загрузки из одного место и немедленно отправить в другое место).

53. Введение в модули

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

  1. Модуль — это файл. Браузер должен использовать
  • По умолчанию отложено.

  • Асинхронность можно использовать во встроенных скриптах.

  • Для загрузки внешних скриптов из другого источника (домена/протокола/порта) требуются заголовки CORS.

  • Дублирующиеся внешние скрипты игнорируются

  1. Модули имеют свою собственную локальную область верхнего уровня и могут обмениваться функциями посредством импорта/экспорта.

  2. Модули всегда используют use strict.

  3. Код модуля выполнен только один раз. Экспорт создан только один раз, а затем разделяет между импортом.

Когда мы используем модули, каждый модуль реализует определенную функцию и экспортирует ее. Затем мы используем импорт, чтобы импортировать его непосредственно в потребности. Браузер автоматически загрузит и разрешит сценарии.

В производственных средах для повышения производительности и по другим причинам разработчики часто используют такие инструменты, какWebpackУпаковочные инструменты, такие как .

54. Экспорт и импорт

  • Перед объявлением класса/функции/… :

    • export [default] class/function/variable ...
  • Автономный экспорт:

    • export {x [as y], ...}.
  • Реэкспорт:

    • export {x [as y], ...} from "module"

    • экспорт * из «модуля» (не реэкспортирует экспорт по умолчанию).

    • экспортировать {по умолчанию [как y]} из «модуля» (повторно экспортирует экспорт по умолчанию).

импорт:

  • Экспорт имени в модуле:

    • import {x [as y], ...} from "module"
  • Экспорт по умолчанию:

    • import x from "module"

    • import {default as x} from "module"

  • все:

    • import * as obj from "module"
  • Импортируйте модуль (его код и запустите), но не назначайте его переменной:

    • import "module"

Мы размещаем операторы импорта/экспорта вверху или внизу скрипта, это не имеет значения.

Итак, технически код, подобный следующему, в порядке:

sayHi();

// ...

import {sayHi} from './say.js'; // 在文件底部导入

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

Обратите внимание, что операторы импорта/экспорта в пределах {...} недействительны.

Условный импорт, подобный этому, недействителен:

if (something) {
  import {sayHi} from "./say.js"; // Error: import must be at top level
}

55. Прокси и отражение

Прокси — это оболочка вокруг объекта, перенаправляющая операции с прокси на объект и, при необходимости, перехватывающая некоторые из этих операций.

Он может обертывать любой тип объекта, включая классы и функции.

Синтаксис:

let proxy = new Proxy(target, {
  /* trap */
});

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

Мы можем захватить:

  • Чтение (получение), запись (установка), удаление (deleteProperty) свойств (даже несуществующих свойств).

  • Вызов функции (применить ловушку).

  • Новая операция (построить ловца).

  • Многие другие действия (полный список см. в начале этой статьи иdocs).

Это позволяет нам создавать «виртуальные» свойства и методы, реализуя значения по умолчанию, наблюдаемые объекты, декораторы функций и т. д.

Мы также можем обернуть объект несколько раз в разные прокси и украсить его несколькими функциями из разных аспектов.

ReflectAPI предназначен для дополненияProxy. Для любого Proxy catcher есть вызов Reflect с теми же параметрами. Мы должны использовать их для переадресации вызовов целевому объекту.

Прокси имеет некоторые ограничения:

  • Встроенные объекты имеют «внутренние слоты», и доступ к этим объектам не может быть проксирован. См. обходной путь выше.

  • То же самое касается полей закрытого класса, так как они также реализуются внутри с помощью слотов. Следовательно, вызовы прокси-методов должны иметь целевой объект, чтобы получить к ним доступ.

  • Строгие проверки равенства для объектов === не могут быть перехвачены.

  • Производительность: тесты зависят от движка, но обычно для доступа к свойствам с простейшими прокси требуется в несколько раз больше времени. На самом деле это имеет значение только для некоторых "узких мест" объектов.

56. Обход DOM

Имея узел DOM, мы можем использовать свойство навигации для доступа к его непосредственным соседям.

Эти свойства в основном делятся на две группы:

  • Для всех узлов: ParentNode, ChildNodes, Firstchild, Lastchild, Previoussibling, NextSibling.

  • Только для узлов элементов: патентерь, дети, персетуэментирование, шипельментирование, превышение температуры, отслеживания.

Некоторые типы элементов DOM, такие как таблицы, предоставляют дополнительные свойства и коллекции для доступа к их содержимому.

57. Поиск: getElement*, querySelector*

Существует 6 основных способов поиска узлов в DOM:

Также: безусловно, наиболее распространенными являются querySelector и querySelectorAll, но getElement(s)By* может быть иногда полезен или может быть найден в старых скриптах.

  • elem.matches(css) используется для проверки соответствия elem заданному селектору CSS.

  • elem.closest(css) используется для поиска ближайшего предка, соответствующего заданному селектору CSS. также проверяется сам элемент.

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

  • elemA.contains(elemB) вернет true, если elemB находится внутри elemA (потомок elemA) или если elemA==elemB.

58. Атрибуты узла: тип, тег и содержимое

Каждый узел DOM принадлежит определенному классу. Эти классы образуют иерархию. Полный набор свойств и методов является результатом наследования.

Основные свойства узла DOM:

nodeType Мы можем использовать это, чтобы увидеть, является ли узел текстовым узлом или узлом элемента. Он имеет числовое значение: 1 для элементов, 3 для текстовых узлов и другие для других типов узлов. Только чтение.

NodeName / Tagname используется для имени элемента, имени метки (кроме режима XML, с заглавной буквы). Для узлов, не являющихся элементами, Nodename описывает, что это такое. Только чтение.

HTML-содержимое элемента innerHTML. можно изменить.

Полный HTML-код элемента externalHTML. Запись в elem.outerHTML не затрагивает сам elem. Вместо этого он заменяется новым HTML во внешнем контексте.

nodeValue/data Содержимое узлов, не являющихся элементами (текст, комментарии). Оба почти одинаковы, мы обычно используем данные. можно изменить.

Текст внутри элемента textContent: HTML минус все . При записи текста текст помещается внутрь элемента, а все специальные символы и теги рассматриваются как текст. Созданный пользователем текст можно безопасно вставлять, а нежелательная вставка HTML предотвращается.

hidden, если установлено значение true, делает то же самое, что и CSS display:none.

Узлы DOM также имеют другие свойства, в зависимости от их класса. Например,элемент (HTMLInputElement) поддерживает значение, тип иЭлементы (HTMLAnchorElement) поддерживают href и т. д. Большинство стандартных атрибутов HTML имеют соответствующие атрибуты DOM.

59. Атрибуты и свойства

  • Атрибут — Контент, написанный в HTML.

  • Свойство — содержимое в объекте DOM.

Краткое сравнение:

elem.hasAttribute(name) — проверяет наличие этого атрибута. Методы управления свойствами:

  • ELEM.GETATTRIBUTE (ИМЯ) — получить значение этой функции.

  • elem.setAttribute(name, value) — установить значение атрибута.

  • elem.removeAttribute(name) — удаляет этот атрибут.

  • elem.attributes — коллекция всех атрибутов.

В большинстве случаев лучше использовать свойства DOM. Атрибуты используются только тогда, когда свойств DOM недостаточно для разработки и они нам очень нужны, например:

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

  • Мы хотим прочитать значение «как написано» в HTML. Соответствующие DOM-атрибуты могут быть разными, например, атрибут href всегда является полным URL-адресом, но нам нужно «сырое» значение.

60. Изменить документ (документ)

  • Как создать новый узел:

    • document.createElement(tag) — создать узел элемента с заданным тегом,

    • document.createTextNode(value) — создать текстовый узел (используется редко),

    • elem.cloneNode(deep) — клонировать элемент, если deep==true, то клонировать с его потомками.

  • Способы вставки и удаления узлов:

    • node.append(...узлы или строки) — вставить в конец узла,

    • node.prepend(...узлы или строки) — вставить в начало узла,

    • node.before(...узлы или строки) — вставить перед узлом,

    • node.after(...узлы или строки) — вставить после узла,

    • node.replaceWith(...узлы или строки) — заменить узел.

    • node.remove() — удалить узел.

Текстовые строки вставляются "как текст".

  • Вот еще способ "по старинке":

    • parent.appendChild(node)

    • parent.insertBefore(node, nextSibling)

    • parent.removeChild(node)

    • parent.replaceChild(newElem, node)

Все эти методы возвращают node.

  • Учитывая некоторый HTML в html, elem.insertAdjacentHTML(where, html) вставит его на основе значения where:

    • "beforebegin" — вставить html перед elem,

    • "afterbegin" — вставить html в начало elem,

    • "beforeend" — вставить html в конец elem,

    • "afterend" — вставить html после elem.

Также есть похожие методы elem.insertAdjacentText и elem.insertAdjacentElement, которые вставляют текстовые строки и элементы, но используются редко.

  • Чтобы добавить HTML к странице до завершения загрузки страницы:

    • document.write(html)

Такой вызов удалит документ после загрузки страницы. Чаще встречается в старых сценариях.

61. Стили и классы

Для управления классами есть два свойства DOM:

  • className — строковое значение, удобное для управления всей коллекцией классов.

  • Классик - объект с добавлением / удалением / переключением / содержит методы, которые поддерживают одно класс хорошо.

Чтобы изменить стиль:

Чтобы прочитать разрешенные стили (для всех классов, после применения всех CSS и вычисления окончательных значений):

  • getComputedStyle(elem, [псевдо]) возвращает объект, похожий на объект стиля и содержащий все классы. Только чтение.

62. Размер элемента и прокрутка

Элементы обладают следующими геометрическими свойствами:

  • offsetParent — ближайший CSS-позиционированный предок, или td, th, table, body.

  • offsetLeft/offsetTop — координаты относительно левого верхнего края offsetParent.

  • offsetWidth/offsetHeight — «внешняя» ширина/высота элемента, включая размер границы.

  • clientLeft/clientTop — Расстояние от внешнего левого верхнего угла элемента до левого верхнего внутреннего угла. Для операционных систем, которые отображают содержимое слева направо, они всегда равны ширине левой/верхней границы. Для операционных систем, которые отображают содержимое справа налево, вертикальная полоса прокрутки находится слева, поэтому clientLeft также включает ширину полосы прокрутки.

  • clientWidth/clientHeight — ширина/высота содержимого, включая отступы, но исключая полосы прокрутки.

  • scrollWidth/scrollHeight — ширина/высота содержимого, как clientWidth/clientHeight, но также включает прокручиваемую невидимую часть элемента.

  • scrollLeft/scrollTop — Начните с верхнего левого угла элемента и прокручивайте ширину/высоту верхней половины элемента.

Все свойства доступны только для чтения, кроме scrollLeft/scrollTop. Если мы изменим scrollLeft/scrollTop, браузер прокрутит соответствующий элемент.

63. Размер окна и прокрутка

геометрия:

  • Ширина/высота видимой части документа (ширина/высота области содержимого): document.documentElement.clientWidth/clientHeight

  • Ширина/высота всего документа, включая прокручиваемую часть:

    let scrollHeight = Math.max( document.body.scrollHeight, document.documentElement.scrollHeight, document.body.offsetHeight, document.documentElement.offsetHeight, document.body.clientHeight, document.documentElement.clientHeight );

прокрутить:

  • Прочитайте текущую прокрутку: window.pageYOffset/pageXOffset.

  • Изменить текущую прокрутку:

    • window.scrollTo(pageX,pageY) — абсолютные координаты,

    • window.scrollBy(x,y) — прокрутка относительно текущей позиции,

    • elem.scrollIntoView(top) — прокручивает, чтобы сделать элемент видимым (элемент выравнивается по верху/низу окна).

64. Введение в события браузера

Существует 3 способа назначения обработчиков событий:

  1. Атрибут HTML: onclick="...".

  2. Свойство DOM (свойство): elem.onclick = функция.

  3. Метод: elem.addEventListener(event, handler[ Phase]) используется для добавления, а removeEventListener используется для удаления.

Возможности HTML используются редко, потому что JavaScript в HTML-тегах выглядит странно и незнакомо. И вы не можете написать слишком много кода в нем.

Свойства DOM работают нормально, но мы не можем назначить несколько обработчиков для определенных событий. Во многих сценариях это ограничение не является серьезным.

Последний способ самый гибкий, но и самый длинный для написания. Есть несколько событий, которые можно использовать только таким образом. Например, transitionend и DOMContentLoaded (упомянутые выше). addEventListener также поддерживает объекты в качестве обработчиков событий. В этом случае, если произойдет событие, будет вызван метод handleEvent.

Как бы вы ни классифицировали обработчик — он получит в качестве первого параметра объект события. Этот объект содержит подробную информацию о том, что произошло.

65. Бульканье и захват

Когда происходит событие — наиболее глубоко вложенный элемент, в котором происходит событие, помечается как «целевой элемент» ( event.target ).

  • Затем событие движется вниз от корня документа к event.target, попутно вызывая назначенный обработчик addEventListener(..., true) (true — это сокращение от {capture: true}).

  • Затем обработчик вызывается для самого целевого элемента.

  • Затем событие всплывает из event.target в корень, вызывая обработчик, назначенный on, HTML-атрибут и addEventListener без третьего параметра или с false/{capture:false} в качестве третьего параметра.

Каждый обработчик может получить доступ к свойствам объекта события:

  • event.target — самый глубокий элемент, вызвавший событие.

  • event.currentTarget(=this) — текущий элемент, обрабатывающий событие (элемент с обработчиком)

  • event.eventPhase – текущая фаза (захват=1, цель=2, всплытие=3).

Любой обработчик событий может остановить событие, вызвав event.stopPropagation() , но это не рекомендуется, потому что мы не уверены, действительно ли всплывающее событие не нужно, возможно, для чего-то совершенно другого.

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

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

То же самое касается обработчиков событий. Код, который устанавливает обработчик для определенного элемента для получения наиболее подробной информации об этом элементе. Для этого может подойти обработчик, специфичный для this , и этот обработчик знает все об элементе. Таким образом, обработчик должен получить шанс первым. Затем его непосредственный родительский элемент также понимает соответствующий контекст, но в меньшей степени, и так далее до самого верхнего элемента, который обрабатывает общую концепцию и запускает последний обработчик.

66. Делегация мероприятия

Он часто используется для добавления одной и той же обработки ко многим похожим элементам, но не ограничивается этим.

алгоритм:

  1. Поместите обработчик на контейнер.

  2. В обработчике — проверяем исходный элемент event.target.

  3. Если событие происходит внутри интересующего нас элемента, то обработайте событие.

выгода:

  • Упрощает инициализацию и экономит память: нет необходимости добавлять множество обработчиков.

  • Меньше кода: не нужно добавлять/удалять обработчики при добавлении или удалении элементов.

  • Модификация DOM: мы можем использовать innerHTML и т. д. для пакетного добавления/удаления элементов.

Делегирование событий также имеет свои ограничения:

  • Во-первых, событие должно всплыть. И некоторые события не всплывают. Кроме того, низкоуровневые обработчики не должны использовать event.stopPropagation().

  • Во-вторых, делегирование может увеличить нагрузку на ЦП, поскольку обработчики уровня контейнера могут реагировать на события в любом месте контейнера, независимо от того, заинтересованы мы в этом событии или нет. Однако обычно нагрузка незначительна, поэтому мы ее не рассматриваем.

67. Поведение браузера по умолчанию

Существует множество вариантов поведения браузера по умолчанию:

  • MouseDown - Начать выбор (Переместите мышь, чтобы выбрать).

  • существуетнажмите - Проверяет/снимает отметку на вводе.

  • отправить - нажмитеИли нажатие клавиши Enter в поле формы вызовет событие, после чего браузер отправит форму.

  • keydown — нажатие клавиши приведет к добавлению символов в поле или запуску других действий.

  • contextmenu — событие происходит, когда щелкают правой кнопкой мыши и инициируется действие, чтобы отобразить контекстное меню браузера.

  • ……есть еще……

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

Чтобы предотвратить поведение по умолчанию, используйте event.preventDefault() или верните false. Второй метод работает только с обработчиками, назначенными через on .

Пассивная: истинная опция addEventListener сообщает браузеру, что это поведение не будет заблокировано. Это полезно для определенных мобильных событий (таких как touchstart и touchmove), чтобы сообщить браузеру, что он не должен ждать завершения всех обработчиков перед прокруткой.

Значение event.defaultPrevented становится истинным, если поведение по умолчанию предотвращено, и ложным в противном случае.

68. Создавайте собственные события

Для генерации события из кода мы сначала нужно создать объект события.

Общий конструктор Event(name, options) принимает произвольное имя события и объект параметров с двумя свойствами:

  • пузырьки: true, если событие должно всплывать.

  • cancelable: true, если event.preventDefault() должен работать.

Конструкторы для других собственных событий, таких как MouseEvent и KeyboardEvent, принимают свойства, специфичные для типа события. Например, clientX для событий мыши.

Для пользовательских событий мы должны использовать конструктор CustomEvent. У него есть дополнительная опция, называемая деталью, которой мы должны назначить данные, относящиеся к событию. Затем все обработчики могут получить к нему доступ как event.detail.

Хотя технически возможно генерировать события браузера, такие как щелчок или нажатие клавиши, мы должны использовать их с осторожностью.

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

Собственные события могут быть сгенерированы:

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

  • Для автоматизированного тестирования «нажмите кнопку» в скрипте и посмотрите, правильно ли реагирует интерфейс.

Пользовательские события с нашими собственными именами обычно создаются для архитектурных целей, чтобы указать, что происходит внутри меню, ползунка, карусели и т. д.

69. События мыши

События мыши имеют следующие свойства:

  • кнопка: кнопка.

  • Комбинация клавиш (истина при нажатии): altKey, ctrlKey, shiftKey и metaKey (Mac).

    • Если вы хотите обрабатывать Ctrl, не забывайте о пользователях Mac, они обычно используют Cmd, поэтому лучше проверить, если (e.metaKey || e.ctrlKey).
  • Относительные координаты окна: clientX/clientY.

  • Относительные координаты документа: pageX/pageY.

Действием браузера по умолчанию для mousedown является выделение текста, которого следует избегать, если это плохо для интерфейса.

70. Перемещение мыши: наведение/отключение, ввод/выход.

Следующий контент должен обратить внимание на:

  • Быстрое перемещение мыши может привести к пропуску промежуточных элементов.

  • События mouseover/out и mouseenter/leave имеют дополнительное свойство: relatedTarget. Это элемент, из/к которому мы пришли, и он дополняет цель.

События mouseover/out запускаются, даже когда мы переходим от родителя к дочернему элементу. Браузер предполагает, что мышь будет одновременно находиться только на одном элементе — самом глубоком.

События mouseenter/leave отличаются в этом отношении: они срабатывают только тогда, когда мышь входит в элемент и покидает его. И не пузырятся.

71. События: изменение, ввод, вырезание, копирование, вставка

Событие изменения данных: