Недавно я разобрался с высокочастотными вопросами фронтенд-интервью и поделился ими со всеми, чтобы учиться вместе. Если у вас есть какие-либо вопросы, пожалуйста, поправьте меня!
Статьи из серии вопросов для фронтенд-интервью:
【1】HTML-глава "2021" с высокочастотными вопросами для фронтенд-интервью
【2】Сводка вопросов по высокочастотным фронтенд-интервью «2021» CSS статьи
【3】Глава "2021" о JavaScript, посвященная высокочастотным вопросам для фронтенд-интервью (часть 1)
【4】Резюме статьи JavaScript "2021" о часто задаваемых вопросах на собеседовании (часть 2)
【5】Глава Vue "2021" о часто задаваемых вопросах на собеседовании (часть 1)
【6】Глава Vue "2021" о часто задаваемых вопросах на собеседовании (часть 2)
【7】Сводка вопросов по высокочастотным фронтенд-интервью «2021» React (часть 1)
【8】Сводка вопросов по высокочастотным фронтенд-интервью «2021» React (часть 2)
【9】Резюме вопросов высокочастотного внешнего интервью "2021" компьютерной сети
【10】«2021» высокочастотные вопросы о фронтенд-интервью краткое изложение принципов браузера
【11】Сводка вопросов по оптимизации производительности высокочастотных предварительных интервью «2021»
【12】«2021» Рукописный свод кода высокочастотных вопросов на собеседовании перед интерфейсом
【13】Сводные результаты вывода кода высокочастотных вопросов внешнего интервью «2021»
6. это/позвонить/применить/связать
1. Понимание этого объекта
это свойство в контексте выполнения, указывающее на объект, для которого в последний раз вызывался метод. В реальном развитии об этом можно судить по четырем способам вызова.
- Первыйшаблон вызова функции, когда функция не является свойством объекта и вызывается непосредственно как функция, this указывает на глобальный объект.
- Второйшаблон вызова метода, если функция вызывается как метод объекта, this относится к объекту.
- ТретийШаблон вызова конструктора, если функция вызывается с new, перед выполнением функции будет создан новый объект, и это указывает на вновь созданный объект.
- Четвертыйприменять, вызывать и связывать режимы вызова, все три метода могут отображать указатель this указанной вызывающей функции. Метод apply получает два параметра: один — объект, связанный с this, а другой — массив параметров. Параметры, полученные методом вызова, первый из которых является объектом, привязанным к этому, а остальные следующие параметры являются параметрами, переданными в выполнение функции. То есть при использовании метода call() параметры, передаваемые в функцию, должны быть перечислены один за другим. Метод привязки принимает объект и возвращает новую функцию с привязкой this к переданному объекту. Указатель this этой функции будет изменен, за исключением случаев, когда используется new, и не изменится в других случаях.
Из этих четырех методов режим вызова конструктора имеет наивысший приоритет, за ним следуют режимы вызова apply, call и bind, затем режим вызова метода, а затем режим вызова функции.
2. В чем разница между call() и apply()?
Функции у них абсолютно одинаковые, разница только в виде передаваемых параметров.
- Apply принимает два параметра, первый параметр указывает точку ЭТОГО объекта в теле функции, второй параметр представляет собой набор smashed, этот набор может быть массивом, и метод apply поместит элементы в этот набор. параметр передается вызываемой функции.
- Количество параметров, передаваемых вызовом, не фиксировано. Как и при применении, первый параметр также представляет указатель this в теле функции.Начиная со второго параметра, каждый параметр передается в функцию по очереди.
3. Реализовать функции вызова, применения и привязки
(1) Этапы реализации функции вызова:
- Определить, является ли вызывающий объект функцией, даже если он определен в прототипе функции, но может быть вызван с помощью call и других методов.
- Определите, существует ли объект входящего контекста, если нет, установите его в window.
- Обработать входящие параметры и перехватить все параметры после первого параметра.
- Как функция свойства объекта контекста.
- Используйте объект контекста, чтобы вызвать этот метод и сохранить возвращенный результат.
- Удалите только что добавленное свойство.
- Вернуть результат.
Function.prototype.myCall = function(context) {
// 判断调用对象
if (typeof this !== "function") {
console.error("type error");
}
// 获取参数
let args = [...arguments].slice(1),
result = null;
// 判断 context 是否传入,如果未传入则设置为 window
context = context || window;
// 将调用函数设为对象的方法
context.fn = this;
// 调用函数
result = context.fn(...args);
// 将属性删除
delete context.fn;
return result;
};
(2) Этапы реализации функции применения:
- Определить, является ли вызывающий объект функцией, даже если он определен в прототипе функции, но может быть вызван с помощью call и других методов.
- Определите, существует ли объект входящего контекста, если нет, установите его в window.
- Сделайте функцию свойством объекта контекста.
- Определить, передается ли значение параметра в
- Используйте объект контекста, чтобы вызвать этот метод и сохранить возвращенный результат.
- Удалить недавно добавленное свойство
- вернуть результат
Function.prototype.myApply = function(context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
let result = null;
// 判断 context 是否存在,如果未传入则为 window
context = context || window;
// 将函数设为对象的方法
context.fn = this;
// 调用方法
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
// 将属性删除
delete context.fn;
return result;
};
(3) Этапы реализации функции привязки:
- Определить, является ли вызывающий объект функцией, даже если он определен в прототипе функции, но может быть вызван с помощью call и других методов.
- Сохраните ссылку на текущую функцию и получите остальные значения входящих параметров.
- Создать функцию, которая возвращается
- Функция использует применение для привязки вызова функции. Необходимо оценить, используется ли функция в качестве конструктора. В это время вам нужно передать this текущей функции в вызов применения, а другие случаи передаются в указанный объект контекста.
Function.prototype.myBind = function(context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
// 获取参数
var args = [...arguments].slice(1),
fn = this;
return function Fn() {
// 根据调用方式,传入不同绑定值
return fn.apply(
this instanceof Fn ? this : context,
args.concat(...arguments)
);
};
};
7. Асинхронное программирование
1. Как реализовано асинхронное программирование?
Асинхронные механизмы в JavaScript можно разделить на следующие категории:
- ПерезвонитеМетод использования функции обратного вызова имеет тот недостаток, что когда несколько функций обратного вызова вложены друг в друга, это вызовет ад функции обратного вызова, а связь кода между верхней и нижней функциями обратного вызова слишком высока, что не способствует ремонтопригодности кода. .
- PromiseСпособ использования Promise может вызывать вложенные функции обратного вызова как цепочку. Однако использование этого метода иногда приводит к множественным последовательным вызовам, что может сделать семантику кода неясной.
- generatorОн может передать право на выполнение функции во время выполнения функции и передать выполнение обратно вне функции. Когда встречается выполнение асинхронной функции, право на выполнение функции передается наружу, а когда выполнение асинхронной функции завершается, право на выполнение передается обратно. Поэтому асинхронную операцию внутри генератора можно записать в синхронном порядке. Проблема, которую необходимо учитывать при использовании этого метода, заключается в том, когда передавать управление функцией обратно, поэтому должен быть механизм автоматического выполнения генератора, такой как модуль co для реализации автоматического выполнения генератора.
- асинхронная функцияАсинхронная функция — это автоматически выполняемый синтаксический сахар, реализованный генератором и обещанием. У него есть собственный исполнитель. Когда функция выполняет оператор ожидания, если оператор возвращает объект обещания, функция будет ожидать объект обещания. Статус меняется на разрешить, а затем продолжить выполнение вниз. Следовательно, асинхронная логика может быть преобразована в синхронную последовательность для записи, и эта функция может выполняться автоматически.
2. Разница между setTimeout, Promise, Async/Await
(1) установить время ожидания
console.log('script start') //1. 打印 script start
setTimeout(function(){
console.log('settimeout') // 4. 打印 settimeout
}) // 2. 调用 setTimeout 函数,并定义其完成后执行的回调函数
console.log('script end') //3. 打印 script start
// 输出顺序:script start->script end->settimeout
(2) Обещания
Обещания сами по себеСинхронизированная функция немедленного исполнения, При выполнении resolve или reject в экзекьюторе это асинхронная операция, и тогда сначала будет выполняться/catch.Когда основной стек будет завершен, для выполнения будет вызван метод, хранящийся в resolve/reject, и когда p будет напечатано , это Print возвращаемый результат, экземпляр Promise.
console.log('script start')
let promise1 = new Promise(function (resolve) {
console.log('promise1')
resolve()
console.log('promise1 end')
}).then(function () {
console.log('promise2')
})
setTimeout(function(){
console.log('settimeout')
})
console.log('script end')
// 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout
Когда основной поток JS выполняется для объекта Promise:
- Обратный вызов promise1.then() — это задача
- обещание1 разрешено или отклонено: тогда эта задача будет помещена в очередь микрозадач текущего раунда цикла событий
- promise1 находится в ожидании: эта задача будет помещена в очередь микрозадач будущего (возможно, следующего) раунда цикла событий
- Обратный вызов setTimeout также является задачей, он будет помещен в очередь макрозадач, даже если он равен 0 мс.
(3) асинхронно/ожидание
async function async1(){
console.log('async1 start');
await async2();
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
console.log('script start');
async1();
console.log('script end')
// 输出顺序:script start->async1 start->async2->script end->async1 end
Асинхронная функция возвращает объект Promise.Когда функция выполняется, как только она сталкивается с ожиданием, она сначала возвращается, ждет завершения запущенной асинхронной операции, а затем выполняет оператор за телом функции. Это можно понимать как отказ от потока и выпрыгивание из тела асинхронной функции.
Например:
async function func1() {
return 1
}
console.log(func1())
Результат func1 на самом деле является объектом Promise. Таким образом, вы также можете использовать then для обработки последующей логики.
func1().then(res => {
console.log(res); // 30
})
Значение await заключается в ожидании, то есть асинхронной функции необходимо дождаться завершения выполнения функции после завершения await и получить возвращаемый результат (объект Promise), прежде чем продолжить выполнение следующего кода. await достигает синхронизации, возвращая объект Promise.
3. Понимание обещаний
Promise — это решение для асинхронного программирования. Это объект, который может получить сообщение об асинхронной операции. Его внешний вид значительно улучшает дилемму асинхронного программирования и позволяет избежать адского обратного вызова. Это более разумно, чем традиционная функция обратного вызова решения и событие. сильнее.
Так называемый Promise — это просто контейнер, содержащий результат события (обычно асинхронной операции), которое завершится в будущем. Синтаксически Promise — это объект, из которого можно получить сообщение для асинхронной операции. Обещания предоставляют унифицированный API, и различные асинхронные операции могут обрабатываться одинаково.
(1) Экземпляр Promise имееттри состояния:
- В ожидании (в процессе)
- Решено (завершено)
- Отклоненный
Когда вещь передается обещанию, ее статус — Ожидание, статус задачи становится Решенной, когда задача завершена, и становится Отклоненной, если она не завершена и не удалась.
(2) Экземпляры обещаниядва процесса:
- в ожидании -> выполнено: решено
- в ожидании -> отклонено: отклонено
Примечание. Как только вы перейдете из одного состояния в другое, вы уже никогда не сможете изменить это состояние.
Особенности обещаний:
- На состояние объекта не влияет внешний мир. Объект обещания представляет собой асинхронную операцию и имеет три состояния:
pending
(в ходе выполнения),fulfilled
(удалось),rejected
(не удалось). Только результат асинхронной операции может определить, в каком состоянии он находится в данный момент, и никакая другая операция не может изменить это состояние, откуда и произошло название обещание…обещать"; - Однажды состояние изменилось, оно больше не изменится, и этот результат можно получить в любой момент. Есть только две возможности изменить состояние объекта-обещания: с
pending
сталиfulfilled
,отpending
сталиrejected
. затем позвонилresolved
(уже доработано). Если изменение уже произошло, и вы добавляете callback-функцию к объекту обещания, вы получите результат немедленно. Это полностью отличается от событий.Характеристики событий таковы: если вы пропустите их, вы не сможете получить результаты, если будете слушать их снова.
Недостатки обещаний:
- Обещание нельзя отменить, оно будет выполнено сразу после создания и не может быть отменено на полпути.
- Если функция обратного вызова не установлена, ошибка, выданная внутри промиса, не будет отражена снаружи.
- Когда он находится в состоянии ожидания, невозможно узнать, на каком этапе он находится в данный момент (только что запущен или вот-вот завершится).
Суммировать:Объекты Promise — это решение для асинхронного программирования, впервые предложенное сообществом. Промис — это конструктор, который принимает функцию в качестве параметра и возвращает экземпляр промиса. Экземпляр Promise имеет три состояния: ожидание, решено и отклонено, которые означают выполнение, успешное и неудачное выполнение соответственно. Состояние экземпляра может быть изменено только с "ожидание" на "разрешено" или "отклонено", и как только состояние изменено, оно замораживается и не может быть изменено.
Изменение состояния достигается с помощью функций resolve() и reject(). Эти две функции можно вызывать после асинхронной операции для изменения состояния экземпляра Promise. Его прототип определяет метод then, который можно использовать для двух состояний. изменяет регистр функций обратного вызова. Эта функция обратного вызова является микрозадачей и будет выполняться в конце текущего цикла обработки событий.
Уведомление:в разработкеPromise
Когда код внутри конструктора выполняется немедленно
4. Базовое использование промисов
(1) Создайте объект обещания
Объект Promise представляет собой асинхронную операцию с тремя состояниями: ожидание (выполняется), выполнено (успешно) и отклонено (сбой).
Конструктор Promise принимает в качестве параметра функцию, два параметра которойresolve
а такжеreject
.
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Обычно используетсяnew Promise()
для создания объектов-обещаний, но вы также можете использоватьpromise.resolve
а такжеpromise.reject
Эти два метода:
- Promise.resolve
Promise.resolve(value)
Возвращаемое значение также является объектом обещания, и для возвращаемого значения можно выполнить вызов .then Код выглядит следующим образом:
Promise.resolve(11).then(function(value){
console.log(value); // 打印出11
});
resolve(11)
В коде пусть объект обещания вводит подтверждение (resolve
статус), а параметр11
перешел к следующемуthen
указанныйonFulfilled
функция;
Чтобы создать объект обещания, вы можете использоватьnew Promise
для создания объектов в виде , вы также можете использоватьPromise.resolve(value)
Создайте объект обещания в форме;
- Promise.reject
Promise.reject
Слишкомnew Promise
Форма ярлыка также создает объект обещания. код показывает, как показано ниже:
Promise.reject(new Error(“我错了,请原谅俺!!”));
Это простая форма следующего нового кода Promise:
new Promise(function(resolve,reject){
reject(new Error("我错了!"));
});
Далее следует использовать метод разрешения и метод отклонения:
function testPromise(ready) {
return new Promise(function(resolve,reject){
if(ready) {
resolve("hello world");
}else {
reject("No thanks");
}
});
};
// 方法调用
testPromise(true).then(function(msg){
console.log(msg);
},function(error){
console.log(error);
});
Смысл приведенного выше кода в том, чтобы датьtestPromise
Метод передает параметр и возвращает обещанный объект, если онtrue
Если это так, то вызовите объект обещания вresolve()
метод и передайте параметры следующемуthen
внутри первой функции, поэтому она печатает "hello world
", еслиfalse
Если это так, он вызовет объект обещания вreject()
метод, вы вводитеthen
внутри второй функции , напечатаетNo thanks
;
(2) Метод обещания
У Promise есть пять часто используемых методов: then(), catch(), all(), race() и finally. Давайте рассмотрим эти методы.
- then()
Вызывается, когда содержимое выполнения Promise соответствует условиям успехаresolve
функция, вызов при неудачеreject
функция. После того, как Promise создан, как его вызвать?
promise.then(function(value) {
// success
}, function(error) {
// failure
});
then
Метод может принимать две функции обратного вызова в качестве параметров. Первая функция обратного вызова заключается в том, что состояние объекта Promise становитсяresolved
При вызове второй функции обратного вызова состояние объекта Promise становитсяrejected
когда звонили. Второй параметр можно не указывать.then
Метод возвращает новый экземпляр Promise (не исходный экземпляр Promise). Поэтому можно использовать метод цепной записи, т.then
Затем метод вызывает другой метод then.
Если вы хотите написать последовательные асинхронные события и их нужно сериализовать, вы можете написать их так:
let promise = new Promise((resolve,reject)=>{
ajax('first').success(function(res){
resolve(res);
})
})
promise.then(res=>{
return new Promise((resovle,reject)=>{
ajax('second').success(function(res){
resolve(res)
})
})
}).then(res=>{
return new Promise((resovle,reject)=>{
ajax('second').success(function(res){
resolve(res)
})
})
}).then(res=>{
})
Тогда как писать, когда события, которые нужно написать, не имеют ни последовательности, ни взаимосвязи? можно использоватьall
метод решения.
2. catch()
В дополнение к методу then объект Promise также имеет метод catch, эквивалентный методуthen
Второй параметр метода, указывающий наreject
функция обратного вызова. ноcatch
Метод также имеет другую функцию, которая заключается в выполненииresolve
При обратном вызове функции, если возникает ошибка, выбрасывается исключение, она не перестанет выполняться, а войдетcatch
метод.
p.then((data) => {
console.log('resolved',data);
},(err) => {
console.log('rejected',err);
}
);
p.then((data) => {
console.log('resolved',data);
}).catch((err) => {
console.log('rejected',err);
});
3. all()
all
Метод может выполнять параллельные задачи, он получает массив, каждый элемент массива представляет собойpromise
объект. когда всеpromise
статус достигresolved
когда,all
Состояние метода становитсяresolved
, если состояние становитсяrejected
,Такall
Состояние метода становитсяrejected
.
javascript
let promise1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(1);
},2000)
});
let promise2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(2);
},1000)
});
let promise3 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(3);
},3000)
});
Promise.all([promise1,promise2,promise3]).then(res=>{
console.log(res);
//结果为:[1,2,3]
})
передачаall
Когда результат метода успешен, параметр функции обратного вызова также является массивом, и этот массив хранит каждый объект промиса по порядку.resolve
значение во время выполнения.
(4) раса ()
race
Методы иall
Например, принятый параметрpromise
, но сall
Разница в том, что при выполнении первого выполненного события оно возвращается непосредственно кpromise
стоимость объекта. если первыйpromise
состояние объекта становитсяresolved
, само состояние становитсяresolved
; иначе первыйpromise
сталиrejected
, то его состояние станетrejected
.
let promise1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject(1);
},2000)
});
let promise2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(2);
},1000)
});
let promise3 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(3);
},3000)
});
Promise.race([promise1,promise2,promise3]).then(res=>{
console.log(res);
//结果:2
},rej=>{
console.log(rej)};
)
Такrace
Что на самом деле делает метод? Когда хочется что-то сделать, но долго не получается, можно использовать для решения такой метод:
Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})
5. finally()
finally
Методы используются для указания действий, которые будут выполняться независимо от конечного состояния объекта Promise. Этот метод является стандартным, представленным в ES2018.
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
В приведенном выше коде, независимо отpromise
конечное состояние, после выполненияthen
илиcatch
После указанной функции обратного вызова она будет выполненаfinally
Функция обратного вызова, указанная методом.
Вот пример, когда сервер обрабатывает запрос с обещанием, а затем используетfinally
способ выключения сервера.
server.listen(port)
.then(function () {
// ...
})
.finally(server.stop);
finally
Функция обратного вызова метода не принимает никаких параметров, а это значит, что невозможно узнать, каково было предыдущее состояние промиса.fulfilled
ещеrejected
. Это указывает,finally
Операции в методе должны быть независимы от состояния и не зависеть от результата выполнения промиса.finally
По сутиthen
Частный случай метода:
promise
.finally(() => {
// 语句
});
// 等同于
promise
.then(
result => {
// 语句
return result;
},
error => {
// 语句
throw error;
}
);
В приведенном выше коде, если вы не используетеfinally
один и тот же оператор должен быть написан один раз как для успеха, так и для неудачи. имеютfinally
метод, вам нужно написать его только один раз.
5. Какую проблему решает обещание?
Я часто сталкиваюсь с таким требованием в своей работе, например, после того, как я использую ajax для отправки запроса A, я получаю данные после успеха, и мне нужно передать данные запросу B, тогда мне нужно написать код как следует:
let fs = require('fs')
fs.readFile('./a.txt','utf8',function(err,data){
fs.readFile(data,'utf8',function(err,data){
fs.readFile(data,'utf8',function(err,data){
console.log(data)
})
})
})
Приведенный выше код имеет следующие недостатки:
- Последний запрос должен полагаться на успех предыдущего запроса, а затем передавать данные вниз, что приведет к вложению нескольких запросов ajax, а код недостаточно интуитивно понятен.
- Если двум запросам до и после не нужно передавать параметры, то последний запрос также должен выполнить следующую операцию после успешного выполнения предыдущего запроса, в этом случае код нужно написать, как указано выше, что делает код не достаточно интуитивно.
Promise
После появления код становится таким:
let fs = require('fs')
function read(url){
return new Promise((resolve,reject)=>{
fs.readFile(url,'utf8',function(error,data){
error && reject(error)
resolve(data)
})
})
}
read('./a.txt').then(data=>{
return read(data)
}).then(data=>{
return read(data)
}).then(data=>{
console.log(data)
})
Таким образом, код выглядит намного проще и решает проблему адского обратного вызова.
6. Сценарии использования разницы между Promise.all и Promise.race
(1) Обещание.все
Promise.all
несколькоPromise
Экземпляр заключен в новый экземпляр Promise. При этом возвращаемые значения успеха и неудачи различны, а возвращаемое значение при успехе равномассив результатов, а в случае неудачи возвращаетсяЗначение первого отклоненного статуса отказа.
В Promise.all передается массив, и возвращаемое значение также является массивом, и оно будет отображено.Значения, возвращаемые входящим объектом обещания, располагаются в массиве по порядку, но обратите внимание, что порядок их выполнение не соответствует Sequential, если только итерация не пуста.
Следует отметить, что порядок данных в массиве успешного результата, полученного Promise.all, такой же, как и порядок массива, полученного Promise.all, так что при отправке нескольких запросов и получении данных и используется в соответствии с порядком запросов, вы можете использовать Promise.all для решения.
(2) Обещание.гонка
Как следует из названия, Promse.race означает выполнение, что означает, что любой результат Promise.race([p1, p2, p3]) дает самый быстрый результат, будет возвращен независимо от того, является ли сам результат состоянием успеха или неудачей. состояние. Когда хочется что-то сделать, но долго не получается, можно использовать для решения такой метод:
Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})
7. Понимание async/await
async/await на самом делеGenerator
Синтаксический сахар, которого можно достичь, может быть достигнут с помощью цепочки then, которая разработана для оптимизации цепочки then. Буквально, async — это сокращение от «асинхронный», а await означает ожидание, поэтому хорошо известно, что async используется для объявления того, что функция является асинхронной, а await используется для ожидания завершения выполнения асинхронного метода. Конечно, синтаксис требует, чтобы ожидание могло появляться только в функциях asnyc.Давайте посмотрим, что возвращает функция async:
async function testAsy(){
return 'hello world';
}
let result = testAsy();
console.log(result)
Итак, асинхронная функция возвращает объект Promise. асинхронные функции (включая операторы функций, функциональные выражения, лямбда-выражения) будут возвращать объект Promise, если в функцииreturn
Литерал, async будет передавать этот литерал черезPromise.resolve()
Инкапсулируется как объект Promise.
Асинхронная функция возвращает объект Promise, поэтому в случае, если самый внешний уровень не может использовать await для получения возвращаемого значения, конечно, следует использовать исходный метод:then()
цепочка для обработки этого объекта Promise, например:
async function testAsy(){
return 'hello world'
}
let result = testAsy()
console.log(result)
result.then(v=>{
console.log(v) // hello world
})
Так что, если асинхронная функция не возвращает значение? Легко думать, что он вернетсяPromise.resolve(undefined)
.
Подумайте о характеристиках Обещания - никакого ожидания, поэтому, когда нетawait
Если выполняется асинхронная функция, она выполняется немедленно, возвращает объект Promise и никогда не блокирует последующие операторы. Это ничем не отличается от обычной функции, которая возвращает объект Promise.
Уведомление:Promise.resolve(x)
можно рассматривать какnew Promise(resolve => resolve(x))
Сокращение для быстрой инкапсуляции литеральных объектов или других объектов в экземпляры Promise.
8. Что ждет ждет?
Что ждет ждет?Вообще говоря, await считается ожиданием завершения асинхронной функции. Однако, согласно синтаксису, await ожидает выражения, результатом которого является объект Promise или другое значение (другими словами, нет специальной квалификации).
Поскольку асинхронная функция возвращает объект Promise, await можно использовать для ожидания возвращаемого значения асинхронной функции, что также можно назвать ожиданием асинхронной функции, но, чтобы быть ясным, на самом деле она ожидает возвращаемого значения. Обратите внимание, что await используется не только для ожидания объектов Promise, но и для ожидания результата любого выражения, так что после await могут следовать обычные вызовы функций или прямые количества. Таким образом, следующий пример отлично работает:
function getSomething() {
return "something";
}
async function testAsync() {
return Promise.resolve("hello async");
}
async function test() {
const v1 = await getSomething();
const v2 = await testAsync();
console.log(v1, v2);
}
test();
Результат выражения ожидания зависит от того, чего оно ожидает.
- Если он не ожидает объекта Promise, он ожидает результата выражения await.
- Если он ожидает объекта Promise, ожидание занято, оно заблокирует код позади, ожидая разрешения объекта Promise, а затем получит значение разрешения в результате выражения await.
Давайте посмотрим пример:
function testAsy(x){
return new Promise(resolve=>{setTimeout(() => {
resolve(x);
}, 3000)
}
)
}
async function testAwt(){
let result = await testAsy('hello world');
console.log(result); // 3秒钟之后出现hello world
console.log('cuger') // 3秒钟之后出现cug
}
testAwt();
console.log('cug') //立即输出cug
По этой причине необходимо дождаться асинхронной функции. вызов асинхронной функции не вызывает препятствий, блокируя все внутри нее, асинхронно инкапсулируется в объект Promise. async await приостанавливает выполнение в данный момент, поэтому «cug», «первый вывод, привет, мир» и «cuger» происходят одновременно через 3 секунды.
9. Преимущества Async / ждут
Одна цепочка промисов не может найти преимущества async/await, но если вам нужно иметь дело с цепочкой then, состоящей из нескольких промисов, преимущества можно отразить (что интересно, Promise решает проблему многоуровневых обратных вызовов через цепочку then, а теперь используйте async/await для дальнейшей оптимизации).
Предположим, бизнес завершается в несколько шагов, каждый шаг асинхронен и зависит от результата предыдущего шага. все еще используюsetTimeout
Чтобы имитировать асинхронную операцию:
/**
* 传入参数 n,表示这个函数执行的时间(毫秒)
* 执行的结果是 n + 200,这个值将用于下一步骤
*/
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
Теперь используйте Promise для реализации обработки этих трех шагов:
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => step2(time2))
.then(time3 => step3(time3))
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms
выходной результатresult
даstep3()
параметры700 + 200
= 900
.doIt()
Три шага выполняются последовательно, и в общей сложности300 + 500 + 700 = 1500
миллисекунды иconsole.time()/console.timeEnd()
Результаты расчетов согласуются.
Если это реализовано с помощью async/await, то будет так:
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
Результат такой же, как и в предыдущей реализации Promise, но этот код выглядит намного чище, почти так же, как синхронный код
10. Преимущества async/await по сравнению с Promise
- Код читается более синхронно.Хотя Promise избавлен от ада обратных вызовов, цепочка вызовов then также создает дополнительную нагрузку на чтение.
- Promise очень хлопотно передавать промежуточные значения, а async/await — это почти синхронный способ записи, очень элегантный
- Дружественная обработка ошибок, async/await может использовать зрелый try/catch, захват ошибок Promise очень избыточен
- Дружественен к отладке, промисы плохо отлаживаются, так как нет блоков кода, вы не можете установить точки останова в стрелочной функции, которая возвращает выражение, если вы используете функцию пошагового (шагового) отладчика, отладчик не переходит в последующие .then блоки кода, потому что отладчик может отслеживать только каждый шаг синхронного кода.
11. Как async/await перехватывает исключения
async function fn(){
try{
let a = await Promise.reject('error')
}catch(error){
console.log(error)
}
}
12. В чем разница между параллелизмом и параллелизмом?
- Параллелизм — это макроконцепция. У меня есть задача A и задача B соответственно, и я завершаю эти две задачи, переключаясь между задачами в течение определенного периода времени. Эту ситуацию можно назвать параллелизмом.
- Параллелизм — это микроконцепция, предполагающая, что в ЦП два ядра, тогда я могу выполнять задачи A и B одновременно. Выполнение нескольких задач одновременно можно назвать параллелизмом.
13. Что такое функция обратного вызова? Каковы недостатки функций обратного вызова? Как решить проблему с callback hell?
Следующий код является примером функции обратного вызова:
ajax(url, () => {
// 处理逻辑
})
Функции обратного вызова имеют фатальную слабость, то есть легко написать ад обратного вызова (Callback hell). Предполагая, что несколько запросов имеют зависимости, может быть такой код:
ajax(url, () => {
// 处理逻辑
ajax(url1, () => {
// 处理逻辑
ajax(url2, () => {
// 处理逻辑
})
})
})
Вышеприведенный код не кажется удобным для чтения и обслуживания.Конечно, функции также могут быть написаны отдельно:
function firstAjax() {
ajax(url1, () => {
// 处理逻辑
secondAjax()
})
}
function secondAjax() {
ajax(url2, () => {
// 处理逻辑
})
}
ajax(url, () => {
// 处理逻辑
firstAjax()
})
Хотя приведенный выше код кажется легко читаемым, он все же не решает фундаментальную проблему. Фундаментальная проблема с callback hell:
- Вложенные функции связаны, и как только они будут изменены, они повлияют на все тело.
- Когда много вложенных функций, становится сложно обрабатывать ошибки
Конечно, функция обратного вызова также имеет несколько других недостатков, таких как невозможность использоватьtry catch
ловить ошибки, а не напрямуюreturn
.
14. Каковы характеристики setTimeout, setInterval и requestAnimationFrame?
Асинхронное программирование, конечно, необходимо для таймеров.Обычные функции таймера включаютsetTimeout
,setInterval
,requestAnimationFrame
. Наиболее часто используютсяsetTimeout
, многие думаютsetTimeout
Как долго длится задержка, через какое время она должна быть выполнена.
На самом деле это мнение ошибочно, т.к. JS выполняется в одном потоке, если предыдущий код повлияет на производительность, то это приведет кsetTimeout
не будет выполнено вовремя. Конечно, это можно исправить кодом.setTimeout
, что делает таймер относительно точным:
let period = 60 * 1000 * 60 * 2
let startTime = new Date().getTime()
let count = 0
let end = new Date().getTime() + period
let interval = 1000
let currentInterval = interval
function loop() {
count++
// 代码执行所消耗的时间
let offset = new Date().getTime() - (startTime + count * interval);
let diff = end - new Date().getTime()
let h = Math.floor(diff / (60 * 1000 * 60))
let hdiff = diff % (60 * 1000 * 60)
let m = Math.floor(hdiff / (60 * 1000))
let mdiff = hdiff % (60 * 1000)
let s = mdiff / (1000)
let sCeil = Math.ceil(s)
let sFloor = Math.floor(s)
// 得到下一次循环所消耗的时间
currentInterval = interval - offset
console.log('时:'+h, '分:'+m, '毫秒:'+s, '秒向上取整:'+sCeil, '代码执行时间:'+offset, '下次循环间隔'+currentInterval)
setTimeout(loop, currentInterval)
}
setTimeout(loop, currentInterval)
см. далееsetInterval
, на самом деле эта функция работает иsetTimeout
В основном то же самое, за исключением того, что эта функция время от времени выполняет функцию обратного вызова.
Обычно не рекомендуетсяsetInterval
. Во-первых, это иsetTimeout
Опять же, нет никакой гарантии, что задача будет выполнена в ожидаемое время. Во-вторых, у него есть проблема с выполнением накопления, см. следующий псевдокод
function demo() {
setInterval(function(){
console.log(2)
},1000)
sleep(2000)
}
demo()
В среде браузера приведенного выше кода, если во время выполнения таймера происходит трудоемкая операция, несколько функций обратного вызова будут выполняться одновременно после завершения трудоемкой операции, что может вызвать проблемы с производительностью.
Если есть необходимость в циклическом таймере, то вполне можно пройтиrequestAnimationFrame
реализовать:
function setInterval(callback, interval) {
let timer
const now = Date.now
let startTime = now()
let endTime = startTime
const loop = () => {
timer = window.requestAnimationFrame(loop)
endTime = now()
if (endTime - startTime >= interval) {
startTime = endTime = now()
callback(timer)
}
}
timer = window.requestAnimationFrame(loop)
return timer
}
let a = 0
setInterval(timer => {
console.log(1)
a++
if (a === 3) cancelAnimationFrame(timer)
}, 1000)
первыйrequestAnimationFrame
Встроенная функция дросселирования может в основном гарантировать, что она выполняется только один раз в течение 16,6 миллисекунд (без потери кадра), и эффект задержки этой функции является точным, и нет проблемы неточного времени других таймеров. , вы также можете быть достигнуты с помощью этой функцииsetTimeout
.
Восемь, объектно-ориентированные
1. Путь объектов создал что?
Как правило, объекты создаются непосредственно в виде литералов, но этот метод создания будет генерировать много повторяющегося кода при создании большого количества похожих объектов. Но js отличается от обычных объектно-ориентированных языков тем, что до ES6 в нем не было понятия классов. Однако для имитации можно использовать функции, что приводит к многократно используемым методам создания объектов.
(1) Первый - это фабричный режим.Основной принцип работы фабричного режима заключается в использовании функций для инкапсуляции деталей создания объектов, чтобы достичь цели повторного использования путем вызова функций. Но у него есть большая проблема в том, что созданный объект нельзя связать с определенным типом, он просто инкапсулирует код повторного использования, не устанавливая отношения между объектом и типом.
(2) Второй — режим конструктора. Каждую функцию в js можно использовать как конструктор, пока функция вызывается через new, ее можно назвать конструктором. Выполнение конструктора сначала создает объект, затем указывает прототип объекта на свойство прототипа конструктора, затем указывает это в контексте выполнения на объект и, наконец, выполняет всю функцию.Если возвращаемое значение не является объектом, вновь созданный объект возвращается. Поскольку значение this указывает на вновь созданный объект, вы можете использовать это для присвоения значений объектам. Преимущество шаблона конструктора перед шаблоном фабрики заключается в том, что созданный объект связан с конструктором, поэтому тип объекта можно определить через прототип. Однако недостатком конструктора является то, что он вызывает создание ненужных объектов-функций, потому что функция тоже является объектом в js, поэтому, если свойство объекта содержит функцию, то каждый раз будет создаваться новый объект-функция, тратя ненужные пространство памяти, потому что функция является общей для всех экземпляров.
(3) Третий шаблон — это шаблон прототипа, поскольку каждая функция имеет свойство прототипа, которое представляет собой объект, содержащий свойства и методы, общие для всех экземпляров, созданных конструктором. Таким образом, вы можете использовать объект-прототип для добавления общедоступных свойств и методов для повторного использования кода. По сравнению с режимом конструктора этот метод решает проблему повторного использования объектов-функций. Но есть и некоторые проблемы с этим режимом: во-первых, нет возможности инициализировать значение путем передачи параметров, во-вторых, если есть значение ссылочного типа, такого как Array, то все экземпляры будут совместно использовать объект , и экземпляр будет иметь общее значение ссылочного типа. Изменения повлияют на все экземпляры.
(4) Четвертый шаблон представляет собой комбинацию шаблона конструктора и шаблона прототипа, что является наиболее распространенным способом создания пользовательских типов. Поскольку существуют некоторые проблемы при использовании режима конструктора и режима прототипа по отдельности, вы можете использовать эти два режима в комбинации, инициализировать свойства объекта через конструктор и реализовать повторное использование методов функции через объект-прототип. Этот метод устраняет недостатки двух режимов, когда они используются по отдельности, но недостатком является то, что поскольку используются два разных режима, инкапсуляция кода недостаточно хороша.
(5) Пятый режим - это режим динамического прототипа. Этот режим перемещает процесс создания назначения метода прототипа интерьера конструктора. Судя, существует ли атрибут, прототип может быть реализован только тогда, когда функция вызывается для первый раз. Эффект присвоения объекта один раз. Таким образом, это хороший способ инкапсулировать вышеуказанный смешанный режим.
(6) Шестой режим — это режим паразитного конструктора. Реализация этого режима в основном такая же, как и у фабричного режима. Насколько я понимаю, этот режим таков, что он в основном основан на существующем типе объекта, подлежащем расширению. Это не требует изменения исходного конструктора, но также позволяет расширить объект. Один из его недостатков такой же, как у фабричного шаблона, который не может реализовать распознавание объектов.
2. Какие существуют способы наследования объектов?
(1) Первый — реализовать наследование в виде цепочки прототипов, но недостатком этого метода реализации является то, что при включении данных ссылочного типа они будут общими для всех экземплярных объектов, что легко может вызвать путаницу в модификация. Также нельзя передавать параметры супертипу при создании подтипа.
(2) Второй способ заключается в использовании заимствованного конструктора, который реализуется путем вызова конструктора супертипа в функции подтипа.Этот метод устраняет недостаток невозможности передать параметры в супертип. , но одна из его проблем заключается в том, что он не может обеспечить повторное использование методов функций, а подтипы методов, определенные прототипом супертипа, недоступны.
(3) Третий способ — составное наследование, то есть способ объединения цепочки прототипов и заимствования конструктора. Наследование свойств типа достигается за счет заимствования функции-конструктора, а наследование методов достигается путем установки прототипа подтипа в экземпляр супертипа. Этот метод решает проблему, когда два вышеуказанных режима используются по отдельности, но поскольку мы используем экземпляр супертипа в качестве прототипа подтипа, конструктор супертипа вызывается дважды, в результате чего получается прототип подтипа. много ненужных атрибутов.
(4)Четвёртый метод прототипное наследование.Основная идея прототипного наследования заключается в создании новых объектов на основе существующих объектов.Принцип реализации заключается в передаче объекта в функцию, а затем возврате объекта с этим объектом как объект-прототип. Идея этого наследования заключается не в создании нового типа, а в реализации простого наследования объекта.Метод Object.create(), определенный в ES5, является реализацией прототипного наследования. Недостаток тот же, что и у прототипа цепным способом.
(5) Пятый способ — это паразитное наследование. Идея паразитического наследования состоит в том, чтобы создать функцию для инкапсуляции процесса преемственности, передав объект, затем дублирующую копию объекта, затем объект расширяется и, наконец, возвращает объект. Этот расширенный процесс может понимать наследование. Это наследование преимуществ простого наследования объектов, если этот объект не является пользовательским типом. Недостатком является то, что нет возможности реализовать функцию мультиплексирования.
(6) Шестой способ — это паразитное комбинированное наследование, недостатком комбинированного наследования является использование экземпляра супертипа в качестве прототипа подтипа, что приводит к ненужному атрибуту прототипа. Паразитическое комбинированное наследование — это использование прототипа супертипа в качестве прототипа подтипа, что позволяет избежать создания ненужных свойств.
9. Сборка мусора и утечки памяти
1. Механизм сборки мусора в браузере
(1) Концепция сбора мусора
вывоз мусора: Когда код JavaScript запускается, ему необходимо выделить место в памяти для хранения переменных и значений. Когда переменная не участвует в операции, системе необходимо освободить занятое пространство памяти, что является сборкой мусора.
механизм рециркуляции:
- Javascript имеет механизм автоматической сборки мусора, который периодически освобождает память, занятую переменными и объектами, которые больше не используются, принцип заключается в том, чтобы найти переменные, которые больше не используются, а затем освободить занимаемую ими память.
- В JavaScript есть два типа переменных: локальные переменные и глобальные переменные. Жизненный цикл глобальных переменных будет продолжаться с выгрузкой страниц, в то время как локальные переменные объявляются в функциях, их жизненный цикл начинается с выполнения функции до конца выполнения функции, во время этого процесса локальные переменные сохраняют свои значения в куче или stack , когда выполнение функции завершается, эти локальные переменные больше не используются, а место, которое они занимают, освобождается.
- Однако, когда локальная переменная используется внешней функцией, одним из случаев является замыкание.После завершения выполнения функции переменная вне функции все еще указывает на локальную переменную внутри функции.В это время локальная переменная все еще используется, поэтому он не будет переработан.
(2) Способ сбора мусора
В браузерах обычно используются два метода сборки мусора: очистка по меткам и подсчет ссылок.1) Отметить как очищенный
- Пометка и очистка — это распространенный метод сборки мусора в браузерах. Когда переменная попадает в среду выполнения, переменная помечается как «входящая в среду». Переменные, помеченные как «входящие в среду», не могут быть переработаны, поскольку они используются. Когда переменная покидает среду, она помечается как «выходящая из среды», а переменная, помеченная как «выходящая из среды», освобождается памятью.
- Когда сборщик мусора запускается, он помечает все переменные, хранящиеся в памяти. Затем он удаляет переменные в среде и теги, на которые ссылаются переменные в среде. Переменные, отмеченные после этого, будут рассматриваться как переменные, готовые к удалению, поскольку переменные в среде больше не доступны. наконец. Сборщик мусора завершает очистку памяти, уничтожает помеченные значения и освобождает место в памяти, которое они занимают.
2) Подсчет ссылок
- Другим механизмом сборки мусора является подсчет ссылок, который используется относительно редко. Подсчет ссылок отслеживает, сколько раз упоминается каждое значение. Когда переменная объявляется и ей присваивается ссылочный тип, количество ссылок на это значение равно 1. И наоборот, если переменная, содержащая ссылку на это значение, принимает другое значение, количество ссылок на это значение уменьшается на 1. Когда количество ссылок становится равным 0, это означает, что переменная не имеет значения, поэтому пространство памяти, занимаемое переменной, будет освобождено при ее следующем запуске в течение периода восстановления машины.
- Этот метод вызоветциклическая ссылкаВопрос: Например:
obj1
а такжеobj2
Ссылаясь друг на друга через свойства, количество ссылок на оба объекта равно 2. При использовании подсчета циклов, поскольку оба объекта покидают область видимости после выполнения функции, выполнение функции завершается,obj1
а такжеobj2
будут продолжать существовать, поэтому их счетчик ссылок никогда не будет равен 0, вызывая циклические ссылки.
function fun() {
let obj1 = {};
let obj2 = {};
obj1.a = obj2; // obj1 引用 obj2
obj2.a = obj1; // obj2 引用 obj1
}
В этом случае необходимо вручную освободить память, занимаемую переменной:
obj1.a = null
obj2.a = null
(3) Уменьшить сбор мусора
Хотя браузеры могут выполнять автоматическую сборку мусора, когда код сложный, стоимость сборки мусора относительно высока, поэтому сбор мусора следует свести к минимуму.
- Оптимизация массивов:При очистке массива проще всего присвоить его [ ], но в то же время будет создан новый пустой объект, а длину массива можно установить равной 0 для достижения цели очистки массива.
-
правильно
object
оптимизировать:Объекты максимально повторно используются.Для объектов, которые больше не используются, им присваивается значение null, и они перерабатываются как можно скорее. - Оптимизируйте функцию:Если выражение функции в цикле можно использовать повторно, попробуйте поместить его вне функции.
2. Какие ситуации могут привести к утечкам памяти
Следующие четыре случая будут утечкой памяти:
- Неожиданная глобальная переменная:Глобальная переменная была случайно создана из-за использования необъявленной переменной, и эта переменная остается в памяти и не может быть перезапущена.
- Забытый таймер или функция обратного вызова:Если установлен таймер setInterval и вы забыли его отменить, если функция цикла имеет ссылку на внешнюю переменную, переменная останется в памяти и не может быть перезапущена.
- Разыменовано из DOM:Получает ссылку на элемент DOM, и последний элемент удаляется. Поскольку ссылка на этот элемент была сохранена, ее нельзя использовать повторно.
- Закрытие:Необоснованное использование замыканий, в результате чего некоторые переменные остаются в памяти.