Checkpoint

https://github.com/4m9fk/developers-roadmap/blob/master/frontend/junior-1/js.md =====> (https://github.com/airbnb/javascript#types) && (https://github.com/leonidlebedev/javascript-airbnb#types)

Данные

Какие типы данных есть в JS?

Простые типы: Когда вы взаимодействуете с простым типом, вы напрямую работаете с его значением.

  • string

  • number

  • boolean

  • null

  • undefined

  • symbol

  • BigInt

const foo = 1;
let bar = foo;

bar = 9;

console.log(foo, bar); // => 1, 9

Символы не могут быть полностью заполифиллены, поэтому они не должны использоваться, если разработка ведётся для браузеров/сред, которые не поддерживают их нативно.

Сложные типы: Когда вы взаимодействуете со сложным типом, вы работаете со ссылкой на его значение.

  • object

  • array

  • function

const foo = [1, 2];
const bar = foo;

bar[0] = 9;

console.log(foo[0], bar[0]); // => 9, 9

Какие типы в JS изменяемые, а какие нет?

Типы данных в языке JavaScript можно разделить на простые и объектные. Их также можно разделить на типы с методами и типы без методов. Кроме того, типы можно характеризовать как изменяемые и неизменяемые.

Значение изменяемого типа можно изменить. Объекты и массивы относятся к изменяемым типам: программа на языке JavaScript может изменять значения свойств объектов и элементов массивов.

Числа, логические значения,nullиundefinedявляются неизменяемыми - не имеет даже смысла говорить об изменчивости, например, значения числа. Строки можно представить себе как массивы символов, отчего можно счесть, что они являются изменяемыми. Однако строки в JavaScript являются неизменяемыми: строки предусматривают возможность обращения к символам по числовым индексам, но в JavaScript отсутствует возможность изменить существующую текстовую строку.

Все простые типы неизменяемы.

// изменим имя объекта

obj.name = "star";

array[1] = 325; console.log(array); // Array [ 1, 325, 3 ]

// мы не можем изменить строку.

var myString = "Some string";

console.log(myString.toUpperCase()); // SOME STRING. Это уже новая строка.

console.log(myString); // выведем строку еще раз и убедимся, что она не изменилась.

Что значит ссылка на переменную?

https://learn.javascript.ru/object

Что такое NaN? Как проверить, что переменная - NaN?

NaN === NaN;        // false
Number.NaN === NaN; // false
isNaN(NaN);         // true
isNaN(Number.NaN);  // true

Тем не менее, обратите внимание на разницу между функцией isNaN() и методом Number.isNaN(): первая вернет true, если значение в настоящий момент является NaN, или если оно станет NaN после того, как преобразуется в число, в то время как последний вернет true, только если текущим значением является NaN:

isNaN('hello world');        // true
Number.isNaN('hello world'); // false

Что значит создавать переменную через конструктор или через литерал?

Переменные можно объявить как по простому (литерально) (var a = ‘str’), так и через функцию-конструктор (обёртка)(var a = new String(‘str’)). Во втором случае мы получим уже не примитив, а объект созданный конструктором String().

Конструкторы следует вызывать при помощи оператора new. Такой вызов создаёт пустой this в начале выполнения и возвращает заполненный в конце.

Когда при обращении к свойству объекта стоит использовать точечную нотацию, а когда через строку в квадратных скобках?

Что произойдет, если попытаться получить несуществующее в объекте свойство?

При доступе к несуществующему свойству объекта JavaScript возвращает undefined.

Обратите внимание на пример:

let favoriteMovie = {  
  title: 'Blade Runner'
};
favoriteMovie.actors; // => undefined

favoriteMovie — это объект с одним значением title. Доступ к несуществующему свойству actors путём использования favoriteMovie.actors приведет к выводу undefined.

Сам по себе доступ к несуществующему свойству не вызывает ошибки. Реальная проблема возникает при попытке получить значение из undefined.

TypeError: Cannot read property <prop> of undefined

Немного изменим предыдущий фрагмент кода, чтобы пояснить поведение TypeError:

let favoriteMovie = {  
  title: 'Blade Runner'
};
favoriteMovie.actors[0];  
// TypeError: Cannot read property '0' of undefined

В favoriteMovie нет свойства actors, поэтому использование favoriteMovie.actors приведет к undefined.

В результате доступ к элементу со значением undefined с помощью выражения favoriteMovie.actors [0] вызывает TypeError.

Возможности JavaScript, которые позволяют получить доступ к несуществующим свойствам, являются источником путаницы. Свойство может быть установлено или может отсутствовать. Идеальным решением этой проблемы будет установка правил для объекта, которые позволят ему содержать только свойства с явно заданными значениями.

Что делает hasOwnProperty?

  • obj.hasOwnProperty('prop') проверяет объект на наличие собственного свойства;

  • 'prop' in obj проверяет объект на наличие собственного или унаследованного свойства.

Рекомендацией в этом случае будет использование оператора in . У него простой и понятный синтаксис. Присутствие оператора in указывает на четкое намерение проверить, имеет ли объект определенное свойство, не обращаясь к фактическому значению этого свойства.

