Джозеф из https://www.toptal.com/ попросил меня повторно опубликовать эту интересную статью, которую они написали, так что начнем:

Соревнование

В сегодняшнем технологическом ландшафте JavaScript, по сути, стал синонимом веб-разработки на стороне клиента, и теперь, с появлением технологий и фреймворков JavaScript, таких как Node.js, Vue.js и React.js, JavaScript становится доминирующей технологией на стороне сервера. Что ж.

Соответственно, резюме разработчика с полным стеком, которые ссылаются хотя бы на некоторую степень опыта работы с JavaScript, по сути, стали универсальными в сообществе разработчиков программного обеспечения. Это делает поиск веб-разработчиков JavaScript довольно простым, но делает поиск среди них избранных немногих гораздо более сложной задачей. Чтобы найти их, требуется высокоэффективный процесс найма, как описано в нашей статье В поисках элитного меньшинства — поиск и наем лучших разработчиков в отрасли. Затем такой процесс можно дополнить вопросами, подобными тем, которые представлены здесь, чтобы определить тех редко распределенных кандидатов по всему миру, которые являются настоящими экспертами по JavaScript.

Да, я знаю JavaScript…

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

С этой целью в этом посте предлагается выборка вопросов, которые являются ключевыми для оценки широты и глубины владения JavaScript кандидатом. Однако важно иметь в виду, что эти примеры вопросов предназначены только для руководства. Не каждый кандидат категории «отлично», которого стоит нанять, сможет должным образом ответить на все вопросы, и ответ на них не гарантирует наличие кандидата категории «отлично». В конце концов, найм остается не только наукой, но и искусством.

Оценка фонда

Слишком часто можно встретить «опытных» программистов на JavaScript, которые слабо или запутанно понимают основы языка.

JavaScript — это язык сценариев на основе прототипов с динамической типизацией. JavaScript может поначалу немного сбивать с толку разработчиков, имеющих опыт работы с языками на основе классов (такими как Java или C++), поскольку он динамичен и не обеспечивает традиционной реализации классов. Поэтому слишком часто можно встретить «опытных» JS-разработчиков, чье понимание основ языка либо слабое, либо запутанное.

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

В: Опишите наследование и цепочку прототипов в JavaScript. Привести пример.

Хотя JavaScript является объектно-ориентированным языком, он основан на прототипах и не реализует традиционную систему наследования на основе классов.

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

Вот простой пример:

function Animal() { this.eatsVeggies = true; this.eatsMeat = false; }
function Herbivore() {}
Herbivore.prototype = new Animal();
function Carnivore() { this.eatsMeat = true; }
Carnivore.prototype = new Animal();
var rabbit = new Herbivore();
var bear = new Carnivore();
console.log(rabbit.eatsMeat);   // logs "false"
console.log(bear.eatsMeat);     // logs "true"

В: Сравните и сопоставьте объекты и хеш-таблицы в JavaScript.

Это вопрос с подвохом, поскольку в JavaScript объекты по сути являются хэш-таблицами; т. е. наборы пар имя-значение. В этих парах имя-значение важно помнить, что имена (также известные как ключи) всегда являются строками. И это фактически приводит нас к нашему следующему вопросу…

Q: Рассмотрим фрагмент кода ниже (исходник). Что будет отображаться в предупреждении? Поясните свой ответ.

var foo = new Object();
var bar = new Object();
var map = new Object();
map[foo] = "foo";
map[bar] = "bar";
alert(map[foo]);  // what will this display??

Немногие кандидаты правильно ответят, что это предупреждает строку «bar». Большинство ошибочно ответит, что это предупреждает строку «foo». Итак, давайте разберемся, почему «бар» действительно правильный, хотя и неожиданный ответ…

Как упоминалось в ответе на предыдущий вопрос, объект JavaScript, по сути, представляет собой хэш-таблицу пар имя-значение, где имена (т. е. ключи) являются строками. И это всегда строки. На самом деле, когда в качестве ключа в JavaScript используется объект, отличный от строки, ошибки не возникает; скорее, JavaScript незаметно преобразует его в строку и вместо этого использует это значение в качестве ключа. Это может привести к удивительным результатам, как показано в приведенном выше коде.

Чтобы понять приведенный выше фрагмент кода, нужно сначала понять, что показанный объект map не сопоставляет объект foo со строкой «foo» и не сопоставляет объект bar со строкой «bar». Поскольку объекты foo и bar не являются строками, когда они используются в качестве ключей для map, JavaScript автоматически преобразует значения ключей в строки, используя метод toString() каждого объекта. И поскольку ни foo, ни bar не определяют свой собственный метод toString(), они оба используют одну и ту же реализацию по умолчанию. Эта реализация просто генерирует литеральную строку «[object Object]» при вызове. Помня об этом объяснении, давайте еще раз рассмотрим приведенный выше фрагмент кода, но на этот раз с поясняющими комментариями:

