Очки знаний по фронтенд-интервью (1)

внешний интерфейс опрос

предисловие

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

Напоминание: в этой статье в основном дается анализ ответов на такие вопросы интервью, как 1 ~ 5, 7 ~ 8, 10 ~ 15, 18, 20 ~ 21, 24, 29 ~ 30.

Базовые знания

Базовые знания в основном включают следующие аспекты:

  • Основы: базовые знания компьютерных принципов, принципов компиляции, структур данных, алгоритмов, шаблонов проектирования, парадигм программирования и т. д.
  • Грамматика: понимание и использование грамматик, таких как JavaScript, ECMAScript, CSS, TypeScript, HTML, Node.js и т. д.
  • Framework: понимать и использовать принципы React, Vue, Egg, Koa, Express, Webpack и т. д.
  • Инжиниринг: инструменты компиляции, инструменты форматирования, Git, NPM, модульное тестирование, Nginx, PM2, понимание и использование CI/CD
  • Сеть: понимание HTTP, TCP, UDP, WebSocket, Cookie, Session, междоменного доступа, кеша, протокола.
  • Производительность: производительность компиляции, мониторинг, обнаружение белого экрана, SEO, Service Worker и т. д.
  • Плагины: понимание идей дизайна плагинов, таких как Chrome, Vue CLI, Webpack и т. д.
  • Система: Практика для Mac, Windows, конфигурации системы Linux
  • Серверная часть: кеш Redis, база данных, Graphql, SSR, механизм шаблонов и т. д., чтобы понять и использовать

Основание

1. Перечислите известные вам типы компьютерных запоминающих устройств?

Память современного компьютера является центральной, в основном состоит из трех частей ЦП, устройств ввода/вывода и основной памяти. Между собой соответствующие части соединены через коммуникационную шину, в частности, как показано ниже:image.pngНа приведенном выше рисунке показана схема многошинной структуры.Все данные между ЦП, оперативной памятью и устройствами ввода/вывода передаются параллельно через шину.Локальная шина используется для повышения пропускной способности ЦП (процессор не требуется прямой связи с устройством ввода-вывода)./связь устройства вывода), а использование высокоскоростной шины (ближе к ЦП) и шины прямого доступа к памяти для повышения эффективности выполнения высокоскоростного ввода-вывода устройства (периферийная память, локальная сеть и мультимедиа и др.).

Основная память включает оперативную память RAM и постоянную память ROM, из которых ROM можно разделить на MROM (одноразовое), PROM, EPROM и EEPROM. Программы (например, загрузочные программы, прошивка) и данные (например, постоянные данные), хранящиеся в ПЗУ, не будут потеряны после сбоя питания. Оперативная память в основном делится на два типа: статическая ОЗУ (SRAM) и динамическая ОЗУ (DRAM) (существует много типов DRAM, включая SDRAM, RDRAM, CDRAM и т. д.), данные будут потеряны после сбоя питания, и это в основном используется для хранения временных программ или временных переменных данных. Доступ к DRAM обычно относительно медленный. Из-за относительно высоких требований к скорости чтения современных ЦП в ядре ЦП разработаны многоуровневые кэши уровней L1, L2 и L3.Эти кэши в основном состоят из SRAM и обычно имеют более высокие скорости доступа.

2. В каком устройстве компьютера хранится общий код? Как код работает в процессоре?

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

  • устройство управления: Под действием такта адрес инструкции в основной памяти или многоуровневом кэше, на который указывает счетчик программ (ПК), отправляется на адресную шину, а затем команда, соответствующая адресу инструкции, получается и помещается в регистре инструкций (Instruction Register, IR), а затем анализировать операции, которые должна выполнять инструкция, с помощью декодера инструкций (Instruction Decoder, ID) и, наконец, отправлять сигналы управления микрооперациями на другие устройства через операционный контроллер (Operation Controller). Контролер, ОС).
  • арифметическая единица: Если управляющий сигнал, посылаемый блоком управления, имеет арифметическую операцию (сложение, вычитание, умножение, деление, увеличение на 1, вычитание на 1, отрицание и т. д.) или логическую операцию (и, или, отрицание, XOR), он необходимо получить через операционный блок. Обрабатываются расчетные данные блока хранения.
  • единица хранения: Включая кэш-память на кристалле и набор регистров, который является местом хранения временных данных в ЦП. ЦП требует сотни машинных циклов для прямого доступа к данным основной памяти, в то время как доступ к регистрам или кэшам на кристалле занимает всего несколько или десятки машинных циклов.Поэтому внутренние регистры или кэши используются для хранения и извлечения временных данных (для рассчитанные или данные после операции), тем самым повышая эффективность работы ЦП.

Кроме того, компьютерной системе требуется время для выполнения программных инструкций, а время для получения инструкции и выполнения этой инструкции называется циклом инструкции. Командный цикл можно разделить на несколько стадий (цикл выборки инструкции, цикл косвенной адресации, цикл выполнения и цикл прерывания).Каждая стадия в основном завершает базовую операцию, а время выполнения базовой операции называется машинным циклом. Машинный цикл — это частотное деление тактового цикла, например, машинный такт самого классического микроконтроллера 8051 составляет 12 тактовых циклов. Тактовый цикл — это основная единица времени работы ЦП, которую также можно назвать биением пульса или Т-циклом (обратным значением частоты ЦП). Предполагая, что основная частота процессора составляет 1 ГГц (1 Гц означает 1 операцию в секунду), это означает, что такт составляет 1/109 с. Теоретически, чем выше основная частота процессора, тем быстрее выполняются программные инструкции.

3. Что такое инструкции и наборы инструкций?

Инструкции в основной памяти в правой части рисунка выше — это команды обработки, которые ЦП может поддерживать, как правило, включая арифметические инструкции (сложение и вычитание), логические инструкции (и, или и нет), инструкции данных (перемещение, ввод , удаление, загрузка и сохранение), инструкции управления потоком и инструкции завершения программы и т. д. Поскольку ЦП может распознавать только двоичные коды, инструкции состоят из двоичных кодов. Кроме того, набор инструкций называется набором инструкций (например, язык ассемблера является формой набора инструкций).Общие наборы инструкций включают сокращенный набор инструкций (ARM) и сложный набор инструкций (Inter X86). Общий набор инструкций определяет аппаратную архитектуру процессора ЦП и определяет соответствующую работу процессора.

4. В чем разница между сложным набором инструкций и сокращенным набором инструкций?

5. Как работает JavaScript? В чем разница между интерпретируемым и компилируемым языками?