Использование obj.hasOwnProperty('prop') — это также неплохое решение. Оно немного длиннее, чем оператор in , и проверяет только собственные свойства объекта.

NULL VS UNDEFINED

  • typeof(null) - object

  • typeof(undefined) - undefined

  • оба примитивные типы

  • if(null) = if(undefined) => false always

  • null - пустота; indefined - отстуствие

  • null нужно объявлять всегда явно; undefined можем быть переменная без присвоения ей undefined (можем и явно, и неявно объявлять)

Когда использовать null, а когда undefined?

В первую очередь null отличается своим применением, и в отличие от undefined, null больше используется для присваивания значения. Как раз из-за этого оператор typeof для null возвращает «object». Изначально это объяснялось тем, что null использовался (и используется) как пустая ссылка там, где ожидается объект, что-то вроде заглушки. Такое поведение typeof было позже признано багом, и, хотя было предложено это поведение исправить, пока что, в целях обратной совместимости, всё остается как есть.

Вот, почему окружение JavaScript не выставляет никаких значений в null, и это делается только программно. В документации на MDN написано следующее:

В различных API null часто возвращается в тех местах, где ожидается объект, но такой объект подобрать нельзя.

Это правдиво для DOM, который не зависит от языка и никак не описывается в документации ECMAScript. Из-за того, что используется внешний API, попытка получить отсутствующий элемент возвращает null, а не undefined.

Вообще, если нужно присвоить «не-значение» переменной или свойству, передать его в функцию, или вернуть из функции, то null — это почти всегда лучший вариант. Упрощённо: JavaScript использует undefined, а программисты должны использовать null.

Другой способ применения null — явное «зануливание» переменной (object = null), когда ссылка на объект больше не требуется. Кстати, это считается хорошей практикой. Присваивая null, вы фактически удаляете ссылку на объект, и если на него нет других ссылок, он отправляется к сборщику мусора, таким образом возвращая доступную память.

Какие есть способы создания глобальных переменных?

  1. Все переменные, которые объявлены вне функций, являются глобальными.

  2. Необъявленные переменные. Если мы не используем ключевое слово при определении переменной в функции, то такая переменная будет глобальной.

Для чего нужна директива use strict?

Определение глобальных переменных в функциях может вести к потенциальным ошибкам. Чтобы их избежать используется строгий режим или strict mode:

В этом случае мы получим ошибку SyntaxError: Unexpected identifier, которая говорит о том, что переменная foo не определена.

Строгий режим изменяет и синтаксис, и поведение кода во время выполнения. Наиболее важные изменения:

  • преобразование ранее допустимых ошибок в ошибки синтаксиса или ошибки выполнения;

  • изменения, упрощающие вычисление конкретных переменных;

  • изменения, упрощающие функцию eval и объект arguments;

  • изменения, которые будут применены в будущей спецификации ES;

  • улучшение производительности.

Он также исправляет ошибки, мешающие движкам JavaScript производить оптимизацию, и запрещает функции, которые могут быть определены в будущих версиях JavaScript.

Выражения

Что такое выражения и инструкции? В чем отличия между ними?

const x = 5;
const y = getAnswer();

Выражение — это фрагмент кода, который превращается в значение. Другими словами — становится значением. Да, знаю, 5 — это уже значение, но для интерпретатора JavaScript это выражение, которое превращается в значение 5. Другое выражение, которое превращается в значение 5 — это, например, 2 + 3.

Вызов функции getAnswer() — это тоже выражение, потому что функция что-то возвращает. Этот вызов будет заменён на значение, которое она возвращает. Другими словами, вызов функции превратится в значение, а поэтому он является выражением.

Выражения делятся на простые (их также называют первичными) и сложные. Простые выражения являются самостоятельными выражениями, они не включают в себя ещё более простых выражений. К простым выражениям относятся: ключевое слово this, идентификаторы и литералы. Сложные выражения состоят из простых выражений. Типичный способ конструирования сложных выражений из простых выражений заключается в использовании операторов:

JavaScript различает выражения и инструкции. Инструкция — это команда, действие. Помните условие с if, циклы с while и for — всё это — инструкции, потому что они только производят и контролируют действия, но не становятся значениями.

Инструкция – это указание на совершение какого-либо действия, например, создать переменную, запустить цикл, выполнить условную инструкцию, выйти из функции и т. п. Любая программа представляет собой последовательность выполняемых инструкций. Окончание инструкции обозначается символом ; (точка с запятой):

// Инструкция объявления и инициализации переменнойvar num = 12;
var num = 12;

Составная инструкция – это просто последовательность из нуля и более инструкций, заключённая в фигурные скобки. Отдельные инструкции внутри составной инструкции надо завершать точками с запятой, но саму составную инструкцию завершать точкой с запятой не нужно:

{
  первая инструкция;
  вторая инструкция;
  третья инструкция;
}

Чем отличаются var, let, const? Почему использование const может быть предпочтительнее?

const user = {
  name: "Вася"
};

