оригинал:Dev.to/Оман_Сингх/…
Переводчик: Front-end Xiaozhi
Ставь лайк и смотри, поиск в WeChat【Переезд в мир】Обратите внимание на этого человека, который не имеет большого фабричного прошлого, но имеет восходящий и позитивный настрой. эта статья
GitHub
GitHub.com/QQ449245884…Он был включен, статьи были классифицированы, и многие мои документы и учебные материалы были систематизированы.
Все говорили, что нет проекта для написания резюме, поэтому я помог вам найти проект, и это было с бонусом.【Учебник по строительству】.
Далее взгляните на несколько вопросов, вы тоже можете попробовать подумать, а потом ответить.
Вопрос 1: Что будет напечатано в консоли браузера?
var a = 10;
function foo() {
console.log(a); // ??
var a = 20;
}
foo();
Вопрос 2: Будет ли вывод таким же, если мы используем let или const вместо var
var a = 10;
function foo() {
console.log(a); // ??
let a = 20;
}
foo();
Вопрос 3: Какие элементы находятся в «newArray»?
var array = [];
for(var i = 0; i <3; i++) {
array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // ??
Вопрос 4: Если мы запустим функцию «foo» в консоли браузера, вызовет ли это ошибку переполнения стека?
function foo() {
setTimeout(foo, 0); // 是否存在堆栈溢出错误?
};
Вопрос 5. По-прежнему ли реагирует пользовательский интерфейс страницы (вкладки), если в консоли запущена следующая функция
function foo() {
return Promise.resolve().then(foo);
};
Вопрос 6: Можем ли мы каким-то образом использовать операцию распространения для следующего оператора, не вызывая ошибки типа
var obj = { x: 1, y: 2, z: 3 };
[...obj]; // TypeError
Вопрос 7: Когда запускается следующий фрагмент кода, что выводится на консоль?
var obj = { a: 1, b: 2 };
Object.setPrototypeOf(obj, {c: 3});
Object.defineProperty(obj, 'd', { value: 4, enumerable: false });
// what properties will be printed when we run the for-in loop?
for(let prop in obj) {
console.log(prop);
}
Вопрос 8: Какое значение выведет xGetter()?
var x = 10;
var foo = {
x: 90,
getX: function() {
return this.x;
}
};
foo.getX(); // prints 90
var xGetter = foo.getX;
xGetter(); // prints ??
Отвечать
Теперь давайте ответим на каждый вопрос от начала до конца. Я дам вам краткое объяснение, пытаясь демистифицировать это поведение и предоставить некоторые ссылки.
Вопрос 1:undefined
Разобрать:
использоватьvar
Переменные, объявленные с ключевыми словами, поднимаются в JavaScript и им присваиваются значения в памяти.undefined
. Но инициализация происходит именно там, где вы присваиваете значения переменным. Кроме того,var
Объявленная переменнаяобласть действия функции,иlet
иconst
имеет блочную область действия. Итак, вот как выглядит процесс:
var a = 10; // 全局使用域
function foo() {
// var a 的声明将被提升到到函数的顶部。
// 比如:var a
console.log(a); // 打印 undefined
// 实际初始化值20只发生在这里
var a = 20; // local scope
}
Вопрос 2:ReferenceError:a undefined
.
Разобрать:
let
иconst
Объявление ограничивает область действия переменной блоком, оператором или выражением, в котором она используется. иvar
Отличие в том, что эти переменные не поднимаются и есть т.н.Временная мертвая зона (TDZ). пытаясь получить доступTDZЭти переменные будут бросатьReferenceError
, потому что к ним можно получить доступ только тогда, когда выполнение достигает объявления.
var a = 10; // 全局使用域
function foo() { // TDZ 开始
// 创建了未初始化的'a'
console.log(a); // ReferenceError
// TDZ结束,'a'仅在此处初始化,值为20
let a = 20;
}
В следующей таблице показано поведение повышения и области использования, соответствующие переменным, объявленным с разными ключевыми словами, используемыми в JavaScript:
Вопрос 3:[3, 3, 3]
Разобрать:
существуетfor
Объявление заголовка цикла имеетvar
Переменная ключевого слова создает единую привязку (пространство хранения) для этой переменной. Подробнее оЗакрытиеИнформация. Давайте снова посмотрим на цикл for.
// 误解作用域:认为存在块级作用域
var array = [];
for (var i = 0; i < 3; i++) {
// 三个箭头函数体中的每个`'i'`都指向相同的绑定,
// 这就是为什么它们在循环结束时返回相同的值'3'。
array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [3, 3, 3]
При использованииlet
Объявление переменной с областью действия блока создает новую привязку для каждой итерации цикла.
// 使用ES6块级作用域
var array = [];
for (let i = 0; i < 3; i++) {
// 这一次,每个'i'指的是一个新的的绑定,并保留当前的值。
// 因此,每个箭头函数返回一个不同的值。
array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [0, 1, 2]
Другой способ решить эту проблему — использоватьЗакрытие.
let array = [];
for (var i = 0; i < 3; i++) {
array[i] = (function(x) {
return function() {
return x;
};
})(i);
}
const newArray = array.map(el => el());
console.log(newArray); // [0, 1, 2]
Проблема 4: Нет переполнения
Разобрать:
Модель параллелизма JavaScript основана на «цикле событий». Когда мы говорим, что «браузер является домом для JS», я на самом деле имею в виду, что браузер предоставляет среду выполнения для выполнения нашего кода JS.
К основным компонентам браузера относятсястек вызовов,цикл событий**, очередь задачиВеб-API**. рисунокsetTimeout
,setInterval
иPromise
Такие глобальные функции не являются частью JavaScript, а являются частью Web API. Визуализация среды JavaScript выглядит так:
Стек вызовов JS работает по принципу «последним пришел — первым обслужен» (LIFO). Механизм берет одну функцию из стека за раз и выполняет код последовательно сверху вниз. Всякий раз, когда он сталкивается с каким-либо асинхронным кодом, напримерsetTimeout
, это даетWeb API
(стрелка 1). Поэтому всякий раз, когда событие запускается,callback
отправляются в очередь задач (стрелка 2).
Цикл событийПостоянно следите за очередью задач и обрабатывайте обратные вызовы по одному в том порядке, в котором они стоят в очереди. в любое времястек вызововКогда пусто,Event loopполучить обратный вызов и ввести егокуча(стрелка 3). Помните, что если стек вызовов не пуст,тогда цикл событий не будет помещать в стек какие-либо обратные вызовы.
Теперь, с этими знаниями, давайте ответим на ранее упомянутые вопросы:
шаг
-
перечислить
foo()
будетfoo
функция поставитьстек вызовов. -
При обработке внутреннего кода движок JS обнаружил
setTimeout
. -
потом
foo
функция обратного вызова, переданнаяWebAPIs(стрелка 1) и возврат из функции, стек вызовов снова пуст -
Таймер установлен на 0, поэтому
foo
будет отправлен вочередь задач(стрелка 2). -
Поскольку стек вызовов пуст, цикл событий выберет
foo
Обратный вызов и поместите его в стек вызовов для обработки. -
Процесс повторяется снова, и стек не переполняется.
Принципиальная схема работы выглядит следующим образом:
Проблема 5: не отвечает
Разобрать:
В большинстве случаев разработчики предполагают, что вцикл событийНа рисунке только одна очередь задач. Но это не так, у нас может быть несколько очередей задач. одна из очередей, выбранных браузером и помещенных в эту очередьобрабатывать обратный вызов.
Под капотом JavaScript есть макрозадачи и микрозадачи.setTimeout
обратный вызовзадача макроса,иPromise
обратный вызовмикрозадачи.
Основное отличие заключается в том, как они выполняются. Макрозадачи помещаются в стек по одной за один цикл, но очередь микрозадач всегда очищается после выполнения перед возвращением в цикл обработки событий. Таким образом, если вы добавляете записи в эту очередь с той скоростью, с которой вы можете их обработать, вы всегда обрабатываете микрозадачи. Цикл событий будет повторно отображать страницу только тогда, когда очередь микрозадач пуста,
Теперь, когда вы запускаете следующий фрагмент в консоли
function foo() {
return Promise.resolve().then(foo);
};
каждый звонок'foo
«оба продолжают добавлять еще одну в очередь микрозадач»foo
' обратный вызов, поэтому цикл обработки событий не может продолжать обработку других событий (прокрутка, нажатие и т. д.), пока очередь не будет полностью очищена. Поэтому он блокирует рендеринг.
Проблема 6: вызовет ошибку TypeError
Разобрать:
Развернуть синтаксисиfor-ofобход инструкцииiterable
Объект определяет данные для обхода.Array
илиMap
— это встроенный итератор с поведением итерации по умолчанию. Объекты не являются итерируемыми, но могут быть получены с помощьюiterableиiteratorПротоколы делают их повторяемыми.
существуетMozillaдокументации, если объект реализует@@iterator
метод, то он является итерируемым, что означает, что объект (или объект в его цепочке прототипов) должен иметь@@iterator
свойство ключа, этот ключ можно передать константойSymbol.iterator
получить.
Приведенное выше утверждение может показаться немного многословным, но следующий пример будет иметь больше смысла:
var obj = { x: 1, y: 2, z: 3 };
obj[Symbol.iterator] = function() {
// iterator 是一个具有 next 方法的对象,
// 它的返回至少有一个对象
// 两个属性:value&done。
// 返回一个 iterator 对象
return {
next: function() {
if (this._countDown === 3) {
const lastValue = this._countDown;
return { value: this._countDown, done: true };
}
this._countDown = this._countDown + 1;
return { value: this._countDown, done: false };
},
_countDown: 0
};
};
[...obj]; // 打印 [1, 2, 3]
также можно использоватьgeneratorфункция для настройки итеративного поведения объекта:
var obj = {x:1, y:2, z: 3}
obj[Symbol.iterator] = function*() {
yield 1;
yield 2;
yield 3;
}
[...obj]; // 打印 [1, 2, 3]
Вопрос 7: а, б, в
Разобрать:
for-in
цикл по самому объектуперечислимое свойствоИ свойства, которые объекты наследуют от своих прототипов. Перечислимые свойства доступны вfor-in
Свойства для включения и доступа во время цикла.
var obj = { a: 1, b: 2 };
var descriptor = Object.getOwnPropertyDescriptor(obj, "a");
console.log(descriptor.enumerable); // true
console.log(descriptor);
// { value: 1, writable: true, enumerable: true, configurable: true }
Теперь, когда у вас есть эти знания, должно быть легко понять, почему наш код выводит эти конкретные свойства.
var obj = { a: 1, b: 2 }; //a,b 都是 enumerables 属性
// 将{c:3}设置为'obj'的原型,并且我们知道
// for-in 循环也迭代 obj 继承的属性
// 从它的原型,'c'也可以被访问。
Object.setPrototypeOf(obj, { c: 3 });
// 我们在'obj'中定义了另外一个属性'd',但是
// 将'enumerable'设置为false。 这意味着'd'将被忽略。
Object.defineProperty(obj, "d", { value: 4, enumerable: false });
for (let prop in obj) {
console.log(prop);
}
// 打印
// a
// b
// c
Вопрос 8 : 10
Разобрать:
Инициализировать в глобальном масштабеx
, он становится свойством объекта окна (не строгий режим). Взгляните на код ниже:
var x = 10; // global scope
var foo = {
x: 90,
getX: function() {
return this.x;
}
};
foo.getX(); // prints 90
let xGetter = foo.getX;
xGetter(); // prints 10
Мы можем утверждать, что:
window.x === 10; // true
this
Всегда указывает на объект, для которого вызывается метод. Таким образом, вfoo.getx()
например, указывает наfoo
объект, возврат90
значение . пока вxGetter()
в случае,this
указывает на объект окна, возвращаетwindowсерединаx
ценность, то есть10
.
получитьfoo.x
Значение , может быть получено с помощьюFunction.prototype.bind
будетthis
Значение связано сfoo
объект для создания новых функций.
let getFooX = foo.getX.bind(foo);
getFooX(); // 90
это все! Если все ваши ответы правильные, хорошая работа. Мы все учимся, делая ошибки. Все дело в понимании «почему».
Ошибки, которые могут существовать после развертывания кода, нельзя узнать в режиме реального времени.Чтобы решить эти ошибки впоследствии, много времени тратится на отладку логов.Кстати, всем рекомендую полезный инструмент мониторинга ошибок.Fundebug.
общаться
Статья постоянно обновляется каждую неделю. Вы можете выполнить поиск «Big Move to the World» в WeChat, чтобы прочитать и обновить ее как можно скорее (на одну или две статьи раньше, чем в блоге). Эта статья находится на GitHub.GitHub.com/QQ449245884…Он был включен, и многие мои документы были разобраны. Добро пожаловать в Звезду и совершенство. Вы можете обратиться в тестовый центр для ознакомления во время собеседования. Кроме того, обратите внимание на паблик-аккаунт и ответьте в фоновом режиме.Благосостояние, вы можете увидеть преимущества, вы знаете.