Когда в первых компьютерах был только машинный язык, программирование должно было использовать двоичные числа (0 и 1) для написания программ, а программисты должны были хорошо разбираться в компьютерном оборудовании и наборах команд, что делало программирование сложным и подверженным ошибкам. Чтобы решить проблему программирования машинного языка, постепенно начал появляться символический язык ассемблера (с использованием ADD, SUB, MUL, DIV и других символов для представления сложения, вычитания, умножения и деления). Чтобы компьютер распознал язык ассемблера, язык ассемблера должен быть переведен на машинный язык (набор инструкций процессора), который машина может распознать:image.pngПоскольку система инструкций каждой машины различна, для ее соответствия требуются разные программы на языке ассемблера, поэтому программистам часто необходимо понимать ее аппаратную структуру и систему инструкций для разных машин. Чтобы сгладить систему команд разных машин, чтобы программисты могли уделять больше внимания самому программированию, последовательно появлялись различные проблемно-ориентированные языки программирования высокого уровня, такие как BASIC и C. Показан конкретный процесс на следующем рисунке:image.pngЯзыки программирования высокого уровня сначала переводятся на язык ассемблера или другие промежуточные языки, а затем переводятся на машинные языки для исполнения на разных машинах. Кроме того, между виртуальной машиной на языке ассемблера и машиной на машинном языке существует уровень виртуальной машины операционной системы, который в основном используется для контроля и управления всеми аппаратными и программными ресурсами операционной системы (с непрерывным развитием технологии СБИС). , Программные функции некоторых операционных систем постепенно заменяются аппаратными.Например, текущая операционная система реализовала затвердевание части программы, называемой прошивкой, которая постоянно хранит программу в ПЗУ). Машины с машинным языком также можно продолжать разлагать на микропрограммные машины, переводя каждую машинную инструкцию в набор микроинструкций (микропрограмм) для выполнения.

Программа преобразования языка, предоставляемая вышеуказанной виртуальной машиной, называется компилятором. Ее основная функция заключается в преобразовании исходной программы, написанной на определенном языке, в эквивалентную программу на машинном языке. Функция компилятора показана на следующем рисунке:image.pngНапример, на языке C можно сначала сгенерировать целевые файлы .o и .obj (объектные файлы, т.е. объектные файлы) под Linux и Windows через компилятор gcc, а затем связать объектные файлы с файлами базовой системной библиотеки, файлы библиотеки приложений и файлы запуска в исполняемый файл, который будет выполняться на целевой машине.

Напоминание: заинтересованные студенты могут узнать о принципе работы программы на чипе ARM, включая использование IDE для компиляции программы (встроенный компилятор IDE, основные компиляторы включают ARMCC, IAR и GCC FOR ARM и т. д. Некоторые из этих компиляторов следуют только IDE идет в комплекте и выпускается, и не предоставляет возможности использования самостоятельно, в то время как некоторые компиляторы выпускаются вместе с IDE, а также обеспечивают самостоятельное использование интерфейса командной строки), загрузка программы через последовательный порт (загружается на кодовая область чипа для начального адреса запуска, отображающего адрес области памяти), отображение адреса начальной памяти (включая системную память, флэш-память, встроенную SRAM и т. д.), установка булавки режима запуска программы микросхемы BOOT (например, при отладке кода часто выбирается встроенная SRAM, при выборе флэш-памяти FLASH запускается реальная программа) и т.д.

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

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

image.png

Интерпретаторы и компиляторы имеют много общего, и оба должны анализировать исходную программу и преобразовывать ее в машинный язык, который целевая машина может распознать для выполнения. Просто интерпретатор сразу выполняет соответствующий машинный язык при преобразовании исходной программы (процесс преобразования и выполнения не разделены), тогда как компилятор должен сначала преобразовать все исходные программы в машинный язык и сгенерировать объектные файлы, а затем написать объектные файлы в соответствующую программную память выполняется (разделение процесса преобразования и выполнения). Например, Perl, Scheme и APL используют интерпретатор для преобразования, C и C++ используют для преобразования компилятор, а преобразование Java и JavaScript включает как процесс компиляции, так и процесс интерпретации.

6. Кратко опишите процесс компиляции Babel?

7. Как массивы и функции в JavaScript хранятся в памяти?

Хранение массива в JavaScript необходимо грубо разделить на две ситуации:

  • Массивы данных одного типа выделяют непрерывное пространство памяти.
  • Массивы с данными разных типов используют хэш-карту для выделения памяти.

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

8. В чем разница между механизмом цикла событий в браузерах и Node.js?

Прочтите ссылку:Обмен интервью: два года опыта работы успешно прошли собеседование с Али P6- Event Loop понять это?

9. Каковы преимущества модулей ES6 по сравнению с CommonJS?

10. Как языки программирования высокого уровня компилируются в машинный язык?

11. Из каких этапов обычно состоит компилятор? На каком этапе обычно выполняется проверка типов данных?

12. Какова роль виртуальной машины в процессе компиляции?

13. Что такое промежуточный код (ИК) и какова его роль?

14. Что такое кросс-компиляция?

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

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

Для решения двух вышеперечисленных проблем конструкция компилятора окончательно разбивается на две стадии компиляции: фронтенд (обратите внимание, что это не веб-фронтенд) и бэкэнд. первой проблемы, а для решения второй задачи используется back-end вопрос, как показано на рисунке ниже:image.pngПромежуточное представление (IR) на приведенном выше рисунке представляет собой представление структуры программы. Оно будет ближе к языку ассемблера или набору инструкций, чем AST (последующее объяснение), а также сохранит некоторую расширенную информацию в исходной программе, за исключением Кроме того, он поставляется во многих вариантах, в том числеТрехадресный код (TAC),Статическая форма одиночного назначения (Static Single Assignment Form, SSA)И IR на основе стека и т. Д. Конкретные функции включают в себя:

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

image.pngПоскольку IR может выполнять оптимизацию программы с несколькими итерациями, в компилятор можно вставить новый этап оптимизации, как показано на следующем рисунке:image.pngОптимизатор может обрабатывать IR один или несколько раз, что приводит к более быстрому выполнению (например, поиск инвариантных вычислений в циклах и их оптимизация для уменьшения количества операций) или уменьшению целевых программ, а также может использоваться для генерации большего количества целевых программ с меньше исключений или меньшее энергопотребление. Кроме того, интерфейс и серверная часть также могут быть разделены на несколько этапов обработки, как показано на следующем рисунке:image.pngКаждый проход процесса оптимизации в оптимизаторе может использовать один или несколько методов оптимизации для улучшения кода.Каждый проход процесса в конечном счете является операцией чтения и записи IR, что не только делает оптимизацию более эффективной, но и снижает стоимость Сложность также повышает гибкость оптимизации, позволяя компилятору настраивать различные параметры оптимизации для достижения эффекта комбинированной оптимизации.

15. В чем разница между шаблоном публикации/подписки и шаблоном наблюдателя?

Прочтите ссылку:Реализовать простой MVVM на основе Vue- Шаблон наблюдателя и шаблон публикации/подписки

16. Где обычно используется шаблон декоратора?

17. Расскажите о своем понимании дизайна разделения кода для крупномасштабных проектов? Что такое МОК? Какой шаблон проектирования используется для общего внедрения зависимостей?

18. Перечислите парадигмы программирования, которые вы знаете?

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

  • Процессно-ориентированное программирование (POP)
  • Объектно-ориентированное программирование (ООП)
  • Интерфейсно-ориентированное программирование (IOP)
  • Аспектно-ориентированное программирование (АОП)
  • Функциональное программирование (ФП)
  • Реактивное программирование (РП)
  • Функциональное реактивное программирование (FRP)

Прочитайте ссылку: Если ваше определение парадигмы программирования относительно расплывчато, вы можете продолжить чтениеWhat is the precise definition of programming paradigm?понять больше.

Различные языки могут поддерживать множество различных парадигм программирования, например, язык C поддерживает парадигму POP, языки C++ и Java поддерживают парадигму OOP, язык Swift может поддерживать парадигму FP, а JavaScript в веб-интерфейсе может поддерживать все перечисленные парадигмы программирования. выше .