var foo = new Object();
var bar = new Object();
var map = new Object();
map[foo] = "foo";    // --> map["[object Object]"] = "foo";
map[bar] = "bar";    // --> map["[object Object]"] = "bar";
                     // NOTE: second mapping REPLACES first mapping!
alert(map[foo]);     // --> alert(map["[object Object]"]);
                     // and since map["[object Object]"] = "bar",
                     // this will alert "bar", not "foo"!!
                     //    SURPRISE! ;-)

В: Объясните замыкания в JavaScript. Кто они такие? Каковы некоторые из их уникальных особенностей? Как и почему вы можете захотеть их использовать? Приведите пример.

Замыкание — это функция вместе со всеми переменными или функциями, которые находились в области видимости на момент создания замыкания. В JavaScript замыкание реализовано как «внутренняя функция»; т. е. функция, определенная в теле другой функции. Вот упрощенный пример:

(function outerFunc(outerArg) {
  var outerVar = 3;
  (function middleFunc(middleArg) {
    var middleVar = 4;
    (function innerFunc(innerArg) {
      var innerVar = 5;
      // EXAMPLE OF SCOPE IN CLOSURE:
      // Variables from innerFunc, middleFunc, and outerFunc,
      // as well as the global namespace, are ALL in scope here.
      console.log("outerArg="+outerArg+
                  " middleArg="+middleArg+
                  " innerArg="+innerArg+"\n"+
                  " outerVar="+outerVar+
                  " middleVar="+middleVar+
                  " innerVar="+innerVar);
      // --------------- THIS WILL LOG: ---------------
      //    outerArg=123 middleArg=456 innerArg=789
      //    outerVar=3 middleVar=4 innerVar=5
    })(789);
  })(456);
})(123);

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

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

function addButtons(numButtons) {
  for (var i = 0; i < numButtons; i++) {
    var button = document.createElement('input');
    button.type = 'button';
    button.value = 'Button ' + (i + 1);
    button.onclick = function() {
      alert('Button ' + (i + 1) + ' clicked');
    };
    document.body.appendChild(button);
    document.body.appendChild(document.createElement('br'));
  }
}
window.onload = function() { addButtons(5); };

Многие ошибочно ответят, что «Кнопка 3 нажата» будет отображаться, когда пользователь нажмет на третью кнопку. На самом деле приведенный выше код содержит ошибку (основанную на непонимании того, как работают замыкания), и при нажатии пользователем любой из пяти кнопок будет отображаться сообщение «Кнопка 6 нажата». Это связано с тем, что в момент вызова метода onclick (для любой кнопки) цикл for уже завершен, и переменная i уже имеет значение 5.

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

function addButtons(numButtons) {
  for (var i = 0; i < numButtons; i++) {
    var button = document.createElement('input');
    button.type = 'button';
    button.value = 'Button ' + (i + 1);
    // HERE'S THE FIX:
    // Employ the Immediately-Invoked Function Expression (IIFE)
    // pattern to achieve the desired behavior:
    button.onclick = function(buttonIndex) {
      return function() {
        alert('Button ' + (buttonIndex + 1) + ' clicked');
      };
    }(i);
    document.body.appendChild(button);
    document.body.appendChild(document.createElement('br'));
  }
}
window.onload = function() { addButtons(5); };

Хотя замыкания ни в коем случае не являются исключительными для JavaScript, они являются особенно полезной конструкцией для многих современных парадигм программирования JavaScript. Они широко используются некоторыми из самых популярных библиотек JavaScript, такими как jQuery и Node.js.

Охватывая разнообразие

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

Язык JavaScript с несколькими парадигмами поддерживает объектно-ориентированный, императивный и функциональный стили программирования. Таким образом, JavaScript поддерживает необычайно широкий спектр методов программирования и шаблонов проектирования. Мастер JavaScript будет хорошо осведомлен о существовании этих альтернатив и, что более важно, о важности и последствиях выбора одного подхода над другим. Вот несколько примеров вопросов, которые могут помочь оценить этот аспект опыта кандидата:

Q: Опишите различные способы создания объектов и ответвления каждого из них. Приведите примеры.

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