user.name = "Петя"; // допустимо
user = 5; // нельзя, будет ошибка

Теперь главный вопрос: что следует использовать var, let, или const? Наиболее популярное мнение, под которым подписываюсь и я, — всегда использовать const, кроме тех случаев, когда вы знаете, что переменная будет изменятся. Используя const, вы как бы говорите себе будущему и другим разработчикам, которые будут читать ваш код, что эту переменную изменять не следует. Если вам нужна изменяемая переменная (например, в цикле for), то используйте let.

Таким образом, между изменяемыми и неизменяемыми переменными осталось не так много различий. Это означает, что вам больше не придётся использовать var.

Что такое тернарный оператор?

Условный (тернарный) оператор - единственный оператор в JavaScript, принимающий три операнда: условие, за которым следует знак вопроса (?), затем выражение, которое выполняется, если условие истинно, сопровождается двоеточием (:), и, наконец, выражение для выполнить, если условие ложно. Он часто используется в качестве укороченного варианта условного оператора if.

При присвоении значения также возможно выполнение более одной операции. В этом случае переменной будет присвоено то значение, которое стоит последним в списке значений, разделенных запятой.

var age = 16;

var url = age > 18 ? (
    alert("Хорошо, вы можете продолжить."), 
    // alert вернет "undefined", но это будет проигнорировано, потому что
    // не является последним в списке значений, разделенных запятой
    "continue.html" // значение будет присвоено, если age > 18
) : (
    alert("Вы слишком молоды!"),
    alert("Простите :-("),
    // ит.д. ит.д.
    "stop.html" // значение будет присвоено, если !(age > 18)
);

location.assign(url); // "stop.html"

Что делает оператор for..in? Какие имеются особенности при использовании этого оператора с массивами?

Цикл for...in проходит только по перечисляемым свойствам. Объекты, созданные встроенными конструкторами, такими как Array и Object имеют неперечисляемые свойства от Object.prototype и String.prototype, например, от String-это indexOf(), а от Object - метод toString(). Цикл пройдёт по всем перечисляемым свойствам объекта, а также тем, что он унаследует от конструктора прототипа (свойства объекта в цепи прототипа).

Упорядочение свойство объекта. Короткий ответ: свойства упорядочены особым образом: свойства с целочисленными ключами сортируются по возрастанию, остальные располагаются в порядке создания. Разберёмся подробнее.

let codes = {
  "49": "Германия",
  "41": "Швейцария",
  "44": "Великобритания",
  // ..,
  "1": "США"
};

for (let code in codes) {
  alert(code); // 1, 41, 44, 49
}

Замечание: for...in не следует использовать для Array, где важен порядок индексов.

Индексы массива - это перечисляемые свойства с целочисленными именами, в остальном они аналогичны свойствам объектов. Нет гарантии, что for...in будет возвращать индексы в конкретном порядке. Цикл for...in возвращает все перечисляемые свойства, включая имеющие нецелочислиненные имена и наследуемые.

Это – плохая идея. Существуют скрытые недостатки этого способа:

  1. Цикл for..in выполняет перебор всех свойств объекта, а не только цифровых.

    В браузере и других программных средах также существуют так называемые «псевдомассивы» – объекты, которые выглядят, как массив. То есть, у них есть свойство length и индексы, но они также могут иметь дополнительные нечисловые свойства и методы, которые нам обычно не нужны. Тем не менее, цикл for..in выведет и их. Поэтому, если нам приходится иметь дело с объектами, похожими на массив, такие «лишние» свойства могут стать проблемой.

  2. Цикл for..in оптимизирован под произвольные объекты, не массивы, и поэтому в 10-100 раз медленнее. Увеличение скорости выполнения может иметь значение только при возникновении узких мест. Но мы всё же должны представлять разницу.

Так как порядок прохода зависит от реализации, проход по массиву может не произойти в правильном порядке. Следовательно лучше с числовыми индексами использовать циклы for, Array.prototype.forEach() или for...of, когда проходим по массивам, где важен порядок доступа к свойствам.

Как безопасно проверить, что переменная существует (была объявлена), и не словить ReferenceError?

Ответ:

if (typeof variable !== 'undefined') {
    // code
}

//or
if (window.variable !== void 0) {
    // code
}

About "void(0)":

Подробнее:

В JavaScript null - это объект. Есть еще одно значение для вещей, которые не существуют, undefined. DOM возвращает null почти во всех случаях, когда не удается найти какую-либо структуру в документе, но в самом JavaScript не undefined используемое значение.

Во-вторых, нет, прямого эквивалента нет. Если вы действительно хотите проверить на наличие null, выполните:

if (yourvar === null) // Does not execute if yourvar is 'undefined'

Если вы хотите проверить, существует ли переменная, это можно сделать только с помощью try/catch, поскольку typeof будет обрабатывать необъявленную переменную и переменную, объявленную со значением undefined как эквивалентную.

Но, чтобы проверить, объявлена ли переменная и не undefined:

if (typeof yourvar !== 'undefined') // Any scope