19. Что такое аспектно-ориентированное программирование (АОП)?

20. Что такое функциональное программирование?

Как следует из названия, функциональное программирование — это способ программирования, в котором используются функции для эффективной обработки данных или потоков данных. В математике тремя элементами функции являются домен, диапазон и соответствующее отношение.Предполагая, что A и B являются непустыми числовыми множествами, для любого числа x в множестве A существует уникальное число f(x), соответствующее ему в множестве B, тогда f можно назвать функцией от A до B, записанной как : у = f (х). В функциональном программировании понятие функции похоже на понятие математической функции, в основном описывающее взаимосвязь между формальным параметром x и возвращаемым значением y.Соответствие, ** как показано на следующем рисунке:

Напоминание: изображение взято изКраткий обзор функционального программирования на JavaScript — учебник для начинающих.

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

Простой пример

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

class MathObject {
  constructor(private value: number) {}
  public add(num: number): MathObject {
    this.value += num;
    return this;
  }
  public multiply(num: number): MathObject {
    this.value *= num;
    return this;
  }
  public getValue(): number {
    return this.value;
  }
}

const a = new MathObject(1);
a.add(1).multiply(2).add(a.multiply(2).getValue()); 

Мы надеемся решить задачу (1 + 2) * 2 + 1 * 2 с помощью приведенной выше программы, но на самом деле вычисляется результат 24, потому что внутри кода естьthis.valueЗначение состояния необходимо отслеживать, что может сделать результаты не такими, как ожидалось. Далее мы используем подход функционального программирования:

function add(a: number, b: number): number {
  return a + b;
}

function multiply(a: number, b: number): number {
  return a * b;
}

const a: number = 1;
const b: number = 2;

add(multiply(add(a, b), b), multiply(a, b));

Результат, рассчитанный вышеприведенной программой, равен 8, что в точности соответствует ожиданиям. мы знаемaddа такжеmultiplyФактическая из двух функцийПереписка, поставивПерепискаЭффективное совмещение и перенос осуществляются для достижения конечного результата расчета. Кроме того, две функции можно более элегантно комбинировать по законам математики:

add(multiply(add(a, b), b), multiply(a, b));

// 根据数学定律分配律:a * b  +  a * c = a * (b + c),得出:
// (a + b) * b + a * b = (2a + b) * b

// 简化上述函数的组合方式
multiply(add(add(a, a), b), b);

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

в общем

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

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

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

Функции

Хотя мы уже не обращаем больше внимания на то, как реализована функция в процессе использования функции (Переписка), но при использовании и проектировании функций необходимо обратить внимание на следующие характеристики:

  • Декларативное программирование
  • Функция первого класса
  • Чистая функция
  • Безгражданство и неизменяемые данные
  • ...

декларативный

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

// 命令式
const array = [0.8, 1.7, 2.5, 3.4];
const filterArray = [];

for (let i = 0; i < array.length; i++) {
  const integer = Math.floor(array[i]);
  if (integer < 2) {
    continue;
  }
  filterArray.push(integer);
}

// 声明式
// map 和 filter 不会修改原有数组,而是产生新的数组返回
[0.8, 1.7, 2.5, 3.4].map((item) => Math.floor(item)).filter((item) => item > 1);

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

гражданин первого класса

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

interface IHello {
  (name: string): string;
  key?: string;
  arr?: number[];
  fn?(name: string): string;
}

// 函数声明提升
console.log(hello instanceof Object); // true

// 函数声明提升
// hello 和其他引用类型的对象一样,都有属性和方法
hello.key = 'key';
hello.arr = [1, 2];
hello.fn = function (name: string) {
  return `hello.fn, ${name}`;
};

// 函数声明提升
// 注意函数表达式不能在声明前执行,例如不能在这里使用 helloCopy('world')
hello('world'); 

// 函数
// 创建新的函数对象,将函数的引用指向变量 hello
// hello 仅仅是变量的名称
function hello(name: string): string {
  return `hello, ${name}`;
}

console.log(hello.key); // key
console.log(hello.arr); // [1,2]
console.log(hello.name); // hello

// 函数表达式
const helloCopy: IHello = hello;
helloCopy('world');

function transferHello(name: string, hello: Hello) {
  return hello('world');
}

// 把函数对象当作实参传递
transferHello('world', helloCopy);

// 把匿名函数当作实参传递
transferHello('world', function (name: string) {
  return `hello, ${name}`;
});

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

чистая функция

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

  • Манипулировать HTTP-запросами
  • Изменяемые данные (включая изменение входных параметров внутри функций)
  • Манипуляции с DOM
  • журнал печати
  • статус системы доступа
  • Работа с файловой системой
  • Работа с базой данных
  • ...

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

// 如果使用 const 声明 min 变量(基本数据类型),则可以保证以下函数的纯粹性
let min: number = 1;

// 非纯函数
// 依赖外部环境变量 min,一旦 min 发生变化则输入和返回不唯一
function isEqual(num: number): boolean {
  return num === min;
}

// 纯函数
function isEqual(num: number): boolean {
  return num === 1;
}

// 非纯函数
function request<T, S>(url: string, params: T): Promise<S> {
  // 会产生请求成功和请求失败两种结果,返回的结果可能不唯一
  return $.getJson(url, params);
}

// 纯函数
function request<T, S>(url: string, params: T) : () => Promise<S> {
  return function() {
    return $.getJson(url, params);
  }
}

Свойства чистых функций придают функциональному программированию следующие свойства:

  • Кэшируемый
  • Портативный
  • Тестируемость (Тестируемость)

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

interface ICache<T> {
  [arg: string]: T;
}

interface ISquare<T> {
  (x: T): T;
}

// 简单的缓存函数(忽略通用性和健壮性)
function memoize<T>(fn: ISquare<T>): ISquare<T> {
  const cache: ICache<T> = {};
  return function (x: T) {
    const arg: string = JSON.stringify(x);
    cache[arg] = cache[arg] || fn.call(fn, x);
    return cache[arg];
  };
}

// 纯函数
function square(x: number): number {
  return x * x;
}

const memoSquare = memoize<number>(square);
memoSquare(4);

// 不会再次调用纯函数 square,而是直接从缓存中获取值
// 由于输入和输出的唯一性,获取缓存结果可靠稳定
// 提升代码的运行效率
memoSquare(4);

Без гражданства и неизменяемые данные

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

Вот отдельное объяснение неизменяемости данных, в JavaScript есть много методов работы с массивами, например:

const arr = [1, 2, 3];

console.log(arr.slice(0, 2)); // [1, 2]
console.log(arr); // [1, 2, 3]
console.log(arr.slice(0, 2)); // [1, 2]
console.log(arr); // [1, 2, 3]

console.log(arr.splice(0, 1)); // [1]
console.log(arr); // [2, 3]
console.log(arr.splice(0, 1)); // [2]
console.log(arr); // [3]

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

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

21. Каковы сценарии использования адаптивного программирования?