В: Есть ли практическая разница между определением функции как функционального выражения (например, var foo = function(){}) или как оператора функции (например, function foo(){})? Поясните свой ответ.

Да, есть разница, исходя из того, как и когда присваивается значение функции.

Когда используется оператор функции (например, function foo(){}), на функцию foo можно ссылаться до того, как она будет определена, с помощью метода, известного как «подъем». Разветвление подъема заключается в том, что последнее определение функции будет использоваться независимо от того, когда на него ссылаются (если это неясно, приведенный ниже пример кода должен помочь прояснить ситуацию).

Напротив, когда используется функциональное выражение (например, var foo = function(){}), на функцию foo нельзя ссылаться до ее определения, как и на любой другой оператор присваивания. По этой причине будет использовано самое последнее определение функции (и, соответственно, определение должно предшествовать ссылке, иначе функция будет неопределенной).

Вот простой пример, демонстрирующий практическую разницу между ними. Рассмотрим следующий фрагмент кода:

function foo() { return 1; }
alert(foo());   // what will this alert?
function foo() { return 2; }

Многие разработчики JavaScript ошибочно ответят, что в приведенном выше предупреждении будет отображаться «1», и будут удивлены, узнав, что на самом деле оно отображает «2». Как описано выше, это происходит из-за подъема. Поскольку для определения функции использовался оператор function, последним определением функции является то, которое поднимается в момент ее вызова (даже если оно следует за ее вызовом в коде!).

Теперь рассмотрим следующий фрагмент кода:

var foo = function() { return 1; }
alert(foo());   // what will this alert?
foo = function() { return 2; }

В этом случае ответ будет более интуитивным, и предупреждение будет отображать «1», как и ожидалось. Поскольку для определения функции использовалось функциональное выражение, самым последним определением функции является то, которое используется во время ее вызова.

Дьявол в деталях

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

В: В чем смысл и смысл помещения всего содержимого исходного файла JavaScript в функциональный блок?

Это становится все более распространенной практикой, используемой многими популярными библиотеками JavaScript (jQuery, Node.js и т. д.). Этот метод создает замыкание вокруг всего содержимого файла, что, возможно, наиболее важно, создает частное пространство имен и тем самым помогает избежать потенциальных конфликтов имен между различными модулями и библиотеками JavaScript.

Еще одна особенность этого метода заключается в том, что он позволяет легко ссылаться на (предположительно более короткий) псевдоним для глобальной переменной. Это часто используется, например, в плагинах jQuery. jQuery позволяет отключить ссылку $ на пространство имен jQuery, используя jQuery.noConflict(). Если это было сделано, ваш код по-прежнему может использовать $, используя эту технику замыкания, как показано ниже:

(function($) { /* jQuery plugin code referencing $ */ } )(jQuery);

В: В чем разница между == и ===? Между != и !==? Привести пример.

Разница между операторами «тройного» сравнения (===, !==) и операторами двойного сравнения (==, !=) в JavaScript заключается в том, что операторы двойного сравнения выполняют неявное преобразование типов операндов перед их сравнением, тогда как с операторами тройного сравнения преобразование типов не выполняется. выполняется (т. е. значения должны быть равны, а типы должны быть одинаковыми, чтобы операнды считались равными).

В качестве простого примера, выражение 123 == '123' будет оценено как истинное, тогда как 123 === '123' будет оценено как ложное.

В: Каково значение включения 'use strict' в начале исходного файла JavaScript?

Хотя по этой теме можно сказать гораздо больше, краткий и самый важный ответ здесь заключается в том, что use strict — это способ добровольно обеспечить более строгий синтаксический анализ и обработку ошибок в вашем коде JavaScript во время выполнения. Ошибки кода, которые в противном случае были бы проигнорированы или завершились бы скрытым сбоем, теперь будут генерировать ошибки или вызывать исключения. В общем, это хорошая практика.

Заворачивать

JavaScript, пожалуй, один из самых неправильно понятых и недооцененных языков программирования, существующих сегодня. Чем больше человек чистит луковицу JavaScript, тем больше он понимает, что возможно. JavaScript достаточно универсален, чтобы его могли использовать как внешние, так и внутренние разработчики. Соответственно, найти настоящих мастеров языка на полный или неполный рабочий день в Соединенных Штатах или за границей — непростая задача. Мы надеемся, что вопросы, представленные в этом посте, послужат вам полезной основой для «отделения зёрен от плевел» в ваших поисках «элитного меньшинства» среди лучших разработчиков JavaScript для добавления в вашу команду разработчиков.

Исходное сообщение: https://www.toptal.com/javascript#hiring-guide