JS Flashcards
Что такое приведение или преобразование типов (type coercion)
Приведение (или преобразование) типов — это процесс преобразования значения из одного типа в другой.
В JavaScript преобразование типов может быть явным и неявным. Преобразование с помощью функции-конструктора является явным. Пример:const num = Number("123");
const boolValue = Boolean(0);
Так как JavaScript — это язык со слабой типизацией, значения в нем могут быть конвертированы между различными типами автоматически - это называется неявным преобразованием типов. Пример:if (value) {…};
const result = "3" * 2;
const comparison = 5 == "5";
Что такое apply?
apply()
- это метод встроенного объекта функции в JavaScript. Он позволяет вызвать функцию с заданным значением this
и аргументами, переданными в виде массива (или массивоподобного объекта).
function.apply(thisArg, [argsArray])
Пример использования apply():
function greeting(greet, punctuation) {
return ${greet}, ${this.name}${punctuation}
;
}
const person = {
name: ‘Alice’
console.log(greeting.apply(person, [‘Hello’, ‘!’]));
// Вывод: Hello, Alice!
Как отфильтровать массив?
Метод filter(), который позволяет отфильтровать элементы массива на основе заданного условия и создать новый массив с соответствующими элементами.
Формат метода filter():array.filter(callback(element[, index[, array]])[, thisArg])
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evenNumbers = numbers.filter((num) => {
return num % 2 === 0;
});
console.log(evenNumbers); // Выводит: [2, 4, 6, 8, 10]
Базовые операторы в JS
Операнд – то, к чему применяется оператор. Например, в умножении 5 * 2 есть два операнда: левый операнд равен 5, а правый операнд равен 2. Иногда их называют «аргументами» вместо «операндов».
Унарным называется оператор, который применяется к одному операнду. Например, оператор унарный минус “-“ меняет знак числа на противоположный
Бинарным называется оператор, который применяется к двум операндам. Тот же минус существует и в бинарной форме.
Поддерживаются следующие математические операторы:
Сложение +,
Вычитание -,
Умножение *,
Деление /,
Взятие остатка от деления %,
Возведение в степень **.
Если бинарный оператор ‘+’ применить к строкам, то он их объединяет в одну - это называется конкатенация.
Унарным плюсом можно быстро привести операнд к числу.
Одной из наиболее частых числовых операций является увеличение или уменьшение на единицу.
Инкремент ++ увеличивает переменную на 1
Декремент – уменьшает переменную на 1
Операторы ++ и – могут быть расположены не только после, но и до переменной. Префиксная форма возвращает новое значение, в то время как постфиксная форма возвращает старое (до увеличения/уменьшения числа).
Когда оператор идёт после переменной — это «постфиксная форма»: counter++.
«Префиксная форма» — это когда оператор идёт перед переменной: ++counter.
Оператор «запятая» предоставляет нам возможность вычислять несколько выражений, разделяя их запятой ,. Каждое выражение выполняется, но возвращается результат только последнего.
Типы данных в JS
Есть восемь основных типов данных в JavaScript.
-
Числовой тип данных (number)
представляет как целочисленные значения, так и числа с плавающей точкой.
Кроме обычных чисел, существуют так называемые «специальные числовые значения», которые относятся к этому типу данных: Infinity, -Infinity (можем получить в результате деления на ноль) и NaN (NaN означает вычислительную ошибку. Это результат неправильной или неопределённой математической операции). -
BigInt
числовой примитив, который позволяет использовать большие числа с высокой точностью. Чтобы создать значение типа BigInt, необходимо добавить n в конец числового литерала: const bigInt = 1234567890123456789012345678901234567890n; -
Строка (string)
в JavaScript должна быть заключена в кавычки. Обратные кавычки имеют расширенную функциональность - позволяют нам встраивать выражения в строку, заключая их в ${…}. -
Булевый тип (boolean)
может принимать только два значения: true (истина) и false (ложь).
Такой тип, как правило, используется для хранения значений да/нет: true значит «да, правильно», а false значит «нет, не правильно». -
null
формирует отдельный тип, который содержит только значение null. Это специальное значение, которое представляет собой «ничего», «пусто» или «значение неизвестно». -
Специальное значение undefined
означает, что «значение не было присвоено». Если переменная объявлена, но ей не присвоено никакого значения, то её значением будет undefined. -
Symbol
примитивный тип данных, использующийся для создания уникальных идентификаторов. Даже если символы имеют одно и то же имя, это – разные символы. -
Тип object (объект)
не является примитивным типом данных. Объекты используются для хранения коллекций различных значений и более сложных сущностей. Объект может быть создан с помощью фигурных скобок {…} с необязательным списком свойств. Свойство – это пара «ключ: значение», где ключ – это строка (также называемая «именем свойства»), а значение может быть чем угодно.
Расскажи про тип данных число.
Тип данных “число” (number) в JavaScript представляет числовые значения. Также для этого типа данных существуют специальные значения Infinity, -Infinity и NaN (Not a Number).
В диапазоне от -2 в 53 степени до 2 в 53 степени, числа могут быть представлены точно.
Для типа данных “число” определены стандартные арифметические операции, такие как сложение (+), вычитание (-), умножение (*), деление (/), взятие остатка от целочисленного деления (%), возведение в степень (**), а также операции сравнения (>, <, >=, <=, ==, ===, !=, !==).
Числа также могут быть представлены в различных системах исчисления, таких как двоичная, восьмеричная или шестнадцатеричная системы. Чтобы перевести число в какую-то систему исчисления можно использовать метод toString:
let decimalNumber = 10;
let binaryNumber = decimalNumber.toString(2);
console.log(binaryNumber); // Выводит: 1010
Стандарт IEEE-754 определяет три специальных значения. Эти значения принадлежат типу number, но не работают, как обычные числа:
- бесконечность Infinity;
- минус бесконечность -Infinity;
- не число (not a number) NaN.
Значение NaN используется, чтобы сообщить об операции, результатом которой оказалось не число. В JavaScript существует пять операций, которые могут вернуть NaN:
1. ошибка парсинга числа (например, при попытке превратить строку в число parseInt('привет'))
.
2. результат математической операции не находится в полей действительных чисел (например, взятие корня от -1).
3. один из операндов в арифметической операции — NaN (5 + NaN)
4. результат арифметической операции не определён для переданных операндов undefined + undefined
.
5. арифметическая операция со строкой, кроме сложения 'привет' * 5
Согласно спецификации, NaN не равен самому себе. Для проверки на NaN пользуйтесь функцией Number.isNaN()
, которая возвращает true если переданное значение — NaNNumber.isFinite()
проверяет и на Infinity, и на Nan - на них возвращается false.
Для округления, взятия корней и других математических операций в JavaScript существует отдельный модуль Math.round()
— округление по обычным правилам;floor()
— округление вниз;ceil()
— округление вверх;trunc()
— отбрасывание дробной части, не обращая внимания на знак аргумента.
Сам по себе примитивный тип «число» не имеет методов. Когда происходит вызов метода у числа, оно автоматически оборачивается в специальную обёртку Number, которая и содержит методы:
- проверки на специальные значения isNaN(), isFinite().
- toString(2) переводит в строку с определенной системой исчисления
- toFixed() форматирует число, обрезая значения после запятой. Возвращает строку, а не число.
Если после округления нужно производить другие арифметические операции, то лучше распарсить число с помощью parseFloat().
Тип данных BigInt
Тип большого целого BigInt — примитивный тип, который представляет целые числа больше 2 в степени 53 - 1. Эти числа уже не помещаются в стандартный примитив «число».
Создать BigInt можно двумя способами.
1️⃣ Добавить суффикс n в конец записи числа:
const biggy = 9997000254740991n
2️⃣ Вызвать конструктор BigInt:
const alsoBig = BigInt(9997000254999999)
Тип данных строка
Строки представляют собой последовательность символов. Созданная строка является иммутабельной (immutable) и не может быть изменена.
Есть несколько способов создать строку:
- одинарными кавычками ‘;
- двойными кавычками “;
- шаблонной строкой через обратный апостроф ` - бэктик.
Если в записи одинарными кавычками нужно поставить апостроф, то символ экранируют обратным слэшем . Так мы даём JavaScript понять, что это просто символ, а не закрывающая кавычка.
Строки можно сравнивать между собой, для сравнения используется лексикографический порядок. Это означает, что первые символы алфавита считаются меньше последних.
Алгоритм посимвольно сравнивает строки до первого несовпадения, либо пока не закончится одна из строк.
console.log(‘А’ < ‘Я’) // true
console.log(‘Кот’ > ‘Код’) // true
console.log(‘Код’ < ‘Кодер’) // true
console.log(‘Код’ === ‘Код’) // true
Сравнение учитывает регистр букв, если необходимо регистронезависимое сравнение, то обе строки приводятся к верхнему или нижнему регистру с помощью методов toUpperCase или toLowerCase.
Сам по себе примитивный тип «строка» не имеет методов. Когда происходит вызов метода, оно автоматически оборачивается в специальную обёртку, которая и содержит методы.
length: Возвращает длину строки.
toUpperCase(): Возвращает новую строку, содержащую все символы исходной строки в верхнем регистре.
toLowerCase(): Возвращает новую строку, содержащую все символы исходной строки в нижнем регистре.
substring(startIndex, endIndex): Возвращает подстроку, которая начинается со значения индекса startIndex
до значения индекса endIndex
(не включительно).const str = "Hello, world!";
console.log(str.substring(0, 5)); // Вывод: "Hello"
indexOf(substring): Возвращает индекс первого вхождения подстроки substring
в строке. Если подстрока не найдена, возвращает -1
.
replace(oldSubstring, newSubstring): Заменяет первое вхождение подстроки oldSubstring
на подстроку newSubstring
и возвращает новую строку.
const str = “Hello, world!”;
console.log(str.replace(“world”, “JavaScript”)); // Вывод: “Hello, JavaScript!”
**split(separator)**: Разбивает строку на массив подстрок с использованием указанного разделителя
separator`.
trim(): Удаляет пробельные символы с начала и конца строки и возвращает новую строку.
Тип данных Boolean
Логический или булев тип boolean может принимать лишь истинное (true) и ложное (false) значения. Значения этого типа используются в условных выражениях.
Создать булевое значение можно несколькими способами.
- явно указать значение, используя ключевые слова true и false:
const truthyValue = true // «Истина»
- использовать метод Boolean:
const falsyValue = Boolean(‘’) // «Ложь»
- использовать выражения, значениями которых будут «истина» или «ложь».
const anotherTruthy = 4 < 5
- Если вызвать одинарное ! или двойное отрицание !!, можно быстро привести любое выражение к логическому типу.
Обычно логическим переменным дают названия, начинающиеся с английских глаголов is, should, does, can и подобных.
Тип данных Undefined
**Undefined — это примитивный тип данных, состоящий из одного значения undefined. Оно используется, чтобы обозначить неопределённое значение. **
- JavaScript автоматически устанавливает значение undefined объявленным переменным, которые не были проинициализированы значением.
- JavaScript автоматически устанавливает значение undefined в аргумент функции, если значение не передали при вызовеhello('Витя') // Привет, Витя
hello() // Привет, undefined
Вручную установленное undefined используют, чтобы обозначить неизвестное значение:const person = {
name: 'Пётр',
lastName: 'Романов',
age: undefined
}
Тип данных Null
Null — это примитивный тип данных, который состоит из единственного значения null. Значение null используют, когда нужно обозначить намеренное отсутствие значения.
null обозначает понятия «отсутствует», «ничего», «пусто» или «значение неизвестно». Оно всегда явно задаётся программистом, JavaScript автоматически не устанавливает его.
В JavaScript null используется только для обозначения конца цепочки прототипов, чтобы показать, что следующий прототип отсутствует.
В языке существует похожий примитив undefined, он обозначает, что значение ещё не установлено. Их можно легко спутать, потому что оба обозначают отсутствие значения. Разница состоит в том, что null обозначает намеренное отсутствие, а undefined — неявное.
Тип данных Symbol
Символ (Symbol) — примитивный тип, значения которого создаются с помощью вызова функции Symbol. Каждый созданный символ уникален. Символы могут использоваться в качестве имён свойств в объектах. Символьные свойства могут быть прочитаны только при прямом обращении и не видны при
обычных операциях.
Для создания символа нужно вызвать функцию Symbol:const sym = Symbol()
const symTwo = Symbol()
console.log(sym === symTwo)
// false
Символы используются для создания скрытых свойств объектов. В отличие от свойств, ключом которых является строка, символьные свойства может читать только владелец символа. Скрытые свойства не видны при его обходе с помощью for…in. Это может пригодиться, когда необходимо добавить свойства объекту, который могут модифицировать другие части программы. Таким образом только вы сможете читать созданное свойство, а гарантия уникальности символов гарантирует и отсутствие конфликтов имён.
Созданный символ уникален, но как быть, если он нужен в нескольких местах программы? Для решения этой проблемы существует глобальный реестр символов, он хранит символы по строковым ключам. При обращении по ключу всегда будет возвращаться один и тот же символ.
Работа с реестром символов организована с помощью двух методов:
- Symbol.for(ключ)
— возвращает символ, хранящийся по ключу. Если символа ещё не существует, он создаётся автоматически.
- Symbol.keyFor(символ)
— возвращает строковый ключ, который хранит переданный символ или undefined, если символ не
хранится в реестре.
Что такое массив?
**Массив - упорядоченная коллекция данных, в которой присутствуют 1-й, 2-й, 3-й элементы и т.д. Например, она понадобится нам для хранения списка чего-либо:
пользователей, товаров, элементов HTML и т.д. Массив – это особый подвид объектов.*
let arr = new Array();
let arr = [];
В массиве могут храниться элементы любого типа. Элементы нумеруются и хранятся в том порядке, в котором их поместили в массив. Элементы массива нумеруются, начиная с нуля. Мы можем получить элемент, указав его номер в квадратных скобках.
Получение последних элементов массива:fruits[fruits.length - 1]
fruits.at (-1)
Методы:slice
: Создает новый массив, содержащий копию части исходного массива. Можно указать начальный и конечный индексы для выбора нужной части массива.const arr = [1, 2, 3, 4, 5];
const slicedArr = arr.slice(1, 4);
console.log(slicedArr); // Вывод: [2, 3, 4]
splice
: Изменяет содержимое массива, удаляя, заменяя или добавляя элементы на указанной позиции.const arr = [1, 2, 3, 4, 5];
arr.splice(2, 2, 'a', 'b'); // индекс, с которого начинается изменение массива, количество элементов, которые нужно удалить из массива, элементы, которые нужно добавить в массив на место удаленных элементов
console.log(arr); // Вывод: [1, 2, 'a', 'b', 5]
push
добавляет элемент в конец.pop
удаляет последний элемент.unshift
добавляет элемент в начало массива:shift
удаляет элемент в начале, сдвигая очередь, так что второй элемент становится первым.
Методы push и unshift могут добавлять сразу несколько элементов. Методы push/pop выполняются быстро, а методы shift/unshift – медленно, потому что работать с концом массива всегда быстрее, чем с началом.
Используйте indexOf()
, чтобы найти, под каким индексом хранится элемент.
Используйте includes()
, чтобы проверить, что элемент есть в массивеconcat
: Объединяет два или более массивов и возвращает новый массив, содержащий элементы из всех объединенных массивов.forEach
: Выполняет указанную функцию один раз для каждого элемента массива. map
: Создает новый массив, содержащий результаты вызова указанной функции для каждого элемента исходного массива.
Многомерные массивы
Массивы могут содержать элементы, которые тоже являются массивами. Это можно использовать для создания многомерных массивов, например, для хранения матриц:let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
alert( matrix[1][1] ); // 5, центральный элемент
В современном JavaScript очень популярна деструктуризация массивов. Этот подход позволяет создавать переменные из элементов массива в одну строку:const catProfile = [
'Maru',
'Scottish Fold',
true,
'https://youtu.be/ChignoxJHXc'
]
const [name, breed] = catProfile
console.log(name) // Maru
Тип данных Object
Объект (object) — это особый тип данных, он единственный не является примитивом в JS.
Каждое свойство состоит из ключа и значения. Ключ может быть строкой или символом, а значение может быть любым.
Чаще всего объекты создают с помощью литеральной записи.const cat = {}
Создать объект также можно с помощью конструктора Object. Это объектно-ориентированный стиль программирования:const book = new Object({ title: 'Война и мир', author: 'Лев Толстой' })
Чтение свойств с помощью точки:console.log(
На полке стоит «${book.title}»)
// На полке стоит «Война и мир»
Альтернативно для чтения можно использовать квадратные скобки:console.log(
На полке стоит «${book[‘title’]}»)
Для добавления и изменения свойств используется одинаковый синтаксис. Нужно обратиться к свойству и присвоить в него значение с помощью стандартного оператора присваивания =.
Если свойство не существует, оно будет создано:const book = {
title: 'Капитанская дочка'
}
book.author = 'А.С.Пушкин' // добавляем новое свойство
book.title = 'Сказка о царе Салтане' // изменяем существующее
console.log(book) // { title: 'Сказка о царе Салтане', author: 'А.С.Пушкин'}
delete book['author']
delete book.title
Чаще всего свойства не удаляют, а сбрасывают значение, устанавливая undefined или подходящее по смыслу:book.title = undefined
book['author'] = undefined
Для проверки, есть ли свойство у объекта, используйте оператор in:const user = {
firstName: 'Марина',
username: 'zloyDuh'
}
console.log('firstName' in user)
// true
console.log('age' in user)
// false
Преобразование в массив:
Object.keys(user) = [“name”, “age”]
Object.values(user) = [“John”, 30]
Object.entries(user) = [ [“name”,”John”], [“age”,30] ]
Преобразование обратно в объект:
Object.fromEntries
Методы объектов
Статические методы объекта - методы, которые предварительно определены и вызываются в классе объектов.Object.keys(obj)
– возвращает массив ключей.Object.values(obj)
– возвращает массив значений.Object.entries(obj)
– возвращает массив пар [ключ, значение].
Обойти их
for (let value of Object.values(user)) {
alert(value); // John, затем 30
}
Object.fromEntries(array)
- преобразует массив в объект
// преобразовать в массив, затем map, затем fromEntries обратно объект
Object.fromEntries( Object.entries(prices).map(([key, value]) => [key, value * 2]) );
Методы экземпляра — это методы, встроенные в объекты, которые работают с конкретным экземпляром объекта, а не с классом объекта.Object.prototype.hasOwnProperty()
возвращает логическое значение, указывающее, имеет ли объект указанное свойство.
const object1 = {};
object1.property1 = 42;
console.log(object1.hasOwnProperty(‘property1’));
// Expected output: true
Метод Object.create()
создаёт новый объект с указанным прототипом и свойствами.
Метод Object.assign()
используется для копирования перечисляемых и собственных свойств из одного или более исходных объектов в целевой объект. После копирования он возвращает целевой объект.
var o1 = { a: 1 };
var o2 = { [Symbol(‘foo’)]: 2 };
var obj = Object.assign({}, o1, o2);
console.log(obj); // { a: 1, [Symbol(“foo”)]: 2 }
Object.preventExtensions(объект)
предотвращает добавление новых свойств к объекту (то есть, предотвращает расширение этого объекта в будущем).Object.isExtensible(объект)
определяет, является ли объект расширяемым (то есть, можно ли к нему добавлять новые свойства).
Метод Object.freeze()
предотвращает модификацию свойств и значений объекта и добавление или удаление свойств объекта.
Метод Object.isFrozen()
позволяет определить, был ли объект заморожен или нет, и возвращает логическое значение.
Метод Object.seal()
предотвращает добавление новых свойств объекта, но позволяет изменять существующие свойства.
// Возвращает true, если объект “запечатан” Object.isSealed(объект)
определяет, является ли объект запечатанным.
Метод Object.getPrototypeOf()
используется для получения внутреннего скрытого [[Prototype]] объекта, также доступного через свойство __proto__.
Существует также связанный с ним метод Object.setPrototypeOf()
, который добавляет один прототип к другому объекту. Но вместо этого рекомендуется использовать Object.create()
, поскольку он быстрее и эффективнее.
Метод Object.defineProperty()
определяет новое или изменяет существующее свойство непосредственно на объекте, возвращая этот объект.
// Добавить или изменить свойство объекта
Object.defineProperty(объект, свойство, описатель)
Object.defineProperty(объект, свойство, {value : значение})
Object.defineProperty(person, “language”, {writable:false}); // делаем свойство language только для чтения
// Добавить или изменить несколько свойств объекта Object.defineProperties(объект, описатель)
- writable : true // Значение свойства можно изменять
- enumerable : true // Свойство может перечисляться
- configurable : true // Свойство может настраиваться
// Определение геттера
get: function() {
return language
}
// Определение сеттера
set: function(value) {
language = value
}
Полифил map
// Добавляем новый метод myMap в прототип массива
Array.prototype.myMap = function(callback) {
// Проверяем, что this является массивом или строкой
// Если тип неверный, выбрасываем ошибку TypeError
if (!this instanceof Array || !this instanceof String) {
throw new TypeError(‘Wrong type’);
}
// Проверяем, что callback является функцией
// Если не является, выбрасываем ошибку TypeError
if (typeof callback !== “function”) {
throw new TypeError(‘Callback isn't function’);
}
const result = [];
// Проходим по каждому элементу исходного массива/строки
// Вызываем callback для каждого элемента и добавляем результат в новый массив
for (let i = 0; i < this.length; i += 1) {
result.push(callback(this[i], i, this));
}
return result;
};
Полифил reduce
Метод reduce применяет функцию reducer к каждому элементу массива (слева-направо), возвращая одно результирующее значение.
Пример reduce:
let people = [
{ name: “Ira”, age: 10 },
{ name: “Kira”, age: 20 },
{ name: “Lira”, age: 30 },
{ name: “Vira”, age: 40 },
];
const amount = people.reduce((total, elem) => (total += elem.age), 0); //100
Полифил reduce:
// Добавляем новый метод myReduce в прототип массива
Array.prototype.myReduce = function(callback, accumulator) {
// Проверяем, что this является массивом или строкой
// Если тип неверный, выбрасываем ошибку TypeError
if (!this instanceof Array || !this instanceof String) {
throw new TypeError(‘Wrong type’);
}
// Проверяем, что callback является функцией
// Если не является, выбрасываем ошибку TypeError
if (typeof callback !== “function”) {
throw new TypeError(‘Callback isn't function’);
}
// Инициализируем аккумулятор значением, переданным в качестве аргумента или первым элементом массива
// Устанавливаем начальное значение индекса в зависимости от наличия аргумента аккумулятора
let acc = arguments.length > 1 ? accumulator : this[0];
let iStart = arguments.length > 1 ? 0 : 1;
// Проходим по каждому элементу массива/строки, начиная с определенного индекса
// Вызываем callback с текущим значением аккумулятора, текущим элементом, индексом и самим массивом
for (let i = iStart; i < this.length; i += 1) {
acc = callback(acc, this[i], i, this);
}
return acc;
};
let arr = [0, 1, 3, 5, 7, 8];
const myReduceResult = arr.myReduce((acc, curr, idx, arr) => {
// Используем myReduce для расчета суммы элементов массива
acc += curr;
return acc;
});
console.log(myReduceResult);
// Вывод: 24
Что такое Big-О
В программировании Big-О показывает эффективность конкретного алгоритма. Всегда рассматривается наихудший вариант расчета. Конcтанты откидываются.
Одними из самых часто встречаемых сложностей являются:
- О(1) - самый эффективный. Вывод 10 элемента в массиве, т.к. это делается по индексу, нам не важно 100 там элементов или миллион, на задачу требуется всегда один шаг. Также хэш-таблицы имеют эту сложность для поиска, индексации и добавления.
- O(log n) - поиск в отсортированном массиве через бинарный поиск. Сюда относятся все функции, где входящий массив или структура данных делится пополам для поиска.
- O(n) - поиск элемента в несортированном массиве - для получения результата, вам придется перебрать весь список. Или суммирование элементов массива - тоже нужно пройти все элементы. Чем больше массив, тем больше операций. Можно сказать «сложность порядка n (order n)». Так же такой тип алгоритмов называют «линейными» или что алгоритм «линейно масштабируется». Интересно: Если мы знаем, что массив начинается с 1, отсортирован и не имеет пропусков, для суммирования можно применить формулу S = n(n+1)/2 (где n последний элемент массива) - такая запись будет уже более эффективной O(1).
- O(n log n) - n раз logn. Как пример сортировка слиянием. Получается O(n * log n), или O(n log n).
- O(n²) - пузырьковая сортировка, поиск дублей в цикле, который в другом цикле. Массив из 4 элементов требует 16 шагов, массив из 10 элементов – 100 шагов.
- O(2^n) - поиск всех подмножеств массива. Каждый элемент множества может быть либо включен, либо исключен из подмножества. Набор из четырех элементов [A,B,C,D, E] будет иметь 2^5 или 32 подмножеств.
[A], [B], [C], [D], [E]
[A,B], [A,C], [A,D], [A, E], [B,C], [B,D], [C,D] и т.д. - O(n!) - самый неэффективный. К примеру, поиск всех возможных вариантов расположения элементов в массиве [A, B, C, D] потребует 4! или 24 шага.
Что такое bind, когда он используется, полифил bind
Метод bind()
является одним из методов встроенного объекта функции в JavaScript. Он используется для создания новой функции, привязанной к определенному контексту выполнения. Это позволяет явно указать значение this
внутри функции и также позволяет привязать предопределенные аргументы.
Привязанная функция, созданная с помощью bind()
, не вызывается немедленно. Вместо этого, она возвращается, чтобы ее можно было вызвать позднее.
bind()
принимает один или более аргументов:
- thisArg: Объект, который будет использоваться в качестве значения this
внутри функции, которая будет создана при вызове bind()
.
- аргументы: Предопределенные аргументы, которые будут переданы в функцию при вызове.
Пример использования bind()
:const obj = {
x: 42,
getY: function() {
return this.x;
}
};
const boundGetY = obj.getY.bind(obj); // Создаем новую функцию, в которой this будет ссылаться на объект obj
console.log(boundGetY()); // Вывод: 42
const anotherObj = {
x: 99
};
const boundGetY2 = obj.getY.bind(anotherObj);
console.log(boundGetY2()); // Вывод: 99
Function.prototype.myBind = function (...args) {
var callback = this,
ctx = args.splice(1);
return function (...a) { //параметр …a содержит все аргументы, которые можно передать result2()методу.
callback.call(args[0], ...[...ctx, ...a]);
// args[0] является первым аргументом, переданным myBind()методу (то есть объекту myName),
// ctx содержит все остальные аргументы, переданные нашему myBind()методу
};
};
const result2 = printName.myBind(myName, "Palia");
result2("India");
Что такое call, когда он используется, полифил call
call()
- это метод встроенного объекта функции в JavaScript. Он используется для вызова функции, устанавливая указанное значение this
и передавая аргументы в виде списка. При использовании call()
, функция вызывается с установленным значением this
, которое передается в thisArg
, и аргументами, которые передаются в списке после thisArg
.
Синтаксис метода call()
:function.call(thisArg, arg1, arg2, ...)
-
thisArg
: Объект, который будет использоваться в качестве значенияthis
внутри функции. -
arg1
,arg2
, …: Аргументы, которые будут переданы в функцию при вызове.
Пример использования call()
:const person1 = {
name: 'Alice',
greeting: function() {
return 'Hello, ' + this.name + '!';
}
};
const person2 = {
name: 'Bob'
};
console.log(person1.greeting.call(person2)); // Output: Hello, Bob!
Полифил:Function.prototype.myCall = function (context, ...args) {
let currentContext = context || globalThis;
let randomProp = Math.random();
while (currentContext[randomProp] !== undefined) {
randomProp = Math.random();
} // генерируем случайное свойство, используя Math.random,чтобы убедиться, что свойство уникально.
currentContext[randomProp] = this;
let result = currentContext[randomProp](...args);
delete currentContext[randomProp];
return result;
};
printName.myCall(myName, "Palia", "India");
Объект Date
Объект JavaScript Date — это глобальный объект, который используется для работы с датами и временем. Объекты Date основаны на значении времени, которое представляет собой количество миллисекунд с 1 января 1970 года по всемирному координированному времени.
Важно: при установке месяца, отчёт идёт с 0, где 0 — это январь. При выводе дня недели возвращаемое значение также начинается с 0 и означает воскресенье.
Основные методыnew Date()
создаёт экземпляр Date с текущей датой и временем.new Date(значение)
создаёт Date с переданным значением времени. Значение должно быть в формате, который распознается методом Date.parse()
, то есть быть совместимым с IETF RFC 2822 или с ISO8601.new Date(год, месяц, день, часы, минуты, секунды, миллисекунды)
создаёт класс Date в местной часовой зоне. Год и месяц являются обязательными параметрами. Остальные параметры, начиная с часов, будут по умолчанию равны 0, а день — 1.new Date(миллисекунды)
создаёт Date со временем в миллисекундах. Количество миллисекунд измеряется с 1 января 1970 года UTC.getFullYear()
— возвращает год;getMonth()
— возвращает месяц с 0 до 11;getDate()
— возвращает день месяца с 1 до 31;getDay()
— возвращает порядковый номер дня недели с 0 до 6;getHours()
— возвращает часы с 0 до 23;getMinutes()
- возвращает минуты от 0 до 59;getSeconds()
- возвращает секунды от 0 до 59;getMilliseconds()
- возвращает миллисекунды от 0 до 999.getTime()
возвращает значение в миллисекундах, прошедших с 1 января 1970 года, соответствующее указанной дате по UTC.getTimezoneOffset()
возвращает смещение в минутах между текущей часовой зоной и UTC.setFullYear(год, месяц, день)
устанавливает год, значения месяца и дня необязательны.setMonth(месяц, день)
устанавливает месяц, передавать день необязательно.setDate(день)
устанавливает день месяца.setHours(часы, минуты, секунды, миллисекунды)
устанавливает часы. Значения минут, секунд, миллисекунд необязательны.setMinutes(минуты, секунды, миллисекунды)
- устанавливает минуты. Секунды и миллисекунды необязательны.setSeconds(секунды, миллисекунды)
устанавливает секунды. Миллисекунды передавать необязательно.
setMilliseconds(миллисекунды) - устанавливает миллисекунды.
Для UTC аналогичные методы, только добавляем UTC после set. Например, setUTCMilliseconds(миллисекунды)
.
И метод, который относится только к UTC:setTime(значение)
устанавливает значение, которое равно количеству миллисекунд, прошедших с 1 января 1970 года.
Метод Date.parse(значение)
используется для разбора (ещё говорят парсинга) строкового представления даты.
Возвращает значение, равное количеству миллисекунд, прошедших с 1 января 1970 года.
Для отображения Date в различных форматах существует метод toLocaleDateString(локаль, опции)
.
Локаль — это необязательный параметр, который является строкой или массивом строк с языковой меткой BCP 47.
Например, en-US или de-DE. Локаль хранит региональные настройки о формате дат, номеров, адресов.
Опции — необязательный параметр с объектом настроек. Доступные свойства:
localeMatcher — алгоритм поиска локали, используется для выбора подходящей локали. Принимает значения lookup или best fit. По умолчанию best fit.
timeZone — значение используемого часовой зоны. Все браузеры должны принимать значение UTC, значение по умолчанию равно значению часовой зоны среды выполнения. Формат принимаемого значения может различаться в различных браузерах.
hour12 — значение, которое определяет, использовать ли 12-часовой формат вывода. Принимает true или false.
formatMatcher — алгоритм поиска формата, используется для выбора формата отображения. Принимает значения basic или best fit. По умолчанию best fit.
timeZoneName — формат названия часовой зоны. Принимает long или short.
weekday — значение дня недели. Принимает narrow, short и long.
era — значение эры. Принимает narrow, short и long.
year — значение года. Принимает numeric и 2-digit.
month — значения месяца. Принимает numeric, 2-digit, narrow, short и long.
day — значения дня. Принимает numeric и 2-digit.
hour — значения часа. Принимает numeric и 2-digit.
minute — значения минут. Принимает numeric и 2-digit.
second — значения секунд. Принимает numeric и 2-digit.
Браузеры обязаны поддерживать следующие наборы настроек отображения:weekday, year, month, day, hour, minute, second
weekday, year, month, day
year, month, day
year, month
month, day
hour, minute, second
hour, minute
Date.now()
— метод, который возвращает текущее время в миллисекундах, прошедших с 1 января 1970 года UTC.
Если вам не хватает функциональности, представленной классом Date, например, недостаточно его возможностей форматирования или парсинга, то можно посмотреть в сторону библиотек day.js или date-fns.
В следующей таблице перечислены стандартные свойства объекта Date.
prototype
- Позволяет добавлять новые свойства и методы к объекту Date.
Примечание. У каждого объекта в JavaScript есть свойство конструктора, которое ссылается на функцию конструктора, которая использовалась для создания экземпляра этого объекта.
В следующей таблице перечислены стандартные методы объекта Date.
- getDate()
: Возвращает день месяца (от 1 до 31).
- getDay()
: Возвращает день недели (от 0 до 6).
- getFullYear()
: Возвращает год (четыре цифры).
- getHours()
: Возвращает час (от 0 до 23).
- getMilliseconds()
: Возвращает миллисекунды (от 0 до 999).
- getMinutes()
: Возвращает минуты (от 0 до 59).
- getMonth()
: Возвращает месяц (от 0 до 11).
- getSeconds()
: Возвращает секунды (от 0 до 59).
- getTime()
: Возвращает количество миллисекунд, прошедших с полуночи 1 января 1970 года.
- getTimezoneOffset()
: Возвращает разницу во времени между временем UTC и местным временем в минутах.
- getUTCDate()
: Возвращает день месяца по всемирному времени (от 1 до 31).
- getUTCDay()
: Возвращает день недели по всемирному времени (от 0 до 6).
- getUTCFullYear()
: Возвращает год по всемирному времени.
- getUTCHours()
: Возвращает час по всемирному времени (от 0 до 23).
- getUTCMilliseconds()
: Возвращает миллисекунды по всемирному времени (от 0 до 999).
- getUTCMinutes()
: Возвращает минуты по всемирному времени (от 0 до 59).
- getUTCMonth()
: Возвращает месяц по всемирному времени (от 0 до 11).
- getUTCSeconds()
: Возвращает секунды по всемирному времени (от 0 до 59).
-
now()
- Возвращает количество миллисекунд, прошедших с полуночи 1 января 1970 года. -
parse()
- Разбирает строку даты и возвращает количество миллисекунд с 1 января 1970 года. -
setDate()
- Устанавливает день месяца объекта даты. -
setFullYear()
- Устанавливает полный год объекта даты. -
setHours()
- Устанавливает часы объекта даты. -
setMilliseconds()
- Устанавливает миллисекунды объекта даты. -
setMinutes()
- Устанавливает минуты объекта даты. -
setMonth()
- Устанавливает месяц объекта даты. -
setSeconds()
- Устанавливает секунды объекта даты. -
setTime()
- Устанавливает дату на указанное количество миллисекунд после/до 1 января 1970 года. -
setUTCDate()
- Устанавливает день месяца объекта даты по всемирному времени -
setUTCFullYear()
- Устанавливает год объекта даты по всемирному времени. -
setUTCHours()
- Устанавливает часы объекта даты по всемирному времени. -
setUTCMilliseconds()
- Устанавливает миллисекунды объекта даты по всемирному времени. -
setUTCMinutes()
- Установите минуты объекта даты по всемирному времени. -
setUTCMonth()
- Устанавливает месяц объекта даты по всемирному времени. -
setUTCSeconds()
- Установите секунды объекта даты по всемирному времени. -
toDateString()
- Преобразует часть даты объекта Date в удобочитаемую форму. -
toISOString()
: Возвращает дату в виде строки, отформатированной в соответствии со стандартом ISO. -
toJSON()
: Возвращает дату в виде строки в формате даты JSON. -
toLocaleDateString()
: Возвращает часть даты объекта Date в виде строки локального формата. -
toLocaleTimeString()
: Возвращает временную часть объекта Date в виде строки локального формата. -
toLocaleString()
: Преобразует объект Date в строку локального формата. -
toString()
: Преобразует объект Date в строку. -
toTimeString()
: Преобразует временную часть объекта Date в строку. -
toUTCString()
: Преобразует объект Date в строку по всемирному времени. -
UTC()
: Возвращает количество миллисекунд в объекте Date с 00:00:00 (полночь) 1 января 1970 года по всемирному времени. -
valueOf()
: Возвращает примитивное значение объекта Date.
Что такое DOM Events?
Событие – это сигнал от браузера о том, что что-то произошло. Все DOM-узлы подают такие сигналы (хотя события бывают и не только в DOM).
События мыши:
- click
– происходит, когда кликнули на элемент левой кнопкой мыши (на устройствах с сенсорными экранами оно происходит при касании).
- contextmenu
– происходит, когда кликнули на элемент правой кнопкой мыши.
- mouseover
/ mouseout
– когда мышь наводится на / покидает элемент.
- mousedown
/ mouseup
– когда нажали / отжали кнопку мыши на элементе.
- mousemove
– при движении мыши.
События на элементах управления:
- submit
– пользователь отправил форму <form>.
- focus
– пользователь фокусируется на элементе, например нажимает на <input></input>.
Клавиатурные события:
- keydown и keyup
– когда пользователь нажимает / отпускает клавишу.
События документа:
- DOMContentLoaded
– когда HTML загружен и обработан, DOM документа полностью построен и доступен.
CSS events:
- transitionend
– когда CSS-анимация завершена.
Существует множество других событий.
Событию можно назначить обработчик, то есть функцию, которая сработает, как только событие произошло.
Именно благодаря обработчикам JavaScript-код может реагировать на действия пользователя.
1. Обработчик может быть назначен прямо в разметке, в атрибуте, который называется on<событие>
. <input value="Нажми меня" onclick="alert('Клик!')" type="button"
2. Можно назначать обработчик в JS, используя свойство DOM-элемента on<событие>
. elem.onclick = function() { alert('Спасибо'); };
Так как у элемента DOM может быть только одно
свойство с именем onclick, то назначить более одного обработчика так нельзя.
3. И последний и более гибкий способ: addEventListener
и removeEventListener
. С помощью него можно повесить более одного обработчика на одно событие, а также можно удалить обработчик, вызвав его и передав ту же функцию. Есть несколько типов событий, которые работают только через него, к примеру transitionend и DOMContentLoaded. Также addEventListener
поддерживает объекты в качестве обработчиков событий. В этом случае вызывается метод объекта handleEvent.
Не важно, как вы назначаете обработчик – он получает объект события первым аргументом. Этот объект содержит подробности о том, что произошло.
Расскажи про Form и Input Events
Form и Input Events - это события, которые позволяют отслеживать и реагировать на действия пользователя, связанные с отправкой формы, изменением значений полей ввода и другими взаимодействиями с элементами форм и ввода.
Некоторые из наиболее часто используемых событий форм и ввода включают:
-
submit
: Событиеsubmit
возникает, когда пользователь отправляет форму. Это может произойти, например, при нажатии на кнопку отправки или использовании клавиши Enter в текстовом поле. Для отслеживания отправки формы вы можете прослушивать событиеsubmit
на элементе. -
input
: Событиеinput
возникает, когда значение в поле ввода изменяется пользователем. Это может быть поле ввода текста, чекбокс, переключатель и другие элементы. Событиеinput
позволяет реагировать на моментальные изменения значения в поле ввода. -
change
: Событиеchange
возникает, когда значение в поле ввода изменяется и пользователь уводит фокус с элемента. Это позволяет отслеживать изменения значения и принимать соответствующие действия, когда пользователь закончил ввод. -
cut
: Событиеcut
возникает, когда пользователь вырезает выделенный текст или другой выделенный контент с использованием горячих клавиш или контекстного меню. Вы можете использовать это событие для выполнения дополнительных действий при вырезании данных пользователем. Это событие можно предотвратить, чтобы запретить вырезание содержимого. -
copy
: Событиеcopy
возникает, когда пользователь копирует выделенный текст или другой выделенный контент с использованием горячих клавиш или контекстного меню. Подобно событиюcut
, вы можете использовать эту возможность для выполнения дополнительных действий при копировании данных пользователем. Можно предотвратить это действие, чтобы запретить копирование содержимого. -
paste
: Событиеpaste
возникает, когда пользователь вставляет данные из буфера обмена в поле ввода или другой элемент на странице. Вы можете использовать это событие для выполнения дополнительной обработки данных, вставленных пользователем. Можно предотвратить вставку данных, чтобы запретить вставку содержимого. -
focus
: Событиеfocus
возникает, когда элемент ввода или другой элемент получает фокус. Например, когда пользователь щелкает на текстовое поле. -
blur
: Событиеblur
возникает, когда элемент ввода или другой элемент теряет фокус. Например, когда пользователь переключается на другой элемент или щелкает вне текстового поля. -
select
: Событиеselect
возникает, когда пользователь выбирает (выделяет) текст в элементе ввода или другом элементе на странице.
Как перебрать ключи и значения объекта в JavaScript?
- Цикл
for...in
используется для перебора ключей объектов, массивов и строк. - Метод
Object.keys()
возвращает массив ключей объекта. - Метод
Object.values()
возвращает значения всех свойств объекта в виде массива. - Метод
Object.entries()
возвращает массив пар ключ-значение объекта.
Метод Object.keys() был добавлен в ES6, тогда как Object.entries() и Object.values() методы были добавлены в ES8. Эти методы преобразуют объект в массив, а затем используют методы цикла массива для перебора этого массива.
Самый простой и популярный способ перебора ключей и значений объекта — использование for…in цикла:
const birds = { owl: '🦉', eagle: '🦅', duck: '🦆', chicken: '🐔' } for (const key in birds) { console.log(`${key} -> ${birds[key]}`) } // owl -> 🦉 // eagle -> 🦅 // duck -> 🦆 // chicken -> 🐔
Единственная проблема с for…in циклом заключается в том, что он перебирает свойства в цепочке прототипов. Поскольку объект JavaScript наследует свойства своего прототипа , for…in цикл также будет перебирать эти свойства. Однако вы можете использовать этот hasOwnProperty()
метод для исключения унаследованных свойств :for (const key in birds) {
if (birds.hasOwnProperty(key)) {
console.log(
${key} -> ${birds[key]})
}
}
Метод Object.keys()
принимает объект в качестве входных данных и возвращает массив имен собственных перечисляемых свойств объекта:const birds = {
owl: '🦉',
eagle: '🦅',
duck: '🦆',
chicken: '🐔'
}
const keys = Object.keys(birds)
console.log(keys)
// [ 'owl', 'eagle', 'duck', 'chicken' ]
Теперь мы можем использовать forEach()цикл для перебора массива и получения значения каждого свойства:
keys.forEach(key => {
console.log(
${key} -> ${birds[key]})
})
// owl -> 🦉
// eagle -> 🦅
// duck -> 🦆
// chicken -> 🐔
Как остановить распространение событий ( stopPropagation() / stopImmediatePropagation() )?
Метод stopPropagation()
интерфейса Event предотвращает дальнейшее распространение текущего события на этапах захвата и всплытия. Однако это не предотвращает появление поведения по умолчанию; например, клики по ссылкам все еще обрабатываются. Если вы хотите остановить такое поведение, используйте метод preventDefault()
. Это также не препятствует немедленному распространению на другие обработчики событий. Если вы хотите остановить их, используйте метод stopImmediatePropagation()
.
Ниже приведен пример кода, который демонстрирует использование метода stopPropagation()
:
const outerElement = document.querySelector(“#outer”);
const innerElement = document.querySelector(“#inner”);
outerElement.addEventListener(“click”, function(event) {
console.log(“Внешний элемент”);
});
innerElement.addEventListener(“click”, function(event) {
console.log(“Внутренний элемент”);
event.stopPropagation();
});
innerElement.click();
// Вывод:
// Внутренний элемент
// Внешний элемент
Объяснение: При клике на внутреннем элементе inner, обработчик события вызывает event.stopPropagation(), что приводит к прекращению распространения события дальше по вложенным элементам. В результате сначала вызывается обработчик внутреннего элемента inner, а затем обработчик внешнего элемента outer.
Метод stopImmediatePropagation()
интерфейса Event предотвращает вызов других слушателей того же события. Если к одному и тому же элементу для одного и того же типа события прикреплено несколько слушателей, они вызываются в том порядке, в котором были добавлены. Если stopImmediatePropagation()
вызывается во время одного такого вызова, остальные прослушиватели вызываться не будут.
Ниже приведен пример кода, который демонстрирует использование метода stopImmediatePropagation()
:
const button = document.querySelector(“#myButton”);
button.addEventListener(“click”, function(event) {
console.log(“Обработчик клика 1”);
event.stopImmediatePropagation();
});
button.addEventListener(“click”, function(event) {
console.log(“Обработчик клика 2”);
});
button.addEventListener(“click”, function(event) {
console.log(“Обработчик клика 3”);
});
button.click();
// Вывод:
// Обработчик клика 1
Объяснение: При клике на кнопке myButton, первый обработчик события прерывает дальнейшее распространение события, вызывая метод event.stopImmediatePropagation(). Поэтому второй и третий обработчики не выполняются, и в консоли выводится только сообщение из первого обработчика.
Итого: stopPropagation()
позволяет выполнять другие обработчики событий для того же элемента, но stopImmediatePropagation()
предотвращает это. stopPropagation()
и stopImmediatePropagation()
предотвращают выполнение обработчиков событий на этапах захвата и всплытия.
Расскажи про HTMLCollection и NodeList
HTMLCollection и NodeList — это очень похожие на массив коллекции. Они хранят элементы веб-страницы (узлы DOM). NodeList может хранить любые типы узлов, а HTMLCollection — только узлы HTML элементов. К элементам коллекций можно обращаться по индексу, но у них нет привычных методов массива. HTMLCollection возвращают методы getElementsByTagName()
и getElementsByClassName()
. NodeList возвращают метод querySelectorAll()
и свойство childNodes
.
Полученная один раз HTMLCollection всегда остаётся актуальной — JavaScript будет обновлять её в случае, если на странице появляется подходящий элемент. Поэтому HTMLCollection называют «живой» коллекцией.
NodeList работает почти так же, как и HTMLCollection.
Разница:
1. NodeList может хранить любые типы узлов, например текстовые узлы и комментарии, а HTMLCollection — только узлы HTML элементов.
2. HTMLCollection позволяет обращаться к элементам не только по индексу, но и по имени с помощью метода namedItem
.
3. NodeList может быть не только «живой» коллекцией, но и статической. Такая коллекция не обновляется при появлении на странице новых элементов.
«Живой» NodeList возвращают метод getElementsByName() и свойство childNodes. Статический NodeList возвращает метод querySelectorAll().
Если очень нужны методы массива, то преобразуйте HTMLCollection или NodeList в массив с помощью Array.from().
Интернационализация в JavaScript
Общая проблема строк, дат, чисел в JavaScript – они «не в курсе» языка и особенностей стран, где находится посетитель.
- Строки- при сравнении сравниваются коды символов, а это неправильно, к примеру, в русском языке оказывается, что “ё” > “я”, хотя я – последняя буква алфавита и это она должна быть больше любой другой.
- Даты в разных странах принята разная запись дат. Где-то пишут 31.12.2014 (Россия), а где-то 12/31/2014 (США), где-то иначе.
- Числа в одних странах выводятся цифрами, в других – иероглифами, длинные числа разделяются где-то пробелом, где-то запятой.
Все современные браузеры, кроме IE10 (но есть библиотеки и для него) поддерживают стандарт ECMA 402, предназначенный решить эти проблемы навсегда.
Интерфейс Intl является встроенным объектом в JavaScript, предоставляющим поддержку интернационализации (i18n) и локализации (l10n) при работе с различными языками и региональными настройками. Он предоставляет различные функциональности для форматирования чисел, дат, времени, валюты и текста с учетом локализации.
Вот некоторые из хорошо известных возможностей Intl в JavaScript:
-
Intl.Collator: умеет правильно сравнивать и сортировать строки.
const names = ['Иван', 'Алексей', 'Ян', 'Джон', 'Élodie']; const collator = new Intl.Collator('ru', { sensitivity: 'base' }); const sortedNames = names.sort(collator.compare); console.log(sortedNames); // ["Алексей", "Джон", "Иван", "Élodie", "Ян"]
-
Intl.NumberFormat: позволяет форматировать числа в соответствии с языковыми представлениями и локальными настройками. С помощью этого класса вы можете задать количество десятичных знаков, разделители тысячных и дробных частей, а также символы для обозначения валюты.
const number = 12345.6789; const formattedNumber = new Intl.NumberFormat('en-US', { style: 'decimal' }).format(number); console.log(formattedNumber); // "12,345.6789"
-
Intl.DateTimeFormat: позволяет форматировать даты и времена с учетом языковых представлений и локальных настроек. С помощью этого класса вы можете установить формат даты и времени, использовать определенные календарные системы и часовые пояса.
const date = new Date(); const formattedDate = new Intl.DateTimeFormat('en-US', { dateStyle: 'full', timeStyle: 'short' }).format(date); console.log(formattedDate); // "Friday, October 29, 2021 at 2:30 PM"
-
Intl.ListFormat: предоставляет возможность форматирования списков значений с использованием языковых представлений. Он позволяет задать разделитель между элементами списка и перед последним элементом списка для более понятного отображения.
const fruits = ['яблоко', 'банан', 'киви', 'апельсин']; const listFormat = new Intl.ListFormat('ru', { style: 'long', type: 'conjunction' }); const formattedList = listFormat.format(fruits); console.log(formattedList); // "яблоко, банан, киви и апельсин"
-
Intl.PluralRules: позволяет определить правила склонения для различных числовых значений в соответствии с языковыми представлениями. Это полезно при отображении правильной формы слова для разных числовых значений, таких как единственное число, множественное число и другие.
const count = 5; const pluralRules = new Intl.PluralRules('en'); const plural = pluralRules.select(count); const message =
У вас ${count} ${plural} яблок${plural === ‘one’ ? ‘о’ : ‘а’}; console.log(message); // "У вас 5 яблок"
-
Intl.RelativeTimeFormat: позволяет форматировать относительное время (например, “5 минут назад” или “через 3 дня”) с учетом языковых представлений. Это полезно для создания дружественных для пользователя сообщений времени.
const minutesAgo = -5; const relativeTimeFormat = new Intl.RelativeTimeFormat('en', { numeric: 'auto' }); const formattedRelativeTime = relativeTimeFormat.format(minutesAgo, 'minute'); console.log(formattedRelativeTime); // "5 minutes ago"
Все эти методы при запуске создают соответствующий объект Intl.* и передают ему опции, можно рассматривать их как укороченные варианты вызова.
Локаль – первый и самый важный аргумент всех методов, связанных с интернационализацией. Локаль описывается строкой из трёх компонентов, которые разделяются дефисом:
- Код языка.
- Код способа записи.
- Код страны.
На практике не всегда указаны три, обычно меньше:
ru – русский язык, без уточнений.
en-GB – английский язык, используемый в Англии (GB).
en-US – английский язык, используемый в США (US).
zh-Hans-CN – китайский язык (zh), записываемый упрощённой иероглифической письменностью (Hans), используемый в Китае.
Все методы принимают локаль в виде строки или массива, содержащего несколько локалей в порядке предпочтения.
Если локаль не указана или undefined – берётся локаль по умолчанию, установленная в окружении (браузере).
Подбор локали localeMatcher
localeMatcher – вспомогательная настройка, которую тоже можно везде указать, она определяет способ подбора локали, если желаемая недоступна.
У него два значения:
1. “lookup” – означает простейший порядок поиска путём обрезания суффикса, например zh-Hans-CN → zh-Hans → zh → локаль по умолчанию.
2. “best fit” – использует встроенные алгоритмы и предпочтения браузера (или другого окружения) для выбора подходящей локали.
По умолчанию стоит “best fit”.
Local Storage vs Session Storage vs Cookie
Local Storage (локальное хранилище)
- Хранит данные бессрочно.
- Очищается только с помощью JavaScript или очистки кэша браузера.
- Хранит данные объёмом до 5 МБ, это самый большой объём из трёх вариантов хранилища.
- Не поддерживается старыми браузерами, например, IE 7 и ниже.
- Работает по правилу ограничения домена (same origin policy). То есть сохранённые данные доступны только для одного источника.
- браузеры на основе движка WebKit, например Safari, очищают localStorage, если к нему не обращались в течение 7 дней
Session Storage (сессионное хранилище)
- Хранит данные, пока продолжается текущая сессия. Когда пользователь закрывает браузер, данные становятся недоступными.
- Используется контекст браузера верхнего уровня, поэтому каждая вкладка браузера хранит уникальные данные.
- Объём данных больше чем в Cookie.
- Не поддерживается старыми браузерами, например, IE 7 и ниже.
Cookie
- Хранит данные, которые можно передавать на сервер через заголовки. Локальное и сессионное хранилище доступны только на клиентской стороне.
- Срок хранения устанавливается при создании cookie.
- Объём данных не превышает 4 Кбайт.
- Cookie могут быть защищёнными, в этом случае их содержимое нельзя получить на стороне клиента. Это важно для аутентификации при хранении пользовательских токенов.
Основные отличия:
1. В отличие от куки, объекты веб-хранилища не отправляются на сервер при каждом запросе. Именно поэтому мы можем хранить гораздо больше данных. Большинство современных браузеров могут выделить как минимум 5 мегабайтов данных (или больше), и этот размер можно поменять в настройках.
2. Ещё одно отличие от куки – сервер не может манипулировать объектами хранилища через HTTP-заголовки. Всё делается при помощи JavaScript.
3. Хранилище привязано к источнику (домен/протокол/порт). Это значит, что разные протоколы или поддомены определяют разные объекты хранилища, и они не могут получить доступ к данным друг друга.
Объекты хранилища localStorage и sessionStorage предоставляют одинаковые методы и свойства:window.localStorage.setItem('name', 'Дока Дог')
- сохранениеwindow.localStorage.getItem('name')
- чтениеwindow.localStorage.removeItem('name')
- удалениеwindow.localStorage.clear()
- очистка хранилищаkey(index)
– получить ключ на заданной позиции.length
– количество элементов в хранилище.
Иногда нам нужно сохранить не просто текст, а целую структуру данных, и в этом нам поможет JSON.stringify() и после парсим JSON.parse(userJSON). Значения хранятся в виде строк. При попытке сохранения других типов данных, они будут приведены к строке. Например, если записать число, то при чтении нам вернётся число, записанное в строку.
Как видим, интерфейс похож на Map (setItem/getItem/removeItem), но также позволяет получить доступ к элементу по индексу – key(index).
null и undefined различия и сходства
Сходства:
- они принадлежат к 7 «примитивам» JS
- являются ложными значениями при преобразовании в булевое значение
Различия:
undefined («неопределенный») представляет собой значение по умолчанию:
- переменной, которой не было присвоено значения, т.е. объявленной, но не инициализированной переменной;
- функции, которая ничего не возвращает явно, например, console.log(1);
- несуществующего свойства объекта.
null — это «значение отсутствия значения». null — это значение, которое присваивается переменной явно.
При сравнении null и undefined мы получаем true, когда используем оператор “==”, и false при использовании оператора “===”.
Что такое Object.is?
Новая функция для проверки равенства значений. Возвращает true, если значения value1 и value2 равны, иначе false.
Она похожа на обычное строгое равенство ===, но есть отличия:
// Сравнение +0 и -0
alert( Object.is(+0, -0)); // false
alert( +0 === -0 ); // true
// Сравнение с NaN
alert( Object.is(NaN, NaN) ); // true
alert( NaN === NaN ); // false
Отличия эти в большинстве ситуаций некритичны, так что не похоже, чтобы эта функция вытеснила обычную проверку ===. Что интересно – этот алгоритм сравнения, который называется SameValue, применяется во внутренних реализациях различных методов современного стандарта.
Что такое return?
return возвращает результат из функции и используется только в функциях. Благодаря return можно использовать результат работы функции где угодно. Например, в условиях или при формировании новых значений. Пример ниже использует функцию с return для проверки условия — действительно ли счёт игрока больше 100:function checkScore(score) {
return score > 100
}
const s1 = 10
const s2 = 15
const s3 = 20
if (checkScore(s1)) alert('игрок 1 проходит')
if (checkScore(s2)) alert('игрок 2 проходит')
if (checkScore(s3)) alert('игрок 3 проходит')
return останавливает выполнение функции. Обычно это ожидаемое поведение, но если про это забыть — возможны баги.
Объект Set
Объект Set – это вид коллекции уникальных значений без ключей.
Основные методы:
- new Set(iterable)
– создаёт Set, и если в качестве аргумента был предоставлен итерируемый объект (обычно это массив), то копирует его значения в новый Set.
- set.add(value)
– добавляет значение (если оно уже есть, то ничего не делает), возвращает тот же объект Set.
- set.delete(value)
– удаляет значение, возвращает true, если value было в множестве на момент вызова, иначе false.
- set.has(value)
– возвращает true, если значение присутствует в множестве, иначе false.
- set.clear()
– удаляет все имеющиеся значения.
- set.size
– возвращает количество элементов в множестве.
Пример использования объекта Set:
const set = new Set();
set.add('apple');
set.add('banana');
set.add('apple'); // не будет добавлено, так как значение уже присутствует в множестве
console.log(set.size); // 2
console.log(set.has('apple')); // true
console.log(set.delete('banana')); // true
console.log(set.size); // 1
set.clear();
console.log(set.size); // 0
Перебор объекта Set:
1. for..of
:const set = new Set(['apple', 'banana', 'orange']);
for (const value of set) {
console.log(value);
}
-
forEach
:const set = new Set(['apple', 'banana', 'orange']); set.forEach((value, valueAgain, set) => { console.log(value); });
Функция в forEach
у объекта Set имеет 3 аргумента: значение value
, затем снова то же самое значение valueAgain
, и только потом целевой объект set
. Это действительно так, значение появляется в списке аргументов дважды. Это сделано для совместимости с объектом Map, в котором колбэк forEach
имеет 3 аргумента. Выглядит немного странно, но в некоторых случаях может помочь легко заменить Map на Set и наоборот.
Set имеет те же встроенные методы, что и Map:
- set.keys()
– возвращает перебираемый объект для значений.
- set.values()
– то же самое, что и set.keys()
, присутствует для обратной совместимости с объектом Map.
- set.entries()
– возвращает перебираемый объект для пар вида [значение, значение]
, присутствует для обратной совместимо
setTimeout и setInterval
Мы можем вызвать функцию не в данный момент, а позже, через заданный интервал времени. Это называется «планирование вызова».
Для этого существуют два метода:
- setTimeout
позволяет вызвать функцию один раз через определённый интервал времени.setTimeout(() => alert('Привет'), 1000);
Вызов setTimeout возвращает «идентификатор таймера» timerId, который можно использовать для отмены дальнейшего выполнения.let timerId = setTimeout(...);
clearTimeout(timerId);
-
setInterval
позволяет вызывать функцию регулярно, повторяя вызов через определённый интервал времени.let timerId = setInterval(() => alert('tick'), 2000);
Чтобы остановить дальнейшее выполнение функции, необходимо вызвать clearInterval(timerId).setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);
Эти методы не являются частью спецификации JavaScript. Но большинство сред выполнения JS-кода имеют внутренний планировщик и предоставляют доступ к этим методам.
Вложенный setTimeoutlet timerId = setTimeout(function tick() {
alert('tick');
timerId = setTimeout(tick, 2000); // (*)
}, 2000);
Методы setInterval(func, delay, ...args) и setTimeout(func, delay, ...args)
позволяют выполнять func регулярно или только один раз после задержки delay, заданной в мс.
Для отмены выполнения необходимо вызвать clearInterval / clearTimeout со значением, которое возвращают методы setInterval/setTimeout.
Вложенный setTimeout – более гибкий метод, чем setInterval. С его помощью последующий вызов может быть задан по-разному в зависимости от результатов предыдущего. Вложенный setTimeout позволяет задать задержку между выполнениями более точно, чем setInterval. Планирование с нулевой задержкой setTimeout(func,0) или, что то же самое, setTimeout(func) используется для вызовов, которые должны быть исполнены как можно скорее, после завершения исполнения текущего кода.
Браузер ограничивает 4-мя мс минимальную задержку между пятью и более вложенными вызовами setTimeout, а также для setInterval, начиная с 5-го вызова.
Обратим внимание, что все методы планирования не гарантируют точную задержку.
Например, таймер в браузере может замедляться по многим причинам:
- Перегружен процессор.
- Вкладка браузера в фоновом режиме.
- Работа ноутбука от аккумулятора.
Всё это может увеличивать минимальный интервал срабатывания таймера (и минимальную задержку) до 300 или даже 1000 мс в зависимости от браузера и настроек производительности ОС.
Агрегация данных
Data aggregation
Агрегация используется со строками и числами и относится к динамической генерации строк.repeat ('icecream', 3); // icecreamicecreamicecream
Аксессоры (геттеры и сеттеры)
Accessor properties
Геттеры и сеттеры — это методы, задача которых контролировать доступ к полям. Геттер считывает и возвращают значение поля, а сеттер — наоборот, принимает в качестве аргумента значение и записывает в поле.
Сеттер при записи значения в поле объекта, может проверить тип, или входит ли значение в диапазон допустимых (валидация). В геттер же можно добавить, ленивую инициализацию или кэширование, если актуальное значение на самом деле лежит в базе данных.
const person = {
firstName: 'John',
lastName: 'Doe',
// Геттер для получения полного имени
get fullName() {
return this.firstName + ' ' + this.lastName;
},
// Сеттер для установки полного имени
set fullName(value) {
const [firstName, lastName] = value.split(' ');
this.firstName = firstName;
this.lastName = lastName;
}
};
console.log(person.fullName); // "John Doe"
person.fullName = 'Jane Smith';
console.log(person.firstName); // "Jane"
console.log(person.lastName); // "Smith"
Бесконечный цикл
Infinite loop (endless loop)
Бесконечный цикл - это конструкция в программировании, которая выполняется бесконечно, без завершения. Такой цикл не содержит условия или логики, которая могла бы привести к его завершению.
Бесконечные циклы обычно являются ошибкой программирования и приводят к “зависанию” программы, потреблению ресурсов процессора и памяти, и, как следствие, к зависанию или падению программы.
В каких случаях используются анонимные функции?
Анонимные функции чаще всего используются в качестве функций обратных вызовов. Также, каждая стрелочная функция является анонимной.
В чем разница между Array.prototype.forEach и Array.prototype.map?
.forEach проходится по массиву с выполнением переданного обратного вызова на каждой итерации.
.map создает и возвращает новый массив на основе исходного, выкладывая по кирпичику на каждой итерации. Он нужен, если мы хотим преобразовать текущий массив в какой-то новый.
В чем разница между Call, Apply и Bind?
Функции в JavaScript никак не привязаны к своему контексту this, с одной стороны, здорово – это позволяет быть максимально гибкими, одалживать методы и так далее.
Но с другой стороны – в некоторых случаях контекст может быть потерян. Способы явно указать this - методы bind, call и apply.
call - вызывает функцию с заданным this значением и аргументами, предоставленными один за другим через запятую.
Синтаксис метода call: func.call(context, arg1, arg2, …)
При этом вызывается функция func, первый аргумент call становится её this, а остальные передаются «как есть». Вызов func.call(context, a, b…) – то же, что обычный вызов func(a, b…), но с явно указанным this(=context).
const employee1 = { firstName: "John", lastName: "Rodson" };
const employee2 = { firstName: "Jimmy", lastName: "Baily" };
function invite(greeting1, greeting2) {
console.log(
greeting1 + " " + this.firstName + " " + this.lastName + ", " + greeting2
);
}
invite("Hello", "How are you?"); // Hello undefined undefined, How are you?
invite.call(employee1, "Hello", "How are you?"); // Hello John Rodson, How are you?
invite.call(employee2, "Hello", "How are you?"); // Hello Jimmy Baily, How are you?
apply - вызов функции с переменным количеством аргументов в виде массива и с подменой контекста.
Если нам неизвестно, с каким количеством аргументов понадобится вызвать функцию, можно использовать более мощный метод: apply. Вызов функции при помощи func.apply работает аналогично func.call, но принимает массив аргументов вместо списка.
func.call(context, arg1, arg2) идентичен вызову func.apply(context, [arg1, arg2]);
function testForApply() {
console.log(this);
for (let i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
testForApply(1, 2, 3); // [object Window], 1, 2, 3
testForApply.apply("abc", [1, 2, 3, 4]); // abc, 1, 2, 3, 4
bind - создаёт “обёртку” над функцией, которая подменяет контекст этой функции. Поведение похоже на call и apply, но, в отличие от них, bind не вызывает функцию, а лишь возвращает “обёртку”, которую можно вызвать позже.
Синтаксис встроенного bind: var wrapper = func.bind(context, [arg1, arg2…])
Методы bind и call/apply близки по синтаксису, но есть важнейшее отличие. Методы call/apply вызывают функцию с заданным контекстом и аргументами. А bind не вызывает функцию. Он только возвращает «обёртку», которую мы можем вызвать позже, и которая передаст вызов в исходную функцию, с привязанным контекстом.
function testForBind() {
console.log(this);
}
let wrapped = testForBind.bind("abc");
testForBind(); // [object Window]
wrapped(); // abc
Также bind умеет подменять не только контекст, но и аргументы функции, осуществляя каррирование:
function add(a, b) {
return a + b;
}
var addOne = add.bind(null, 1);
alert(add(1, 2)); // 3
alert(addOne(2)); // 3
В чем разница между операторами == и ===?
Оператор == сравнивает на равенство, а === на идентичность. Плюс оператора === состоит в том, что он не приводит два значения к одному типу.
Важно, при ===:
NaN ничему не равен, в том числе и NaN.
Положительные и отрицательные нули равны друг другу.
Два объекта строго равны, если они ссылаются на один и тот же объект.
Типы Null и Undefined не равны ===, но равны ==. т. е. null===undefined → false, но null==undefined → true
Интересно: Есть еще Object.is (новшество из ECMAScript 6). Object.is ведёт себя так же, как и тройное равно, но со специальной обработкой для NaN, -0 и +0, возвращая false при сравнении -0 и +0, и true для операции Object.is(NaN, NaN). (В то время как двойное или тройное равенство вернут false согласно стандарту IEEE 754.)
В чем разница между методами event.preventDefault() и event.stopPropagation()?
Метод event.preventDefault()
отключает поведение элемента по умолчанию. Если использовать этот метод в элементе form, то он предотвратит отправку формы (submit). Если использовать его в contextmenu, то контекстное меню будет отключено (данный метод часто используется в keydown для переопределения клавиатуры, например, при создании музыкального/видео плеера или текстового редактора — прим. пер.). Чтобы узнать применен ли к элементу event.preventDefault()
можно использовать event.defaulPrevented
, возвращающее логическое значение, служащее индикатором применения к элементу метода event.preventDefault
.
Метод event.stopPropagation()
отключает распространение события (его всплытие или погружение).
Виртуальный метод (виртуальная функция)
Virtual function
Виртуальный метод (виртуальная функция) — в объектно-ориентированном программировании метод (функция) класса, который может быть переопределён в классах-наследниках так, что конкретная реализация метода для вызова будет определяться во время исполнения. Таким образом, программисту необязательно знать точный тип объекта для работы с ним через виртуальные методы: достаточно лишь знать, что объект принадлежит классу или наследнику класса, в котором объявлен метод. Одним из переводов слова virtual с английского языка может быть «фактический», что больше подходит по смыслу.
Члены класса - это переменные состояния и методы этого класса, иными словами членами класса могут быть как переменные, так и функции. Функции и переменные, объявленные внутри объявления класса, становятся членами этого класса. Функции-члены класса будем называть методами этого класса.
Пример с самолетами: Параметры унифицированного самолета, которые заданы, но еще не определенны, или действия, которые он должен делать (взлет/посадка) это виртуальные методы и члены класса.
Вложенные функции
Nested functions
Вложенной называется функция, созданная внутри функции. Она может быть возвращена в качестве свойства нового объекта или сама по себе.
function sayHiBye(firstName, lastName) {
// helper nested function to use below
function getFullName() {
return firstName + " " + lastName;
}
alert( "Hello, " + getFullName() );
alert( "Bye, " + getFullName() );
}
Здесь вложенная функция getFullName()
сделана для удобства. Он может обращаться к внешним переменным и поэтому может возвращать полное имя. Вложенные функции довольно распространены в JavaScript.
Что гораздо интереснее, вложенную функцию можно вернуть: либо как свойство нового объекта, либо как результат сам по себе. Затем его можно использовать в другом месте. Независимо от того, где, он по-прежнему имеет доступ к одним и тем же внешним переменным.
function makeCounter() {
let count = 0;
return function() {
return count++;
};
}
let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
alert( counter() ); // 2
Замыкание — это функция , которая запоминает свои внешние переменные и может обращаться к ним. В некоторых языках это невозможно, или функция должна быть написана особым образом, чтобы это произошло. Но, как объяснялось выше, в JavaScript все функции по своей природе являются замыканиями (есть только одно исключение, которое будет рассмотрено в синтаксисе «новой функции» ).
Внутреннее и внешнее лексическое окружение
Внутреннее и внешнее лексическое окружение (или “scope”) являются важными концепциями в JavaScript, определяющими доступность переменных и функций во время выполнения кода.
Внутреннее лексическое окружение (Inner Lexical Environment) - это область видимости, в которой происходит определение и доступ к переменным и функциям внутри функции. Каждый раз, когда вызывается функция, создается новое внутреннее лексическое окружение для этой функции.
Пример внутреннего лексического окружения:
function greeting() {
const message = 'Hello';
console.log(message);
}
greeting(); // Выводит "Hello"
В примере выше, внутреннее лексическое окружение функции greeting
содержит переменную message
, которая объявлена внутри функции и доступна только в этой функции.
Внешнее лексическое окружение (Outer Lexical Environment) - это область видимости, которая окружает текущее внутреннее лексическое окружение. Внешнее окружение предоставляет доступ к переменным и функциям, объявленным вне текущего внутреннего окружения.
Пример внешнего лексического окружения:
const count = 10;
function increment() {
console.log(count);
}
increment(); // Выводит 10, так как функция имеет доступ к переменной count из внешнего лексического окружения
В примере выше, функция increment
имеет доступ к переменной count
, которая объявлена во внешнем лексическом окружении.
Всплытие и погружение (перехват) событий DOM
Распространение события - Event Propagation
Когда какое-либо событие происходит в элементе DOM, оно на самом деле происходит не только в нем. Событие «распространяется» от объекта Window до вызвавшего его элемента (event.target). При этом событие последовательно пронизывает (затрагивает) всех предков целевого элемента. Распространение события имеет три стадии или фазы:
- фаза погружения (capturing phase) – событие сначала идёт сверху вниз;
- фаза цели (target phase) – событие достигло целевого(исходного) элемента;
- фаза всплытия (bubbling stage) – событие начинает всплывать.
При наступлении события, элемент, на котором оно произошло, помечается как «целевой» (event.target
). Затем событие сначала двигается сверху вниз от корня документа (document
) к целевому элементу, на каждом уровне (дочернем элементе) вызывая обработчики, назначенные через addEventListener(type, listener, true)
, где true
– это сокращение для {capture: true}
. При достижении целевого элемента, обработчики вызываются на самом целевом элементе (event.target
). Затем событие начинает “всплывать”, т.е. двигается от целевого элемента вверх к корню документа, по пути вызывая обработчики, назначенные с префиксом on
(например, onclick
) или через addEventListener(type, listener, false)
(или без третьего аргумента).
Каждый обработчик имеет доступ к свойствам события event
:
- event.target
– самый глубокий элемент, на котором произошло событие.
- event.currentTarget
(== this
) – элемент, на котором в данный момент сработал обработчик (тот, которому назначен конкретный обработчик).
- event.eventPhase
– фаза, на которой сработал обработчик (1 - погружение, 2 - цели, 3 - всплытие).
Любой обработчик может остановить событие вызовом методов:
- event.stopPropagation()
- препятствует дальнейшему всплытию события дальше по цепочке элементов.
- event.stopImmediatePropagation()
- не только предотвращает всплытие, но и останавливает обработку событий на текущем элементе.
Всплытие событий DOM - это процесс последовательного срабатывания обработчиков события вверх по цепочке “родителей”, начиная с объекта DOM, инициировавшего событие, до объекта document.
Пример (learn.javascript.ru):
Например, есть 3 вложенных элемента FORM > DIV > P с обработчиком на каждом:
`<form onclick="alert('form')">FORM
<div>DIV
<p>P</p>
</div>
</form>
`
Клик по внутреннему <p> вызовет обработчик onclick:
- сначала на самом <p>;
- потом на внешнем <div>;
- затем на внешнем <form>, и т.д. вверх по цепочке до самого document.
Поэтому если кликнуть на <p>, то мы увидим три оповещения: p → div → form (события «всплывают» от внутреннего элемента вверх через родителей подобно тому, как всплывает пузырёк воздуха в воде).
Элемент, который вызывает событие, называется **целевым элементом**, и он доступен через свойство `event.target`.
Отличие event.target от this:event.target
– это элемент, на котором произошло событие, в процессе всплытия он не меняется;this
– это текущий элемент, до которого дошло всплытие, на нём сейчас выполняется обработчик.
Всплывают большинство событий, но есть исключения, например, событие focus не всплывает.
Прерывание всплытия
Событие “всплывает”, начиная с «целевого» элемента, вверх по цепочке родительских элементов до элемента <html>, а затем до объекта document (а иногда и до window).
Для прерывания всплытия любым промежуточным обработчиком используется метод event.stopPropagation()
, например: <body onclick="alert(
сюда всплытие не дойдёт)">
<button onclick="event.stopPropagation()">Кликни меня</button>
</body>
Метод event.stopPropagation() препятствует всплытию события дальше по цепочке элементов, однако если у элемента, на котором вызывается event.stopPropagation()
, есть несколько обработчиков на одно событие, то все они будут выполнены.
Метод event.stopImmediatePropagation()
не только предотвращает всплытие, но и останавливает обработку событий на текущем элементе.
Не рекомендуется прекращать всплытие без необходимости.
Погружение (перехват) событий
Обработчики, добавленные через свойство DOM-объекта, или через HTML-атрибуты, или через addEventListener(event, handler)
с двумя аргументами работают только на фазах цели (target phase) и всплытия (bubbling stage).
Чтобы поймать событие на стадии погружения, нужно использовать аргумент capture
метода addEventListener(event, handler)
:
- если аргумент false (по умолчанию), то событие будет поймано при всплытии;
- если аргумент true, то событие будет перехвачено при погружении.
elem.addEventListener(..., {capture: true})
// или просто "true", как сокращение для {capture: true}
elem.addEventListener(..., true)
Как выровнять (сгладить) вложенный массив
Использование Array.prototype.concat()
Это можно сделать рекурсивно с помощью reduce() метод с concat() метод. В следующем примере показано, как рекурсивно сгладить массив с помощью reduce а также concat метод.
function flatten(arr) {
return arr.reduce((acc, cur) => acc.concat(Array.isArray(cur) ? flatten(cur) : cur), []);
};
const arr = [[1,2],[3,[4,[5]]]];
const flattened = flatten(arr);
console.log(flattened);
// результат: [ 1, 2, 3, 4, 5 ]
Использование Array.prototype.flat()
ECMA 2019 представила новый метод под названием flat() для рекурсивного выравнивания массива. В качестве параметра принимает глубину вложенного массива, т.е. 1 по умолчанию. Чтобы сгладить любую глубину вложенного массива, используйте Infinity с flat() метод.
const arr = [[1,2],[3,[4,[5]]]];
const flattened = arr.flat(Infinity);
console.log(flattened);
// результат: [ 1, 2, 3, 4, 5 ]
Использование функции генератора
В качестве альтернативы вы можете написать функцию-генератор для глубокого выравнивания массива любой глубины. В следующем примере кода показано, как реализовать это с помощью Array.isArray() метод.
function* flatten(arr)
{
for (const val of arr) {
Array.isArray(val) ? yield* flatten(val) : yield val;
}
}
const arr = [[1,2],[3,[4,[5]]]];
const flattened = [...flatten(arr)];
console.log(flattened);
// результат: [ 1, 2, 3, 4, 5 ]
Использование библиотеки подчеркивания
Библиотека Underscore JavaScript предлагает _.flatten метод, который может сгладить вложенный массив любой глубины.
const _ = require('underscore');
const arr = [[1,2], [3,[4,5]]];
const flattened = _.flatten(arr);
console.log(flattened);
результат: [ 1, 2, 3, 4, 5 ]
Разница между выражением и инструкцией
Выражение (англ. expression) — это код, который после выполнения возвращает какое-либо значение. Например, 5 + 3 вернёт 8, а Math.random() — случайное число. Выражения оперируют с данными — это могут быть не только числа, но и строки, и сложные структуры данных. Данные сочетаются с операциями над ними (например, сложение, вычитание, умножение), и программа выдаёт результат выражения. Удобно представлять выражение как наборы данных в сочетании с операциями, которые их обрабатывают.
Инструкция (англ. statement) — это отдельная команда в коде, которая выполняет определённое действие. Инструкции ничего не вычисляют и не возвращают результат, поэтому они не являются выражениями. Например, if позволяет создать ветвление в программе, for позволяет повторять одно и то же действие.
- управление потоком выполнения (if и else, switch, throw и так далее);
- итерации (for, while и так далее);
- объявление значений (var, let, const);
- функции (function, return и так далее);
- прочие (debugger, import, export).
Исключение: в JavaScript есть выражение, которое позволяет возвращать значение по условию. Таким выражением является тернарный оператор. Как любое выражение, он возвращает значение.const result = someNumber > 10 ? 'Больше десяти' : 'Меньше десяти'
Вычисляемые свойства объекта
Object computed props
Мы можем использовать квадратные скобки в литеральной нотации для создания вычисляемого свойства.
Пример:let fruit = prompt("Какой фрукт купить?", "apple");
let bag = {
[fruit]: 5, // имя свойства будет взято из переменной fruit
};
alert( bag.apple ); // 5, если fruit="apple"
Смысл вычисляемого свойства прост: запись [fruit] означает, что имя свойства необходимо взять из переменной fruit.
И если посетитель введёт слово “apple”, то в объекте bag теперь будет лежать свойство {apple: 5}.
По сути, пример выше работает так же, как и следующий пример:let fruit = prompt("Какой фрукт купить?", "apple");
let bag = {};
// имя свойства будет взято из переменной fruit
bag[fruit] = 5;
…Но первый пример выглядит лаконичнее.
Мы можем использовать и более сложные выражения в квадратных скобках:let fruit = 'apple';
let bag = {
[fruit + 'Computers']: 5 // bag.appleComputers = 5
};
Квадратные скобки дают намного больше возможностей, чем запись через точку. Они позволяют использовать любые имена свойств и переменные, хотя и требуют более громоздких конструкций кода.
Подведём итог: в большинстве случаев, когда имена свойств известны и просты, используется запись через точку. Если же нам нужно что-то более сложное, то мы используем квадратные скобки.
Граф (Структуры данных)
Граф – это нелинейная структура, состоящая из конечного множества вершин, соединенных между собой ребрами. Порядок соединения может быть любым.
Если две вершины графа соединены одним ребром, они называются соседними, или смежными. В графе также могут быть отдельно стоящие вершины, не связанные с другими, они называются изолированными. Количество ребер, исходящих из вершины, определяют ее степень связности.
Интересно: В настоящее время это очень востребованная структура данных, так как она позволяет работать с большими объемами плохо структурированной информации. На графах, например, основываются разнообразные системы рекомендаций и ранжирования контента.
Основные методы:
addVertex – добавление новой вершины;
addEdge – добавление нового ребра.
class Graph {
dfs(startVertex, callback) {
let list = this.vertices; // список смежности
let stack = [startVertex]; // стек вершин для перебора
let visited = { [startVertex]: 1 }; // посещенные вершины
//
function handleVertex(vertex) {
// вызываем коллбэк для посещенной вершины
callback(vertex);
//
// получаем список смежных вершин
let reversedNeighboursList = [...list[vertex]].reverse();
//
reversedNeighboursList.forEach(neighbour => {
if (!visited[neighbour]) {
// отмечаем вершину как посещенную
visited[neighbour] = 1;
// добавляем в стек
stack.push(neighbour);
}
});
}
//
// перебираем вершины из стека, пока он не опустеет
while(stack.length) {
let activeVertex = stack.pop();
handleVertex(activeVertex);
}
//
// проверка на изолированные фрагменты
stack = Object.keys(this.vertices);
//
while(stack.length) {
let activeVertex = stack.pop();
if (!visited[activeVertex]) {
visited[activeVertex] = 1;
handleVertex(activeVertex);
}
}
}
}
Двоичное дерево (Структуры данных)
Binary Tree
Двоичное дерево — структура данных, в которой каждый узел имеет максимум два дочерних элемента. Дочерние элементы бывают левым и правым. Ключ левого дочернего узла меньше, чем у родительского. Ключ правого дочернего узла больше, чем у родительского.
Оптимальны для сортировки и поиска.
Эффективность («О» большое):
Индексирование: O(log n).
Поиск: O(log n).
Вставка: O(log n).
Корень - верхний элемент дерева
У корневого элемента есть два дочерних узла, каждый из которых в свою очередь сам является корнем поддерева. Таких уровней может быть сколько угодно.
Элемент, у которого нет дочерних узлов, называется листом, или терминальным узлом.
Каждый узел дерева имеет только одного родителя, не больше. Кроме корневого узла, у которого родителей нет вообще.
Высота дерева – длина самой длинной ветви (количество ребер).
Основные операции:
Добавление нового узла (add);
Удаление узла по его значению (remove);
Поиск узла по значению (find);
Обход всех элементов (traverse).
findMin: получить минимальный узел
findMax: получить максимальный узел
isPresent: проверить наличие определенного узла
Бинарное дерево поиска:class Node {
constructor(data, left = null, right = null) {
this.data = data
this.left = left
this.right = right
}
}
//
class BST {
constructor() {
this.root = null
}
//
add(data) {
const node = this.root
if (node === null) {
this.root = new Node(data)
return
} else {
const searchTree = function(node) {
if (data < node.data) {
if (node.left === null) {
node.left = new Node(data)
return
} else if (node.left !== null) {
return searchTree(node.left)
}
} else if (data > node.data) {
if (node.right === null) {
node.right = new Node(data)
return
} else if (node.right !== null) {
return searchTree(node.right)
}
} else {
return null
}
}
return searchTree(node)
}
}
//
findMin() {
let current = this.root
while (current.left !== null) {
current = current.left
}
return current.data
}
//
findMax() {
let current = this.root
while (current.right !== null) {
current = current.right
}
return current.data
}
//
find(data) {
let current = this.root
while (current.data !== data) {
if (data < current.data) {
current = current.left
} else {
current = current.right
}
if (current === null) {
return null
}
}
return current
}
//
isPresent(data) {
let current = this.root
while (current) {
if (data === current.data) {
return true
}
data < current.data ? current = current.left : current = current.right
}
return false
}
//
remove(data) {
const removeNode = function(node, data) {
if (node === null) return null
if (data === node.data) {
// потомки отсутствуют
if (node.left === null && node.right === null) return null
// отсутствует левый узел
if (node.left === null) return node.right
// отсутствует правый узел
if (node.right === null) return node.left
// имеется два узла
let tempNode = node.right
while (tempNode.left !== null) {
tempNode = tempNode.left
}
node.data = tempNode.data
node.right = removeNode(node.right, tempNode.data)
return node
} else if (data < node.data) {
node.left = removeNode(node.right, data)
return node
} else {
node.right = removeNode(node.right, data)
return node
}
}
this.root = removeNode(this.root, data)
}
}
Интересно: Кроме двоичных, широкое практическое применение имеют деревья с четырьмя узлами (дерево квадрантов). Они используются в геймдеве для организации сетки. Каждый узел в таком дереве представляет одно направление (север-запад, юго-восток и так далее).
Делегирование событий
В случае, когда нам нужно обработать событие на нескольких элементах, имеющих общего предка мы «вешаем» слушатель не на элементы, а на предка. После, с помощью event.target, мы можем получить конкретный элемент, на котором было совершено целевое событие и обработать его.
Всплытие и перехват событий позволяет реализовать один из самых важных приёмов разработки – делегирование.
Идея в том, что если у нас есть много элементов, события на которых нужно обрабатывать похожим образом, то вместо того, чтобы назначать обработчик каждому, мы ставим один обработчик на их общего предка. Из него можно получить целевой элемент event.target, понять на каком именно потомке произошло событие и обработать его.
К примеру есть табличка и наша задача – реализовать подсветку ячейки <td> при клике.
Вместо того, чтобы назначать обработчик onclick для каждой ячейки <td> (их может быть очень много) – мы повесим «единый» обработчик на элемент <table>. Он будет использовать event.target, чтобы получить элемент, на котором произошло событие, и подсветить его.
Например, нам нужно сделать меню с разными кнопками: «Сохранить (save)», «Загрузить (load)», «Поиск (search)» и т.д. И есть объект с соответствующими методами save, load, search… Как их состыковать?
Первое, что может прийти в голову – это найти каждую кнопку и назначить ей свой обработчик среди методов объекта. Но существует более элегантное решение. Мы можем добавить один обработчик для всего меню и атрибуты data-action для каждой кнопки в соответствии с методами, которые они вызывают:<button data-action="save">Нажмите, чтобы Сохранить</button>
Обработчик считывает содержимое атрибута и выполняет метод.
onClick(event) {
let action = event.target.dataset.action;
if (action) {
this[action]();
}
}
Зачем использовать:
Упрощает процесс инициализации и экономит память: не нужно вешать много обработчиков.
Меньше кода: при добавлении и удалении элементов не нужно ставить или снимать обработчики.
Удобство изменений DOM: можно массово добавлять или удалять элементы путём изменения innerHTML и ему подобных.
Конечно, у делегирования событий есть свои ограничения:
Во-первых, событие должно всплывать. Некоторые события этого не делают. Также, низкоуровневые обработчики не должны вызывать event.stopPropagation()
.
Во-вторых, делегирование создаёт дополнительную нагрузку на браузер, ведь обработчик запускается, когда событие происходит в любом месте контейнера, не обязательно на элементах, которые нам интересны. Но обычно эта нагрузка настолько пустяковая, что её даже не стоит принимать во внимание.
Деструктурирующее присваивание
Деструктуризация позволяет разбивать объект или массив на переменные при присвоении.
Полный синтаксис для объекта:
let {prop : varName = default, ...rest} = object
Cвойство prop объекта object здесь должно быть присвоено переменной varName. Если в объекте отсутствует такое свойство, переменной varName присваивается значение по умолчанию.
Свойства, которые не были упомянуты, копируются в объект rest.
Полный синтаксис для массива:
let [item1 = default, item2, ...rest] = array
Первый элемент отправляется в item1; второй отправляется в item2, все остальные элементы попадают в массив rest.
Можно извлекать данные из вложенных объектов и массивов, для этого левая сторона должна иметь ту же структуру, что и правая.
Нежелательные элементы массива также могут быть отброшены с помощью дополнительной запятой:
// второй элемент не нужен
let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
alert( title ); // Consul
Работает с любым перебираемым объектом с правой стороны
…На самом деле мы можем использовать любой перебираемый объект, не только массивы:
let [a, b, c] = "abc";
let [one, two, three] = new Set([1, 2, 3]);
Присваивайте чему угодно с левой стороны
Мы можем использовать что угодно «присваивающее» с левой стороны.
Например, можно присвоить свойству объекта:
let user = {};
[user.name, user.surname] = "Ilya Kantor".split(' ');
alert(user.name); // Ilya
alert(user.surname); // Kantor
Цикл с .entries()
В предыдущей главе мы видели метод Object.entries(obj).
Мы можем использовать его с деструктуризацией для цикличного перебора ключей и значений объекта:
let user = {
name: "John",
age: 30
};
// цикл по ключам и значениям
for (let [key, value] of Object.entries(user)) {
alert(
${key}:${value}); // name:John, затем age:30
}
…то же самое для map:
let user = new Map();
user.set("name", "John");
user.set("age", "30");
// Map перебирает как пары [ключ, значение], что очень удобно для деструктурирования
for (let [key, value] of user) {
alert(
${key}:${value}); // name:John, затем age:30
}
Трюк обмена переменных
Существует хорошо известный трюк для обмена значений двух переменных с использованием деструктурирующего присваивания:
let guest = "Jane";
let admin = "Pete";
// Давайте поменяем местами значения: сделаем guest = "Pete", а admin = "Jane"
[guest, admin] = [admin, guest];
alert(
${guest} ${admin}); // Pete Jane (успешно заменено!)
Здесь мы создаём временный массив из двух переменных и немедленно деструктурируем его в порядке замены.
Таким образом, мы можем поменять местами даже более двух переменных.
Динамические импорты
Динамические импорты являются синтаксической особенностью в JavaScript, которая позволяет загружать модули по требованию во время выполнения программы, а не во время статического анализа и компиляции кода.
Пример использования динамического импорта:
// Загрузка модуля по требованию
import('./module.js')
.then(module => {
// Использование экспорта модуля
module.myFunction();
})
.catch(error => {
// Обработка ошибок при загрузке модуля
console.error('Ошибка загрузки модуля:', error);
});
В примере выше мы используем синтаксис import()
для динамической загрузки модуля из файла module.js
. После успешной загрузки модуля, мы можем использовать его экспорты, например, вызвать myFunction()
.
Динамические импорты возвращают Promise, поэтому мы можем использовать .then()
для обработки успешного завершения загрузки и .catch()
для обработки ошибок.
Динамическое количество параметров функции
Многие встроенные функции JavaScript поддерживают произвольное количество аргументов.
Math.max(arg1, arg2, ..., argN)
– вычисляет максимальное число из переданных.Object.assign(dest, src1, ..., srcN)
– копирует свойства из исходных объектов src1..N в целевой объект dest.
Вызывать функцию можно с любым количеством аргументов независимо от того, как она была определена. Лишние аргументы не вызовут ошибку. Но, конечно, посчитаются только первые два.
Остаточные параметры могут быть обозначены через три точки …. Буквально это значит: «собери оставшиеся параметры и положи их в массив».function sumAll(...args) {
// args — имя массива
let sum = 0;
for (let arg of args) sum += arg;
return sum;
}
Все аргументы функции находятся в псевдомассиве arguments
под своими порядковыми номерами. Раньше в языке не было остаточных параметров, и получить все аргументы функции можно было только с помощью arguments. Хотя arguments похож на массив, и его тоже можно перебирать, это всё же не массив. Он не поддерживает методы массивов, поэтому мы не можем, например, вызвать arguments.map(...)
. Стрелочные функции не имеют “arguments”
Оператор расширения.
Он похож на остаточные параметры – тоже использует …, но делает совершенно противоположное. Когда …arr используется при вызове функции, он «расширяет» перебираемый объект arr в список аргументов.let arr = [3, 5, 1];
alert( Math.max(...arr) ); // 5
(оператор "раскрывает" массив в список аргументов)
Мы также могли бы использовать Array.from()
, но между Array.from(obj)
и [...obj]
есть разница:
- Array.from работает как с псевдомассивами, так и с итерируемыми объектами
- Оператор расширения работает только с итерируемыми объектами
Выходит, что если нужно сделать из чего угодно массив, то Array.from — более универсальный метод.
Если … располагается в конце списка параметров функции, то это «остаточные параметры». Он собирает остальные неуказанные аргументы и делает из них массив.
Если … встретился в вызове функции или где-либо ещё, то это «оператор расширения». Он извлекает элементы из массива.
Для чего используется ключевое слово «new»?
Ключевое слово «new» используется в функциях-конструкторах для создания нового объекта (нового экземпляра класса или так называемого инстанса класса).
Для чего используется директива «use strict»?
«use strict» — это директива ES5, которая заставляет весь наш код или код отдельной функции выполняться в строгом режиме. Строгий режим вводит некоторые ограничения по написанию кода, тем самым позволяя избегать ошибок на ранних этапах.
1. Нельзя присваивать значения или обращаться к необъявленным переменным.
2. Запрещено присваивать значения глобальный переменным, доступным только для чтения или записи
3. Нельзя удалить «неудаляемое» свойство объекта
4. Запрещено дублирование параметров
5. Нельзя создавать функции с помощью функции eval
6. Значением «this» по умолчанию является undefined
и тд
Доступ к внутренним (вложенным) элементам массива
Accessing nested array elements
Через array [index] [index]
Что такое замыкание?
Closure [‘klouzhe]
Замыкание — это способность функции в JS запомнить лексическое окружение в котором она была создана, т.е. хранить ссылку на это окружение. Другими словами, замыкание даёт вам доступ к области видимости(Scope) внешней функции из внутренней функции, даже если первая уже была прекращена. В JavaScript, почти все функции изначально являются замыканиями (кроме созданных через new Function, в её [[Environment]] записывается ссылка на глобальное окружение).
В JavaScript у каждой выполняемой функции, блока кода и скрипта есть связанный с ними внутренний (скрытый) объект, называемый лексическим окружением LexicalEnvironment. Лексическое окружение - это скрытый объект, который есть у любого блока, скрипта или функции в JS.
Объект лексического окружения состоит из двух частей:
- Environment Record – объект, в котором как свойства хранятся все локальные переменные (а также некоторая другая информация, такая как значение this).
- Ссылка на внешнее лексическое окружение – то есть то, которое соответствует коду снаружи
const x = 1;
const y = function (){
i = “hi”;
console.log(i)
}
y()
В данном примере глобальное лексическое окружение включает: 1. переменные x и y, 2. ссылку на Null, так как глобальному окружению не на что ссылаться. Локальное лексическое окружение (внутри функции) имеет 1. переменную i и 2. ссылку на ближайшее внешнее окружение: глобальное. Локальное лексическое окружение имеет доступ как к своим переменным, так и к переменным снаружи, так как у него есть ссылка на внешнее лексическое окружение. Важно, что локальное лексическое окружение будет создано только во время вызова функции y().
Когда код хочет получить доступ к переменной – сначала происходит поиск во внутреннем лексическом окружении, затем во внешнем, затем в следующем и так далее, до глобального.
function getCounter() {
let counter = 0;
return function() {
return counter++;
}}
let count = getCounter();
console.log(count()); // 0
console.log(count()); // 1
console.log(count()); // 2
Благодаря замыканиям появляется доступ к внешней функции, поэтому они обычно используются для двух целей:
- контроля побочных эффектов;
- создания приватных переменных.
Основное отличие между замыканиями обычных функций и стрелочных функций заключается в том, что обычные функции создают собственное лексическое окружение, в то время как стрелочные функции наследуют лексическое окружение своего родителя.
Изменение массива
Изменение элементов массива воможно по индексуconst animals = ['cats', 'dogs', 'birds'];
// Меняется первый элемент массиваanimals[0] = 'horses';
Метод arr.splice(str)
– это универсальный «швейцарский нож» для работы с массивами. Умеет всё: добавлять, удалять и заменять элементы.arr.splice(1, 1); // начиная с позиции 1, удалить 1 элемент
arr.splice(0, 3, "Давай", "танцевать"); // удалить 3 первых элемента и заменить их другими
let removed = arr.splice(0, 2); // удалить 2 первых элемента
Добавление элемента в массив
Метод push()
добавляет элемент в конец массива
Метод unshift()
добавляет элемент в начало массива
По индексу const animals = ['cats', 'dogs', 'birds'];
animals[3] = 'horses';
Удаление элемента из массива
Удалить элемент из массива можно с помощью специальной конструкции delete: delete arr[index].
const animals = ['cats', 'dogs', 'birds'];
delete animals[1]; // удаляем элемент под индексом 1
console.log(animals); // => [ 'cats', <1 empty item>, 'birds' ]
arr.pop()
– извлекает элемент из концаarr.shift()
– извлекает элемент из начала
Интерполяция строк
String interpolation
Интерполяция строк - способ соединения строк через вставку значений переменных в строку-шаблон с помощью фигурных скобок.Hi, ${name}!
` - обратные апострофы или бэктики
Итератор
Объекты, которые можно использовать в цикле for..of
, называются итерируемыми. Технически итерируемые объекты должны иметь метод Symbol.iterator
.
Результат вызова obj[Symbol.iterator]
называется итератором. Он управляет процессом итерации.
Итератор должен иметь метод next()
, который возвращает объект {done: Boolean, value: any}
, где done:true сигнализирует об окончании процесса итерации, в противном случае value – следующее значение.
Метод Symbol.iterator
автоматически вызывается циклом for..of, но можно вызвать его и напрямую.
Встроенные итерируемые объекты, такие как строки или массивы, также реализуют метод Symbol.iterator
.const rangeIterator = '012'[Symbol.iterator]();
console.log(rangeIterator.next()); // {"value": "0", "done": false}
console.log(rangeIterator.next()); // {"value": "1", "done": false}
console.log(rangeIterator.next()); // {"value": "1", "done": false}
console.log(rangeIterator.next()); // {"value": undefined, "done": true}
//
const person = {
age: 40,
name: "Kolya",
[Symbol.iterator]: function*() {
yield this.age;
yield this.name;
}
};
//
for (const value of person) {
console.log(value);
// 40
// Kolya
}
Итератор — это объект, который умеет обращаться к элементам коллекции по одному за раз, при этом отслеживая своё текущее положение внутри этой последовательности.
В JavaScript итератор — это объект, который возвращает следующий элемент последовательности, через метод next(). Этот метод возвращает объект с двумя свойствами:
value — значение текущего элемента коллекции.
done — индикатор, указывающий, есть ли ещё в коллекции значения, доступные для перебора.
В некоторых случаях интерфейс итератора вызывается по умолчанию. Такие объекты как String, Array, Map и Set являются
итерируемыми, потому что их прототипы содержат Symbol.iterator.
Итерируемый (перебираемый) объект
Итерируемые или, иными словами, «перебираемые» объекты – это те, содержимое которых можно перебрать в цикле.
Symbol — это уникальный и иммутабельный идентификатор. Создается с помощью функции Symbol(), также может иметь метку Symbol(‘foo’). Символы с одинаковыми метками не равны друг другу, и вообще, любые символы не равны между собой (помним про уникальность). Существуют системные символы, такие как Symbol.iterator , Symbol.toPrimitive и другие. Системные символы используются самим языком, но мы также можем применять их, чтобы изменять дефолтное поведение некоторых объектов.
Symbol.iterator в основном используется языком в цикле for…of при переборе свойств объекта. Так же его можно использовать напрямую со встроенными типами данных. Список итерируемых типов в js: String, Array, TypedArray, Map, Set.
Для возможности использовать объект в for..of нужно создать в нём свойство с названием Symbol.iterator (системный символ).
При вызове метода Symbol.iterator перебираемый объект должен возвращать другой объект («итератор»), который умеет осуществлять перебор.
По стандарту у такого объекта должен быть метод next(), который при каждом вызове возвращает очередное значение и проверяет, окончен ли перебор.
const myObject = {
values: [1, 2, 3],
[Symbol.iterator]() {
let index = 0;
const values = this.values;
//
return {
next() {
if (index < values.length) {
return { value: values[index++], done: false };
}
//
return { done: true };
}
};
}
};
//
for (const value of myObject) {
console.log(value);
}
В этом примере мы создали объект myObject
, у которого есть свойство с символом Symbol.iterator. Метод, связанный с символом, возвращает объект-итератор, который в свою очередь имеет метод next()
. Метод next()
отвечает за возврат очередного значения и проверку, закончен ли перебор. В цикле for..of
мы проходим по объекту myObject
, и на каждой итерации получаем значение и выводим его в консоль.
Symbol.iterator используется при деструктуризации:
const [a, b, c] = route;
// a - "Москва"
// b - "Питер"
// с - "Казань"
и со spread оператором:
function test(a, b, c) { console.log(a, b, c) }
test(…route) // "Москва" "Питер" "Казань"
Как можно клонировать объект?
- Можно использовать оператор остатка ….
- Можно использовать Object.assign(newObj, oldObj).
Но эти подходы не позволяют выполнить глубокое клонирование. Поэтому, если нам нужно клонировать объект со вложенными объектами, мы можем использовать - либо метод какой-либо библиотеки (lodash),
- либо сделать это средствами встроенного объекта JSON.
JSON.parse(JSON.stringify(objectToClone))
Как определить наличие свойства в объекте?
- Первый способ состоит в использовании оператора «in»:
console.log('prop' in o) // true
console.log('prop1' in o) // false
- Второй — использовать метод hasOwnProperty:
console.log(o.hasOwnProperty('prop2')) // true
console.log(o.hasOwnProperty('prop1')) // false
- Третий — индексная нотация массива:
console.log(o['prop']) // bwahahah
console.log(o['prop1']) // undefined
Важно: Оператор «in» проверяет наличие свойства не только в самом объекте, но и в его прототипах, а метод hasOwnProperty — только в объекте.
Как проверить, является ли значение null?
typeof null == ‘object’ всегда будет возвращать true по историческим причинам. Для проверки, является ли значение null можно использовать оператор строгого равенства (===):
function isNull(value){
return value === null
}
Как работает прототипное наследование?
Если отвечать кратко, в JavaScript все является объектами. Эти объекты связаны цепочками прототипов, по которым им передаются методы и свойства. При обращении к свойству или методу объекта сначала происходит поиск этого свойства у самого объекта. В случае неудачи поиск перенаправляется в его прототип, затем в прототип прототипа и так далее, пока искомое свойство не будет найдено, либо пока не закончится цепочка прототипов.
Как можно добавить элемент в конец массива?
Array.prototype.push()
Как можно добавить элемент в начало массива?
Array.prototype.unshift()
Какие конструкции языка вы используете для обхода массивов и объектов?
В случае с массивами, это чаще всего forEach и map. Реже возникает необходимость в for, for in, for of, reduce, filter и подобных.
Метод «arr.forEach(callback[, thisArg])» используется для перебора массива.
Он для каждого элемента массива вызывает функцию callback.
Этой функции он передаёт три параметра callback(item, i, arr):
item – очередной элемент массива.
i – его номер.
arr – массив, который перебирается.
Метод «arr.filter(callback[, thisArg])» используется для фильтрации массива через функцию.
Он создаёт новый массив, в который войдут только те элементы arr, для которых вызов callback(item, i, arr) возвратит true.
Метод «arr.map(callback[, thisArg])» используется для трансформации массива.
Он создаёт новый массив, который будет состоять из результатов вызова callback(item, i, arr) для каждого элемента arr.
Эти методы используются для проверки массива.
Метод «arr.every(callback[, thisArg])» возвращает true, если вызов callback вернёт true для каждого элемента arr.
Метод «arr.some(callback[, thisArg])» возвращает true, если вызов callback вернёт true для какого-нибудь элемента arr.
Метод «arr.reduce(callback[, initialValue])» используется для последовательной обработки каждого элемента массива с сохранением промежуточного результата.
А чтобы обойти объект, понадобится немного изобретательности. Один из вариантов - получить ключи с помощью Object.keys, по которым впоследствии пройти с помощью forEach. Либо же можно воспользоваться Object.values, Object.entries, Object.keys.
Какие приемы работы с асинхронным кодом в JS Вы знаете?
Функции обратного вызова (Callbacks).
Промисы (Promises).
Async/await.
Библиотеки вроде async.js, blueprint, q, co.
Что такое контекст? Какое значение имеет this?
В JavaScript, контекст (this) представляет текущий объект, на котором выполняется код во время выполнения функции. Контекст this в JavaScript создается во время выполнения функции и зависит от контекста вызова функции.
Проще говоря this - это ссылка на объект, который вызывает функцию в данный момент.
Контекст (this) в JavaScript может быть определен следующими способами:
- Глобальный контекст: В глобальной области видимости контекст (this) ссылается на глобальный объект, такой как window в браузере или global в Node.js.
function getThis() {
console.log(‘this’);
}
getThis() // window
- Функциональный контекст: Когда функция вызывается как метод объекта, контекст (this) ссылается на сам этот объект.
const obj = {
name: ‘John’,
greet: function() {
console.log(‘Hello, ‘ + this.name);
}
}
obj.greet(); // Выведет ‘Hello, John’
- Контекст при использовании call, apply или bind: Методы call, apply и bind позволяют явно определить контекст (this) при вызове функции.
function sayHello() {
console.log(‘Hello, ‘ + this.name);
}
const person = {
name: ‘Mary’
};
sayHello.call(person); // Выведет ‘Hello, Mary’
- Контекст в стрелочных функциях: Стрелочные функции имеют лексический контекст, что означает, что контекст (this) внутри стрелочной функции берется из окружающего контекста. Например:
const obj = {
name: ‘Alice’,
greet: function() {
const innerFn = () => {
console.log(‘Hello, ‘ + this.name);
}
innerFn();
}
}
obj.greet(); // Выведет ‘Hello, Alice’
Каррирование
Каррирование (Currying) - это техника в функциональном программировании, которая позволяет превращать функцию с несколькими аргументами в последовательность функций с одним аргументом. Это позволяет нам создавать новые функции на основе существующих, фиксируя некоторые из аргументов заранее.
В JavaScript каррирование можно реализовать с использованием замыканий. При вызове функции с некоторыми аргументами, она возвращает новую функцию, которая ожидает оставшиеся аргументы и выполняет нужные операции. Каррирование позволяет легко получать частичные функции. Как мы видели в примерах с логами: универсальная функция log(date, importance, message) после каррирования возвращает нам частично применённую функцию, когда вызывается с одним аргументом, как log(date) или двумя аргументами, как log(date, importance).
Пример каррирования в JavaScript:
function multiply(a) {
return function(b) {
return a * b;
};
}
//
const multiplyByTwo = multiply(2); // Функция, которая умножает число на 2
console.log(multiplyByTwo(4)); // Выводит: 8
В этом примере функция multiply
принимает один аргумент a
и возвращает новую функцию, которая принимает аргумент b
. Во время вызова multiplyByTwo(4)
, значение a
равно 2, и мы передаем аргумент b = 4
. Результатом будет произведение числа 2 на 4, то есть 8.
Каррирование особенно полезно, когда нам нужно частично применить аргументы к функции и затем использовать ее в контексте с новыми значениями аргументов. Это позволяет нам создавать более гибкий и модульный код, который легко адаптировать под различные сценарии использования.
Кастомные события
Можно не только назначать обработчики, но и генерировать события из JavaScript-кода. События DOM предоставляют большую функциональность. В некоторых случаях может потребоваться создать пользовательское событие для более общего использования.
Чтобы создать пользовательское событие, вы используете Пользовательское событие CustomEvent.
var myEvent;
var customEvent = function () {
try {
myEvent = new CustomEvent(
"anAction",
{
detail: {
description: "a description of the event",
timeofevent: new Date(),
eventcode: 2
},
bubbles: true,
cancelable: true
}
);
document.addEventListener("anAction", customEventHandler);
//Finally, the event is raised by using the dispatchEvent method:
document.dispatchEvent(myEvent);
} catch (e) {
alert(e.message);
}
}
//A function called customEventHandler must exist for all this to work:
function customEventHandler() {
alert(window.event.detail.description);
}
Конструктор объекта CustomEvent принимает два параметра:
Первый параметр — это имя события . Это все, что имеет смысл для того, что событие должно представлять. В этом примере событие называется anAction.
Второй параметр — это динамический объект , который содержит свойство детали, которому могут быть назначены свойства, содержащие информацию, которая должна быть передана обработчику событий.
Кроме того, параметр предоставляет возможность указать, должно ли событие всплывать и может ли событие быть отменено.
Затем событие назначается элементу на странице с помощью метода addEventListener , а событие вызывается с помощью метода dispatchEvent
Коммутативное значение
Commutative property
Коммутативность - свойство операции, когда изменение порядка операндов не влияет на результат.
5 * 2 === 2 * 5
3 + 4 === 4 + 3
Конкатенация строк
String concatenation
Объединение строк через оператор +
let first = "hello";
let second = "world";
let concat = first + second;
// "hello world"
Конструкция “switch”
Конструкция switch заменяет собой сразу несколько if. Она представляет собой более наглядный способ сравнить выражение сразу с несколькими вариантами.
let a = 2 + 2;
switch (a) {
case 3:
alert( 'Маловато' );
break;
case 4:
alert( 'В точку!' );
break;
case 5:
alert( 'Перебор' );
break;
default:
alert( "Нет таких значений" );
}
Если break нет, то выполнение пойдёт ниже по следующим case, при этом остальные проверки игнорируются.
Любое выражение может быть аргументом для switch/case
Несколько вариантов case, использующих один код, можно группировать.
case 3: // группируем оба case
case 5: alert('Неправильно!');
alert("Может вам посетить урок математики?");
break;
Нужно отметить, что проверка на равенство всегда строгая. Значения должны быть одного типа, чтобы выполнялось равенство.
Контекст выполнения
Контекст выполнения в JavaScript (Execution Context) - это внутренняя структура данных, которая содержит информацию о текущем состоянии выполнения кода во время работы программы. Каждый раз, когда JavaScript-код запускается, создается новый контекст выполнения, который содержит переменные, функции и другие данные, необходимые для выполнения кода. Так как Javascript является однопоточным (single-threaded), в любой момент времени может быть запущен только один контекст выполнения.
// глобальный контекст выполнения
function printMyName () {
// новый контекст выполнения
return
Alex;
}
//
function sayMyName () {
// новый контекст выполнения
return printMyName();
}
//
sayMyName();
//
// глобальный контекст выполнения
Контекст выполнения состоит из трех основных компонентов:
- Global Execution Context (глобальный контекст выполнения) - создается при загрузке скрипта и представляет собой начальный контекст выполнения. В нем определены все глобальные переменные, функции и другие данные, доступные на уровне всего скрипта.
- Function Execution Context (контекст выполнения функции) - создается каждый раз при вызове функции. Он содержит локальные переменные и параметры функции, а также ссылку на контекст выполнения, из которого она была вызвана (внешний контекст выполнения).
- Eval Function Execution Context (контекст выполнения функции eval) - создается внутри функции eval(). Он работает подобно контексту выполнения функции, но используется для выполнения кода, переданного в функцию eval().
Стек выполнения (execution stack), который ещё называют стеком вызовов (call stack), это LIFO-стек, который используется для хранения контекстов выполнения, создаваемых в ходе работы кода.
Когда JS-движок начинает обрабатывать скрипт, движок создаёт глобальный контекст выполнения и помещает его в текущий стек. При обнаружении команды вызова функции движок создаёт новый контекст выполнения для этой функции и помещает его в верхнюю часть стека.
Движок выполняет функцию, контекст выполнения которой находится в верхней части стека. Когда работа функции завершается, её контекст извлекается из стека и управление передаётся тому контексту, который находится в предыдущем элементе стека.
Область видимости в JavaScript
Область видимости в JavaScript определяет доступность переменных, функций и других идентификаторов внутри блока кода. Она определяет, где переменная может быть использована и где она будет иметь значение.
В JavaScript есть два типа областей видимости:
- Глобальная область видимости (Global Scope) - переменные и функции, объявленные вне всех функций, имеют глобальную область видимости. Они могут быть доступны из любого места кода. Пример:
const globalVar = 'Global Variable';
function globalFunction() {
console.log(globalVar); // Выводит 'Global Variable'
}
globalFunction();
В данном примере переменная globalVar
объявлена в глобальной области видимости и доступна из функции globalFunction
.
- Локальная область видимости (Local Scope) - переменные и функции, объявленные внутри функций, имеют локальную область видимости. Они видны только внутри функции, где были объявлены. Пример:
function localFunction() {
const localVar = 'Local Variable';
console.log(localVar); // Выводит 'Local Variable'
}
localFunction();
console.log(localVar); // Ошибка: localVar is not defined
В данном примере переменная localVar
объявлена внутри функции localFunction
и доступна только внутри этой функции. Попытка обратиться к ней за пределами функции вызовет ошибку.
Область видимости определяет, откуда можно обращаться к переменным и функциям. Она позволяет изолировать и организовывать код, предотвращая конфликты имён и обеспечивая контроль доступа к различным частям программы.
Как копировать часть массива
Метод slice()
позволяет скопировать часть массива. Для этого он принимает два параметра:
slice(начальный_индекс, конечный_индекс)
Первый параметр указывает на начальный индекс элемента, с которого которые используются для выборки значений из массива. А второй параметр - конечный индекс, по который надо выполнить копирование.
Например, выберем в новый массив элементы, начиная с 1 индекса по индекс 4 не включая:
const users = [“Tom”, “Sam”, “Bill”, “Alice”, “Kate”];
const people = users.slice(1, 4);
console.log(people); // [“Sam”, “Bill”, “Alice”]
И поскольку индексация массивов начинается с нуля, то в новом массиве окажутся второй, третий и четвертый элемент.
Если указан только начальный индекс, то копирование выполняется до конца массива
Куча (Структуры данных)
Heap
Куча – это самобалансирующаяся структура, при каждой операции она сортирует себя, чтобы все уровни (кроме последнего) были заполнены. Похожа на деревья: у них также есть корневые и дочерние узлы, но отличается иерархия. Бывает max-heap - где корень всегда является максимальным элементом и min-heap, где корень является минимальным элементом. Кучи используют для сортировки объектов или реализации очередей с приоритетом.
class MinHeap {
//
constructor () {
/* Initialing the array heap and adding a dummy element at index 0 */
this.heap = [null]
}
//
getMin () {
/* Accessing the min element at index 1 in the heap array */
return this.heap[1]
}
//
insert (node) {
//
/* Inserting the new node at the end of the heap array */
this.heap.push(node)
//
/* Finding the correct position for the new node */
//
if (this.heap.length > 1) {
let current = this.heap.length - 1
//
/* Traversing up the parent node until the current node (current) is greater than the parent (current/2)*/
while (current > 1 && this.heap[Math.floor(current/2)] > this.heap[current]) {
//
/* Swapping the two nodes by using the ES6 destructuring syntax*/
[this.heap[Math.floor(current/2)], this.heap[current]] = [this.heap[current], this.heap[Math.floor(current/2)]]
current = Math.floor(current/2)
}
}
}
//
remove() {
/* Smallest element is at the index 1 in the heap array */
let smallest = this.heap[1]
//
/* When there are more than two elements in the array, we put the right most element at the first position
and start comparing nodes with the child nodes
*/
if (this.heap.length > 2) {
this.heap[1] = this.heap[this.heap.length-1]
this.heap.splice(this.heap.length - 1)
//
if (this.heap.length === 3) {
if (this.heap[1] > this.heap[2]) {
[this.heap[1], this.heap[2]] = [this.heap[2], this.heap[1]]
}
return smallest
}
//
let current = 1
let leftChildIndex = current * 2
let rightChildIndex = current * 2 + 1
//
while (this.heap[leftChildIndex] &&
this.heap[rightChildIndex] &&
(this.heap[current] > this.heap[leftChildIndex] ||
this.heap[current] > this.heap[rightChildIndex])) {
if (this.heap[leftChildIndex] < this.heap[rightChildIndex]) {
[this.heap[current], this.heap[leftChildIndex]] = [this.heap[leftChildIndex], this.heap[current]]
current = leftChildIndex
} else {
[this.heap[current], this.heap[rightChildIndex]] = [this.heap[rightChildIndex], this.heap[current]]
current = rightChildIndex
}
//
leftChildIndex = current * 2
rightChildIndex = current * 2 + 1
}
}
//
/* If there are only two elements in the array, we directly splice out the first element */
//
else if (this.heap.length === 2) {
this.heap.splice(1, 1)
} else {
return null
}
//
return smallest
}
}
Лексическое окружение
Lexical Environment
Внутренний (скрытый) объект в JS, в котором хранится запись(environment record) всех локальных переменных в виде свойств и this, а также ссылка на внешнее лексическое окружение.
Литерал
literal
Литералы - это фиксированные значения, которые вы указываете в своём скрипте.
25 - литерал целого числа
23.8 - литерал дробного числа
‘js’ - литерал строки
[] - литерал массива
{} - литерал объекта
Ложные значения
Falsy values
const falsyValues = [’‘(пустая строка без пробела), 0, null, undefined, NaN, false]
Матрица
Matrix
Матрица — двумерный массив, выглядящий как список столбцов и строк, на пересечении которых находятся элементы данных. Это прямоугольный массив, в котором количество строк и столбцов задает его размер. В математике их используют для компактной записи линейных алгебраических или дифференциальных уравнений.
Матрицы используют для описания вероятностей. Например, для ранжирования страниц в поиске Google при помощи алгоритма PageRank. В компьютерной графике — для работы с 3D-моделями и проецирования их на двумерный экран.
// Матрица 3x3
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
console.log(matrix);
// Доступ к элементу матрицы
console.log(matrix[0][0]); // Выводит 1
console.log(matrix[1][2]); // Выводит 6
console.log(matrix[2][1]); // Выводит 8
Метод preventdefault
При разработке таких типичных элементов интерфейса, как форма или попап, часто нужно изменить поведение браузера по умолчанию. Допустим, при клике по ссылке мы хотим, чтобы открывался попап, но вместо этого браузер будет автоматически переходить по адресу, указанному в атрибуте href. Или вот другая проблема — мы хотим перед отправкой формы проверять корректность введённых данных, но после нажатия на кнопку submit форма каждый раз будет отправляться на сервер, даже если там куча ошибок. Такое поведение браузера нам не подходит, поэтому мы научимся его переопределять.
Объект события и метод preventDefault
Событие — это какое-то действие, произошедшее на странице. Например, клик, нажатие кнопки, движение мыши, отправка формы и так далее. Когда срабатывает событие, браузер создаёт объект события Event. Этот объект содержит всю информацию о событии. У него есть свои свойства и методы, с помощью которых можно эту информацию получить и использовать. Один из методов как раз позволяет отменить действие браузера по умолчанию — preventDefault().
Event можно передать в функцию-обработчик события и в ней указать инструкции, которые должны быть выполнены, когда оно сработает. При передаче объекта события в обработчик обычно используется сокращённое написание — evt.
Мы хотим при клике на ссылку click-button добавлять элементу с классом content класс show. Он сделает попап видимым, поменяв значение свойства display с none на block. Напишем логику добавления этого класса с помощью JavaScript:
// Находим на странице кнопку и попап
const button = document.querySelector('.click-button');
const popup = document.querySelector('.content');
//
// Навешиваем на кнопку обработчик клика
button.onclick = function (evt) {
// Отменяем переход по ссылке
evt.preventDefault();
//
// Добавляем попапу класс show, делая его видимым
popup.classList.add('show');
};
Если мы уберём строку evt.preventDefault()
, вместо попапа откроется отдельная страница pop-up.html, адрес которой прописан в атрибуте href у ссылки. Такая страница нужна, потому что мы хотим, чтобы вся функциональность сайта была доступна, если скрипт по какой-то причине не будет загружен. Именно поэтому мы изначально реализовали кнопку с помощью тега a, а не button.
Не для всех событий можно отменить действие по умолчанию. Например, событие прокручивания страницы scroll проигнорирует попытки отменить его. Чтобы узнать, можно отменить действие по умолчанию или нет, нужно обратиться к свойству cancelable объекта Event. Оно будет равно true, если событие можно отменить, и false — в обратном случае.
Метод массива .filter()
Метод массива .filter() позволяет получить новый массив, отфильтровав элементы с помощью переданной колбэк-функции. Колбэк-функция будет вызвана для каждого элемента массива и по результату функции примет решение включать этот элемент в новый массив или нет.
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const evenOnly = nums.filter(function (n) {
const remainder = n % 2
return remainder === 0
})
Результат будет [2, 4, 6, 8, 10].
Аналогично методу .forEach(), методу .filter() необходимо передать аргументом функцию. Главное отличие — функция должна возвращать boolean, т. е. результатом должен быть true или false. Такие функции называют предикатами.
Функция, которую мы передаём в метод .filter(), принимает три параметра:
- item — элемент массива в текущей итерации;
- index — индекс текущего элемента;
- arr — сам массив, который мы перебираем.
В новом массиве отфильтрованные элементы будут находиться в том же порядке, в котором они были в исходном массиве.
💡 .filter() возвращает новый массив, при этом исходный массив никак не изменится.
💡 Из-за того, что JavaScript имеет динамическую типизацию, то нам ничего не мешает возвращать какое угодно значение из функции. В этом случае JavaScript сам определит его истинность. Стоит помнить, что значения 0, undefined, null и пустая строка ‘’ считаются ложными и равны false.
В JavaScript функция, в которой нет явного возвращаемого значения (т. е. нет return) все равно возвращает undefined. Потому, если забыть вернуть результат в функции в методе .filter(), то в результате получим пустой массив, так как отфильтруются все элементы. Получим [], потому что undefined считается как false.
Методы массивов
Методы для добавления/удаления элементов массива:
-
push(...items)
– добавляет элементы в конец массива. -
pop()
– извлекает элемент с конца массива. -
unshift(...items)
– добавляет элементы в начало массива. -
shift()
– извлекает элемент с начала массива. -
splice(pos, deleteCount, ...items)
– начиная с индексаpos
, удаляетdeleteCount
элементов и вставляетitems
. -
slice(start, end)
– создаёт новый массив, копируя в него элементы с позицииstart
доend
(не включаяend
). -
concat(...items)
– возвращает новый массив: копирует все элементы текущего массива и добавляет к немуitems
. Если какой-то изitems
является массивом, то его элементы будут добавлены по отдельности.
Методы для поиска среди элементов массива:
-
indexOf(item, pos)
– ищетitem
, начиная с позицииpos
, и возвращает его индекс или -1, если ничего не найдено. -
lastIndexOf(item, pos)
– работает аналогичноindexOf
, но ищет с конца массива. -
includes(value)
– возвращаетtrue
, если в массиве имеется элементvalue
, иfalse
, если элемент не найден. -
find/filter(func)
– фильтрует элементы с помощью функции и возвращает первое/все значения, для которых функция возвращает true. -
findIndex(func)
– похож наfind
, но возвращает индекс элемента вместо значения.
Методы для перебора элементов массива:
-
forEach(func)
– вызывает функцию для каждого элемента массива. Не возвращает результат.
Методы для преобразования и изменения массива:
-
map(func)
– создаёт новый массив из результатов вызова функции для каждого элемента. -
sort(func)
– сортирует массив «на месте» и возвращает изменённый массив. -
reverse()
– меняет порядок следования элементов на противоположный “на месте” и возвращает изменённый массив.
Методы для преобразования массива в строку и обратно:
-
split()
/join()
– преобразуют строку в массив и обратно.
Методы для вычисления одного значения на основе всего массива:
-
reduce(func, initial)
/reduceRight(func, initial)
– вычисляют одно значение на основе всего массива, вызывая функцию для каждого элемента и передавая промежуточный результат между вызовами.
Дополнительные методы массивов:
-
Array.isArray(arr)
- проверяет, является лиarr
массивом.
Важно: Методы sort
, reverse
и splice
изменяют исходный массив.
Другие методы:
-
arr.some(fn)
/arr.every(fn)
- проверяет массив. Функцияfn
вызывается для каждого элемента массива аналогично методуmap
. Если какой-либо/все результаты вызовов являютсяtrue
, то методsome
возвращаетtrue
, иначеfalse
. Эти методы ведут себя примерно так же, как логические операторы||
и&&
: еслиfn
возвращает истинное значение,arr.some()
немедленно возвращаетtrue
и прекращает перебор остальных элементов; еслиfn
возвращает ложное значение,arr.every()
немедленно возвращаетfalse
и также прекращает перебор остальных элементов.
Пример сравнения массивов:
function arraysEqual(arr1, arr2) {
return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]);
}
alert(arraysEqual([1, 2], [1, 2])); // true
-
arr.fill(value, start, end)
– заполняет массив повторяющимисяvalue
, начиная с индексаstart
доend
. -
arr.copyWithin(target, start, end)
– копирует свои элементы, начиная с индексаstart
и заканчиваяend
, в заданную позициюtarget
(перезаписывает существующие элементы). -
arr.flat(depth)
/arr.flatMap(fn)
– создают новый плоский массив из многомерного массива.
Методы объектов
Статические методы объекта - методы, которые предварительно определены и вызываются в классе объектов.
-
Object.keys(obj)
– возвращает массив ключей. -
Object.values(obj)
– возвращает массив значений. -
Object.entries(obj)
– возвращает массив пар [ключ, значение].
Пример обхода массива значений с помощью цикла for...of
:
for (let value of Object.values(user)) {
alert(value); // John, затем 30
}
Метод Object.fromEntries(array)
- преобразует массив в объект. Пример использования:
Object.fromEntries(Object.entries(prices).map(([key, value]) => [key, value * 2]));
Методы экземпляра — это методы, встроенные в объекты, которые работают с конкретным экземпляром объекта, а не с классом объекта.
-
Object.prototype.hasOwnProperty()
возвращает логическое значение, указывающее, имеет ли объект указанное свойство.
const object1 = {};
object1.property1 = 42;
console.log(object1.hasOwnProperty('property1')); // Ожидаемый результат: true
Метод Object.create()
создаёт новый объект с указанным прототипом и свойствами.
Метод Object.assign()
используется для копирования перечисляемых и собственных свойств из одного или более исходных объектов в целевой объект. После копирования он возвращает целевой объект.
var o1 = { a: 1 };
var o2 = { [Symbol('foo')]: 2 };
var obj = Object.assign({}, o1, o2);
console.log(obj); // { a: 1, [Symbol("foo")]: 2 }
Object.preventExtensions(объект)
предотвращает добавление новых свойств к объекту (то есть, предотвращает расширение этого объекта в будущем).
Object.isExtensible(объект)
- метод, который позволяет определить, можно ли расширять объект, и возвращает логическое значение.
Метод Object.freeze()
предотвращает модификацию свойств и значений объекта, а также добавление и удаление свойств объекта.
Метод Object.isFrozen()
позволяет определить, был ли объект заморожен или нет, и возвращает логическое значение.
Метод Object.seal()
предотвращает добавление новых свойств объекта, но позволяет изменять существующие свойства.
Object.isSealed(объект)
- метод, который позволяет определить, запечатан ли объект, и возвращает логическое значение.
Метод Object.getPrototypeOf()
используется для получения внутреннего скрытого [[Prototype]] объекта, также доступного через свойство \_\_proto\_\_
.
Существует также связанный с ним метод Object.setPrototypeOf()
, который добавляет один прототип к другому объекту. Однако рекомендуется использовать Object.create()
, поскольку он является более быстрым и эффективным.
Метод Object.defineProperty()
определяет новое или изменяет существующее свойство напрямую на объекте, и возвращает этот объект.
Пример использования:
Object.defineProperty(объект, свойство, описатель);
Object.defineProperty(объект, свойство, {value : значение});
Object.defineProperty(person, "language", {writable:false}); // делаем свойство language только для чтения
Метод Object.defineProperties()
позволяет добавлять или изменять несколько свойств объекта в одном вызове.
Пример использования:
Object.defineProperties(объект, описатель);
- writable : true // Значение свойства можно изменять
- enumerable : true // Свойство может перечисляться
- configurable : true // Свойство может настраиваться
// Определение геттера
get: function() {
return language
}
// Определение сеттера
set: function(value) {
language = value
}
Методы строк
Есть три типа кавычек. Строки, использующие обратные кавычки, могут занимать более одной строки в коде и включать выражения ${...}
. Строки в JavaScript кодируются в UTF-16. Есть специальные символы, такие как разрыв строки \n
.
Для получения символа используйте []
или метод at()
.
slice(start, end)
извлекает часть строки и возвращает новую строку без изменения оригинальной строки.
Для того, чтобы перевести строку в нижний или верхний регистр, используйте toLowerCase()
/toUpperCase()
.
Для поиска подстроки используйте indexOf()
/lastIndexOf()
или includes()
/startsWith()
/endsWith()
, когда необходимо только проверить, есть ли вхождение.
Чтобы сравнить строки с учётом правил языка, используйте localeCompare()
.
concat()
объединяет две или более строки и возвращает одну строку.
-
split()
- Разбивает строку на массив по указанному разделителю, который может быть подстрокой или регулярным выражением. -
str.trim()
- Убирает пробелы в начале и конце строки. -
str.repeat(n)
- Повторяет строкуn
раз. -
charCodeAt
- Возвращает числовое значение Юникода для символа по указанному индексу. Обратите внимание, что у символов в верхнем и нижнем регистрах разные коды. -
fromCharCode
- Преобразует числовые значения Юникода в соответствующие символы. -
search
- Проверяет, содержит ли строка указанное значение или совпадение с регулярным выражением и возвращает индекс начала совпадения. -
replace
- Ищет в строке указанное значение или совпадение с регулярным выражением и возвращает новую строку, в которой произведена замена на второй параметр. Можно заменить найденные значения другой строкой или передать функцию для дополнительной обработки совпадений. -
padEnd
- Добавляет отступы до тех пор, пока строка не достигнет заданной длины, указанной первым параметром. Вторым параметром можно указать другой символ вместо пробела. -
padStart
- Добавляет в начале отступы, пока строка не достигнет длины, заданной первым параметром. Вторым параметром можно указать другой символ вместо пробела.
Методы чисел
Чтобы писать числа с большим количеством нулей: Используйте краткую форму записи чисел – “e”, с указанным количеством нулей. Например: 123e6 это 123 с 6-ю нулями 123000000. Отрицательное число после “e” приводит к делению числа на 1 с указанным количеством нулей. Например: 123e-6 это 0.000123 (123 миллионных).
Для других систем счисления: Можно записывать числа сразу в шестнадцатеричной (0x), восьмеричной (0o) и бинарной (0b) системах счисления. parseInt(str, base)
преобразует строку в целое число в соответствии с указанной системой счисления: 2 ≤ base ≤ 36. num.toString(base)
представляет число в строковом виде в указанной системе счисления base.
Для проверки на NaN и Infinity: isNaN(value)
преобразует аргумент в число и проверяет, является ли оно NaN. Number.isNaN(value)
проверяет, является ли аргумент числом, и если да, то проверяет, является ли оно NaN.
isFinite(value)
преобразует аргумент в число и проверяет, что оно не является NaN/Infinity/-Infinity.Number.isFinite(value)
проверяет, является ли аргумент числом, и если да, то проверяет, что оно не является NaN/Infinity/-Infinity.
Для преобразования значений типа 12pt и 100px в число: Используйте parseInt
/parseFloat
для «мягкого» преобразования строки в число, данные функции по порядку считывают число из строки до тех пор пока не возникнет ошибка.
Для дробей: Используйте округления Math.floor
, Math.ceil
, Math.trunc
, Math.round
или num.toFixed(precision)
. Помните, что при работе с дробями происходит потеря точности.
Ещё больше математических функций:
Также есть математические функции с помощью объекта Math
.
Функция abs()
возвращает абсолютное значение числа.
Функции min()
и max()
возвращают соответственно минимальное и максимальное значение из набора чисел.
Функция ceil()
округляет число до следующего наибольшего целого числа.
Функция floor()
округляет число до следующего наименьшего целого числа
Функция round()
округляет число до следующего наименьшего целого числа, если его десятичная часть меньше 0.5. Если же десятичная часть равна или больше 0.5, то округление идет до ближайшего наибольшего целого числа
Функция random()
возвращает случайное число с плавающей точкой их диапазона от 0 до 1
Функция pow()
возвращает число в определенной степени. Например, возведем число 2 в степень 3
Функция sqrt()
возвращает квадратный корень числа
Функция log()
возвращает натуральный логарифм числа
Механизм обхода лексического окружения
Механизм обхода лексического окружения в JavaScript основан на концепции области видимости. Область видимости определяет доступность переменных, функций и объектов в определенной области кода.
В JavaScript лексическое окружение создается при выполнении функции или блока кода. Оно состоит из двух основных компонентов: содержащего окружения и ссылки на внешнее окружение.
Содержащее окружение содержит все локальные переменные, функции и объекты, объявленные внутри функции или блока кода. Ссылка на внешнее окружение указывает на область видимости, в которой была создана функция или блок кода.
При обращении к переменной или функции внутри функции или блока кода, JavaScript сначала ищет эту переменную или функцию в содержащем окружении. Если переменная или функция не найдена, JavaScript рекурсивно продолжает поиск во внешнем окружении, пока не достигнет глобальной области видимости.
Вот пример, иллюстрирующий механизм обхода лексического окружения:
function outer() {
var outerVar = 'I am in the outer function';
//
function inner() {
var innerVar = 'I am in the inner function';
console.log(innerVar); // Output: I am in the inner function
console.log(outerVar); // Output: I am in the outer function
console.log(globalVar); // Output: I am a global variable
}
//
inner();
}
//
var globalVar = 'I am a global variable';
outer();
В этом примере у нас есть функция outer()
, которая содержит переменную outerVar
, и вложенная функция inner()
, которая содержит переменную innerVar
. Внутри функции inner()
мы можем обращаться и получать доступ к переменным innerVar
и outerVar
, а также к глобальной переменной globalVar
.
Пример демонстрирует, как обходится лексическое окружение: при обращении к переменной innerVar
, оно сначала ищется внутри функции inner()
, а затем во внешнем окружении - функции outer()
. Аналогично, при обращении к переменной outerVar
, оно сначала ищется внутри функции outer()
, а затем в глобальной области видимости.
Модули
Modules
Модули позволяют объединять (использовать) код из разных файлов и избавляют нас от необходимости держать весь код в одном большом файле. По мере роста нашего приложения, мы обычно хотим разделить его на много файлов, так называемых «модулей». Модуль обычно содержит класс или библиотеку с функциями.
Синтаксис модулей: мы используем import
для импорта функциональности или значений из другого файла или файлов, и export
для экспорта.
Зачем нужны модули:
- Ремонтопригодность
- Возможность повторного использования
- Пространство имен
- Библиотеки для динамической подгрузки модулей
Например:
- AMD – одна из самых старых модульных систем, изначально реализована библиотекой require.js.
- CommonJS – модульная система, созданная для сервера Node.js.
- UMD – ещё одна модульная система, предлагается как универсальная, совместима с AMD и CommonJS.
Модуль - это просто файл. Один скрипт - это один модуль. Модули могут загружать друг друга и использовать директивы export
и import
, чтобы обмениваться функциональностью и вызывать функции одного модуля из другого.
-
export
отмечает переменные и функции, которые должны быть доступны вне текущего модуля. -
import
позволяет импортировать функциональность из других модулей.
Отличия модулей от «обычных» скриптов
В модулях всегда используется режим use strict
. Например, присваивание к необъявленной переменной вызовет ошибку.
Каждый модуль имеет свою собственную область видимости. Другими словами, переменные и функции, объявленные в модуле, не видны в других скриптах.
Если один и тот же модуль используется в нескольких местах, то его код выполнится только один раз, после чего экспортируемая функциональность передаётся всем импортёрам.
В модуле на верхнем уровне this
не определён (undefined).
Модули всегда выполняются в отложенном (deferred) режиме, точно так же, как скрипты с атрибутом defer
. Это верно и для внешних и встроенных скриптов-модулей.
Объект import.meta
содержит информацию о текущем модуле. Содержимое зависит от окружения. В браузере он содержит ссылку на скрипт или ссылку на текущую веб-страницу, если модуль встроен в HTML.
<script type="module">
alert(import.meta.url); // ссылка на html страницу для встроенного скрипта
</script>
При использовании модулей каждый модуль реализует свою функциональность и экспортирует её. Затем мы используем import
, чтобы напрямую импортировать эту функциональность туда, где она необходима.
Браузер загружает и анализирует скрипты модулей автоматически.
В реальной жизни часто используется сборщик Webpack для объединения модулей. Это позволяет улучшить производительность и получить другие “плюшки” в работе с модулями.
Вот все варианты export, которые мы разобрали в этой и предыдущих главах. Вы можете проверить себя, читая их и вспоминая, что они означают:
Перед объявлением класса/функции/…:
- export [default] class/function/variable ...
Отдельный экспорт:
- export {x [as y], ...}
Реэкспорт:
- export {x [as y], ...} from "module"
- export * from "module"
(не реэкспортирует export default
)
- export {default [as y]} from "module"
(реэкспортирует только export default
)
Импорт:
Именованные экспорты из модуля:
- import {x [as y], ...} from "module"
Импорт по умолчанию:
- import x from "module"
- import {default as x} from "module"
Все сразу:
- import * as obj from "module"
Только подключить модуль (его код запустится), но не присваивать его переменной:
- import "module"
Мы можем разместить операторы import/export в начале или в конце скрипта, это не имеет значения.
Модули пакеты
Packages
К модулям-пакетам относятся папки с кодом, описываемые при помощи находящегося в них файла package.json. С модулями-пакетами удобно работать при помощи менеджеров пакетов, таких как npm или yarn. Если мы хотим использовать уже написанные кем-то модули-пакеты (частый способ использования кода других разработчиков), их нужно установить, затем подключить, затем использовать.
Установка модуля-пакета при помощи npm осуществляется командой
npm install <имя модуля>
Модуль Path
Модуль Path
Одним из стандартных модулей является path. Модуль path предназначен для того, чтобы работать с путями в Node.js. При помощи него можно получить имя файла, расширение файла, имя папки, указать путь к файлу.
Чтобы использовать path, его необходимо подключить:
`const path = require(‘path’);
console.log(path.basename(__filename)); // index.js - имя файла на Windows, полный путь к файлу на POSIX-системах
console.log(path.dirname(__filename)); // C:\Users\Admin\Desktop\nodejs-basic - название папки
console.log(path.extname(__filename)); // .js - расширение файла
console.log(path.parse(__filename)); // возвращает объект в котором указывается корень диска, имя папки, имя файла, расширение файла, имя файла без расширения
path.join() объединяет заданные сегменты пути вместе, используя в качестве разделителя разделитель данной конкретной платформы (для Linux - прямой слэш, для Windows - обратный слэш), результат - относительный путь
console.log(path.join(__dirname, ‘test’, ‘second.html’)); // вернет C:\Users\Admin\Desktop\nodejs-basic\test\second.html
path.resolve() преобразует последовательность путей или сегментов пути в абсолютный путь справа налево и нормализует его: если в некоторых сегментах пути указываются слэши, а в некоторых нет, всё равно будет сгенерирован правильный путь.
console.log(path.resolve(__dirname, ‘./test’, ‘/second.html’));`
NaN
Не число - NaN (not a number)
Говорит о том, что выполнена бессмысленная операция (деление строки на число, бесконечности на бесконечность и тд).
Относится к типу Number
Область видимости
Variable scope
Область видимости в JavaScript определяет, какие переменные доступны вам. Существуют два типа областей видимости: глобальная и локальная.
Глобальная область видимости — переменные и функции, объявленные в глобальном пространстве имен, имеют глобальную область видимости и доступны из любого места в коде. Существует вероятность пересечения имен, когда двум или более переменным присваивают одинаковое имя. Если переменные объявляются через const или let, то каждый раз, когда будет происходить пересечение имён, будет показываться сообщение об ошибке.
Если объявлять переменные через var, то вторая переменная после объявления перепишет первую.
Локальная область видимости включает:
Функциональная область видимости (область видимости функции) — переменные, функции и параметры, объявленные внутри функции, доступны только внутри этой функции.
Блочная область видимости — переменные (объявленные с помощью ключевых слов «let» и «const») внутри блока ({ }), доступны только внутри него. Блочная область видимости является частным случаем области видимости функции, т.к. функции объявляются с фигурными скобками (кроме случаев использования стрелочных функций с неявным возвращением значения).
Область видимости — это также набор правил, по которым осуществляется поиск переменной. Если переменной не существует в текущей области видимости, ее поиск производится выше, во внешней по отношению к текущей области видимости. Если и во внешней области видимости переменная отсутствует, ее поиск продолжается вплоть до глобальной области видимости. Если в глобальной области видимости переменная обнаружена, поиск прекращается, если нет — выбрасывается исключение. Поиск осуществляется по ближайшим к текущей областям видимости и останавливается с нахождением переменной. Это называется цепочкой областей видимости (Scope Chain).
Функции, объявленные как «function declaration» (прим. перев.: функция вида function имя(параметры) {…}), всегда поднимаются наверх в текущей области видимости. Если же функция объявляется как «function expression» (функциональное выражение) (прим. перев.: функция вида const f = function (параметры) {…}), то такая функция не поднимается в текущей области видимости.
Вложенные области видимости
Когда функция объявляется в другой функции, то внутренняя функция имеет доступ к переменным внешней функции. Такой поведение называется разграничением лексических областей видимости.
В тоже время внешняя функция не имеет доступа к переменным внутренней функции.
function outerFunction () {
const outer =
I’m the outer function!;
//
function innerFunction() {
const inner =
I’m the inner function!;
console.log(outer); // I'm the outer function!
}
//
console.log(inner); // Ошибка, inner не определена
}
Всякий раз, когда вы вызываете функцию внутри другой функции, вы создаете замыкание. Говорят, что внутренняя функция является замыканием. Результатом замыкания обычно является то, что в дальнейшем становятся доступными переменные внешней функции.
function outerFunction () {
const outer =
I see the outer variable!;
//
function innerFunction() {
console.log(outer);
}
//
return innerFunction;
}
//
outerFunction()(); // I see the outer variable!
Так как внутренняя функция является возвращаемым значением внешней функции, то можно немного сократить код, совместив возврат значения с объявлением функции.
function outerFunction () {
const outer =
I see the outer variable!;
//
return function innerFunction() {
console.log(outer);
}
}
//
outerFunction()(); // I see the outer variable!
Благодаря замыканиям появляется доступ к внешней функции, поэтому они обычно используются для двух целей:
- контроля побочных эффектов;
- создания приватных переменных.
Объявить переменную
declare variable
Переменная - способ сохранить информацию и дать ей имя для последующего использования в коде.
const name = "Nastya";
let age = 31;
В чем разница между let, const и var?
VAR*
- Поддерживает повторное объявление переменных. Количество объявлений не ограничено.
var greeting = 'Hello world!';
var greeting = 'Hello Mary!'; // значение переменной теперь 'Hello Mary!'
- Игнорирует блочную область видимости. Переменные, объявленные с помощью
var
, будут видны за пределами блока.
{
var varVrb = 2;
}
console.log(varVrb); // 2
- Поддерживает hoisting. Объявление переменной перемещается вверх в пределах области видимости во время компиляции кода, поэтому можно использовать переменную до ее фактического объявления.
varVrb = 3;
var varVrb;
- Значение может быть определено позже. Переменную можно объявить без присваивания значения, а затем присвоить значение позже.
var greeting;
greeting = 'Hello world!';
- Значение может быть переопределено в будущем. После объявления переменную с помощью
var
можно переопределить, присвоив ей новое значение.
var greeting = 'Hello world!';
greeting = 'Hello Mary!'; // значение теперь 'Hello Mary!'
const
и let
- Не поддерживают повторное объявление. Если попытаться повторно объявить переменную с использованием
const
илиlet
, будет выдана ошибка:
let greeting = 'Hello world!';
let greeting = 'Hello Karl!'; // SyntaxError: Identifier 'greeting' has already been declared
- Соблюдают блочную область видимости. Переменные, объявленные с помощью
const
илиlet
, видны только внутри блока, в котором они объявлены:
{
const constVrb = 1;
let letVrb = 2;
}
console.log(constVrb); // ReferenceError: constVrb is not defined
console.log(letVrb); // ReferenceError: letVrb is not defined
- Не поддерживают hoisting. Объявление переменных с помощью
const
илиlet
остается в области видимости, где они были объявлены. Поэтому необходимо сначала объявить переменную, а затем присвоить ей значение:
letVrb = 2; // ReferenceError: Cannot access 'letVrb' before initialization
let letVrb;
console.log(constVrb); // ReferenceError: constVrb is not defined
console.log(letVrb); // ReferenceError: letVrb is not defined
- Значение должно быть определено при объявлении переменной (сразу) для константной переменной
const
:
const greeting = 'Hello world!';
- Значение константной переменной
const
не может быть переопределено после инициализации:
const greeting = 'Hello world!';
greeting = 'Hello Marry!'; // TypeError: Assignment to constant variable.
Итого:
- Переменные, объявленные через var, могут быть глобальными или иметь область видимости в рамках функции; let и const имеют блочную область видимости.
- var-переменные могут быть как обновлены, так и переопределены внутри области видимости; let-переменные можно обновлять, но не переопределять; const-переменные нельзя ни обновлять, ни переопределять.
- Со всеми ними осуществляется поднятие наверх области видимости. Но если var-переменные при этом инициализируются как undefined, let и const не инициализируются.
- В то время как var и let можно объявить, но не инициализировать, const необходимо инициализировать во время объявления.
Объясни разницу между изменяемыми и неизменяемыми значениями
Значения примитивных типов (например, строка или число) не могут быть изменены после того, как попали в память.
Значения объектных типов (объекты, массивы) могут изменяться в ходе работы программы.
Объясни разницу между синхронными и асинхронными функциями
JavaScript - это однопоточный язык, то-есть функции выполняются в синхронном порядке. Приложение блокируется на время выполнения каждой конкретной функции. Так происходит по той причине, что JavaScript имеет только один стек вызовов.
С другой стороны, есть асинхронный способ выполнения функций, когда мы не блокируем весь интерфейс благодаря тому, что не дожидаемся выполнения функции, а подписываемся на событие с передачей обратного вызова. Ну, или мы можем иметь дело с обещанием или с прочими внешними API вроде setTimeout.
В таком случае браузер помещает обработчик события в очередь задач, а когда наступает время его вызвать, он перемещает его в стек вызовов.
Оператор и операнд
Operator and operand
console.log(2 + 8);
Оператор +, -, / и тд. Операнды 2 и 8.
Операторы инкремент и декремент
Increment and decrement operators
Унарные операторы, которые добавляют или вячитают единицу от своего операнда.
++ инкремент
– декремент
– 1 предекремент
++1 преинкремент
1 – постдекремени
1 ++ постинкремент
Операторы сравнения
Comparison operators
Операторы сравнения возвращают значения логического типа.
Строки сравниваются посимвольно в лексикографическом порядке.
Значения разных типов при сравнении приводятся к числу. Исключением является сравнение с помощью операторов строгого равенства/неравенства.
Значения null и undefined равны == друг другу и не равны любому другому значению.
Будьте осторожны при использовании операторов сравнений вроде > и < с переменными, которые могут принимать значения null/undefined. Хорошей идеей будет сделать отдельную проверку на null/undefined.
Значение NaN считается не равным никакому другому значению, включая само себя. При его наличии оператор равенства всегда возвращает false, а оператор неравенства – true.
Если оба операнда являются объектами, то они сравниваются, чтобы выяснить, один ли это объект. Если да, возвращается true, иначе – false.
-
==
- оператор равенства. Сравнивает значения с приведением типов, поэтому может быть неоднозначным. Например,1 == '1'
вернетtrue
, так как значения равны после приведения типов. -
===
- оператор строгого равенства. Сравнивает значения без приведения типов. В этом случае,1 === '1'
вернетfalse
, так как значения имеют различные типы. -
!=
- оператор неравенства. Сравнивает значения с приведением типов. Например,1 != '1'
вернетfalse
, так как значения равны после приведения типов. -
!==
- оператор строгого неравенства. Сравнивает значения без приведения типов. В этом случае,1 !== '1'
вернетtrue
, так как значения имеют различные типы. -
>
- оператор больше. Проверяет, является ли первое значение больше второго. -
<
- оператор меньше. Проверяет, является ли первое значение меньше второго. -
>=
- оператор больше или равно. Проверяет, является ли первое значение больше или равно второму. -
<=
- оператор меньше или равно. Проверяет, является ли первое значение меньше или равно второму. -
&&
- оператор логического И. Возвращает true, если оба операнда истинны. -
||
- оператор логического ИЛИ. Возвращает true, если хотя бы один из операндов истинен. -
!
- оператор логического отрицания. Инвертирует значение операнда.
Очередь (Структуры данных)
Queue
Как и стек очереди могут быть реализованы с помощью связного списка или массива.
Очереди — это FIFO-структуры данных (first in, first out).
Аналог очереди - очередь в магазине: первого покупателя обслужат первым
Элементы удаляются из головы, а добавляются в хвост.
Эффективность списка, стека, очереди («О» большое):
Индексирование: O(n).
Поиск: O(n).
Двоичный поиск: O(n).
Вставка: O(1).
Основные операции:
добавление нового элемента в конец очереди (enqueue);
удаление элемента из начала очереди (dequeue);
чтение элемента из начала очереди без удаления (peek).
Как и стек, очередь может быть реализована как на базе массива, так и на базе связного списка. И опять же, массивы в JavaScript фактически могут работать как очереди, благодаря встроенным методам.
class Queue {
constructor() {
this.linkedList = new LinkedList();
}
//
isEmpty() {
return !this.linkedList.head;
}
//
peek() {
if (!this.linkedList.head) {
return null;
}
//
return this.linkedList.head.value;
}
//
enqueue(value) {
this.linkedList.append(value);
}
//
dequeue() {
const removedHead = this.linkedList.deleteHead();
return removedHead ? removedHead.value : null;
}
}
Параметры по умолчанию
Default Parameters
Параметры функции по умолчанию позволяют инициализировать параметры со значениями по умолчанию, если значение не передано или не определено.
function add(a = 0, b = 0){
return a + b
}
// если мы не присвоим переменным "a" и "b" какие-нибудь значения, они будут равняться 0
add(1) // 1
Инициализаторы параметров по умолчанию живут в своей собственной области, которая является родителем области, созданной для тела функции.
function f(a = go()) {
function go() {
return ":P";
}
}
f(); // ReferenceError: go is not defined
Параметры функции
В JavaScript параметры — это переменные, которые мы перечисляем в объявлении функции. Когда мы создаем функцию, мы берем параметры, которые включены в определение функции. Внутри круглых скобок можно добавить несколько параметров, разделенных запятыми, как показано в приведенном ниже синтаксисе:
имя функции ( параметр1 , параметр2 , параметр3 ) {
// тело функции
}
Значение, которое мы передаем функции, называется аргументом функции. Аргументы передаются функции, когда мы ее вызываем.
Вы можете определить любое количество параметров для вашей функции JavaScript, и количество добавляемых аргументов не должно совпадать с количеством параметров.
Вы также можете определить значения параметров по умолчанию при объявлении функций.
Параметры оцениваются в JavaScript слева направо.
Функция JavaScript не выдаст никакой ошибки, если переданные вами аргументы больше или меньше количества параметров. Недостающие параметры будут определены как undefined,а лишние - будут проигнорированы.
Существует еще один метод для доступа к аргументам внутри нашей функции, который называется « Arguments Object ». Объект Arguments содержит значения аргументов в объекте с механизмом, подобным массивам.
Передать динамическое количество параметров функции
В JavaScript есть несколько способов передачи динамического количества параметров функции:
- Аргументы переменной длины с помощью объекта
arguments
: Внутри тела функции вы можете использовать объектarguments
, который представляет все аргументы, переданные в функцию.arguments
похож на массив, но не является полноценным массивом. Вы можете получить доступ к каждому аргументу по индексу. Например:
function sum() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
//
console.log(sum(1, 2, 3)); // Output: 6
console.log(sum(1, 2, 3, 4, 5)); // Output: 15
- Оператор “Rest” (
...
): Можно использовать оператор “Rest” для сбора дополнительных аргументов в виде массива. Оператор “Rest” должен быть последним параметром функции. Например:
function sum(...numbers) {
let total = 0;
for (let i = 0; i < numbers.length; i++) {
total += numbers[i];
}
return total;
}
//
console.log(sum(1, 2, 3)); // Output: 6
console.log(sum(1, 2, 3, 4, 5)); // Output: 15
Передача параметров по значению и по ссылке
Передача параметров по значению
Строки, числа, логические значения передаются в функцию по значению. Иными словами при передаче значения в функцию, эта функция получает копию данного значения. Рассмотрим, что это значит в практическом плане:
function change(x){
x = 2 * x;
console.log("x in change:", x);
}
//
var n = 10;
console.log("n before change:", n); // n before change: 10
change(n); // x in change: 20
console.log("n after change:", n); // n after change: 10
Функция change
получает некоторое число и увеличивает его в два раза. При вызове функции change ей передается число n. Однако после вызова функции мы видим, что число n не изменилось, хотя в самой функции произошло увеличение значения параметра. Потому что при вызове функция change получает копию значения переменной n. И любые изменения с этой копией никак не затрагивают саму переменную n.
Передача по ссылке
Объекты и массивы передаются по ссылке. То есть функция получает сам объект или массив, а не их копию.
function change(user){
user.name = "Tom";
}
//
var bob ={
name: "Bob"
};
console.log("before change:", bob.name); // Bob
change(bob);
console.log("after change:", bob.name); // Tom
В данном случае функция change получает объект и меняет его свойство name. В итоге мы увидим, что после вызова функции изменился оригинальный объект bob, который передавался в функцию.
Однако если мы попробуем переустановить объект или массив полностью, оригинальное значение не изменится.
function change(user){
// полная переустановка объекта
user= {
name:"Tom"
};
}
//
var bob ={
name: "Bob"
};
console.log("before change:", bob.name); // Bob
change(bob);
console.log("after change:", bob.name); // Bob
То же самое касается массивов.
Перемешивание Фишера-Ейтса
Fisher-Yates shuffle algorithm
function fyShuffle(arr) {
let i = arr.length;
while (--i > 0) {
let randIndex = Math.floor(Math.random() * (i + 1));
[arr[randIndex], arr[i]] = [arr[i], arr[randIndex]];
}
return arr;
}
Поверхностное и глубокое копирование
Shallow or deep clone
Глубокое копирование
Если необходимо полностью скопировать сложную структуру данных, например, массив с объектами, то нужно делать глубокое (deep) или полное копирование данных. 1. JavaScript не содержит функций для глубокого копирования, лучший вариант сделать глубокую копию — сериализовать структуру в JSON и тут же распарсить.const newArr = JSON.parse(JSON.stringify(arr))
- Для глубокой копии можно написать функцию`const deepClone = obj => { if (obj === null) return null; // Создаем поверхностный клона оригинала. let clone = Object.assign({}, obj);// // Определяем, какие пары ключ-значение // необходимо глубоко клонировать. Object.keys(clone).forEach( key => (clone[key] = typeof obj[key] === “object” ? deepClone(obj[key]) : obj[key]) );// // Проверяем является ли obj массивом и не пустой ли он. return Array.isArray(obj) && obj.length // Если obj массив и он не пуст, тогда // указываем объекту clone длину исходного массива что бы // конвертировать clone в массив и вернуть его. ? (clone.length = obj.length) && Array.from(clone) // Если obj пустой массив,
: Array.isArray(obj)
// то возвращаем его
? Array.from(obj)
// в других случаях obj это объект и мы возвращаем копию clone.clone;
};
//
// Пример:
const a = { foo: “bar”, obj: { a: 1, b: 2 } };
const b = deepClone(a);
// a !== b true
// a.obj !== b.obj true` - Воспользоваться готовой библиотекой. Например, функцию глубокого копирования содержит популярная библиотека утилит lodash.
- Использовать structuredClone
const obj = { name: "Mike", friends: [{ name: "Sam" }] };
const clonedObj = structuredClone(obj);
console.log(obj === clonedObj); // false
console.log(obj.friends === clonedObj.friends); // false
Неглубокое копирование
- Spread syntax, появившийся в ES6, позволяет «вытаскивать» перебираемые элементы из своего контейнера.
- Клонировать/копировать содержимое массива, можно через метод slice, передав 0 в качестве первого аргумента:
var clone = myArray.slice(0);
Код выше создает копию исходного массива; имейте в виду, если в вашем массиве существуют объекты - они хранятся как ссылки;
т.е. код выше не делает “deep” клонирование содержимого массива. - Function.prototype.apply()
var first = [1, 2, 3];
var second = [4, 5];
Array.prototype.push.apply(first, second);
console.log(first); //[1,2,3,4,5]
console.log(second); // [4,5]
- Другой способ с использованием Array.from:
const cloneSheeps = Array.from(sheeps)
Поднятие (всплытие)
Hoisting
Механизм, из-за которого переменные и объявления функций передвигаются вверх своей области видимости перед тем, как код будет выполнен.
Код выполняется в рамках контекста выполнения (среды, в которой выполняется код). Контекст выполнения имеет две фазы — компиляция и выполнение.
1. В фазе компиляции function declaration и переменные, объявленные с помощью ключевого слова «var» поднимаются в самый верх глобальной (или функциональной) области видимости.
2. В фазе выполнения переменным присваиваются значения, а функции вызываются или выполняются.
temporal dead zone
Временная мертвая зона (TDZ) — это область блока, в которой переменная недоступна до момента, когда ей будет инициализировано значение.
Блок представляет собой пару фигурных скобок ({…}), используемых для группировки.
Инициализация происходит, когда вы присваиваете начальное значение переменной.
Предположим, вы пытаетесь получить доступ к переменной до ее полной инициализации. В таком случае JavaScript выдаст ошибку ReferenceError.
Если коротко, мы должны сначала инициализировать переменную значением, а потом уже обращаться к ней в коде.
let bestFood = "Vegetable Fried Rice"; // bestFood’s TDZ ends here
console.log(bestFood);
Основное различие между временной мертвой зоной переменной var, let, и constпеременной заключается в том, когда заканчивается их TDZ. TDZ переменной let(или const) заканчивается, когда JavaScript полностью инициализирует ее значением, указанным при ее объявлении. А var TDZ переменной заканчивается сразу после ее подъема, а не тогда, когда переменная полностью инициализируется значением, указанным при ее объявлении.
Область видимости — это место, где (или откуда) мы имеем доступ к переменным или функциям. JS имеем три типа областей видимости: глобальная, функциональная и блочная (ES6).
Преобразование типов
-
undefined:
- В строку:String(undefined)
дает результат"undefined"
.
- В число:Number(undefined)
дает результатNaN
.
- В логический тип:Boolean(undefined)
дает результатfalse
. -
null:
- В строку:String(null)
дает результат"null"
.
- В число:Number(null)
дает результат0
.
- В логический тип:Boolean(null)
дает результатfalse
. -
Булевый тип (true/false):
- В строку:String(true)
дает результат"true"
.String(false)
дает результат"false"
.
- В число:Number(true)
дает результат1
.Number(false)
дает результат0
.
- В логический тип:Boolean(true)
дает результатtrue
.Boolean(false)
дает результатfalse
.
- При преобразовании в объектный тип:Object(true)
иObject(false)
создают обертки объектов Boolean. -
Числовой тип:
- В строку:String(123)
дает результат"123"
.
- В число:Number("123")
дает результат123
. Если строка содержит некорректные символы или является пустой, результат будетNaN
.
- В логический тип: Любое число, кроме0
,NaN
или пустой строки, преобразуется вtrue
. Число0
,NaN
или пустая строка преобразуются вfalse
.
- При преобразовании в объектный тип:Object(123)
создает обертку объекта Number.
4.1. NaN:
- В строку: String(NaN)
дает результат "NaN"
.
- В число: Number(NaN)
дает результат NaN
.
- В логический тип: Boolean(NaN)
дает результат false
.
- При преобразовании в объектный тип: Object(NaN)
создает объектное представление числа NaN.
4.2. Infinity:
- В строку: String(Infinity)
дает результат "Infinity"
.
- В число: Number(Infinity)
дает результат Infinity
.
- В логический тип: Boolean(Infinity)
дает результат true
.
- При преобразовании в объектный тип: Object(Infinity)
создает объектное представление числа Infinity.
4.3. -Infinity:
- В строку: String(-Infinity)
дает результат "-Infinity"
.
- В число: Number(-Infinity)
дает результат -Infinity
.
- В логический тип: Boolean(-Infinity)
дает результат true
.
- При преобразовании в объектный тип: Object(-Infinity)
создает объектное представление числа -Infinity.
-
Строковый тип:
- В число:Number("123")
дает результат123
. Если строка содержит некорреткные символы или является пустой, результат будетNaN
.
- В логический тип: Пустая строка преобразуется вfalse
, а непустая строка преобразуется вtrue
.
- При преобразовании в объектный тип:Object("Hello")
создает обертку объекта String со значением “Hello”. -
Объектный тип:
- В строку: Объект вызывает методtoString()
. Если методtoString()
не переопределен, то возвращается строка “[object Object]”.
- В число: Объект вызывает методvalueOf()
. Если методvalueOf()
не переопределен, то вызывается методtoString()
, а затем результат пытается быть преобразован в число. Если преобразование не удается, результат будетNaN
.
- В логический тип: Любой объект, кромеnull
иundefined
, преобразуется вtrue
.
Потеря контекста
Как только метод передаётся отдельно от объекта – this теряется.
let user = {
firstName: "Вася",
sayHi() {
alert(Привет, ${this.firstName}!);
}
};
setTimeout(user.sayHi, 1000); // Привет, undefined!
setTimeout получил функцию sayHi отдельно от объекта user (именно здесь функция и потеряла контекст). Метод setTimeout в браузере имеет особенность: он устанавливает this=window для вызова функции. Таким образом, для this.firstName он пытается получить window.firstName, которого не существует. В других подобных случаях this обычно просто становится undefined.
Решения:
1. функция обертка (user достаётся из замыкания, а затем вызывается его метод sayHi) - хуже
setTimeout(function() {
user.sayHi(); // Привет, Вася!
}, 1000); 2.
или
setTimeout(() => user.sayHi(), 1000); // Привет, Вася!
- привязать контекст с помощью bind - лучше
В современном JavaScript у функций есть встроенный метод bind, который позволяет зафиксировать this.
let user = {
firstName: "Вася",
sayHi() {
alert(Привет, ${this.firstName}!);
}
};
//
let sayHi = user.sayHi.bind(user); // берём метод user.sayHi и привязываем его к user.Теперь sayHi – это «связанная» функция, которая может быть вызвана отдельно или передана в setTimeout (контекст всегда будет правильным).
//
sayHi(); // Привет, Вася!
//
setTimeout(sayHi, 1000); // Привет, Вася!
Интересно:
Если у объекта много методов и мы планируем их активно передавать, то можно привязать контекст для них всех в цикле через метод: bindAll
Полифилы
Полифил — это фрагмент кода (в сети — обычно JavaScript), который позволяет использовать современную функциональность в более старых браузерах, которые не поддерживают ее по умолчанию.
Полифил forEach
if (!Array.prototype.forEach) {
// Проверяем, не был ли уже добавлен метод forEach
//
Array.prototype.myForEach = function(callback) {
// Добавляем свой собственный метод myForEach в прототип массива
//
if (!this instanceof Array) {
// Проверяем, является ли текущий объект массивом
throw new Error("Isn't array");
// Если объект не является массивом, выбрасываем ошибку
}
//
if (typeof callback !== "function") {
// Проверяем, является ли callback функцией
throw new Error("Callback isn't function");
// Если callback не является функцией, выбрасываем ошибку
}
//
for (let i = 0; i < this.length; i += 1) {
// Итерируем по элементам массива
callback(this[i], i, this);
// Вызываем callback для каждого элемента массива, передавая значение элемента, индекс и сам массив
}
};
}
Преобразование числа в двоичное и обратно в десятеричное
Двоичная система счисления — это позиционная система счисления с основанием 2. В этой системе счисления числа записываются с помощью двух символов: 0 и 1.
Из десятеричного в двоичное:
Функция toString(2)
при использовании с числовым объектом возвращает двоичный эквивалент числового значения, как показано в примерах ниже. (8).toString(2)// "1000"
(25).toString(2)// "11001"
(235).toString(2)// "11101011"
8..toString(2) - если нам надо вызвать метод непосредственно на числе, как toString в примере выше, то нам надо поставить две точки .. после числа. Если мы поставим одну точку: 123456.toString(36), тогда это будет ошибкой, поскольку синтаксис JavaScript предполагает, что после первой точки начинается десятичная часть.
Своя функция:
function convertDecToBinary(num) {
const binary = [];
while (num >= 1) {
binary.unshift(num % 2); // получаем остаток от деления на 2 (1 или 0) и пушим в начало массива
num = Math.floor(num / 2); // меняем num - делим пополам и округляем к меньшему
}
return +binary.join(""); // массив переводим в строку и делаем числом
}
Из двоичного в десятеричное:
ES6 поддерживает двоичные числовые литералы для целых чисел, поэтому, если двоичная строка неизменяема, можно просто ввести ее как есть с префиксом 0b или 0B: const binary = 0b1101000; // code for 104
console.log(binary); // prints 104
Функция parseInt(value,2) принимает строку и систему исчисления числа строки в качестве аргумента и возвращает целое число в десятичной
системе исчисления.parseInt("1111", 2); //15
Префиксное дерево (Структуры данных)
Prefix tree или trie
Префиксное дерево (также бор, луч, нагруженное или суффиксное дерево) в информатике - упорядоченная древовидная структура данных, которая используется для хранения динамических множеств или ассоциативных массивов, где ключом обычно выступают строки. Дерево называется префиксным, потому что поиск осуществляется по префиксам.
В отличие от бинарного дерева, узлы не содержат ключи, соответствующие узлу. Представляет собой корневое дерево, каждое ребро которого помечено каким-то символом так, что для любого узла все рёбра, соединяющие этот узел с его сыновьями, помечены разными символами. Некоторые узлы префиксного дерева выделены (на рисунке они подписаны цифрами) и считается, что префиксное дерево содержит данную строку-ключ тогда и только тогда, когда эту строку можно прочитать на пути из корня до некоторого выделенного узла.
Таким образом, в отличие от бинарных деревьев поиска, ключ, идентифицирующий конкретный узел дерева, не явно хранится в данном узле, а неявно задаётся положением данного узла в дереве. Получить ключ можно выписыванием подряд символов, помечающих рёбра на пути от корня до узла. Ключ корня дерева — пустая строка. Часто в выделенных узлах хранят дополнительную информацию, связанную с ключом, и обычно выделенными являются только листья и, возможно, некоторые внутренние узлы.
Используется, к примеру, для автозаполнения.
Префиксный, инфиксный, постфиксный оператор
Prefix, infix, postfix operator
Префиксный +5
Инфиксный 3 + 5
Постфиксный 5 +
Принцип DRY
don’t repeat yourself / не повторяйте себя
Если код не дублируется, то для изменения логики достаточно внесения исправлений всего в одном месте и проще тестировать одну (пусть и более сложную) функцию, а не набор из десятков однотипных. Следование принципу DRY всегда приводит к декомпозиции сложных алгоритмов на простые функции. А декомпозиция сложных операций на более простые (и повторно используемые) значительно упрощает понимание программного кода. Повторное использование функций, вынесенных из сложных алгоритмов, позволяет сократить время разработки и тестирования новой функциональности.
Следование принципу DRY приводит к модульной архитектуре приложения и к чёткому разделению ответственности за бизнес-логику между программными классами. А это — залог сопровождаемой архитектуры. Хотя чаще не DRY приводит к модульности, а уже модульность, в свою очередь, обеспечивает принципиальную возможность соблюдения этого принципа в больших проектах.
В рамках одного программного класса (или модуля) следовать DRY и не повторяться обычно достаточно просто. Также не требует титанических усилий делать это в рамках небольших проектов, где все разработчики «владеют» всем кодом системы. А вот в больших проектах ситуация с DRY несколько сложнее — повторы чаще всего появляются из-за отсутствия у разработчиков целостной картины или несогласованности действий в рамках команды. Следовать принципу «don’t repeat yourself» в рамках больших проектов не так просто, как это может показаться на первый взгляд.
От разработчиков требуется тщательное планирование архитектуры, а от архитектора или тимлида требуется наличие видения системы в целом и чёткая постановка задач разработчикам.
В проектировании DRY тоже имеет место — доступ к конкретному функционалу должен быть доступен в одном месте, унифицирован и сгруппирован по какому-либо принципу, а не «разбросан» по системе в произвольных вариациях. Этот подход пересекается с принципом единственной ответственности из пяти принципов SOLID, сформулированных Робертом Мартином.
Принцип KISS
Keep It Short and Simple
В проектировании следование принципу KISS выражается в том, что:
- не имеет смысла реализовывать дополнительные функции, которые не будут использоваться вовсе или их использование крайне маловероятно, как правило, большинству пользователей достаточно базового функционала, а усложнение только вредит удобству приложения;
- не стоит перегружать интерфейс теми опциями, которые не будут нужны большинству пользователей, гораздо проще предусмотреть для них отдельный «расширенный» интерфейс (или вовсе отказаться от данного функционала);
- бессмысленно делать реализацию сложной бизнес-логики, которая учитывает абсолютно все возможные варианты поведения системы, пользователя и окружающей среды, — во-первых, это просто невозможно, а во-вторых, такая фанатичность заставляет собирать «звездолёт», что чаще всего иррационально с коммерческой точки зрения.
В программировании следование принципу KISS можно описать так:
- не имеет смысла беспредельно увеличивать уровень абстракции, надо уметь вовремя остановиться;
- бессмысленно закладывать в проект избыточные функции «про запас», которые может быть когда-нибудь кому-либо понадобятся (тут скорее правильнее подход согласно принципу YAGNI);
- не стоит подключать огромную библиотеку, если вам от неё нужна лишь пара функций;
- декомпозиция чего-то сложного на простые составляющие — это архитектурно верный подход (тут KISS перекликается с DRY);
абсолютная математическая точность или предельная детализация нужны не всегда — большинство систем создаются не для запуска космических шаттлов, данные можно и нужно обрабатывать с той точностью, которая достаточна для качественного решения задачи, а детализацию выдавать в нужном пользователю объёме, а не в максимально возможном объёме.
Также KISS имеет много общего c принципом разделения интерфейса из пяти принципов SOLID, сформулированных Робертом Мартином.
Принцип YAGNI
«Вам это не понадобится»
Если упрощенно, то следование данному принципу заключается в том, что возможности, которые не описаны в требованиях к системе, просто не должны реализовываться. Это позволяет вести разработку, руководствуясь экономическими критериями — Заказчик не должен оплачивать ненужные ему функции, а разработчики не должны тратить своё оплачиваемое время на реализацию того, что не требуется.
Основная проблема, которую решает принцип YAGNI — это устранение тяги программистов к излишней абстракции, к экспериментам «из интереса» и к реализации функционала, который сейчас не нужен, но, по мнению разработчика, может либо вскоре понадобиться, либо просто будет полезен, хотя в реальности такого очень часто не происходит.
Принцип SOLID
SOLID — это аббревиатура пяти основных принципов проектирования в объектно-ориентированном программировании —
Single responsibility — принцип единственной ответственности
Open-closed — принцип открытости / закрытости
Liskov substitution — принцип подстановки Барбары Лисков
Interface segregation — принцип разделения интерфейса
Dependency inversion — принцип инверсии зависимостей
Принцип единственной обязанности / ответственности (single responsibility principle / SRP) обозначает, что каждый объект должен иметь одну обязанность и эта обязанность должна быть полностью инкапсулирована в класс. Все его сервисы должны быть направлены исключительно на обеспечение этой обязанности. Подробнее про SRP…
Принцип открытости / закрытости (open-closed principle / OCP) декларирует, что программные сущности (классы, модули, функции и т. п.) должны быть открыты для расширения, но закрыты для изменения. Это означает, что эти сущности могут менять свое поведение без изменения их исходного кода. Подробнее про OCP…
Принцип подстановки Барбары Лисков (Liskov substitution principle / LSP) в формулировке Роберта Мартина: «функции, которые используют базовый тип, должны иметь возможность использовать подтипы базового типа не зная об этом». Подробнее про LSP…
Принцип разделения интерфейса (interface segregation principle / ISP) в формулировке Роберта Мартина: «клиенты не должны зависеть от методов, которые они не используют». Принцип разделения интерфейсов говорит о том, что слишком «толстые» интерфейсы необходимо разделять на более маленькие и специфические, чтобы клиенты маленьких интерфейсов знали только о методах, которые необходимы им в работе. В итоге, при изменении метода интерфейса не должны меняться клиенты, которые этот метод не используют. Подробнее про ISP…
Принцип инверсии зависимостей (dependency inversion principle / DIP) — модули верхних уровней не должны зависеть от модулей нижних уровней, а оба типа модулей должны зависеть от абстракций; сами абстракции не должны зависеть от деталей, а вот детали должны зависеть от абстракций. Подробнее про DIP…
Пройти циклом по ключам объекта
- Использование цикла
for..in
:
const obj = {a: 1, b: 2, c: 3};
for (let key in obj) {
console.log(key); // Выводит каждый ключ объекта
console.log(obj[key]); // Выводит значение для каждого ключа
}
- Использование метода
Object.keys()
:
const obj = {a: 1, b: 2, c: 3};
Object.keys(obj).forEach(key => {
console.log(key); // Выводит каждый ключ объекта
console.log(obj[key]); // Выводит значение для каждого ключа
});
- Использование метода
Object.getOwnPropertyNames()
:
const obj = {a: 1, b: 2, c: 3};
Object.getOwnPropertyNames(obj).forEach(key => {
console.log(key); // Выводит каждый ключ объекта
console.log(obj[key]); // Выводит значение для каждого ключа
});
- Использование метода
Object.entries()
вместе с цикломfor..of
:
const obj = {a: 1, b: 2, c: 3};
for (let [key, value] of Object.entries(obj)) {
console.log(key); // Выводит каждый ключ объекта
console.log(value); // Выводит значение для каждого ключа
}
Что такое промис?
Promise (обещание) (resolve, reject)
Промис (Promise) — специальный объект JavaScript, который используется для написания и обработки асинхронного кода. Асинхронные функции возвращают объект Promise в качестве значения. Промисы были придуманы для решения проблемы так называемого «ада функций обратного вызова».
Промис может находиться в одном из трёх состояний:
1. pending (в ожидании) — стартовое состояние, операция стартовала;
2.
- fulfilled (выполнено) — получен результат;
или
- rejected (отклонено) — ошибка.
Поменять состояние можно только один раз: перейти из pending либо в fulfilled, либо в rejected.
В качестве параметров конструктор промиса принимает resolve и reject. В resolve записывается результат выполнения операции, в - reject — причина невыполнения операции. Результат может быть обработан в методе .then, ошибка — в методе .catch. Метод then() используют, чтобы выполнить код после успешного выполнения асинхронной операции. Метод .then также возвращает промис, поэтому мы можем использовать цепочку, состоящую из нескольких .then. Метод catch() используют,
чтобы выполнить код в случае ошибки при выполнении асинхронной операции. Метод finally() используют, чтобы выполнить код при завершении асинхронной
операции. Он будет выполнен вне зависимости от того, была ли операция успешной или завершилась ошибкой.
Синтаксис создания Promise:
var promise = new Promise(function(resolve, reject) {
// Эта функция будет вызвана автоматически
// В ней можно делать любые асинхронные операции,
// А когда они завершатся — нужно вызвать одно из:
// resolve(результат) при успешном выполнении
// reject(ошибка) при ошибке
})
Универсальный метод для навешивания обработчиков:
promise.then(onFulfilled, onRejected)
onFulfilled – функция, которая будет вызвана с результатом при resolve.
onRejected – функция, которая будет вызвана с ошибкой при reject.
С помощью методов then(), catch() и finally() мы можем реагировать на изменение состояния промиса и выполнять код.
// Создаётся объект promise
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
// переведёт промис в состояние fulfilled с результатом "result"
resolve("result");
}, 1000);
});
//
// promise.then навешивает обработчики на успешный результат или ошибку
promise.then(
(result) => {
// первая функция-обработчик - запустится при вызове resolve
alert("Fulfilled: " + result); // result - аргумент resolve
},
(error) => {
// вторая функция - запустится при вызове reject
alert("Rejected: " + error); // error - аргумент reject
}
);
Promise.all — это промис, который принимает массив промисов в качестве входных данных (итерируемый), и он разрешается, когда все промисы разрешаются или любое из них отклоняется. Например, синтаксис метода promise.all приведен ниже:
Promise.all([Promise1, Promise2, Promise3]) .then(result) => { console.log(result) }) .catch(error => console.log(Error in promises ${error}
))
Псевдомассивы
Array-likes
Псевдомассивы - это объекты, у которых есть индексы и length.
Разница между slice и splice
Array.splice («splice» — «наращивать, сращивать») принимает начальный (позицию) и необязательный конечный (количество элементов) аргументы и изменяет содержимое массива, удаляя существующие элементы и/или добавляя новые. Возвращается массив, содержащий удалённые элементы. Если будет удалён только один элемент, вернётся массив из одного элемента. Если никакие элементы не будут удалены, вернётся пустой массив. Используется для вставки или удаления элементов в/из массива.var x = [14, 3, 77];
var y = x.splice(1, 2);
console.log(x); // [14]
console.log(y); // [3, 77]
Array.slice (срез) принимает два аргумента, начальный и необязательный конечный. Далее он возвращает новый массив, содержащий элементы начиная с указанного начального индекса вплоть до элемента, расположенного сразу перед конечным индексом. Если вы опустите второй аргумент, он будет выбран до конца. Он не изменяет исходный массив. Используется для выбора элементов из массиваvar x = [14, 3, 77];
var y = x.slice(1, 2);
console.log(x); // [14, 3, 77]
console.log(y); // [3]
Общий пример:[14, 3, 77].slice(1, 2) // [3]
[14, 3, 77].splice(1, 2) // [3, 77]
Разница между scope и context
Область видимости относится к видимости переменных, а контекст относится к объекту, в котором выполняется функция.
Область действия : в JavaScript область действия достигается за счет использования функций. Когда вы используете ключевое слово «var» внутри функции, инициализируемая вами переменная является частной и не может быть видна за пределами этой функции. Но если внутри этой функции есть функции, то эти «внутренние» функции могут «видеть» эту переменную; говорят, что эта переменная находится в области видимости. Функции могут «видеть» переменные, объявленные внутри них. Они также могут «видеть» все, что объявлено вне их, но никогда не могут видеть те, которые объявлены внутри функций, вложенных в эту функцию. Это область видимости в JavaScript.
Контекст : относится к объекту, в котором выполняется функция. Когда вы используете ключевое слово JavaScript «this», это слово относится к объекту, в котором выполняется функция.
Разница между window и document
window
и document
- это два разных объекта, которые управляются браузером и предоставляют различные функциональности при работе с HTML-страницей.
window
объект представляет текущее окно браузера и предоставляет доступ к различным свойствам и методам, таким как открытие и закрытие окон, установка таймеров, управление размерами и положением окна, обращение к URL-адресу текущей страницы и многое другое. Он также является глобальным объектом JavaScript в браузере, что означает, что его свойства и методы могут быть доступны из любого места в JavaScript-коде.
document
объект представляет текущий HTML-документ, загруженный в окне браузера, и предоставляет доступ к различным методам и свойствам, связанным с документом. Например, через document
вы можете получить доступ и изменять содержимое документа, создавать новые элементы, добавлять обработчики событий, изменять стили элементов и многое другое. Он также является частью DOM (Document Object Model), который представляет структуру и содержимое HTML-документа в виде дерева объектов, к которым можно обращаться и изменять.
Вкратце говоря, window
отвечает за окно браузера и его функциональности, в то время как document
относится к HTML-документу, его структуре и возможности работы с ним.
Разница между функцией и методом
Разница между функцией и методом заключается в их контексте и способе вызова.
Функция - это совокупность инструкций, которая выполняет определенную операцию или вычисление. Функции могут быть определены независимо от объектов и вызываться в любом месте кода. Они могут принимать аргументы и возвращать значения. Пример:
function greet(name) {
console.log(
Hello, ${name}!);
}
greet('John'); // Output: Hello, John!
Метод - это функция, которая принадлежит определенному объекту или классу. Методы могут выполнять различные операции с данными объекта и имеют доступ к его свойствам. Они вызываются через объект с использованием точечной нотации. Пример:
const person = {
name: 'John',
greet: function() {
console.log(
Hello, ${this.name}!);
}
};
person.greet(); // Output: Hello, John!