- Автор: Чен Даютоу
- гитхаб:KRISACHAN
Процесс браузера Chrome
На устройствах с низким уровнем ресурсов объедините службу с процессом браузера.
основной процесс браузера
-
Отвечает за отображение интерфейса браузера
-
Управление, создание и уничтожение отдельных страниц
-
рисует результат процесса рендеринга на пользовательском интерфейсе
-
Управление сетевыми ресурсами
Процесс графического процессора
- для 3D-рендеринга
сетевой процесс
- инициировать сетевой запрос
процесс плагина
- Обработка сторонних плагинов, работающая в песочнице
процесс рендеринга
-
рендеринг страницы
-
выполнение скрипта
-
обработка событий
процесс передачи по сети
Сгенерировать сообщение HTTP-запроса
-
Введите URL
-
Браузер браузер анализирует URL
-
Создание информации HTTP-запроса
-
получил ответ
код состояния имея в виду 1xx Уведомлять о ходе и статусе запроса 2xx успех 3xx Указывает, что дополнительное действие требуется 4xx ошибка клиента 5xx Ошибка сервера
Запросите у DNS-сервера IP-адрес веб-сервера.
-
Библиотека сокетов предоставляет функцию запроса IP-адреса.
-
Сделать запрос к DNS-серверу через резолвер
Большой ретранслятор DNS-серверов по всему миру
-
Найдите соответствующий DNS-сервер и получите IP-адрес
-
Ускорение ответов DNS-сервера за счет кэширования
Стек протоколов делегирования для отправки сообщения
Стек протоколов отправляет и получает данные по протоколу TCP.
- создать сокет
- Браузер, почта и другие общие приложения используют TCP для отправки и получения данных.
- UDP используется при отправке и получении коротких управляющих данных, таких как запрос DNS.
- подключиться к серверу
Браузер вызывает Socket.connect
- Создайте заголовок, представляющий информацию об управлении соединением в модуле TCP.
- Найдите сокет для подключения по номерам портов отправителя и получателя в заголовке TCP.
- Отправка и получение данных
Браузер вызывает Socket.write
-
Передать сообщение HTTP-запроса в стек протоколов
-
Разделите большие данные, добавьте заголовок TCP к каждой части разделенных данных и отправьте их модулем IP.
-
Используйте номер ACK, чтобы подтвердить получение сетевого пакета.
-
Отрегулируйте время ожидания номера ACK на основе среднего времени прохождения сетевых пакетов туда и обратно.
-
Эффективно управляйте номерами ACK с помощью Windows
-
ACK и слияние окон
-
Получить ответное HTTP-сообщение
- Отсоединяем и снимаем патрубок трубы
Браузер вызывает Socket.close
-
Отключить после отправки данных
-
удалить сокет
- Клиент отправляет FIN
- Сервер возвращает номер ACK
- Сервер отправляет FIN
- Клиент возвращает номер ACK
перекрестный домен
Та же политика происхождения
Политика того же источника — это важная политика безопасности, которая ограничивает взаимодействие документа источника или загружаемых им сценариев с ресурсами из другого источника. Это может помочь заблокировать вредоносные документы и уменьшить возможные векторы атак.
Если два URL-адресаprotocol,port(если указано) иhostТаким же образом, два URL-адреса являются гомологичными.
Например:
URL | результат | причина |
---|---|---|
http://store.company.com/dir2/other.html |
гомология | только путь разный |
http://store.company.com/dir/inner/another.html |
гомология | только путь разный |
https://store.company.com/secure.html |
потерпеть неудачу | разные протоколы |
http://store.company.com:81/dir/etc.html |
потерпеть неудачу | разные порты(http:// Порт по умолчанию 80) |
http://news.company.com/dir/other.html |
потерпеть неудачу | разные хосты |
Основная междоменная обработка
JSONP
Принцип JSONP заключается в том, что на запросы статических ресурсов не влияет политика одного и того же источника. Реализация выглядит следующим образом:
const script = document.createElement('script')
script.type = 'text/javascript'
script.src = 'https://www.domain.com/a?data=1&callback=cb'
const cb = res => {
console.log(JSON.stringify(res))
}
CORS
CORS: совместное использование ресурсов между источниками (CORS) — это механизм, который использует дополнительные заголовки HTTP, чтобы сообщить браузерам, что веб-приложениям, работающим в одном источнике (домене), разрешен доступ к указанным ресурсам с серверов в разных источниках.
Различные серверные коды реализованы следующим образом:
// 根据不同语言规则,具体语法有所不同,此处以NodeJs的express为例
//设置跨域访问
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
next();
});
Nginx реализован следующим образом:
server {
...
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Origin $http_origin;
location /file {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods $http_access_control_request_method;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Headers $http_access_control_request_headers;
add_header Access-Control-Max-Age 1728000;
return 204;
}
}
...
}
Сетевой протокол
TCP
Протокол управления передачей (TCP, протокол управления передачей) — это ориентированный на соединение, надежный протокол связи на транспортном уровне на основе потока байтов, определенный RFC 793 IETF.
- потоковый подход
- ориентированный на соединение
- повторная передача потерянных пакетов
- Гарантированный порядок данных
UDP
Набор интернет-протоколов поддерживает транспортный протокол без установления соединения, называемый протоколом пользовательских дейтаграмм (UDP). UDP позволяет приложениям отправлять инкапсулированные IP-пакеты без установления соединения. RFC 768 описывает UDP.
- UDP — это протокол без установления соединения, то есть он не устанавливает соединение с терминалом
- Информация о пакете UDP составляет всего 8 байт.
- UDP ориентирован на пакеты. Не разделять и не объединять, а сохранять границы этих пакетов
- UDP может терять пакеты
- UDP не гарантирует порядок данных
HTTP
-
HTTP/0.9: GET, формирование признаков без сохранения состояния
-
HTTP/1.0: поддержка POST, HEAD, добавление заголовков запросов и заголовков ответов, поддержка отправки файлов в любом формате, добавление кодов состояния, поддержка набора нескольких символов, отправка нескольких частей, разрешения, кэширование, кодирование контента и т. д.
-
HTTP/1.1: длинное соединение по умолчанию, 6 TCP-соединений одновременно, фрагментация доменного имени CDN
-
HTTPS: HTTP + TLS (Асимметричное шифрованиеа такжеСимметричное шифрование)
- Клиент отправляет https-запрос, чтобы запросить у сервера установку SSL-соединения.
- Сервер получает запрос https, подает заявку на цифровой сертификат или создает его, получает открытый ключ и закрытый ключ сервера и отправляет открытый ключ клиенту.
- Клиент проверяет открытый ключ. Если проверка не пройдена, будет выдано предупреждение. Если проверка пройдена, будет сгенерирован случайный закрытый ключ клиента.
- Клиент симметрично шифрует открытый ключ и закрытый ключ клиента и передает их на сервер.
- После того, как сервер получает зашифрованный контент, он выполняет асимметричное дешифрование с помощью закрытого ключа сервера, чтобы получить закрытый ключ клиента.
- Сервер симметрично шифрует закрытый ключ и контент клиента и отправляет зашифрованный контент клиенту.
- После того, как клиент получает зашифрованное содержимое, он выполняет симметричное дешифрование с помощью закрытого ключа клиента для получения содержимого.
-
HTTP/2.0: мультиплексирование (одно TCP-соединение может обрабатывать несколько запросов), активная отправка сервером, потоковая передача.
-
HTTP/3: протокол QUIC, реализованный через UDP.
- Установите хорошее соединение HTTP2
- Отправить кадр расширения HTTP2
- Установить соединение с помощью QUIC
- Отключить HTTP2 в случае успеха
- Обновление до соединений HTTP3
Примечание: RTT = время приема-передачи
процесс рендеринга страницы
Построение деревьев DOM, расчеты стилей, этапы компоновки, наслоение, рисование, фрагментация, растеризация и композитинг
-
Создать DOM-дерево
- Пройдитесь по всем видимым узлам в дереве DOM и добавьте эти узлы в дерево компоновки.
- Невидимые узлы игнорируются деревом компоновки.
-
расчет стиля
- Создать дерево CSSOM
- Преобразование значений свойств в таблицах стилей
- Вычислить стиль узла DOM
-
Создать дерево компоновки
-
Слоистый
- Сгенерировать дерево слоев (LayerTree)
- Элементы со свойствами контекста наложения переносятся на отдельный слой.
- Места, которые необходимо обрезать, также будут созданы как слои.
- рисунок слоя
-
Преобразование слоя в растровое изображение
-
Составление растрового изображения и его отображение на странице
механизм обновления страницы
- Обновлены геометрические свойства элементов (переформатирование)
- Обновить свойство рисования элемента (перекрасить)
- прямой синтез
- Свойства CSS3 можно сразу перейти к этому шагу.
Механизм выполнения JS
Улучшения кода (для компиляции)
- переменное продвижение
- Продвижение функции (высший приоритет)
скомпилировать код
Процесс компиляции кода JS в V8
-
Создание абстрактного синтаксического дерева (AST) и контекста выполнения
-
Первый этап — токенизация, также известная как лексический анализ.
-
Второй этап — синтаксический анализ (parse), также известный как синтаксический анализ
-
генерировать байт-код
Байт-код — это нечто среднее между AST и машинным кодом. Но независимо от конкретного типа машинного кода байт-код должен быть преобразован в машинный код интерпретатором, прежде чем он сможет быть выполнен.
-
выполнить код
Шаги компилятора языка высокого уровня:
- исходный поток символов ввода
- лексический анализ
- Разбор
- Семантический анализ
- Генерация промежуточного кода
- Машинно-независимая оптимизация кода
- генерация кода
- Машинно-зависимая оптимизация кода
- генерация объектного кода
выполнить код
- При выполнении глобального кода создайте глобальный контекст
- При вызове функции создайте контекст функции
- При использовании функции eval создайте контекст eval
- При выполнении локального кода создайте локальный контекст
Типы
базовый тип
- Undefined
- Null
- Boolean
- String
- Symbol
- Number
- Object
- BigInt
сложный тип
- Object
Неявные правила преобразования
основная ситуация
- преобразовать в логическое значение
- преобразовать в число
- преобразовать в строку
Преобразовать в примитивный тип
Когда объект преобразует тип, он выполнит собственный методToPrimitive.
Его алгоритм следующий:
- если ужепримитивный тип, возвращается текущее значение;
- Поверните, если необходимонитьтогда сначала позвони
toSting
метод, если это времяпримитивный типВернитесь напрямую, иначе позвоните еще разvalueOf
метод и вернуть результат; - если ненить, то звоните сначала
valueOf
метод, если это времяпримитивный типВернитесь напрямую, иначе позвоните еще разtoString
метод и вернуть результат; - если нетпримитивный типвернуть, броситьTypeErrorОшибка типа.
Конечно, мы можем переписатьSymbol.toPrimitive
Для формулировки правил преобразования этот метод имеет наивысший приоритет при преобразовании в примитивные типы.
const data = {
valueOf() {
return 1;
},
toString() {
return "1";
},
[Symbol.toPrimitive]() {
return 2;
}
};
data + 1; // 3
преобразовать в логическое значение
Правила преобразования объектов в логические значения следующие:
Тип параметра | результат |
---|---|
Undefined | вернутьfalse . |
Null | вернутьfalse . |
Boolean | Возвращает текущий параметр. |
Number | Если параметр+0 ,-0 илиNaN , затем вернутьсяfalse ; иначе возвращаетсяtrue . |
String | Возвращает, если аргумент является пустой строкойfalse ; иначе возвратtrue . |
Symbol | вернутьtrue . |
Object | вернутьtrue . |
преобразовать в число
Правила преобразования объектов в числа следующие:
Тип параметра | результат |
---|---|
Undefined | вернутьNaN . |
Null | Return +0. |
Boolean | Если параметрtrue , затем вернуться1 ;false затем вернуться+0 . |
Number | Возвращает текущий параметр. |
String | сначала позвониToPrimitive, затем позвонитеToNumber, и вернуть результат. |
Symbol | бросатьTypeError ошибка. |
Object | сначала позвониToPrimitive, затем позвонитеToNumber, и вернуть результат. |
преобразовать в строку
Правила преобразования объектов в строки следующие:
Тип параметра | результат |
---|---|
Undefined | вернуть"undefined" . |
Null | вернуть"null" . |
Boolean | Если параметрtrue , затем вернуться"true" ; иначе возврат"false" . |
Number | передачаNumberToString, и вернуть результат. |
String | Возвращает текущий параметр. |
Symbol | бросатьTypeError ошибка. |
Object | сначала позвониToPrimitive, затем позвонитеToString, и вернуть результат. |
this
это связано с контекстом выполнения.
Контекст выполнения:
- Глобальный контекст выполнения: это в глобальном контексте выполнения также указывает на объект окна.
- Контекст выполнения функции: используйте объект для вызова метода внутри него, метод this относится к самому объекту.
- Контекст EVAL-исполнения: выполнить предыдущие два случая внутри Eval Environment.
Определите наивысший приоритетthis
Где это заканчивается.
первый,new
метод имеет наивысший приоритет, за ним следуетbind
эти функции, тоobj.foo()
Этот способ вызова, наконецfoo
Этот способ одновременного вызова стрелочной функцииthis
Однажды привязанный, он не может быть изменен каким-либо образом.
Три момента, на которые следует обратить внимание:
- Когда функция вызывается как метод объекта, this в функции является объектом;
- Когда функция вызывается нормально, в строгом режиме это значение не определено, в нестрогом режиме указывает на окно глобального объекта;
- Это во вложенной функции не наследует значение this внешней функции.
- Мы также упомянули стрелочные функции, потому что стрелочные функции не имеют собственного контекста выполнения, поэтому this стрелочной функции — это this ее внешней функции.
Закрытие
Замыкания, на которые нет ссылок, будут автоматически переработаны, но если они все еще существуют в глобальных переменных, все равно будут утечки памяти.
В JavaScript, в соответствии с правилами лексической области видимости, внутренняя функция всегда может получить доступ к переменным, объявленным во внешней функции.Когда внутренняя функция возвращается вызовом внешней функции, даже если внешняя функция завершила выполнение, внутренняя функция ссылается Переменные внешней функции по-прежнему хранятся в памяти, и мы называем набор этих переменных замыканием. Например, внешняя функция — это foo, тогда совокупность этих переменных называется замыканием функции foo.
var getNum;
function getCounter() {
var n = 1;
var inner = function() {
n++;
};
return inner;
}
getNum = getCounter();
getNum(); // 2
getNum(); // 3
getNum(); // 4
getNum(); // 5
объем
глобальная область
Объекты доступны в любом месте кода, и их жизненный цикл соответствует жизненному циклу страницы.
объем функции
Переменная или функция, определенная внутри функции, и доступ к определенной переменной или функции возможен только внутри функции. После завершения выполнения функции переменные, определенные внутри функции, будут уничтожены.
локальная область
Фрагмент кода, заключенный в пару фигурных скобок, такой как функция, оператор предиката, оператор цикла или даже один {}, можно рассматривать как область действия блока.
цепочка прицелов
лексический объем
Лексическая область видимости означает, что область видимости определяется позицией объявления функции в коде, поэтому лексическая область видимости является статической областью видимости, которая может предсказать, как код ищет идентификатор в процессе выполнения.
Лексическая область видимости определяется на этапе кода и не имеет ничего общего с тем, как вызывается функция.
Прототип и цепочка прототипов
На самом деле каждый объект JS имеет__proto__
свойство, это свойство указывает на прототип.
Прототип — это тоже объект, и этот объект содержит множество функций, дляobj
, это можно сделать по__proto__
Найдите объект-прототип, в котором для нас определено множество функций.
Цепь прототипа:
-
Object
папа всех объектов, все объекты могут проходить через__proto__
Найди это -
Function
является папой всех функций, и все функции могут быть переданы через__proto__
Найди это - функциональный
prototype
является объектом - объект
__proto__
свойство указывает на прототип,__proto__
Соединение объектов и прототипов образует цепочку прототипов.
Как работает V8
хранилище данных
- Пространство стека: структуры данных «первым пришел — последним обслужен», стеки вызовов, контексты выполнения хранения и данные примитивных типов.
- Пространство кучи: двоичное дерево, реализованное с помощью массивов, хранящих ссылочные типы. Пространство кучи велико и может хранить много больших данных. Объекты, хранящиеся в куче памяти, переменные фактически содержат указатель, указывающий на другое место.
Присваивание примитивного типа полностью копирует значение переменной, а присваивание ссылочному типу копирует ссылочный адрес.
вывоз мусора
-
Повторно используйте данные в стеке вызовов: когда контекст выполнения завершается и на него не ссылаются, он перемещается вниз наУказатель для записи текущего состояния выполнения (называемый ESP).чтобы уничтожить контекст выполнения, хранящийся функцией в стеке.
-
Переработайте данные в куче:
В V8 куча будет разделена на две области: новое поколение и старое поколение.
Объекты с коротким временем жизни сохраняются в новом поколении.
Долгоживущие объекты хранятся в старом поколении.
Важные условия по сбору мусора:
- Гипотеза поколения
- Большинство объектов существуют в памяти в течение короткого времени
- Бессмертные объекты будут жить дольше
- Коллекция поколений
- Гипотеза поколения
Вторичный сборщик мусора:
Коллекция мусора в основном несет ответственность за новое поколение.
Эта площадь невелика, но вывоз мусора происходит чаще.
Алгоритм сборки мусора нового поколения — алгоритм Scavenge.
Пространство нового поколения в основном разделено на две области: область объекта и свободная область.
Когда область объекта почти заполнена, выполняется очистка от мусора.
Процесс выглядит следующим образом:
- Отметить мусор в районе объекта
- Скопируйте живые объекты в свободную область
- Расположите эти объекты в упорядоченном порядке
- После очистки область объекта будет заменена свободной областью.
Главный сборщик мусора:
Главный сборщик мусора в основном отвечает за сбор мусора в старом районе.
В дополнение к продвинутым объектам в зоне для первокурсников некоторые крупные объекты будут напрямую назначены старой зоне для студентов.
Следовательно, у объектов в старой области есть две характеристики: одна — объект большой, а другая — время выживания цели.
Процесс выглядит следующим образом:
- Начав с набора корневых элементов, рекурсивно пройтись по набору корневых элементов.Во время этого процесса обхода различать живые объекты и мусорные данные
- Процесс маркировки и очистки использует алгоритм маркировки и очистки.
- Алгоритм пометки и сортировки используется, когда слишком большая фрагментация препятствует выделению достаточного количества непрерывной памяти для больших объектов.
Как только алгоритм сборки мусора будет выполнен, это приведет кОстанови мир.
Но у V8 естьАлгоритм пошаговой маркировки.
V8 делит процесс маркировки на процессы субмаркировки и чередует маркировку сборки мусора и логику приложения JavaScript, пока фаза маркировки не будет завершена.
цикл событий
микрозадача
- process.nextTick
- promise
- Object.observe (устарело)
- MutationObserver
макрозадача
- script
- setTimeout
- setInterval
- setImmediate
- I/O
- UI rendering
исполнительный лист
- Выполнение синхронного кода, который является задачей макроса
- Стек выполнения пуст, проверьте, есть ли микрозадачи для выполнения
- Отрисовка пользовательского интерфейса при необходимости
- Затем запустите следующий раунд цикла событий и выполните асинхронный код в задаче макроса.
Безопасность браузера
Метод атаки
-
XSS: Inject Code на веб-страницы
- Настойчивый: запись в базу данных
- непостоянный: изменить код пользователя
-
csrf: подделка межсайтового запроса. Злоумышленник создаст внутренний адрес запроса и побудит пользователей отправлять запросы по определенным каналам.
-
Атака «человек посередине»: Атака «человек посередине» заключается в том, что злоумышленник устанавливает соединение с сервером и клиентом одновременно и заставляет другую сторону думать, что соединение безопасно, но на самом деле весь коммуникационный процесс контролируется злоумышленником. Злоумышленник может не только получить коммуникационную информацию обеих сторон, но и изменить коммуникационную информацию.
- DNS SPOOFING: взлом DNS для перенаправления доступа пользователя к обозначенной машине злоумышленника
- Перехват сеанса: В обычном процессе связи злоумышленник участвует в качестве третьей стороны, либо добавляет другую информацию к данным, либо даже тайно меняет режим связи двух сторон, то есть с прямого контакта на контакт с злоумышленником.
защитные меры
- Предотвращение XSS
-
Фильтровать html-код с escape-символами
const escapeHTML = value => { if (!value || !value.length) { return value; } return value .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); };
-
Фильтр SQL-кода
const replaceSql = value => { if (!value || !value.length) { return value; } return value.replace(/select|update|delete|exec|count|'|"|=|;|>|<|%/gi, ""); };
-
Предотвратить CSRF
- Проверьте поле HTTP Referer
- Добавьте токен для запроса адреса и подтвердите
- Настройте свойства в заголовках HTTP и подтвердите
- Запросы Get не изменяют данные
- Анти-междоменная обработка интерфейса
- Не позволяйте сторонним веб-сайтам получать доступ к файлам cookie пользователя
-
Предотвратить атаки человека
- Для спуфинга DNS: проверьте файл HOSTS на вашем компьютере.
- Для перехвата сеанса: используйте коммутируемую сеть вместо общей сети, статический ARP, связанный MAC+IP и т. д. также должны использоваться для ограничения спуфинга и аутентифицированных соединений.
- Политика безопасности контента (CSP)
Политика безопасности контента (CSP) — это дополнительный уровень безопасности, используемый для обнаружения и смягчения определенных типов атак, включая межсайтовые сценарии (XSS) и атаки с внедрением данных. Будь то кража данных, загрязнение содержимого веб-сайта или распространение вредоносных программ, эти атаки являются основными средствами.
Меры следующие:
- HTTP-голова
Content-Security-Policy
<meta http-equiv="Content-Security-Policy">
производительность браузера
Предварительное разрешение DNS
<link rel="dns-prefetch" href="" />
- Chrome и Firefox 3.5+ могут автоматически подготавливать
- Отключите предварительное разрешение DNS:
<meta http-equiv="x-dns-prefetch-control" content="off|on">
Сильный кеш
-
Expires
- Время действия кэша используется для указания времени, когда ресурс истекает, что является определенной точкой времени на стороне сервера.
- Expires является продуктом HTTP/1 и ограничено местным временем. Если местное время изменено, это может привести к аннулированию кеша.
-
Cache-Control
Согласовать кеш
Согласование кеша — это процесс, в котором браузер инициирует запрос с идентификатором кеша на сервер после принудительного отказа кеша, и сервер решает, использовать ли кеш в соответствии с идентификатором кеша.
- Заголовки ответа сервера: Last-Modified, Etag
- Заголовки запроса браузера: If-Modified-Since, If-None-Match
Last-Modifiedа такжеIf-Modified-Sinceпара.Last-Modified
Сообщите клиенту время последней модификации веб-приложения, и клиент отправит его на следующий запрос.If-Modified-Since
Значение отправляется на сервер, и сервер решает, нужно ли ему повторно отправить ресурс, если нет, он возвращает 304, а если да, то возвращает 200. Недостаток этой комбинации в том, что она может быть точной только до секунды, и она записывается по местному открытому времени, поэтому она будет неточной.
Etagа такжеIf-None-Matchпара. Вместо того, чтобы использовать время в качестве критерия, они использовали набор характерных строк.Etag
Отправьте эту строку характеристик клиенту, и клиент будет использовать эту строку характеристик в качестве следующего запроса.If-None-Match
Значение отправляется на сервер, и сервер решает, нужно ли ему повторно отправить ресурс, если нет, он вернет 304, а если да, то вернет 200.
NodeJs
один поток
Основная концепция:
-
Процесс: процесс — это программа, которая уже запущена на компьютере. Когда-то процессы были основной единицей работы систем с разделением времени.
-
Поток: Поток (англ. thread) — это наименьшая единица, над которой операционная система может планировать операции. В большинстве случаев он содержится в процессе и является фактической единицей операции в процессе.
-
Сопрограммы: сопрограммы (англ. coroutines), также известные как микропотоки, представляют собой класс компонентов компьютерных программ, которые поддерживают совместные многозадачные подпрограммы, позволяя приостанавливать и возобновлять выполнение.
Ядром Node является движок v8. После запуска Node будет создан экземпляр v8. Этот экземпляр является многопоточным. Потоки следующие:
-
Основной поток: компиляция и выполнение кода.
-
Компиляция/оптимизация потока: код можно оптимизировать во время выполнения основного потока.
-
Поток профилировщика: записывает время выполнения профилируемого кода и предоставляет Crankshaft основу для оптимизации выполнения кода.
-
Несколько потоков сборки мусора.
Неблокирующий ввод-вывод
блокироватьЭто означает, что в программе Node.js выполнение других операторов JavaScript должно ожидать завершения операции, отличной от JavaScript. Это потому, что когдаблокироватьКогда это произойдет, цикл события не может продолжать работать JavaScript.
В Node.js JavaScript работает плохо из-за выполнения операций с интенсивным использованием ЦП вместо ожидания операции, не связанной с JavaScript (например, ввода-вывода), которая часто не вызывается.блокировать. Наиболее часто используется синхронный подход с использованием libuv в стандартной библиотеке Node.js.блокироватьработать. Есть также нативные модулиблокироватьметод.
цикл событий
┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘
Примечание. Каждое поле называется механизмом цикла событий этапа.
Между реализациями Windows и Unix/Linux есть небольшие различия, но для демонстрации это не важно.
Обзор фазы:
-
таймер: Выполнение на этом этапе было
setTimeout()
а такжеsetInterval()
Функция обратного вызова диспетчеризации. -
В ожидании обратного обратного вызова: выполнение обратного вызова ввода/вывода откладывается до следующей итерации цикла.
-
idle, prepare: Только для внутреннего использования.
-
голосование: извлекает новые события ввода-вывода, выполняет обратные вызовы, связанные с вводом-выводом (почти во всех случаях, за исключением обратных вызовов выключения, генерируемых таймерами и
setImmediate()
кроме запланированных), в остальных случаях узел будет блокировать здесь, когда это уместно. -
обнаружить:
setImmediate()
Здесь выполняется функция обратного вызова. -
Закрыть функцию обратного вызова: Некоторые функции обратного вызова завершения работы, такие как:
socket.on('close', ...)
.
Между каждым запуском цикла событий Node.js проверяет, не ожидает ли он каких-либо асинхронных операций ввода-вывода или таймеров, и полностью отключается, если нет.
process.nextTick()
: это часть асинхронного API. Технически не является частью цикла событий. Будет обработан после завершения текущей операции, независимо от текущей фазы цикла событий.nextTickQueue
. один здесьдействоватьРассматривается как переход от базового процессора C/C++ к обработке кода JavaScript, который необходимо выполнить.
Libuv
Libuv — это кроссплатформенная библиотека асинхронного ввода-вывода, которая сочетает в себе функции libev для UNIX и IOCP для Windows. Впервые она была разработана автором Node.js и обеспечивает поддержку асинхронного ввода-вывода на нескольких платформах для Node.js. Сама Libuv реализована на языке C++, а неблокирующий ввод-вывод в Node.js и базовый механизм цикла обработки событий реализованы в libuv.
В среде Windows libuv напрямую использует Windows IOCP для реализации асинхронного ввода-вывода. В среде, отличной от Windows, libuv использует многопоточность (пул потоков) для имитации асинхронного ввода-вывода. Здесь мы лишь кратко упомянем концепцию пула потоков в libuv. В последующих статьях будет рассказано, как libuv реализует межпроцессное взаимодействие.
рукописный код
новый оператор
var New = function(Fn) {
var obj = {}; // 创建空对象
var arg = Array.prototype.slice.call(arguments, 1);
obj.__proto__ = Fn.prototype; // 将obj的原型链__proto__指向构造函数的原型prototype
obj.__proto__.constructor = Fn; // 在原型链 __proto__上设置构造函数的构造器constructor,为了实例化Fn
Fn.apply(obj, arg); // 执行Fn,并将构造函数Fn执行obj
return obj; // 返回结果
};
глубокая копия
const getType = data => {
// 获取数据类型
const baseType = Object.prototype.toString
.call(data)
.replace(/^\[object\s(.+)\]$/g, "$1")
.toLowerCase();
const type = data instanceof Element ? "element" : baseType;
return type;
};
const isPrimitive = data => {
// 判断是否是基本数据类型
const primitiveType = "undefined,null,boolean,string,symbol,number,bigint,map,set,weakmap,weakset".split(
","
); // 其实还有很多类型
return primitiveType.includes(getType(data));
};
const isObject = data => getType(data) === "object";
const isArray = data => getType(data) === "array";
const deepClone = data => {
let cache = {}; // 缓存值,防止循环引用
const baseClone = _data => {
let res;
if (isPrimitive(_data)) {
return data;
} else if (isObject(_data)) {
res = { ..._data };
} else if (isArray(_data)) {
res = [..._data];
}
// 判断是否有复杂类型的数据,有就递归
Reflect.ownKeys(res).forEach(key => {
if (res[key] && getType(res[key]) === "object") {
// 用cache来记录已经被复制过的引用地址。用来解决循环引用的问题
if (cache[res[key]]) {
res[key] = cache[res[key]];
} else {
cache[res[key]] = res[key];
res[key] = baseClone(res[key]);
}
}
});
return res;
};
return baseClone(data);
};
рукописный переплет
Function.prototype.bind2 = function(context) {
if (typeof this !== "function") {
throw new Error("...");
}
var that = this;
var args1 = Array.prototype.slice.call(arguments, 1);
var bindFn = function() {
var args2 = Array.prototype.slice.call(arguments);
var that2 = this instanceof bindFn ? this : context; // 如果当前函数的this指向的是构造函数中的this 则判定为new 操作。如果this是构造函数bindFn new出来的实例,那么此处的this一定是该实例本身。
return that.apply(that2, args1.concat(args2));
};
var Fn = function() {}; // 连接原型链用Fn
// 原型赋值
Fn.prototype = this.prototype; // bindFn的prototype指向和this的prototype一样,指向同一个原型对象
bindFn.prototype = new Fn();
return bindFn;
};
Каррирование рукописных функций
const curry = fn => {
if (typeof fn !== "function") {
throw Error("No function provided");
}
return function curriedFn(...args) {
if (args.length < fn.length) {
return function() {
return curriedFn.apply(null, args.concat([].slice.call(arguments)));
};
}
return fn.apply(null, args);
};
};
Рукописные обещания
// 来源于 https://github.com/bailnl/promise/blob/master/src/promise.js
const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2;
const isFunction = fn => typeof fn === "function";
const isObject = obj => obj !== null && typeof obj === "object";
const noop = () => {};
const nextTick = fn => setTimeout(fn, 0);
const resolve = (promise, x) => {
if (promise === x) {
reject(promise, new TypeError("You cannot resolve a promise with itself"));
} else if (x && x.constructor === Promise) {
if (x._stauts === PENDING) {
const handler = statusHandler => value => statusHandler(promise, value);
x.then(handler(resolve), handler(reject));
} else if (x._stauts === FULFILLED) {
fulfill(promise, x._value);
} else if (x._stauts === REJECTED) {
reject(promise, x._value);
}
} else if (isFunction(x) || isObject(x)) {
let isCalled = false;
try {
const then = x.then;
if (isFunction(then)) {
const handler = statusHandler => value => {
if (!isCalled) {
statusHandler(promise, value);
}
isCalled = true;
};
then.call(x, handler(resolve), handler(reject));
} else {
fulfill(promise, x);
}
} catch (e) {
if (!isCalled) {
reject(promise, e);
}
}
} else {
fulfill(promise, x);
}
};
const reject = (promise, reason) => {
if (promise._stauts !== PENDING) {
return;
}
promise._stauts = REJECTED;
promise._value = reason;
invokeCallback(promise);
};
const fulfill = (promise, value) => {
if (promise._stauts !== PENDING) {
return;
}
promise._stauts = FULFILLED;
promise._value = value;
invokeCallback(promise);
};
const invokeCallback = promise => {
if (promise._stauts === PENDING) {
return;
}
nextTick(() => {
while (promise._callbacks.length) {
const {
onFulfilled = value => value,
onRejected = reason => {
throw reason;
},
thenPromise
} = promise._callbacks.shift();
let value;
try {
value = (promise._stauts === FULFILLED ? onFulfilled : onRejected)(
promise._value
);
} catch (e) {
reject(thenPromise, e);
continue;
}
resolve(thenPromise, value);
}
});
};
class Promise {
static resolve(value) {
return new Promise((resolve, reject) => resolve(value));
}
static reject(reason) {
return new Promise((resolve, reject) => reject(reason));
}
constructor(resolver) {
if (!(this instanceof Promise)) {
throw new TypeError(
`Class constructor Promise cannot be invoked without 'new'`
);
}
if (!isFunction(resolver)) {
throw new TypeError(`Promise resolver ${resolver} is not a function`);
}
this._stauts = PENDING;
this._value = undefined;
this._callbacks = [];
try {
resolver(value => resolve(this, value), reason => reject(this, reason));
} catch (e) {
reject(this, e);
}
}
then(onFulfilled, onRejected) {
const thenPromise = new this.constructor(noop);
this._callbacks = this._callbacks.concat([
{
onFulfilled: isFunction(onFulfilled) ? onFulfilled : void 0,
onRejected: isFunction(onRejected) ? onRejected : void 0,
thenPromise
}
]);
invokeCallback(this);
return thenPromise;
}
catch(onRejected) {
return this.then(void 0, onRejected);
}
}
Рукописная функция защиты от сотрясений
const debounce = (fn = {}, wait = 50, immediate) => {
let timer;
return function() {
if (immediate) {
fn.apply(this, arguments);
}
if (timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(() => {
fn.apply(this, arguments);
}, wait);
};
};
Рукописная функция дросселирования
var throttle = (fn = {}, wait = 0) => {
let prev = new Date();
return function() {
const args = arguments;
const now = new Date();
if (now - prev > wait) {
fn.apply(this, args);
prev = new Date();
}
};
};
рукописный instanceOf
const instanceOf = (left, right) => {
let proto = left.__proto__;
let prototype = right.prototype;
while (true) {
if (proto === null) {
return false;
} else if (proto === prototype) {
return true;
}
proto = proto.__proto__;
}
};
другие знания
typeof vs instanceof
instanceof
оператор для обнаруженияconstructor.prototype
существует в параметреobject
в цепочке прототипов.
typeof
Оператор возвращает строку, представляющую тип неоцененного операнда.
В исходной реализации JavaScript значение в JavaScript было представлено тегом, представляющим тип и фактическое значение данных. Метка типа объекта равна 0. из-заnull
представляет нулевой указатель (0x00 на большинстве платформ), поэтому метка типа null равна 0,typeof null
также вернуться"object"
.
Рекурсия
Рекурсия (англ. Recursion), также переводится как рекурсия, в математике и информатике относится к методу использования самой функции в определении функции.
Например:
Нобита был в комнате и смотрел будущее по Time TV. В это время на экране телевизора он находился в комнате, используя время телевизора, наблюдая за будущей ситуацией. В момент появления экрана телевизора на экране телевизора он находился в комнате, используя Time TV, наблюдая за будущей ситуацией...
Проще говоря, этоБесконечные матрешки
Давайте возьмем последовательность Фибоначчи в качестве примера, чтобы увидеть различные рекурсивные ситуации, когда входным результатом будет положительное значение бесконечности.
Первая обычная версия
const fib1 = n => {
if (typeof n !== "number") {
throw new Error("..");
}
if (n < 2) {
return n;
}
return fib1(n - 1) + fib1(n - 2);
};
Из приведенного выше анализа кода нетрудно обнаружить, что вfib1
JS будет продолжать создавать контекст выполнения, помещать его в стек и не уничтожать до получения результата, поэтому стек легко разбить, когда число большое.
Таким образом, мы можем оптимизировать его, используяхвостовой вызовоптимизировать.
Хвостовой вызов означает, что последний шаг функции возвращает только вызов чистой функции, и никакие другие данные не занимают ссылку. код показывает, как показано ниже:
const fib2 = (n, a = 0, b = 1) => {
if (typeof n !== "number") {
throw new Error("..");
}
if (n === 0) {
return a;
}
return fib2(n - 1, b, a + b);
};
К сожалению, он все еще взрывается в Chrome 83.0.4103.61.
Затем у нас есть метод рекурсии мемо, который заключается в применении дополнительного пространства для хранения значения каждой рекурсии, что является алгоритмом сверху вниз.
К сожалению, до сих пор висит.
Однако в некоторых рекурсивных задачах мы также можем использовать для решения динамическое программирование (динамическое программирование, называемое DP).
Динамическое программирование — одна из самых сложных для понимания концепций алгоритмов, но проблемы, которые в основном могут быть решены с помощью рекурсии, могут быть решены с помощью динамического программирования.
Основная идея динамического программирования очень проста. Грубо говоря, чтобы решить данную проблему, нам нужно решить разные ее части (то есть подзадачи), а затем использовать решения подзадач, чтобы прийти к решению исходной задачи.
В отличие от рекурсии мемо, это восходящий алгоритм. Конкретный код выглядит следующим образом:
const fib3 = n => {
if (typeof n !== "number") {
throw new Error("..");
}
if (n < 2) {
return n;
}
let a = 0;
let b = 1;
while (n--) {
[a, b] = [b, a + b];
}
return a;
};
Эффект очень хороший, положительная бесконечность выводится правильно~
использованная литература
- Принцип работы браузера и практика
- Механизм работы браузера - 2. Какие процессы включены в браузер?
- «Intermediate and Advanced Front-end Interview» JavaScript Рукописный код Invincible Cheats
- Глубокая копия JavaScript
- bailnl/promise
- Как подключена сеть?
- Принцип работы браузера и практика
- Как работают браузеры: за кулисами современных веб-браузеров
- Политика безопасности контента (CSP)
- предварительное интервью
- Различия между версиями HTTP
- CORS решает междоменные проблемы (междоменная конфигурация Nginx)
- Как вы думаете, правильный ли вывод о том, что Node.js является однопоточным?
- Руководство по узлу
- Глубокое понимание механизма кэширования браузера
постскриптум
Если вам нравится обсуждать технологии или у вас есть какие-либо комментарии или предложения по этой статье, вы можете добавить друзей Yutou в WeChat для совместного обсуждения.Конечно, Yutou также надеется поговорить с вами о жизни, хобби и поболтать. WeChat ID Fish Head: krisChans95