Если вы знаете, что переменная существует, и хотите проверить, есть ли в ней какое-либо значение:

if (yourvar !== undefined)

Если вы хотите знать, существует ли член независимо, но не волнует его значение:

if ('membername' in object) // With inheritance
if (object.hasOwnProperty('membername')) // Without inheritance

Если вы хотите узнать, является ли переменная истинной:

if (yourvar)

Массивы

Способы создания массивов (литерал, конструктор, фабричные методы Array.from() и Array.of());

Удаление элемента из массива (какие есть способы и в чем особенности);

  • arr.pop() – извлекает элемент из конца

  • arr.shift() – извлекает элемент из начала

  • delete(длина не меняется, если не юзать delete obj.key):

    let arr = ["I", "go", "home"];
    
    delete arr[1]; // удалить "go"
    
    alert( arr[1] ); // undefined
    
    // теперь arr = ["I",  , "home"];
    alert( arr.length ); // 3
  • arr.splice:

    let arr = ["Я", "изучаю", "JavaScript"];
    
    arr.splice(1, 1); // начиная с позиции 1, удалить 1 элемент
    
    alert( arr ); // осталось ["Я", "JavaScript"]
  • arr.slice:

    let arr = ["t", "e", "s", "t"];
    
    alert( arr.slice(1, 3) ); // e,s (копирует с 1 до 3)
    
    alert( arr.slice(-2) ); // s,t (копирует с -2 до конца)

  • _reject (lodash)

  • filter:

let numbers = [1,2,3,4];
numbers = numbers.filter((n) => {return n != 3});
console.log(numbers); // [1,2,4]

Свойство length у массива

  • Какое значение будет у свойства length массива a и почему:

    const a = [1, 2, 3];
    a[10] = 4;
    • Что будет, если переприсвоить новое значение?

    • Влияет ли на length удаление элемента посередине массива? Какие способы удаления элементов влияют на длину, а какие нет?

Как проверить, что в переменной лежит массив?

  • variable instanceof Array

  • Array.isArray(variable)

  • variable.constructor === Array

    This is the fastest method on Chrome, and most likely all other browsers. All arrays are objects, so checking the constructor property is a fast process for JavaScript engines.

    If you are having issues with finding out if an objects property is an array, you must first check if the property is there.

    variable.prop && variable.prop.constructor === Array

Что делают, как и когда использовать следующие методы:

  • reduce

для прохода по массиву с вычислением значения.

var arr = [1, 2, 3, 4, 5]

// для каждого элемента массива запустить функцию,
// промежуточный результат передавать первым аргументом далее
var result = arr.reduce(function(sum, current) {
  return sum + current;
}, 0);

alert( result ); // 15
  • sort

Метод sort() на месте сортирует элементы массива и возвращает отсортированный массив. Сортировка не обязательно устойчива (англ.). Порядок cортировки по умолчанию соответствует порядку кодовых точек Unicode.

arr.sort([compareFunction])
var fruit = ['арбузы', 'бананы', 'Вишня'];
fruit.sort(); // ['Вишня', 'арбузы', 'бананы']

var scores = [1, 2, 10, 21];
scores.sort(); // [1, 10, 2, 21]

var things = ['слово', 'Слово', '1 Слово', '2 Слова'];
things.sort(); // ['1 Слово', '2 Слова', 'Слово', 'слово']
// В Unicode, числа находятся перед буквами в верхнем регистре,
// а те, в свою очередь, перед буквами в нижнем регистре.
  • filter

var arr = [1, -1, 2, -2, 3];

var positiveArr = arr.filter(function(number) {
  return number > 0;
});

alert( positiveArr ); // 1,2,3
  • map

var names = ['HTML', 'CSS', 'JavaScript'];

var nameLengths = names.map(function(name) {
  return name.length;
});

// получили массив с длинами
alert( nameLengths ); // 4,3,10

НЕ мутирует массив, создает новый

  • forEach

Мутирует массив.

var arr = ["Яблоко", "Апельсин", "Груша"];

arr.forEach(function(item, i, arr) {
  alert( i + ": " + item + " (массив:" + arr + ")" );
});
  • some

Метод some() проверяет, удовлетворяет ли какой-либо элемент массива условию, заданному в передаваемой функции.

метод возвращает false при любом условии для пустого массива.

const array = [1, 2, 3, 4, 5];

// checks whether an element is even
const even = (element) => element % 2 === 0;

console.log(array.some(even));
// expected output: true
  • every

Метод every() проверяет, удовлетворяют ли все элементы массива условию, заданному в передаваемой функции.

function isBigEnough(element, index, array) {
  return element >= 10;
}
[12, 5, 8, 130, 44].every(isBigEnough);   // false
[12, 54, 18, 130, 44].every(isBigEnough); // true

Hash Tables(map). Ассоциативные массивы.

Ассоциативный массив — абстрактный тип данных, с помощью которого хранятся пары ключ-значение. У него есть и другие названия: "словарь", "мап" (от слова map).