Реактивное программирование – этоПаттерн Observer (pub/sub)и лицоасинхронный(Асинхронный)поток данных(поток данных) ираспространение измененийдекларативная парадигма программирования. К основным применимым сценариям реактивного программирования относятся:

  • Непрерывная обработка событий, инициированных пользователем и системой, таких как щелчки мышью, клавиши клавиатуры или сигналы, инициированные устройствами связи и т. д.
  • Ненадежная сеть или обработка связи (например, повторные запросы сети HTTP)
  • Непрерывная асинхронная обработка ввода-вывода
  • Сложная вторичная обработка транзакций (например, событие, включающее несколько последующих сетевых запросов)
  • Высокая одновременная обработка сообщений (например, мгновенный чат)
  • ...

грамматика

22. Как реализовать верхнюю, среднюю и нижнюю трехстрочную верстку, минимальная высота верха и низа 100px, а средняя адаптивная?

23. Как оценить переполнение стиля CSS элемента, чтобы можно было выборочно добавить заголовок или всплывающую подсказку?

24, как сделать так, чтобы левая часть элемента CSS автоматически переполнялась (переполнение слева...)?

The direction CSS property sets the direction of text, table columns, and horizontal overflow. Use rtl for languages written from right to left (like Hebrew or Arabic), and ltr for those written from left to right (like English and most other languages).

Конкретные взгляды:developer.Mozilla.org/en-US/docs/…

25. Что такое песочница? Что делает песочница браузера?

26. Как решить проблему автозаполнения паролей элементов формы в браузере?

27. Каковы различия, преимущества и недостатки маршрутизации Hash и History?

28. Что такое дескрипторы свойств объектов в JavaScript? Каков эффект каждого?

29. Какие API есть в консоли в JavaScript?

The console object provides access to the browser's debugging console (e.g. the Web console in Firefox). The specifics of how it works varies from browser to browser, but there is a de facto set of features that are typically provided.

Вот некоторые из API, которые я часто использую:

  • console.log
  • console.error
  • console.time
  • console.timeEnd
  • console.group

Проверить:developer.Mozilla.org/en-US/docs/…

30. Кратко сравните преимущества и недостатки нескольких асинхронных API, таких как Callback, Promise, Generator и Async?

Эксплойт в JavaScriptмеханизм цикла событий(Цикл событий) может реализовывать неблокирующие асинхронные операции в одном потоке. Например

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

Callback

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

export interface IObj {
  value: string;
  deferExec(): void;
  deferExecAnonymous(): void;
  console(): void;
}

export const obj: IObj = {
  value: 'hello',

  deferExecBind() {
    // 使用箭头函数可达到一样的效果
    setTimeout(this.console.bind(this), 1000);
  },

  deferExec() {
    setTimeout(this.console, 1000);
  },

  console() {
    console.log(this.value);
  },
};

obj.deferExecBind(); // hello
obj.deferExec(); // undefined

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

fs.readFile(fileA, 'utf-8', function (err, data) {
  fs.readFile(fileB, 'utf-8', function (err, data) {
    fs.readFile(fileC, 'utf-8', function (err, data) {
      fs.readFile(fileD, 'utf-8', function (err, data) {
        // 假设在业务中 fileD 的读写依次依赖 fileA、fileB 和 fileC
        // 或者经常也可以在业务中看到多个 HTTP 请求的操作有前后依赖(继发 HTTP 请求)
        // 这些异步任务之间纵向嵌套强耦合,无法进行横向复用
        // 如果某个异步发生变化,那它的所有上层或下层回调可能都需要跟着变化(比如 fileA 和 fileB 的依赖关系倒置)
        // 因此称这种现象为 回调地狱
        // ....
      });
    });
  });
});

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

// 希望延迟 3s 后执行并拿到结果
function getAsyncResult(result: number) {
  setTimeout(() => {
    return result * 3;
  }, 1000);
}

// 尽管这是常规的编程思维方式
const result = getAsyncResult(3000);
// 但是打印 undefined
console.log('result: ', result);

function getAsyncResultWithCb(result: number, cb: (result: number) => void) {
  setTimeout(() => {
    cb(result * 3);
  }, 1000);
}

// 通过回调的形式获取结果
getAsyncResultWithCb(3000, (result) => {
  console.log('result: ', result); // 9000
});

Для стандартных Async API в JavaScript может быть невозможно сделать внешнеtry...catch...способ поймать ошибки:

try {
  setTimeout(() => {
    // 下述是异常代码
    // 你可以在回调函数的内部进行 try...catch...
    console.log(a.b.c)
  }, 1000)

} catch(err) {
  // 这里不会执行
  // 进程会被终止
  console.error(err)
}

Все приведенные выше примеры относятся к стандартным асинхронным API в JavaScript. Если вы используете сторонние асинхронные API и предоставляете возможности обратного вызова, эти API могут быть ненадежными.выполнить реверсирование(Право на выполнение callback-функции находится в сторонней библиотеке) вызывает следующие проблемы:

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

Возьмем простой пример:

interface ILib<T> {
  params: T;
  emit(params: T): void;
  on(callback: (params: T) => void): void;
}

// 假设以下是一个三方库,并发布成了npm 包
export const lib: ILib<string> = {
  params: '',

  emit(params) {
    this.params = params;
  },

  on(callback) {
    try {
      // callback 回调执行权在 lib 上
      // lib 库可以决定回调执行多次
      callback(this.params);
      callback(this.params);
      callback(this.params);
      // lib 库甚至可以决定回调延迟执行
      // 异步执行回调函数
      setTimeout(() => {
        callback(this.params);
      }, 3000);
    } catch (err) {
      // 假设 lib 库的捕获没有抛出任何异常信息
    }
  },
};

// 开发者引入 lib 库开始使用
lib.emit('hello');

lib.on((value) => {
  // 使用者希望 on 里的回调只执行一次
	// 这里的回调函数的执行时机是由三方库 lib 决定
  // 实际上打印四次,并且其中一次是异步执行
  console.log(value);
});

lib.on((value) => {
  // 下述是异常代码
  // 但是执行下述代码不会抛出任何异常信息
  // 开发者无法感知自己的代码设计错误
  console.log(value.a.b.c)
});

Promise

Помимо создания ада обратного вызова, асинхронная форма операции обратного вызова также может вызвать проблемы, которые трудно протестировать. Обещания в ES6 (на основе Promise A +каноническое решение для асинхронного программирования) используетКонечный автоматДля решения проблемы асинхронной обработки объект Promise предоставляет унифицированный API асинхронного программирования со следующими характеристиками:

  • Внешний мир не влияет на состояние выполнения объекта Promise. Асинхронная операция объекта Promise имеет три состояния:pending(в ходе выполнения),fulfilled(успешно) иrejected(Не удалось), только результат асинхронной операции самого объекта Promise может определить текущее состояние выполнения, и любая другая операция не может изменить результат состояния
  • Состояние выполнения объекта Promise неизменно. Есть только два возможных изменения состояния промиса: отpending(в процессе) становитсяfulfilled(успешно) или изpending(в процессе) становитсяrejected(не удалось)

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

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

const promise = new Promise<number>((resolve, reject) => {
  // 状态变更为 fulfilled 并返回结果 1 后不会再变更状态
  resolve(1);
  // 不会变更状态
  reject(4);
});

promise
  .then((result) => {
    // 在 ES 6 中 Promise 的 then 回调执行是异步执行(微任务)
    // 在当前 then 被调用的那轮事件循环(Event Loop)的末尾执行
    console.log('result: ', result);
  })
  .catch((error) => {
    // 不执行
    console.error('error: ', error);
  });

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

