Эта статья включена в ежедневный вопрос GitHub:DailyQuestion, включая возможность продвижения по службе на большой фабрике, личную книгу и несколько вопросов для интервью, изучение по пять минут в день и поступление на большую фабрику на один год.
01 Что такое антивибрация и дросселирование и каковы сценарии их применения
Общайтесь и обсуждайте в выпуске:01 Что такое антивибрация и дросселирование и каковы сценарии их применения
опровергать
Anti-shake, как следует из названия, предотвращает дрожание, чтобы не перепутать одно событие с несколькими, набор текста на клавиатуре — это операция анти-дрожания, с которой вы будете сталкиваться каждый день.
Чтобы понять концепцию, вы должны сначала понять контекст, в котором она применяется. Какие в мире JS есть сцены, предотвращающие тряску?
- Кнопки, такие как вход в систему и отправка текстовых сообщений, не позволяют пользователям нажимать слишком быстро, поэтому отправляются несколько запросов, и требуется защита от сотрясений.
- При изменении размера окна браузера число изменений слишком частое, что приводит к слишком большому количеству вычислений. В настоящее время оно должно быть на месте один раз, поэтому используется защита от сотрясений.
- Текстовый редактор сохраняет в реальном времени и сохраняет через одну секунду без каких-либо изменений
Код выглядит следующим образом, вы можете увидетьAnti-shake фокусируется на очисткеclearTimeout(timer)
function debounce (f, wait) {
let timer
return (...args) => {
clearTimeout(timer)
timer = setTimeout(() => {
f(...args)
}, wait)
}
}
дроссель
Дросселирование, как следует из названия, контролирует поток воды. Контролируйте частоту событий, например, раз в 1 с или даже раз в минуту. Аналогично пределу скорости, контролируемому сервером и шлюзом.
-
scroll
события, расчет информации о местоположении каждую секунду и т. д. - События воспроизведения браузера, информация о прогрессе рассчитывается каждую секунду и т. д.
- Поле ввода выполняет поиск в режиме реального времени и отправляет запрос на отображение выпадающего списка, а также отправляет запрос каждую секунду (также может выполнять анти-встряску)
Код выглядит следующим образом, вы можете увидетьДросселирование фокусируется на блокировкеtimer=timeout
function throttle (f, wait) {
let timer
return (...args) => {
if (timer) { return }
timer = setTimeout(() => {
f(...args)
timer = null
}, wait)
}
}
Резюме (краткий ответ)
- Anti-shake: чтобы предотвратить дрожание, триггер события будет сброшен в течение единицы времени, чтобы избежать многократного запуска события из-за случайной травмы.Реализация кода фокусируется на очистке
clearTimeout
. Антишейк можно сравнить с ожиданием лифта, пока входит один человек, ему нужно какое-то время подождать. Бизнес-сценарии имеют дублированные представления, чтобы избежать многократного нажатия кнопки входа. - Регулирование: для управления потоком событие может запускаться только один раз в единицу времени, аналогично ограничению скорости на стороне сервера.Реализация кода фокусируется на разблокировке и блокировке
timer=timeout; timer=null
. Дроссельную заслонку можно сравнить со светофором, и у вас может быть партия каждый раз на красный свет.
02 Во фронтенд-разработке, как получить уникальный идентификатор браузера
Подробнее: Как получить уникальный идентификатор браузера, по какому принципу
Общайтесь и обсуждайте в выпуске:02 Во фронтенд-разработке, как получить уникальный идентификатор браузера
Из-за разного рисунка системной видеокартыcanvas
Алгоритм, антиалгоритм и т. д., так втянутые в данные изображенияCRC
Верификация тоже бывает разной.
function getCanvasFp () {
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
ctx.font = '14px Arial'
ctx.fillStyle = '#ccc'
ctx.fillText('hello, shanyue', 2, 2)
return canvas.toDataURL('image/jpeg')
}
Поэтому согласноcanvas
Можно получить информацию об отпечатках пальцев браузера.
- рисовать
canvas
,Получатьbase64
датаурл - Сделайте это в строке dataurl
md5
Расчет дайджеста для получения информации об отпечатках пальцев
Тем не менее, есть зрелые решения для общих нужд.Если вы используете его в производственной среде, вы можете использовать следующие библиотеки
Он получает информацию об отпечатках пальцев браузера на основе следующей информации:И эта информация становитсяcomponent
canvas
webgl
UserAgent
AudioContext
- Поддержка современных API и др.
requestIdleCallback(function () {
Fingerprint2.get((components) => {
const values = components.map((component) => component.value)
const fp = Fingerprint2.x64hash128(values.join(''), 31)
})
})
существуетfingerprintjs2
в, дляcomponent
также классифицируется
-
browser independent component:немного
component
Одно и то же устройство может получать одинаковое значение в разных браузерах, а некоторые независимые браузеры получают разные значения. -
stable component: немного
component
Значения изменяются после обновления, известные как нестабильные компоненты.
В реальном бизнесе соответствующие компоненты могут быть выбраны в соответствии с бизнесом.
const options = {
excludes: {userAgent: true, language: true}
}
краткий ответ
согласно сcanvas
Информация об отпечатках пальцев браузера может быть получена
- рисовать
canvas
,Получатьbase64
датаурл - Сделайте это в строке dataurl
md5
Расчет дайджеста для получения информации об отпечатках пальцев
Для производственного использования вы можете использоватьfingerprintjs2, в соответствии с потребностями бизнеса, например, может ли одно устройство быть кроссбраузерным, выберите соответствующийcomponent
03 Как получить IP клиента в серверном приложении
Общайтесь и обсуждайте в выпуске:03 Как получить IP клиента в серверном приложении
Если естьx-forwarded-for
заголовок запроса, затем берем первый среди них IP, иначе берем remoteAddr сокета, установившего соединение.
а такжеx-forwarded-for
По сути, он стал стандартным HTTP-заголовком на основе прокси. Формат выглядит следующим образом. Видно, что первый IP-адрес представляет его реальный IP-адрес. Вы можете обратиться к MDNX-Forwarded-For
X-Forwarded-For: 203.0.113.195, 70.41.3.18, 150.172.238.178
X-Forwarded-For: <client>, <proxy1>, <proxy2>
Ниже приведеныkoa
Как получить ИП
get ips() {
const proxy = this.app.proxy;
const val = this.get(this.app.proxyIpHeader);
let ips = proxy && val
? val.split(/\s*,\s*/)
: [];
if (this.app.maxIpsCount > 0) {
ips = ips.slice(-this.app.maxIpsCount);
}
return ips;
},
get ip() {
if (!this[IP]) {
this[IP] = this.ips[0] || this.socket.remoteAddress || '';
}
return this[IP];
},
См. исходный код:GitHub.com/ смотри, о, да/ смотри, о, нет…
04 Как js заменяет одну подстроку другой подстрокой?
Дополнительное описание: Предположим, есть строка `hello.hello.hello.`, которую нужно заменить на `AAA`, то есть заменить `hello.` на `A`
Общайтесь и обсуждайте в выпуске:04 Как js заменяет одну подстроку другой подстрокой?
Если вам нужно полностью заменить строку, вы можете использоватьString.prototype.replace(re, replacer)
, где необходимо включить регулярные выраженияglobal
flag
const s = 'foo foo foo'
s.replce(/foo/g, 'bar')
Как и в названии,Можно ли использовать регулярные выражения для замены подстрок
отвечать:Нет, потому что могут быть специальные символы при использовании подстрок для построения регулярных выражений, что может вызвать проблемы,следующим образом
// 期待结果: 'AhelloX hello3 '
> 'hello. helloX hello3 '.replace(new RegExp('hello. ', 'g'), 'A')
< "AAA"
пока вjavascript
Есть только один умный способ заменить подстроки в:str.split('foo').join('bar')
> 'hello. hello. hello. '.split('hello. ').join('A')
< "AAA"
Какой умный (глупый) чудесный (неуклюжий) способ! ! ! ! !Вероятно, TC39 тоже осознал проблему, поэтому вышел новый API,существуетESNext
середина
String.prototype.replaceAll()
'aabbcc'.replaceAll('b', '.');
// 'aa..cc'
Подробная документация находится наString.prototype.replaceAll
Резюме (и прямой ответ)
два пути
str.split('foo').join('bar')
-
str.replaceAll('foo', 'bar')
,существуетESNext
Средний, в настоящее время плохо поддерживается
05 Как получить память процесса и следить за ней
Подробнее: При написании скриптов иногда возникает OOM из-за избыточной памяти, так как же узнать память процесса? И как это контролировать
Общайтесь и обсуждайте в выпуске:05 Как получить память процесса и следить за ней
пройти черезps
Может знать память, занятую процессом
$ ps -O rss -p 3506
PID RSS S TTY TIME COMMAND
3506 6984 S pts/1 00:00:00 vim
Если вы хотите контролировать память, вы должны использовать команду, которая является всемогущей для процессаpidstat
(PS: Как только вы услышите название, вы поймете, для чего оно нужно)
## -r 显示内存信息
## -p 指定 pid
## 1: 每个一秒打印一次
$ pidstat -r -p 3506 1
Linux 3.10.0-957.21.3.el7.x86_64 (shanyue) 11/04/19 _x86_64_ (2 CPU)
20:47:35 UID PID minflt/s majflt/s VSZ RSS %MEM Command
20:47:36 0 3506 0.00 0.00 139940 6984 0.18 vim
20:47:37 0 3506 0.00 0.00 139940 6984 0.18 vim
20:47:38 0 3506 0.00 0.00 139940 6984 0.18 vim
20:47:39 0 3506 0.00 0.00 139940 6984 0.18 vim
20:47:40 0 3506 0.00 0.00 139940 6984 0.18 vim
20:47:41 0 3506 0.00 0.00 139940 6984 0.18 vim
pidstat
принадлежатьsysstat
инструменты производительности Linux под , но в Mac, как найти изменения памяти? Можно использовать универсальныйtop/htop
$ htop -p 31796
Суммировать
В двух словах, есть следующие три команды
pidstat -r
htop/top -p
ps -O rss -p
Подробнее о мониторинге метрик читайте в моей статье:Заметки о различных индикаторах мониторинга Linux
06 CORS Что делать, если вам нужно указать несколько доменных имен
Общайтесь и обсуждайте в выпуске:06 CORS Что делать, если вам нужно указать несколько доменных имен
CORS
контролируяAccess-Control-Allow-Origin
Контролируйте, какие доменные имена могут совместно использовать ресурсы, значения следующие
Access-Control-Allow-Origin: <origin> | *
в*
представляет все доменные имена,origin
От имени указания конкретного доменного имени, как настроить несколько доменных имен?
На данный момент это должно быть реализовано через код,Согласно заголовку запросаOrigin
установить заголовок ответаAccess-Control-Allow-Origin
, то что такое происхождение?
Заголовок запроса: Происхождение
Не все запросы автоматически принесутOrigin
, в браузере сOrigin
Логика следующая
- Если есть перекрестный домен, принесите
Origin
, значение — текущее доменное имя - Если нет перекрестного домена, без
Origin
После того, как логика понятна, оAccess-Control-Allow-Origin
Логика настройки нескольких доменных имен также очень понятна.
- Если в заголовке запроса нет
Origin
, если окажется, что это не междоменный, обработка не будет выполняться - Если в заголовке запроса есть
Origin
, проверенный междоменный, согласноOrigin
установить соответствующийAccess-Control-Allow-Origin: <Origin>
Это делается с использованием псевдокода следующим образом:
// 获取 Origin 请求头
const requestOrigin = ctx.get('Origin');
// 如果没有,则跳过
if (!requestOrigin) {
return await next();
}
// 设置响应头
ctx.set('Access-Control-Allow-Origin', requestOrigin)
Vary: Origin
В настоящее время CORS можно контролировать для нескольких доменных имен, но в настоящее время предполагается, что есть два доменных имени, обращающихся кstatic.shanyue.tech
кросс-происхождения ресурсов
-
foo.shanyue.tech
, возвращается в заголовке ответаAccess-Control-Allow-Origin: foo.shanyue.tech
-
bar.shanyue.tech
, возвращается в заголовке ответаAccess-Control-Allow-Origin: bar.shanyue.tech
Вроде все работает, а если посередине кеш?
-
foo.shanyue.tech
, возвращается в заголовке ответаAccess-Control-Allow-Origin: foo.shanyue.tech
, кэшируется CDN bar.shanyue.tech
, из-за кэширования, возвращается в заголовке ответаAccess-Control-Allow-Origin: foo.shanyue.tech
, возникает междоменная проблема
В настоящее время,Vary: Origin
на сцене, представляющие разныеOrigin
Кешировать разные ресурсы
Резюме (краткий ответ)
Как CORS указывает несколько доменных имен?
Согласно заголовку запросаOrigin
установить заголовок ответаAccess-Control-Allow-Origin
, идея такая
- всегда устанавливайте
Vary: Origin
, чтобы избежать повреждения конфигурации CORS кешем CDN. - Если в заголовке запроса нет
Origin
, если окажется, что это не междоменный, обработка не будет выполняться - Если в заголовке запроса есть
Origin
Подтвердите междоменный доступ браузера, согласноOrigin
установить соответствующийAccess-Control-Allow-Origin: <Origin>
Используйте псевдокод для реализации следующим образом
// 获取 Origin 请求头
const requestOrigin = ctx.get('Origin');
ctx.set('Vary', 'Origin')
// 如果没有,则跳过
if (!requestOrigin) {
return await next();
}
// 设置响应头
ctx.set('Access-Control-Allow-Origin', requestOrigin)
Связанные вопросы:Как избежать кэширования мобильных страниц CDN для ПК
07 Поскольку конфигурация cors может использоваться для междоменного контроля, может ли она предотвратить атаки CSRF?
Общайтесь и обсуждайте в выпуске:07 Поскольку конфигурация cors может использоваться для междоменного контроля, может ли она предотвратить атаки CSRF?
Не работает вообще для CORS
-
form
Отправить не удалосьCORS
обнаружить, вы можете протестировать локально - несмотря на
xhr
а такжеfetch
Коммит заблокирован CORS,Но для простых запросов запрос все равно отправляется, спровоцировал нападение
08 Как избежать кэширования мобильных страниц CDN для ПК
Общайтесь и обсуждайте в выпуске:08 Как избежать кэширования мобильных страниц CDN для ПК
Если сторона ПК и мобильная сторона представляют собой набор кодов, этой проблемы не возникнет.Эта проблема возникает на стороне ПК и на стороне мобильного устройства, которые представляют собой два набора кодов, но имеют общее доменное имя.
использоватьnginx
Конфигурация выглядит следующим образом, согласно UA, чтобы определить, является ли мобильный терминал, и использовать другую логику (судя, подвержен ли мобильный терминал UA проблемам)
location / {
// 默认 PC 端
root /usr/local/website/web;
# 判断 UA,访问移动端
if ( $http_user_agent ~* "(Android|webOS|iPhone|iPad|BlackBerry)" ){
root /usr/local/website/mobile;
}
index index.html index.htm;
}
Решение обычно заключается в использованииVary
Заголовки ответов для управления кэшированием CDN различных заголовков запросов.
можно использовать здесьVary: User-Agent
, что означает, что если User-Agent отличается, повторно инициируйте запрос вместо чтения страницы из кеша.
Vary: User-Agent
Конечно,User-Agent
Это слишком много, и инвалидация кеша будет слишком большой в это время.
краткий ответ
использоватьVary: User-Agent
, кэшируется согласно UA.
Vary: User-Agent
Но лучше, чтобы такой ситуации не было.При наличии двух наборов кодов на ПК и мобильных терминалах рекомендуется использовать два доменных имени по следующим причинам.
-
nginx
Определение склонности мобильных терминалов к ошибкам - Не поддерживает кеширование
09 Как реализовать стиль таблицы с однорядной и двухрядной полосой
Общайтесь и обсуждайте в выпуске:09 Как реализовать стиль таблицы с однорядной и двухрядной полосой
пройти черезcss3
средний класс:nth-child
реализовать. в:nth-child(an+b)
соответствие нижнему индексу{ an + b; n = 0, 1, 2, ...}
и результат является подэлементом целого числа
-
nth-child(2n)
/nth-child(even)
: стиль двойной линии -
nth-child(2n+1)
/nth-child(odd)
: стиль одной линии
вtr
Представляя строки в таблице, очень просто внедрить одно- и двойные стили строки в таблице:
tr:nth-child(2n) {
background-color: red;
}
tr:nth-child(2n+1) {
background-color: blue;
}
По аналогии:
- Как сопоставить первые три дочерних элемента:
:nth-child(-n+3)
- Как сопоставить последние три дочерних элемента:
:nth-last-child(-n+3)
10 Кратко опишите специфику css
Общайтесь и обсуждайте в выпуске:10 Кратко опишите специфику css
css specificity
То есть вес селектора в css, по очереди спускаются следующие три типа селекторов
-
id
селектор, например#app
-
class
,attribute
а такжеpseudo-classes
селектор, например.header
,[type="radio"]
а также:hover
-
type
Селекторы тегов и селекторы псевдоэлементов, такие какh1
,p
а также::before
где селектор подстановочных знаков*
, комбинированный селектор+ ~ >
, отрицая селектор псевдокласса:not()
Не влияет на приоритет
Встроенные стили<div class="foo" style="color: red;"></div>
а также!important
(самый высокий) имеет больший вес
:not
Приоритетное влияние - codepenКак можно заметить:not
Не влияет на приоритет селектора
11 В чем разница между module.exports и экспортом в node
Общайтесь и обсуждайте в выпуске:11 В чем разница между module.exports и экспортом в node
В одном предложении:exports
даmodule.exports
цитаты, еслиexports
Без переназначения нет никакой разницы между двумя
похоже на следующее
const exports = module.exports
Как будут получены следующие результаты?
module.exports = 100
exports = 3
в конце концов, очевидно, экспортировал бы 100exports
переназначен.
Как это реализовано в исходном коде узла?Это видно из исходного кодаexportsсуть
Подробности смотрите в исходном коде:GitHub.com/node будет /node…, видно, что гипотеза
Как мы все знаем, весь код модуля в узле заключен в эту функцию.
(function(exports, require, module, __filename, __dirname) {
exports.a = 3
});
Хотя в следующем исходном коде указано,exports
Как ты получил это
const dirname = path.dirname(filename);
const require = makeRequireFunction(this, redirects);
let result;
// 从这里可以看出来 exports 的实质
const exports = this.exports;
const thisValue = exports;
const module = this;
if (requireDepth === 0) statCache = new Map();
if (inspectorWrapper) {
result = inspectorWrapper(compiledWrapper, thisValue, exports,
require, module, filename, dirname);
} else {
// 这里是模块包装函数
result = compiledWrapper.call(thisValue, exports, require, module,
filename, dirname);
}
12 Как получить количество онлайн-пользователей в текущей системе (количество одновременных пользователей)
Подробное описание: Некоторые системы SaaS ограничивают количество команд и количество одновременных онлайн-подключений из соображений ценообразования Как этого добиться?
Общайтесь и обсуждайте в выпуске:12 Как получить количество онлайн-пользователей в текущей системе (количество одновременных пользователей)
Некоторые SaaS-системы ограничивают количество команд и количество одновременных онлайн-подключений исходя из соображений ценовой политики.Как этого добиться?
пройти черезredis
изzset
Количество одновременных пользователей может быть достигнуто.
Когда пользователь запрашивает любой интерфейс, реализуйте промежуточное ПО и обрабатывайте следующую логику.
// 当一个用户访问任何接口时,对该用户Id,写入 zset
await redis.zadd(`Organization:${organizationId}:concurrent`, Date.now(), `User:${userId}`)
// 查询当前机构的并发数
// 通过查询一分钟内的活跃用户来确认并发数,如果超过则抛出特定异常
const activeUsers = await redis.zrangebyscore(`Organization:${organizationId}:concurrent`, Date.now() - 1000 * 60, Date.now())
// 查出并发数
const count = activeUsers.length
// 删掉过期的用户
await redis.zrembyscore(`Organization:${organizationId}:concurrent`, Date.now() - 1000 * 60, Date.now())
Суммировать
- Всякий раз, когда пользователь обращается к сервису, записывайте идентификатор пользователя в приоритетную очередь с весом текущего времени.
- Рассчитать количество пользователей заведения за одну минуту исходя из веса (т.е. времени)
- Удалить пользователей, срок действия которых истек более одной минуты
13 Как конвертировать данные JSON в Demo.json и загрузите файл
Общайтесь и обсуждайте в выпуске:13 Как преобразовать данные json в demo.json и скачать файл
json рассматривается как строка, которую можно использоватьDataURL
скачивать
Text -> DataURL
Помимо использования DataURL, его также можно преобразовать в URL-адрес объекта для загрузки.
Text -> Blob -> Object URL
Вы можете вставить следующий код прямо в консоль, чтобы загрузить файл
function download (url, name) {
const a = document.createElement('a')
a.download = name
a.rel = 'noopener'
a.href = url
// 触发模拟点击
a.dispatchEvent(new MouseEvent('click'))
// 或者 a.click()
}
const json = {
a: 3,
b: 4,
c: 5
}
const str = JSON.stringify(json, null, 2)
// 方案一:Text -> DataURL
const dataUrl = `data:,${str}`
download(dataUrl, 'demo.json')
// 方案二:Text -> Blob -> ObjectURL
const url = URL.createObjectURL(new Blob(str.split('')))
download(url, 'demo1.json')
Суммировать
- Чтобы имитировать загрузку, вы можете создать новую,
<a href="url" download><a>
маркировать и устанавливатьurl
а такжеdownload
свойства для загрузки - можно сделать, поставив
json
превратиться вdataurl
для создания URL - можно сделать, поставив
json
Перевести вBlob
конвертировано вObjectURL
для создания URL
14 Как следить за содержимым буфера обмена в браузере
Общайтесь и обсуждайте в выпуске:14 Как следить за содержимым буфера обмена в браузере
пройти черезClipboard API
Вы можете получить содержимое буфера обмена, но вам нужно получитьclipboard-read
, вот код для чтения содержимого буфера обмена:
// 是否能够有读取剪贴板的权限
// result.state == "granted" || result.state == "prompt"
const result = await navigator.permissions.query({ name: "clipboard-read" })
// 获取剪贴板内容
const text = await navigator.clipboard.readText()
Примечание. Этот метод находится в
devtools
не действует в
Связанные вопросы:【Q019】Как реализовать функцию выбранной копии
Подписывайтесь на меня
Я Шаньюэ, и я работаю надОтветьте на большой заводской высокочастотный вопрос интервью короткими ответами, которые можно прочитать за пять минут каждый день..
Добро пожаловать, чтобы обратить внимание на публичный аккаунт [Internet Big Factory Recruitment], регулярно размещайте внутреннюю информацию о большой фабрике и краткие ответы на вопросы интервью, учитесь по пять минут в день и входите в большую фабрику в течение полугода.