Ассоциативный массив, в отличие от обычного массива (называемого индексированным, так как значения в нем расположены по индексам), нельзя положить в память "как есть". У него нет индексов, которые бы могли определить порядок и простой способ добраться до значений. Для реализации ассоциативных массивов часто используют специальную структуру данных — хеш-таблицу. Она позволяет организовать данные ассоциативного массива удобным для хранения способом. Для этого хеш-таблица использует две вещи: индексированный массив и функцию для хеширования ключей.

Хеширование

Любая операция внутри хеш-таблицы начинается с того, что ключ каким-то образом преобразуется в индекс обычного массива. Для получения индекса из ключа, нужно выполнить два действия: найти хеш (хешировать ключ) и привести его к индексу (например через остаток от деления).

Хеширование — операция, которая преобразует любые входные данные в строку (реже число) фиксированной длины. Функция, реализующая алгоритм преобразования, называется "хеш-функцией", а результат называют "хешем" или "хеш-суммой". Наиболее известны CRC32, MD5 и SHA (много разновидностей).

// В JavaScript нет встроенной поддержки алгоритма хеширования crc32 (удобен для наглядности)
// Поэтому используется сторонняя библиотека
import crc32 from 'crc-32';

const data = 'Hello, world!'; // Любые данные которые мы хотим хешировать
const hash = crc32.str(data);

// Хеш всегда одинаковый для одних и тех же данных!
console.log(hash); // => -337197338

С хешированием мы встречаемся в разработке часто. Например, идентификатор коммита в git 0481e0692e2501192d67d7da506c6e70ba41e913 не что иное, как хеш, полученный в результате хеширования данных коммита.

После того как хеш получен, его можно преобразовать в индекс массива, например, через получение остатка от деления:

const index = Math.abs(hash) % 1000; // по модулю
console.log(index); // => 338

Ассоциативный массив в JavaScript представлен типом данных Object.

Функции

В JavaScript функции — это объекты. Поэтому функции могут принимать другие функции в качестве аргументов, а также функции могут возвращать функции в качестве результата. Функции, которые это умеют, называются функциями высшего порядка. А любая функция, которая передается как аргумент, называется callback-функцией (также это передача контроля другой функции).

Какие есть 4 шаблона вызова функции, которые задают контекст выполнения этой функции?

Оператор вызова — круглые скобки (), которые могу заключать в себе параметры, разделенные через запятую. К сожалению, существует несколько паттернов для вызова функций. Нужно понять, потому как, в зависимости от выбранного паттерна, вы получите разные результаты.

Оператор для вызова функции один, а способов вызова — четыре. Итак, существует четыре пути вызова функций:

  • Вызов метода — Method Invocation

  • Вызов функции — Function Invocation

  • Вызов конструктора — Constructor Invocation

  • Вызов apply и call — Apply And Call Invocation

Вызов метода — Method Invocation Когда функция является частью объекта, она называется методом. «Вызов метода» представляет из себя вызов функции, принадлежащей объекту. Пример:

var obj = {
    value: 0,
    increment: function() {
        this.value+=1;
    }
};
 
obj.increment(); 

В «вызове метода» значение this будет ссылаться на объект, которому принадлежит функция, в нашем случае на obj, причем данная связь будет установлена после запуска функции, что носит термин позднего привязывания (late binding).

Вызов функции — Function Invocation Вызов функции выполняется с помощью оператора ():

add(2,3); //5

Используя данный паттерн, this привязывается к global object. Это, несомненно, является ошибкой языка — постоянная привязка this к глобальному объекту может уничтожить его контекст. Это особенно заметно, если использовать функцию внутри метода. Давайте посмотрим на пример:

var value = 500; //Global variable
var obj = {
    value: 0,
    increment: function() {
        this.value++;
 
        var innerFunction = function() {
            alert(this.value);
        }
 
        innerFunction(); //Function invocation pattern
    }
}
obj.increment(); //Method invocation pattern

Обратите внимание, innerFunction вызывается с использованием вышеупомянутого паттерна «вызова функции», соответственно this привязывается к global object. В результате мы и получаем 500. Можно легко обойти эту проблему путем создания переменной this, но это, по моему мнению, является хаком.

Вызов конструктора — Constructor Invocation В классическом ООП объект является реализацией класса. В С++ и Java для такой реализации используется оператор new. Судя по всему, создатели JS решили не ходить далеко за примером, и реализовать нечто подобное в паттерне «вызов конструктора»… Паттерн запускается путем размещения оператора new прямо перед вызовом, например:

var Cheese = function(type) {
    cheeseType = type;
    return cheeseType;
}
 
cheddar = new Cheese("cheddar"); //Возвращается объект, а не тип

Несмотря на то, что Cheese является функциональным объектом (а значит умеет переваривать код), мы создали новый объект путем вызова функции с new. this в данном случае будет относиться к свежесозданному объекту, и поведение return будет изменено. К слову о return. Его использования в «вызове конструктора» имеет две особенности:

  • если функция возвращает число, цепочку, логическое выражение (true/false), null или undefined, return не сработает, a мы получим this

  • если функция возвращает реализацию объекта (то есть все, кроме простых переменных), мы увидим данный объект, а не this