// 回调地狱
const doubble = (result: number, callback: (finallResult: number) => void) => {
  // Mock 第一个异步请求
  setTimeout(() => {
    // Mock 第二个异步请求(假设第二个请求的参数依赖第一个请求的返回结果)
    setTimeout(() => {
      callback(result * 2);
    }, 2000);
  }, 1000);
};

doubble(1000, (result) => {
  console.log('result: ', result);
});

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

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

const firstPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    // Mock 异步请求
    // 将 resolve 改成 reject 会被 catch 捕获
    setTimeout(() => resolve(result), 1000);
  });
};

const nextPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    // Mock 异步请求
    // 将 resolve 改成 reject 会被 catch 捕获
    setTimeout(() => resolve(result * 2), 1000);
  });
};

firstPromise(1000)
  .then((result) => {
    return nextPromise(result);
  })
  .then((result) => {
    // 2s 后打印 2000
    console.log('result: ', result);
  })
  // 任何一个 Promise 到达 rejected 状态都能被 catch 捕获
  .catch((err) => {
    console.error('err: ', err);
  });

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

const firstPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    // Mock 异步请求
    setTimeout(() => resolve(result), 1000);
  });
};

const nextPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    // Mock 异步请求
    setTimeout(() => resolve(result * 2), 1000);
  });
};

firstPromise(1000)
  .then((result) => {
    nextPromise(result).then((result) => {
      // 后打印
      console.log('nextPromise result: ', result);
    });
  })
  .then((result) => {
    // 先打印
    // 由于上一个 then 没有返回值,这里打印 undefined
    console.log('firstPromise result: ', result);
  })
  .catch((err) => {
    console.error('err: ', err);
  });

First Promise может зарегистрировать несколькоthen(помещены в очередь на выполнение), и этиthenОн будет выполняться последовательно в соответствии с результатом последнего возвращаемого значения. Кроме того, каждое ОбещаниеthenИсполнение не мешает друг другу. Сделаем простое преобразование примера:

const firstPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    // Mock 异步请求
    setTimeout(() => resolve(result), 1000);
  });
};

const nextPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    // Mock 异步请求
    setTimeout(() => resolve(result * 2), 1000);
  });
};

firstPromise(1000)
  .then((result) => {
    // 返回了 nextPromise 的 then 执行后的结果
    return nextPromise(result).then((result) => {
      return result;
    });
  })
  // 接着 nextPromise 的 then 执行的返回结果继续执行
  .then((result) => {
    // 2s 后打印 2000
    console.log('nextPromise result: ', result);
  })
  .catch((err) => {
    console.error('err: ', err);
  });

Результат выполнения в приведенном выше примере является потому, чтоthenвыполнение возвращает новый объект Promise, и еслиthenПосле выполнения возвращается объект Promise, затем следующийthenСвязанный вызов будет ожидать изменения состояния объекта Promise перед вызовом (чтобы получить результат обработки этого Promise). Далее сосредоточимся на обработке ошибок Promise:

const promise = new Promise<string>((resolve, reject) => {
  // 下述是异常代码
  console.log(a.b.c);
  resolve('hello');
});

promise
  .then((result) => {
    console.log('result: ', result);
  })
  // 去掉 catch 仍然会抛出错误,但不会退出进程终止脚本执行
  .catch((err) => {
    // 执行
    // ReferenceError: a is not defined
    console.error(err);
  });

setTimeout(() => {
  // 继续执行
  console.log('hello world!');
}, 2000);

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

  • Promise.all: подходит для одновременного выполнения нескольких асинхронных задач, но не допускает сбоя ни одной из них
  • Promise.race: Подходит для упреждающего исполнения нескольких асинхронных задач
  • Promise.allSettled: подходит для одновременного выполнения нескольких асинхронных задач, но допускает сбой некоторых задач.

По сравнению с Callback, Promise более элегантен и мощен для асинхронной обработки, но и у него есть некоторые недостатки:

  • Невозможно отменить выполнение обещания
  • Не может пройти за пределы Обещанияtry...catch...Перехват ошибок в виде (Promise внутренне перехватывает ошибки)
  • Состояние одно, и каждое решение может привести только к одному результату состояния, что требует непрерывных цепных вызовов.

Напоминание: Рукописный промис — письменный тестовый вопрос, который очень нравится интервьюерам.Суть в том, чтобы надеяться, что интервьюер сможет правильно понять использование промиса через базовый дизайн.Если вы не знакомы с принципом дизайна промиса, вы можете узнать подробнее об этом или спроектируйте вручную. .

Generator

Promise решает проблему ада обратных вызовов Callback, но также вызывает избыточность кода.Если некоторые асинхронные задачи не поддерживают синтаксис Promise, требуется слой инкапсуляции Promise. Генераторы выводят асинхронное программирование в JavaScript на совершенно новый уровень, благодаря чему разработка и выполнение асинхронного кода выглядят так же, как синхронный код. Простой пример использования Генератора выглядит следующим образом:

const firstPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 2), 1000);
  });
};

const nextPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 3), 1000);
  });
};

// 在 Generator 函数里执行的异步代码看起来和同步代码一致
function* gen(result: number): Generator<Promise<number>, Promise<number>, number> {
  // 异步代码
  const firstResult = yield firstPromise(result)
  console.log('firstResult: ', firstResult) // 2
	// 异步代码
  const nextResult = yield nextPromise(firstResult)
  console.log('nextResult: ', nextResult) // 6
  return nextPromise(firstResult)
}

const g = gen(1)

// 手动执行 Generator 函数
g.next().value.then((res: number) => {
  // 将 firstPromise 的返回值传递给第一个 yield 表单式对应的 firstResult
  return g.next(res).value
}).then((res: number) => {
  // 将 nextPromise 的返回值传递给第二个 yield 表单式对应的 nextResult
  return g.next(res).value
})

Из приведенного выше кода видно, что Generator имеет следующие преимущества перед Promise:

  • Расширенные типы состояний, проходы генератораnextМожет быть сгенерирована различная информация о состоянии, а также может быть сгенерированаreturnСостояние выполнения конечной функции относительно промисаresolveНеизменяемое состояние богаче
  • Выполнение асинхронного кода внутри функции генератора выглядит так же, как и выполнение синхронного кода, которое очень полезно для обслуживания кода
  • Логика выполнения внутри функции Generator отделена от соответствующей логики изменения состояния, что снижает сложность кода.

nextможет постоянно менять состояние так, чтоyieldКод, который продолжает выполняться, может стать очень регулярным, например, как показано выше.Вручную выполнить функцию ГенератораВидно, что его можно инкапсулировать в автоматически исполняемый исполнитель следующим образом:

const firstPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 2), 1000);
  });
};

const nextPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 3), 1000);
  });
};

type Gen =  Generator<Promise<number>, Promise<number>, number>

function* gen(): Gen {
  const firstResult = yield firstPromise(1)
  console.log('firstResult: ', firstResult) // 2
  const nextResult = yield nextPromise(firstResult)
  console.log('nextResult: ', nextResult) // 6
  return nextPromise(firstResult)
}

// Generator 自动执行器
function co(gen: () => Gen) {
  const g = gen()
  function next(data: number) {
    const result = g.next(data)
    if(result.done) {
      return result.value
    }
    result.value.then(data => {
      // 通过递归的方式处理相同的逻辑
      next(data)
    })
  }
  // 第一次调用 next 主要用于启动 Generator 函数
  // 内部指针会从函数头部开始执行,直到遇到第一个 yield 表达式
  // 因此第一次 next 传递的参数没有任何含义(这里传递只是为了防止 TS 报错)
  next(0)
}

co(gen)

Советы:TJ Holowaychuk Дизайн генератора автоматического исполнителяCo, предпосылкой использования Co являетсяyieldЗа командой должен следовать объект Promise или функция Thunk. Co также может поддерживать параллельную асинхронную обработку.Документация API.

Следует отметить, что возвращаемое значение функции Generator является объектом итератора Iterator, как показано ниже:

const firstPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 2), 1000);
  });
};

const nextPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 3), 1000);
  });
};

type Gen = Generator<Promise<number>>;

function* gen(): Gen {
  yield firstPromise(1);
  yield nextPromise(2);
}

// 注意使用 next 是继发执行,而这里是并发执行
Promise.all([...gen()]).then((res) => {
  console.log('res: ', res);
});

for (const promise of gen()) {
  promise.then((res) => {
    console.log('res: ', res);
  });
}

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

const firstPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    // 需要注意这里的reject 没有被捕获
    setTimeout(() => reject(result * 2), 1000);
  });
};

const nextPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 3), 1000);
  });
};

type Gen = Generator<Promise<number>>;

function* gen(): Gen {
  try {
    yield firstPromise(1);
    yield nextPromise(2);
  } catch (err) {
    console.error('Generator 函数错误捕获: ', err);
  }
}

try {
  const g = gen();
  g.next();
  // 返回 Promise 后还需要通过 Promise.prototype.catch 进行错误捕获
  g.next();
  // Generator 函数错误捕获
  g.throw('err');
  // 执行器错误捕获
  g.throw('err');
} catch (err) {
  console.error('执行错误捕获: ', err);
}

В использованииg.throwТакже необходимо обратить внимание на следующие вещи:

  • Если сама функция генератора не перехватывает ошибку, то ошибка, выдаваемая внутри функции генератора, может быть ошибкой, обнаруженной в точке выполнения.
  • Если внутри функции Generator и в месте выполнения нет перехвата ошибок, завершить процесс и выдать сообщение об ошибке
  • Если не выполненоg.next,ноg.throwНе будет перехватываться в функции-генераторе (поскольку указатель выполнения не запускает выполнение функции-генератора), а захват ошибки выполнения может выполняться в точке выполнения

Async

Async — это синтаксический сахар для функций Generator.По сравнению с Generator, Async имеет следующие характеристики:

  • Встроенный исполнитель: Генераторные функции должны проектировать ручные исполнители или общие исполнители (такие как соисполнители) для выполнения.Асинхронный синтаксис имеет встроенные автоматические исполнители, поэтому вам не нужно заботиться о шагах выполнения при разработке кода.
  • yieldКоманда без ограничений: при использовании Co executor в Generatoryieldдолжен быть объектом Promise или функцией Thunk послеawaitмогут сопровождаться объектами Promise или объектами примитивных типов данных, числами, строками, логическими значениями и т. д.Promise.resolve()упаковка)
  • Обещания возврата:asyncВозвращаемое значение функции промиса является объектом (примитивные возвращаемые типы данных инкапсулированы в промис), и, следовательно, также какawaitПараметр команды , который является более кратким и практичным, чем итератор Iterator, возвращаемый генератором.

Возьмем простой пример:

const firstPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 2), 1000);
  });
};

const nextPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 3), 1000);
  });
};

async function co() {
  const firstResult = await firstPromise(1);
  // 1s 后打印 2
  console.log('firstResult: ', firstResult); 
  // 等待 firstPromise 的状态发生变化后执行
  const nextResult = await nextPromise(firstResult);
  // 2s 后打印 6
  console.log('nextResult: ', nextResult); 
  return nextResult;
}

co();

co().then((res) => {
  console.log('res: ', res); // 6
});

Как видно из приведенного выше примера,asyncСвойства функции следующие:

  • передачаasyncТо, что возвращается после функции, является объектом Promise, который передается черезthenОбратный вызов можно получить внутри асинхронной функции.returnВозвращаемое значение утверждения
  • передачаasyncОбъект Promise, возвращаемый после того, как функция должна ждать всех внутреннихawaitСоответствующее обещание выполняется (это делаетasyncФункция может блокировать выполнение) до того, как произойдет изменение состояния, если только она не будет обнаружена на полпути.returnутверждение
  • awaitПосле команды, если это объект Promise, она вернет результат обработки объекта Promise, если это примитивный тип данных, она вернет напрямую исходный тип данных.

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

const firstPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 2), 1000);
  });
};

const nextPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 3), 1000);
  });
};

async function co() {
  return await Promise.all([firstPromise(1), nextPromise(1)]);
}

co().then((res) => {
  console.log('res: ', res); // [2,3]
});

В дополнение к использованию API параллельного выполнения, который поставляется с промисами, с этим также можно справиться, предварительно запустив все промисы одновременно:

const firstPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    console.log('firstPromise');
    setTimeout(() => resolve(result * 2), 10000);
  });
};

const nextPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    console.log('nextPromise');
    setTimeout(() => resolve(result * 3), 1000);
  });
};

async function co() {
  // 执行 firstPromise
  const first = firstPromise(1);
  // 和 firstPromise 同时执行 nextPromise
  const next = nextPromise(1);
  // 等待 firstPromise 结果回来
  const firstResult = await first;
  console.log('firstResult: ', firstResult);
  // 等待 nextPromise 结果回来
  const nextResult = await next;
  console.log('nextResult: ', nextResult);
  return nextResult;
}

co().then((res) => {
  console.log('res: ', res); // 3
});

Обработка ошибок Async проще, чем у Generator. Конкретный пример выглядит следующим образом:

const firstPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    // Promise 决断错误
    setTimeout(() => reject(result * 2), 1000);
  });
};

const nextPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 3), 1000);
  });
};

async function co() {
  const firstResult = await firstPromise(1);
  console.log('firstResult: ', firstResult);
  const nextResult = await nextPromise(1);
  console.log('nextResult: ', nextResult);
  return nextResult;
}

co()
  .then((res) => {
    console.log('res: ', res);
  })
  .catch((err) => {
    console.error('err: ', err); // err: 2
  });

asyncОшибка, вызванная внутри функции, приведет к тому, что объект Promise, возвращаемый функцией, станетrejectedсостояние, которое может бытьcatchЗахватите, приведенный выше код — это просто грубая детальная отказоустойчивость, если хотитеfirstPromiseМожно продолжить после ошибкиnextPromise, вы можете пройтиtry...catch...существуетasyncЛокальный отлов ошибок в функции:

const firstPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    // Promise 决断错误
    setTimeout(() => reject(result * 2), 1000);
  });
};

const nextPromise = (result: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 3), 1000);
  });
};

async function co() {
  try {
    await firstPromise(1);
  } catch (err) {
    console.error('err: ', err); // err: 2
  }
  
  // nextPromise 继续执行
  const nextResult = await nextPromise(1);
  return nextResult;
}

co()
  .then((res) => {
    console.log('res: ', res); // res: 3
  })
  .catch((err) => {
    console.error('err: ', err);
  });