Вызов apply и call — Apply And Call Invocation Этот паттерн продуман гораздо лучше остальных. Он позволяет вручную запустить функцию, попутно снабдив ее параметрами и обозначив this. Из-за того, что функции у нас являются полноправными объектами, каждая функция в JavaScript связана с Function.prototype, а значит мы можем легко добавлять к ним методы. Данный паттерн использует два параметра: первый — это объект, к которому привязывается this, второй — это массив, связанный с параметрами:

var add = function(num1, num2) {
        return num1+num2;
}
 
array = [3,4];
add.apply(null,array); //7

В примере выше this относится к null (функция не является объектом), а массив привязан к num1 и num2. Но продолжим эксперементировать с первым параметром:

var obj = {
    data:'Hello World'
}
 
var displayData = function() {
    alert(this.data);
}
 
displayData(); //undefined
displayData.apply(obj); //Hello World

Этот пример использует apply для привязки this к obj. В результате мы в состоянии получить значение this.data. Настоящая ценность apply заключается именно в привязывании this. В JavaScript также существует оператор call, похожий на apply всем, за исключением того что получает не параметры, а список аргументов.

Как директива use strict влияет на this внутри функции?

В функциях как f(), значением this является глобальный объект. В строгом режиме он теперь равен undefined. Когда функция вызывалась с помощью call или apply, если значением был примитив, он упаковывался в соответствующий объект (или в глобальный объект для undefined и null). В строгом режиме значение передается без каких-либо преобразований и замен. Используйте this только тогда, когда он ссылается на объект, созданный вами.

Какой наиболее простой паттерн, позволяющий облегчить читаемость функции, когда у нее огромное количество аргументов?

Вызов функции — Function Invocation

Как получить все аргументы функции (включая те, что не объявлены, но все-таки были переданы)?

Что такое рекурсия? Когда удобно её использовать?

Рекурсия – это приём программирования, полезный в ситуациях, когда задача может быть естественно разделена на несколько аналогичных, но более простых задач. Или когда задача может быть упрощена до несложных действий плюс простой вариант той же задачи. Их суть состоит в том, что функция вызывает саму себя.

Итак, рекурсию используют, когда вычисление функции можно свести к её более простому вызову, а его – к ещё более простому и так далее, пока значение не станет очевидно.

Максимальная глубина рекурсии ограничена движком JavaScript. Точно можно рассчитывать на 10000 вложенных вызовов, некоторые интерпретаторы допускают и больше, но для большинства из них 100000 вызовов – за пределами возможностей.

Любая рекурсия может быть переделана в цикл. Как правило, вариант с циклом будет эффективнее.

Но переделка рекурсии в цикл может быть нетривиальной, особенно когда в функции в зависимости от условий используются различные рекурсивные подвызовы, результаты которых объединяются, или когда ветвление более сложное. Оптимизация может быть ненужной и совершенно не стоящей усилий.

Часто код с использованием рекурсии более короткий, лёгкий для понимания и поддержки. Оптимизация требуется не везде, как правило, нам важен хороший код, поэтому она и используется.

Что такое замыкания, и в каких случаях они могут быть полезны? Как сохранить состояние с помощью замыкания, и для чего это состояние может быть использовано?

Замыкание это функция у которой есть доступ к своей внешней функции по области видимости, даже после того, как внешняя функция прекратилась. Это говорит о том, что замыкание может запоминать и получать доступ к переменным, и аргументам своей внешней функции, даже после того, как та прекратит выполнение.

function person() {
  let name = 'Peter';
  
  return function displayName() {
    console.log(name);
  };
}
let peter = person();
peter(); // выведет 'Peter'

Замыкания подобны объектам в том смысле, что они представляют собой механизм для хранения состояния:

function getCounter() {
  let counter = 0;
  return function() {
    return counter++;
  }
}
let count = getCounter();
console.log(count());  // 0
console.log(count());  // 1
let count2 = getCounter()
console.log(count2());  // 0
console.log(count2());  // 1
console.log(count());  // 2

Как реализовать функцию bind?

Метод bind() создаёт новую функцию, которая при вызове устанавливает в качестве контекста выполнения this предоставленное значение. В метод также передаётся набор аргументов, которые будут установлены перед переданными в привязанную функцию аргументами при её вызове.

this.x = 9;
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 81

var getX = module.getX;
getX(); // 9, поскольку в этом случае this ссылается на глобальный объект

// создаём новую функцию с this, привязанным к module
var boundGetX = getX.bind(module);
boundGetX(); // 81

Методы apply() и call() практически идентичны при работе с выставлением значения this, за исключением того, что вы передаёте параметры функции в apply() как массив, в то время, как в call(), параметры передаются в индивидуальном порядке. Но дальше ещё интереснее.

Решить такую вот проблему: пускай у нас есть массив ссылок, и наша задача — сделать так, чтобы при клике на каждую выводился alertом ее порядковый номер. Первое решение, что приходит в голову, выглядит так:

for (var i = 0; i < links.length; i++) {
   links[i].onclick = function() {
      alert(i);
   }
}