Напоминание: обратный вызов — это метод программирования, часто используемый в Node.js. Многие нативные API в Node.js асинхронно разработаны в форме обратного вызова. В ранних версиях Node.js обратный вызов и обещание часто смешивались. Асинхронный синтаксис не был удачным. поддерживается давно. Если вас интересует Node.js и его альтернатива Deno, посмотрите классический доклад Райана Даля на TS Conf 2019.Deno is a New Way to JavaScript.

31. Сколько параметров имеет Object.defineProperty? Что делает каждый?

32. В чем разница между Object.defineProperty и прокси ES6?

Прочтите ссылку:Реализовать MVVM на основе Vue- Осуществление захвата данных.

33. Каковы сценарии использования Symbol, Map и Decorator в ES6? Или вы видели использование этих API в исходном коде каких библиотек?

34. Зачем использовать TypeScript и в чем преимущества TypeScript перед JavaScript?

35. В чем разница между const и readonly в TypeScript? Разница между перечислением и постоянным перечислением? Разница между интерфейсом и псевдонимом типа?

36. Какова роль любого типа в TypeScript?

37. В чем разница между any, never, unknown и void в TypeScript?

38. Может ли интерфейс в TypeScript объявлять функцию/массив/класс (индексируемый)?

39. Можно ли использовать String, Number, Boolean, Symbol, Object и т. д. для объявления типов в TypeScript?

40. В чем разница между этим в TypeScript и этим в JavaScript?

41. Каковы меры предосторожности при использовании союзов в TypeScript?

42. Как TypeScript разрабатывает объявление класса?

43. Как комбинировать ключи типов перечисления в TypeScript?

44. Что означают такие символы, как ?., ??, !., _, ** в TypeScript?

45. Какие есть предопределенные условные типы в TypeScript?

46. ​​Кратко расскажите о механизме загрузки модулей TypeScript?

47. Кратко расскажите о своем понимании совместимости типов TypeScript? Простое понимание антивариантности, бивариантности, ковариации и контравариантности?

48. Будут ли побочные эффекты расширения объекта в TypeScript?

49. Имеют ли объявления интерфейсов, типов и перечислений в TypeScript функции области видимости?

50. Можно ли объединить одноименный интерфейс или одноименный интерфейс и класс в TypeScript?

51. Как заставить проект TypeScript импортировать и распознавать пакет библиотеки npm, скомпилированный в JavaScript?

52. Какая информация об элементе конфигурации содержится в файле tsconfig.json TypeScript?

53. Как установить псевдоним пути для импорта модуля в TypeScript?

Рамка

54. Каковы периодические функции компонентов класса React? Каков эффект каждого?

55. Могут ли запросы в компонентах React Class инициироваться в componentWillMount? Почему?

56. В чем разница между компонентами React Class и React Hook?

57. Каковы преимущества и недостатки функций высшего порядка и пользовательских хуков в React?

58. Кратко объясните принцип работы useState и useEffect в React Hook?

59. Как React обнаруживает повторный рендеринг, что вызывает повторный рендеринг и как избежать повторного рендеринга?

60. Какие параметры есть у useEffect в React Hook и как обнаружить изменения в зависимостях массива?

61. Как монитор React useEffect изменяет зависимости массива?

62. Какая связь между React Hook и замыканием?

63, React in useState, как инициализируются данные?

64. Перечислите ваши часто используемые методы оптимизации производительности React?

65. Как разбираются и реализуются директивы в шаблоне Vue 2.x?

66. Кратко объясните полный механизм работы Vue 2.x?

67. Кратко представить дизайн рамок Element UI?

68. Как понять, что Vue — прогрессивный фреймворк?

69. Каковы способы реализации перекрестной коммуникации в Vue?

70. Как отзывчивые данные в Vue отслеживают глубинные свойства объекта?

71. В чем разница между MVVM, MVC и MVP? Какие сценарии применения у них есть? ,

72. Что такое структура MVVM?

проект

73. Каковы функции Vue CLI 3.x? Вы понимаете систему плагинов Vue CLI 3.x?

74. Как собирается Webpack в Vue CLI 3.x?

75, Vue 2.x Как поддерживать TeampStry Grammar?

76. Как настроить среду, чтобы проект JavaScript мог поддерживать синтаксис TypeScript?

77. Как выполнить проверку Lint на TypeScript? В чем разница между ESLint и TSLint?

78. Как Node.js поддерживает синтаксис TypeScript?

79. Как TypeScript автоматически генерирует файл объявления пакета библиотеки?

80. Каковы ограничения поддержки TypeScript в Babel?

81. В чем разница между загрузчиком и плагином в Webpack?

82. Как обеспечить позиционирование Sourcemap, аналогичное синтаксису JSX в Webpack?

83. Как указать адрес импорта при публикации пакетов Npm?

84. Как опубликовать конкретную папку проекта разработки в качестве корневого каталога пакета Npm?

85. Как опубликовать пакет Npm, поддерживающий механизм Tree Shaking?

86. Какова роль peerDependencies в пакете Npm?

87. Как изящно отлаживать пакет Npm, который нужно выпустить?

88. Как генерировать журналы версии при разработке некоторых библиотечных пакетов?

89. Знаете ли вы о подмодулях Git (Submodule)? Кратко опишите роль подмодулей Git?

90. Как Git изменяет отправленную информацию о коммите?

91. Как Git отменяет коммит и сохраняет предыдущие изменения?

92. Как Git игнорирует зафиксированные файлы?

93. Как стандартизировать инструкции Git по коммитам (информация о коммитах) при использовании Git?

94. Кратко опишите структурный состав спецификации представления, которая соответствует спецификации Angular?

95, Commit и информация о том, как связаны проблемы с Github?

96. Какая роль в проекте?

97. Каковы функции клиентских и серверных хуков в Git Hook?

98. Какие хуки обычно используются в Git Hook?

99. В чем разница между хуками pre-commit и commit-msg? Для чего можно использовать каждый?

Каков принцип создания Git Hook с помощью таких инструментов, как 100, husky и gook?

101. Как спроектировать общий Git Hook?

102. Можно ли разработать Git Hook с помощью скриптов Node? Как это сделать?

103. Как убедиться, что в загруженном другими коде нет ошибок Lint? Как я могу убедиться, что мой код строится без ошибок Lint?

104. Как выполнить проверку Lint в Vs Code? Как сохранить форматирование в Vs Code?

105. В чем разница между ESLint и Prettier? Будут ли проблемы, когда они будут работать вместе?

106. Как эффективно определить правила формата, которые могут конфликтовать между ESLint и Prettier? Как решить такие конфликты правил?

107. Как ESLint может печатать сообщения об ошибках проверки в режиме реального времени при выполнении горячей замены модуля в обычном проекте строительных лесов?

108. Расскажите о своем понимании SourceMap?

109. Как отлаживать код Node.js? Как отлаживать код Node.js TypeScript? Как отлаживать код Node.js в браузере?

110, ссылаясь на все инструменты сборки, вы знаете и говорите о преимуществах и недостатках этих инструментов? Как следует выбирать эти строительные инструменты в различных сценариях?

111. В чем разница между пользователем и рабочей областью в конфигурации VS Code?

112. Может ли плагин VS Code работать только для текущего проекта?