На деле же оказывается, что при клике на любую ссылку выводится одно и то же число — значение links.length. Почему так происходит и как эту гадость исправить?

Ответ: В связи с замыканием объявленная вспомогательная переменная i продолжает существовать, при чем и в тот момент, когда мы кликаем по ссылке. Поскольку к тому времени цикл уже прошел, i остается равным кол-ву ссылок - это значение мы и видим при кликах.

Решается эта проблема следующим образом:

for (var i = 0; i < links.length; i++) {
    (function(i) {
         links[i].onclick = function() {
             alert(i);
         }
    })(i);
}

Здесь с помощью еще одного замыкания мы «затеняем» переменную i, создавая ее копию в его локальной области видимости на каждом шаге цикла. Благодаря этому все теперь работает как задумывалось.

Что такое callback (функция обратного вызова)? Когда они обычно применяются?

Функция обратного вызова - это функция, переданная в другую функцию в качестве аргумента, которая затем вызывается по завершению какого-либо действия.

function greeting(name) {
  alert('Hello ' + name);
}

function processUserInput(callback) {
  var name = prompt('Please enter your name.');
  callback(name);
}

processUserInput(greeting);

Выше приведен пример синхронного обратного вызова, поскольку функция processUserInput выполняется синхронно.

Функции обратного вызова часто используются для продолжения выполнения кода после завершения асинхронной операции - они называются асинхронными обратными вызовами.

async function pageLoader(callback) {
  const data = await fetch('/ru/docs/Словарь/функция_обратного_вызова')
  callback(data)
}

function onPageLoadingFinished(pageData) {
  console.log('Page was sucessfully loaded!')
  console.log('Response:')
  console.log(pageData)
}

pageLoader(onPageLoadingFinished)

Что такое каррирование?

Каррирование или карринг (currying) в функциональном программирование — это преобразование функции с множеством аргументов в набор вложенных функций с одним аргументом. При вызове каррированной функции с передачей ей одного аргумента, она возвращает новую функцию, которая ожидает поступления следующего аргумента. Новые функции, ожидающие следующего аргумента, возвращаются при каждом вызове каррированной функции — до тех пор, пока функция не получит все необходимые ей аргументы. Ранее полученные аргументы, благодаря механизму замыканий, ждут того момента, когда функция получит всё, что ей нужно для выполнения вычислений. После получения последнего аргумента функция выполняет вычисления и возвращает результат.

function multiply(a, b, c) {
    return a * b * c;
}

==> multiply(1,2,3); // 6

//currying
function multiply(a) {
    return (b) => {
        return (c) => {
            return a * b * c
        }
    }
}

==> log(multiply(1)(2)(3)) // 6

OR

const mul1 = multiply(1);
const mul2 = mul1(2);
const result = mul2(3);
log(result); // 6

Что такое частичное применение функций?

Возникает ощущение, что количество вложенных функций, при представлении функции в виде набора вложенных функций, зависит от количества аргументов функции. И, если речь идёт о каррировании, то это так.

Каррирование и частичное применение функций очень похожи друг на друга, но концепции это разные.

При частичном применении функцию преобразуют в другую функцию, обладающую меньшим числом аргументов (меньшей арностью). Некоторые аргументы такой функции оказываются зафиксированными (для них задаются значения по умолчанию).

function acidityRatio(x, y, z) {
    return performOp(x,y,z)
}


///partial function application

function acidityRatio(x) {
    return (y,z) => {
        return performOp(x,y,z)
    }
}

///currying

function acidityRatio(x) {
    return (y) = > {
        return (z) = > {
            return performOp(x,y,z)
        }
    }
}

Как видите, при каррировании число вложенных функций равно числу аргументов исходной функции. Каждая из этих функций ожидает собственный аргумент. При этом понятно, что если функция аргументов не принимает, или принимает лишь один аргумент, то каррировать её нельзя.

Что такое мемоизация?

Очевидно, что цель мемоизации — сокращение времени и количества ресурсов, потребляемых при исполнении «дорогостоящих вызовы функций».

Мемоизация использует кеширование для хранения результатов наших вызовов для быстрого и легкого доступа в более позднее время.

Кеш — это просто временное хранилище данных.

В первый раз она делает необходимые вычисления и «запоминает» результат, сохраняя его в кеше. Если в будущем она получит те же самые входные данные, то не будет вычислять повторно, а просто возьмет ответ из памяти (кеша).

Идея мемоизации в JavaScript основывается на двух концепциях:

  • замыканиях

  • и функциях высшего порядка — функциях, которые возвращают другие функции.

// простая чистая функция, которая возвращает сумму аргумента и 10
const add = (n) => (n + 10);
console.log('Simple call', add(3));
// простая функция, принимающая другую функцию и
// возвращающая её же, но с мемоизацией
const memoize = (fn) => {
  let cache = {};
  return (...args) => {
    let n = args[0];  // тут работаем с единственным аргументом
    if (n in cache) {
      console.log('Fetching from cache');
      return cache[n];
    }
    else {
      console.log('Calculating result');
      let result = fn(n);
      cache[n] = result;
      return result;
    }
  }
}
// создание функции с мемоизацией из чистой функции 'add'
const memoizedAdd = memoize(add);
console.log(memoizedAdd(3));  // вычислено
console.log(memoizedAdd(3));  // взято из кэша
console.log(memoizedAdd(4));  // вычислено
console.log(memoizedAdd(4));  // взято из кэша

Прототипы

Что такое функция-конструктор? Как их создавать и как ими пользоваться?

Функции-конструкторы являются обычными функциями. Но есть два соглашения:

  1. Имя функции-конструктора должно начинаться с большой буквы.

  2. Функция-конструктор должна вызываться при помощи оператора "new".

function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("Вася");

alert(user.name); // Вася
alert(user.isAdmin); // false

Когда функция вызывается как new User(...), происходит следующее:

  1. Создаётся новый пустой объект, и он присваивается this.

  2. Выполняется код функции. Обычно он модифицирует this, добавляет туда новые свойства.

  3. Возвращается значение this.

Другими словами, вызов new User(...) делает примерно вот что:

function User(name) {
  // this = {};  (неявно)

  // добавляет свойства к this
  this.name = name;
  this.isAdmin = false;

  // return this;  (неявно)
}

То есть, результат вызова new User("Вася") – это тот же объект, что и:

let user = {
  name: "Вася",
  isAdmin: false
};

Используя специальное свойство new.target внутри функции, мы можем проверить, вызвана ли функция при помощи оператора new или без него.

В случае, если функция вызвана при помощи new, то в new.target будет сама функция, в противном случае undefined.

function User() {
  alert(new.target);
}

// без "new":
User(); // undefined

// с "new":
new User(); // function User { ... }

Обычно конструкторы ничего не возвращают явно. Их задача – записать все необходимое в this, который в итоге станет результатом.

Но если return всё же есть, то применяется простое правило:

  • При вызове return с объектом, будет возвращён объект, а не this.

  • При вызове return с примитивным значением, примитивное значение будет отброшено.

Другими словами, return с объектом возвращает объект, в любом другом случае конструктор вернёт this.

В примере ниже return возвращает объект вместо this:

function BigUser() {

  this.name = "Вася";

  return { name: "Godzilla" };  // <-- возвращает этот объект
}

alert( new BigUser().name );  // Godzilla, получили этот объект

А вот пример с пустым return (или мы могли бы поставить примитив после return, неважно)

function SmallUser() {

  this.name = "Вася";

  return; // <-- возвращает this
}

alert( new SmallUser().name );  // Вася

В this мы можем добавлять не только свойства, но и методы.

Например, в примере ниже, new User(name) создаёт объект с данным именем name и методом sayHi:

function User(name) {
  this.name = name;

  this.sayHi = function() {
    alert( "Меня зовут: " + this.name );
  };
}

let vasya = new User("Вася");

vasya.sayHi(); // Меня зовут: Вася

/*
vasya = {
   name: "Вася",
   sayHi: function() { ... }
}
*/

Что такое прототип? Какие возможности имеет/дает?

Почему методы объекта лучше хранить в прототипе, а не в самом объекте?

It’s neat and definitely saves on resources!

Можно ли создать инстанс функции через конструктор? И если да, то как, а если нет, то какой бы интерфейс вы реализовали бы для этой задачи?

Function constructor создает новый объект Function. Вызов constructor создает функцию динамически, но страдает от проблем безопасности и аналогичных (но гораздо менее значительных) проблем производительности eval. Однако, в отличие от eval, конструктор функций создает функции, которые выполняются только в глобальной области..

const sum = new Function('a', 'b', 'return a + b');

console.log(sum(2, 6));
// expected output: 8

Примечание: функции, созданные конструктором Function, не создают замыканий на их контексты создания; они всегда создаются в глобальной области видимости. При их вызове, они получат доступ только к своим локальным переменным и переменным из глобальной области видимости, но не к переменным в той области видимости, в которой вызывался конструктор Function. Это поведение отличается от поведения при использовании функции eval с кодом создания функции.

Как создать объект, который ни от чего не наследуется?

Очень просто сказано, new X есть Object.create(X.prototype) с дополнительным запуском функции constructor.

Чтобы создать объект, не имеющий прототипа, можно передать значение null, но в этом случае вновь созданный объект не унаследует ни каких-либо свойств, ни базовых методов, таких как toString() (а это означает, что этот объект нельзя будет использовать в выражениях с оператором +):

// obj2 не наследует ни свойств, ни методов
var obj2 = Object.create(null);	

Какие 3 (как минимум) способа есть отнаследоваться в JavaScript-е? В чем отличия и нюансы?

Как в переопределенном методе у наследующего класса вызвать переопределяемый метод родительского? Пример псевдокода:

class Person
    method getFullName()
        return this.name + this.surname

class Employee extends Person
    method getFullName()
        return super() + this.position

Какие есть способы навсегда привязать метод класса к его инстансу (чтобы this всегда был текущим экземпляром класса)?

Last updated