113. Какие типы тестов вы знаете?

114. Какие среды тестирования вы знаете?

115. Что такое тест e2e? Какие существуют тестовые фреймворки для e2e?

116. Предположим, что есть алгоритм сортировки вставки, как установить флажок «Проверьте алгоритм»?

Интернет

117. Как служба CDN обеспечивает ускорение сети?

118. Использует ли WebSocket протокол TCP или UDP?

119. Что такое симплексная, полудуплексная и полнодуплексная связь?

120. Кратко опишите процесс передачи протокола протокола HTTP, отправляющего URL-запрос с доменным именем? (DNS, TCP, IP, ссылка)

121. Что такое прямой прокси? Что такое обратный прокси?

122. Могут ли файлы cookie создаваться на стороне сервера? Каков рабочий процесс после создания файла cookie на сервере?

123. В чем разница и связь между сеансом и файлом cookie? Как сделать временное и постоянное хранилище сеансов?

124. Как предотвратить XSS-атаки при установке файлов cookie?

125. Кратко опишите процесс реализации безпользовательского входа? Какие проблемы с безопасностью могут возникнуть? Как зашифровать пароль пользователя для входа?

126. Какие есть способы увеличить скорость передачи в HTTP? Каковы наиболее часто используемые методы кодирования контента?

127. В случае внезапного прерывания процесса передачи изображений, как после восстановления возобновить передачу с предыдущего прерывания?

128. Что такое агент? Что такое шлюз? Какова роль прокси и шлюзов?

129. Почему HTTPS более безопасен и надежен, чем HTTP?

130. Что такое шифрование с симметричным ключом (общим ключом)? Что такое шифрование с асимметричным ключом (открытым ключом)? Что более безопасно?

131. Каковы, по вашему мнению, недостатки протокола HTTP в настоящее время?

представление

133. Как идентифицировать форму в элементе формы в React, чтобы добиться минимальной детализации/стоимости рендеринга?

134. Какие методы вы можете придумать для контроля затрат на рендеринг при разработке React?

плагин

135. Как устроена система плагинов Vue CLI 3.x?

136, подключаемый модуль механизма Webpack, как разработать?

система

137. В чем разница между \r\n (CRLF) и \n (LF)? (Нижний правый угол Vs Code можно переключать)

138. Какова функция /dev/null?

139. Как установить псевдоним команды в терминале Mac?

140. Как установить переменные окружения в Windows?

141. Различает ли файловая операционная система Mac верхний и нижний регистр пути к файлу по умолчанию?

142. Как установить абсолютный путь к файлу при написании сценария Shell?

задняя часть

143. В чем разница и связь между сеансом и файлом cookie? Как сделать временное и постоянное хранилище сеансов?

144. Как развернуть приложения Node.js? Как быть с согласованностью сессий при балансировке нагрузки?

145. Как повысить стабильность работы кода Node.js?

146. В чем разница между GraphQL и Restful?

147. Как работает Vue SSR? Как отображать данные Vuex изоморфно?

148, каковы преимущества и недостатки технологии SSR и технологии SPA?

149. Как справиться с чрезмерным давлением рендеринга HTML в Node.js?

деловое мышление

Бизнес-мышление — это скорее конкретная деловая практика в сочетании с широтой и глубиной базовых знаний, которые в основном включают следующие аспекты:

  • Инжиниринг: развертывание кода, проектирование процессов CI/CD, Jenkins, Gitlab, Docker и т. д.
  • Универсальность: дизайн фреймворка, такой как строительные леса, SDK, библиотека компонентов и т. д.
  • Среда приложения: гибрид, микро-интерфейс, BFF, монорепозиторий
  • Визуализируйте:
  • Низкий уровень кода: общий дизайн формы, общий дизайн макета, общий дизайн страницы, дизайн протокола JSON Schema и т. д.
  • Тестирование: E2E-тестирование, модульное тестирование, тестовое покрытие, тестовые отчеты и т. д.
  • Бизнес: данные, опыт, сложность, мониторинг

Инжиниринг

150. Какие инструменты CI/CD вы знаете? Сталкивались ли вы с подобным процессом в проекте?

151. Если вас попросили осуществить веб-интерфейс CI / CD Engineer Engineer Engineer Engineering и Development, как бы вы это спроектировали?

152. Какие у вас есть решения, если нам нужно преобразовать ресурсы онлайн-продуктов (например, изображения) в существующих проектах в локальные приватизированные ресурсы?

153. Как использовать Vue CLI 3.x для настройки скаффолда? Например, i18n, axios, Element UI, защита маршрутизации и т. д. автоматически интегрируются внутри?

154. Как Jenkins взаимодействует со сценариями Node.js для проектирования CI/CD?

общность

155. Если бы вас попросили разработать общий проект строительных лесов, как бы вы его спроектировали? Какими возможностями вообще должны обладать общие леса?

156. Если бы вас попросили разработать общую библиотеку инструментов, как бы вы ее разработали? Какими возможностями обычно должна обладать общая библиотека инструментов?

157. Предположим, вы реализуете библиотеку компонентов React или Vue для разработки демонстрационного документа, как бы вы ее разработали? Какие функции должны выполнять разработанные документы?

158. Как вы разрабатываете документы API при разработке пакета библиотеки инструментов?

структура приложения

159. Расскажите о понимании Electron, Nw.js, CEF, Flutter и нативной разработки?

160. Расскажите о понимании HotFix в настольных приложениях?

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

бизнес

162. Что такое единый вход? Как сделать единый вход?

163. Как составить план интернационализации проекта?

164. Как сделать план мониторинга и внедрения проекта?

165. Как обеспечить стабильность проекта (мониторинг, оттенки серого, понижение версии ошибок, откат...)?

166. Какие оптимизации производительности следует учитывать для фоновых приложений общего управления?

167. Кратко опишите некоторые случаи и технические решения для улучшения работы над проектом (скелетный экран, обработка загрузки, кэширование, уменьшение количества ошибок, повтор запроса...)?

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

низкий код

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

170. Какие возможности должна иметь общая платформа с низким кодом?

Письменная тестовая практика

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

  • регулярное выражение
  • алгоритм
  • структура данных
  • Шаблоны проектирования
  • Часть принципа реализации фреймворка
  • Синтаксис TypeScript
  • Разбор шаблона

структура данных

171. Используйте синтаксис TypeScript для преобразования плоских данных без иерархии в данные с древовидной структурой.

// 扁平数据
[{
  name: '文本1',
  parent: null,
  id: 1,
}, {
  name: '文本2',
  id: 2,
  parent: 1
}, {
  name: '文本3',
  parent: 2,
  id: 3,
}]

// 树状数据
[{
  name: '文本1',
  id: 1,
  children: [{
    name: '文本2',
    id: 2,
    children: [{
      name: '文本3',
      id: 3
    }]
  }]
}]

Разбор шаблона

172. Реализуйте простой шаблонизатор

const template = '嗨,{{ info.name.value }}您好,今天是星期 {{ day.value }}';

const data = {
  info: {
    name: {
      value: '张三'
    }
  },
  day: {
    value: '三'
  }
};

render(template, data); // 嗨,张三您好,今天是星期三

Шаблоны проектирования

173. Простая реализация модели публикации/подписки

регулярное выражение

174. Сопоставьте xxx в const a = require('xxx') в строке