Pull to refresh

Comments 72

Странный вопрос со странным ответом:


В чём разница между ES6-классами и конструкторами функций?

Что такое конструктор функции? Может функция-конструктор класса? Ладно, наверно, ошибка перевода, но ответ ещё интересней, зачем-то наследование приплели, как будто нельзя наследоваться новым синтаксисом от класса созданного по старинке или наоборот — наследоваться по старинке от класса созданного новым синтаксисом. Правильный ответ: класс — это функция-конструктор плюс прототип класса, а функция-конструктор — это только функция конструктор.

Там есть ещё маленькая хитрость, что прототип класса нельзя переписать (им всегда будет конструктор), а прототип функции можно переопределить.


Это легко увидеть из дескрипторов. Эти знания могут пригодиться, если вдруг понадобится когда — то определять, перед нами класс или функция. image


Такой кейс, например, возник перед создателями React. В React нужно определить, что если перед нами функция, то вызывать её как функцию MyComponent(), а если класс то вызывать через new MyComponent(), правда в React используется другой механизм определения класса/функции — в реакте все компоненты созданные через класс наследуются от React.Component, поэтому проверка делается через instanceof прототипа


if (MyComponent.prototype instanceof React.Component) {
  new MyComponent(); 
  ...
} else {
  MyComponent(); 
  ... 
}

А почему просто и функцию не вызывать через new в реакте?

Из-за стрелочных функций, может. Проверил, new (() => {}) кидает ошибку.

Да, это в свою очередь связано с тем, что у стрелочных функций нет собственного контекста (то есть собственного this), а есть контекст который привязан к месту объявления. Было бы немного парадоксально вызывать стрелку через new, создав тем самым ей другой контекст.

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

Слишком вымученно. Минимальный набор правил сравнения гораздо проще:
1) == null и != null
2) === всё остальное.
Исключение из первого пункта — код, в котором за null и undefined закреплены разные семантические значения. Такой код обычно категорически не стоит писать, но иногда бывают экзотические случаи, когда он уместен.
Почему JavaScript-программисты испытывают проблемы при использовании ключевого слова this?
Программисты не испытывают проблем при использовании слова this. Проблемы испытывают недопрограммисты. А вообще, я бы на собесах просил написать три функции для числа фибоначчи: рекурсивно, динамическим программированием и с помощью генераторов. Если человек это все знает и умеет, с высокой вероятностью он программист. Недостаток знаний по либам и тулзам не важен. Решать гуано-кодище в уме, что выведет фигня за которую в реальности нужно рубить пальцы… Зачем такие вопросы, мне не понятно.

Наверное имелось ввиду через цикл?

Так рекурсивный подход используется в динамическом программировании, разве нет? И если рекурсия хвостовая, то ее можно написать в виде цикла.

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

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


function* genNumbersFromOneToTen() {
    for (let i = 1; i <= 10; i++) {
        yield i
    }
}

// По-сути достижимо и просто через

function genNumbersFromOneToTen() {
    let i = 1;
    const iterator = {
        next: function() {
            return {
                done: i > 10,
                value: i++
            }
        }
    }
    iterator[Symbol.iterator] = () => iterator
    return iterator
}

Но генератор располагает более удобным для этого интерфейсом. Следующие качества, которые делают из генератора совсем не бесполезную фичу:


1) Возможность использовать spread (...) и for-of (по скольку итератор инстанции генератора это он же и есть).
2) Возможность lazy-вычислений — пожалуй это и есть самый главный козырь генераторов — вы можете засунуть туда сложный синхронный алгоритм, который можете спокойно ставить на паузу где вам угодно и "заводить" опять по мере необходимости. Более того, с помощью next() вы можете прокидывать новые данные внутрь уже работающего генератора.
3) Около-асинхронные трюки: по скольку генератор эффективно останавливает даже синхронный код в любой его точке исполнения — вы можете смело делать обертки-генераторы вокруг более сложных и времязатратных синхронных операций — и с помощью того же yield выходить на определенное время из работы — дабы дать остальным таскам, ожидающим в event-queue запуститься. То есть, иными словами, вы относительно безболезненно можете разбить сложную синхронную операцию на более мелкие — и эти мелкие запускать асинхронно — для того, чтобы браузер, например, не фризанул при каком-то сложном алгоритме.

Это уже гораздо лучше.
Но всё равно не кажется мне вполне годным ответом на обсуждаемый вопрос. Всё это достижимо без генераторов, а для освобождения от вычислений основного потока, как я понимаю, есть Worker-ы.
Так для какой же практической надобности действительно нужны генераторы? Где они реально применяются в практических задачах? Например, в фреймворках?

Это просто более удобная запись для итератора. Синтаксический сахар. И они с воркерами не взаимозаменяемые. Асинхронщина и многопоточность — совершенно разные вещи.

23 причины послать лесом собеседника. =)
Однако количество добавленных закладок явно говорит, что видимо помимо javascript-собеседований вероятно есть и javascript-компании. XD
Но на деле чем больше пишу код тем меньше вижу смысла, что либо спрашивать про тонкости языка.
Я добавлю только из-за толковых комментариев
После объясения стрелочных функций и классов перестал дальше читать. Автор, пожалуйста не надо, остановись. Не пиши вредные советы.

Что бы было понятно о чем я.

Пункт 4

Используйте ключевое слово function в глобальной области видимости и для свойств Object.prototype.

А в модулях уже обявлять нельзя? Не надо объявлять функции в глобальной видимости от слова совсем. Для это и появились модули.

Используйте ключевое слово function для конструкторов объектов.

Что? Т.е используем ES6, но декларируем классы по старинке как в ES5? А как же «class»?

В остальных случаях используйте стрелочные функции.

Одна из причин появления стрелочных функций это потеря контекста функции. И стрелочные функции надо использовать там, где есть вероятность это получить, высока. Их надо использовать в методах класса и как callback функции.

Я настоятельно рекомендую почитать официальную документацию по стрелочным функциям.

Пунк 5

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

«class» это всего лишь синтаксический сахар. Под капотом это все тоже наследование на прототипах. Это не разные вещи — это одно и тоже.

Основная разница между конструкторами и классами проявляется при использовании наследования

Основная разница — стиль написания. Повторюсь, под капотом это одно и тоже. Рекомендую внимательно почитать документацию о классах в ES6 и как происходит наследование в JS.

Дальше решил не читать, что бы не расстраиваться еще больше.

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


const MyComponent: React.FC<MyComponentProps> = ({ myProp }) => {
   ...
}
Вот список значений, которые можно назвать «ложными» (falsy). Они, при преобразовании к логическому типу, превращаются в значение false:

(...)

Любое значение, которое не входит в этот список, при его преобразовании к логическому типу, превращается в true (такие значения называют «истинными» — truthy).

Как всегда, все забывают про BigInt:


console.log(Boolean(BigInt(0))); // false

Вот простые правила по использованию различных способов объявления функций, которыми я руководствуюсь, разрабатывая код для сред, поддерживающих стандарт ES6 и более новые стандарты:

(...)

Используйте ключевое слово function для конструкторов объектов.

В ES6 уже появились классы, так что это скорее вредный совет.


Вот — «глубокая заморозка»:

Такая заморозка ломается на циклических ссылках:


const foo = {};
foo.self = foo;
deepFreeze(foo); // InternalError: too much recursion

runner(function* () {
const res1 = await doTask1(); // SyntaxError: await is only valid in async functions and async generators

Возможно, имелся в виду yield, а не await?

Некоторые вопросы не совсем корректны. Например
> Когда следует использовать стрелочные функции, которые появились в ES6?
Что такое «когда»? Когда хочу, тогда и использую. Стоило поставить вопрос в чем отличие стрелочных функций от обычных.

> Что такое IIFE?
Больше 10 лет в вебе, даже и не понял что за аббревиатура такая. Лучше писать нормальное название, тогда все сразу понятно.

> Что выведет следующий код?
За такие вопросы я готов убивать. Я бы ответил «введите в консоль и посмотрите». Такие вопросы надо строить по принципу «Нам надо сделать _то-то_, вот тут правильно сделано или нет?»
Когда хочу, тогда и использую.

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


Больше 10 лет в вебе, даже и не понял что за аббревиатура такая. Лучше писать нормальное название, тогда все сразу понятно.

Это общепринятное сокращение, которое используется уже много-много лет. Либо вы не особо в JS, либо вы не читаете материал, третьего представить не могу.


За такие вопросы я готов убивать.

Воу-воу, попридержите коней. Вполне нормальная постановка вопроса.

Судя по остальным комментариям, я все же прав.

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

Ну смотрите, я тогда объясню. Сематика лямбд особенная — именно поэтому использовать их абсолютно везде неправильно. Дело в том, что они были созданы именно как функции-помощники, которые не должны полностью представлять все качества функции обычной:
1) Они всегда анонимные.
2) У них нет своего this — и поэтому вы можете смело их использовать/вызывать внутри функций нормальных и не бояться, что контекст этой главной функции подменится контекстом лямбды.
3) У них нет своего arguments — по той же причине, чтобы был прямой и простой доступ к arguments родительской "полноценной" функции.
4) У них более сокращенный синтаксис.
Можно показать это на примере полифила к bind:


// Нам нужно сохранить ссылку на оригинальную функцию, так как если я буду юзать this позже напрямую - то это уже будет this возвращаемой функции - коллбека. 
Function.prototype.bind = Function.prototype.bind || function(ctx, ...args) {
    const fn = this
    return function(...restArgs) {
        return fn.apply(ctx, [...args, ...restArgs])
    }
}
// Тут this берется от первой "полноценной" функций - можно не переживать, что он подменится на this лямбды.
Function.prototype.bind = Function.prototype.bind || function(ctx, ...args) {
    return (...restArgs) => {
        return this.apply(ctx, [...args, ...restArgs])
    }
}
Ну так и что из этого следует? :) А то, что вопрос надо ставить «в чем отличие стрелочных функций?», а не когда их использовать.

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

Ну, мне кажется, вы слишком прискребаетесь к формулировке вопросов в подобном "собеседовании". Как бы, наверное, еще правильнее было бы спросить: "объясните разницу спецификаций Function Declaration, Function Expression и Arrow Function Expression", но, как мне кажется, это уже вопрос человеческого понимания и soft skills, нежели самой темы вопроса.

Возможно, но, опять же, судя по комментам, я такой дотошный не один :)
Интересно видеть такую статью на ресурсе на котором толпа может написать заведомо ложную статью только потому что это толпа :)

На самом деле это не совсем относится ко мне. Я не утверждаю, что прав, потому что так же думают многие. Я утверждаю что прав и, посмотрите, многие думают так же, но я не завишу от их мнения.
И да — существуют вполне ясные и понятные причины почему в том или ином случае нужно использовать именно то или другое.

А ещё существуют случаи где можно использовать и то и другое равнозначно. И выбор уже производится исходя из предпочтений команды. К примеру я использую function только тогда, когда:


  • мне нужно работать с this (т.е. почти никогда)
  • нужно написать export default ... (тупо экономия строки, вкусовщина)
  • нужен overload типа в TS
  • нужно читаемое имя которое не удаётся задать другим способом (скажем из-за какого-нибудь wrapper-а)

В остальных же случаях я выбираю =>. Исходя из читаемости. С моей точки зрения, опытному разработчику, куда комфортнее когда keyword-ы языка не мешаются перед глазами. Но я отчётливо понимаю, что есть люди, которые мыслят иначе. Ибо это вкусовщина.

Отчасти могу с вами согласиться — безусловно существует масса случаев, где можно использовать оба варианта. Но чисто из "правильности" семантики мне все же ближе использование arrow только в качестве каких-то небольших коллбеков. В остальных случаях я использую fe/fd. Причем я не слепо на них сижу, я сперва как и вы — пересел на "arrow везде, где можно", а потом, поняв их смысл и почему они были сделаны все-таки вернулся обратно.

Полагаю это сильно зависит от кода. Последние годы я пишу в 90+% случаев околофункциональный код. Для меня нет смысла в function, т.к. не существует this. Зато существует необходимость в разнообразных пайпах, комбинациях функций, разных видов функций высшего порядка и пр… Чем меньше я вижу keyword-ов и чем больше я вижу бизнес-логики тем лучше. К примеру я не использую readonly при описании типов, т.к. они по-умолчанию у меня все readonly, и это будет просто визуальным мусором. Очень не хватает поддержки |> в typescript :(

Может быть вам поможет что-то типа:


function pipe() {
    return [...arguments].reduce((value, fn) => fn(value))
}
function double(value) {
    return value * 2
}
function plus10(value) {
    return value + 10
}

const value = 42
pipe(42, double, plus10)
Может быть вам поможет что-то типа:

Подобный механизм мы как раз используем. Не то, конечно, но сгодится. К сожалению в случае сложных generic-типов TypeScript очень часто теряет в таком вот pipe все generic-параметры, превращая всё в any. Мы даже сделали alias для React.memo специально чтобы переписать сложные React types на элементарную конструкцию, чтобы TypeScript не терял генерики.

К сожалению в случае сложных generic-типов TypeScript очень часто теряет в таком вот pipe все generic-параметры, превращая всё в any

А если что-то вроде такого? По идее тут полегче будет описать корректные возвращаемые типы:


Function.prototype.pipe = function (fn) {
  var self = this;
  return function () {
    return fn(self.apply(null, arguments));
  }
}
function dbl (x) { return x*2; }
function p10 (x) { return x+10; }
function hlf (x) { return x/2; }

var f = dbl.pipe(p10).pipe(hlf);
f(7)

Хотя это, скорее, compose получился, но идея ясна

Так не пробовал (правка прототипа, да и очень громоздко как-то). У нас пока такая реализация:


/* prettier-ignore */
declare function pipe<A extends unknown, B, C>
  (
    ab: (arg: A) => B,
    bc: (b: B) => C
  ): (arg: A) => C;
/* prettier-ignore */
declare function pipe<A extends unknown[], B, C, D>
  (
    ab: (...args: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D
  ): (...args: A) => D;
/* prettier-ignore */
declare function pipe<A extends unknown[], B, C, D, E>
  (
    ab: (...args: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
  ): (...args: A) => E;
/* prettier-ignore */
declare function pipe<A extends unknown[], B, C, D, E, F>
  (
    ab: (...args: A) => B,
    bc: (b: B) => C,
    cd: (c: C) => D,
    de: (d: D) => E,
    ef: (e: E) => F,
  ): (...args: A) => F;
Я думал, что я не люблю головоломки. Но, кажется, я неправильно понимал про себя, и на самом деле я их люблю. А уж столько головоломок, сколько естественным образом как-то образуется в JS, нарочно не придумаешь. Например, можно написать так:
Y = f => (x => x(x))(x => f(y => x(x)(y)))

WTF?
(да, это Y-комбинатор)
Поставлю пока статью в закладки, потом почитаю, может быть, внимательнее.

Причем тут Y-комбинатор?

Y = f => (x => x(x))(x => f(y => x(x)(y)))

Y = f => (
  (x => x(x))(
    x => (
      f(y => (
        x(x)(y)
      ))
    )
  )
);

Y = function(f){ return (function(x){ return x(x); })(function(x){ return f(function(y){ return x(x)(y); }); }) };

Y = function (f) {
  return (function (x) {
    return x(x);
  })(function (x) {
    return f(function (y) {
      return x(x)(y);
    });
  });
};

Даже не знаю что из это меньшее месиво. Мне кажется тут вопрос вовсе не в => vs function. А в разбиении кода на отдельные именованные части.

По-скольку ruvds скорее всего все равно до качества статей, то всем новичкам, читающим статью хочу сказать, что здесь есть достаточное количество вредных советов, чтобы к этой статье относиться несерьезно.

Весьма скептично относясь к вопросам, которые указаны в статье, таки не могу удержаться от приведения моего любимого вопроса по JS на собеседованиях


Что вернёт каждый из вызовов?


const array = [1,2,3]
const callback = x => x*2
array.map(callback)
array.map(callback())
array.map(x => callback)
array.map(x => callback())

На удивление, довольно много собеседуемых мной завалились на этом вопросе, что в прочем не оказало решительного влияния на результат собеседования

const callback уже настораживает
Думаю, компилятор откажется это всё выполнять...

А что вас смущает в const callback? Это нормальный код. Единственное, что array.map(callback()) упадёт с ошибкой что NaN это не function.

А что вас смущает в const callback?

Я их боюсь просто, не очень силён. ДЖун я.
Но ошибку разглядел, по наитию.

Uncaught error: interviewee has left the interview.

Если кандидат (не на junior позицию):


  • или валится на таком простом вопросе
  • или же считает ниже своего достоинства решать такие простые задачи

То это даже хорошо, что он has left the interview. Не будет тратить ни своё ни чужое время. Сразу продемонстрирует свои soft skills.

Признаюсь честно, мидлы тоже не очень этот вопрос берут в среднем.
Да даже опытные сеньоры не из мира фронтенда, иногда пишущие на JS, немного впадают в ступор от, казалось бы, несложного вопроса

"функции обратного вызова" я ещё сумел перевести обратно в "callback", но на этом мой мозг сломался:


Главное в генераторах — это то, что получить следующее значение, возвращаемое генератором, можно только тогда, когда оно нужно в коде, использующем генератор

А когда не нужно — то попробуешь получить, но не получишь? "TypeError: You don't really need this value"

Последний пример пал жертвой копипасты. В генераторе надо все же yield использовать.

Особенности сравнения значений — 1 звезда из 5.
Зачем нужны анонимные функции — 3 звезды из 5.
Разница между map и forEach — 4 звезды из 5.


У вас очень сильно искажено представление того, что такое "непростые вопросы" по JS.

var output = (function(x) {
  delete x;
  return x;
})(0);
Тут прямо очень захотелось спросить автора, понимает ли он, для чего какие скобки в IIFE?

А что тут не так со скобками? Скорее здесь оператор delete неправильно используется.

Первая скобка и парная ей в присвоении (как и в любом другом применении в выражении, хоть в коллбэке, хоть после запятой) не нужны.

Ну, если вы про скобки, обрамляющие Function Expression, то да, они не обязательны. Но я бы не сказал, что это уж сильно большой недочет, возможно чисто косметический. А вот с delete тут не все здорово, так как delete не удаляет переменные, а удаляет ключи в объекте.

Ну если в глобальном объекте есть ключ 'x', то delete свою задачу честно выполнит.
Любое значение, которое не входит в этот список, при его преобразовании к логическому типу, превращается в true

Простите, нет.
[0]==true //false
['']==true //false
[null]==false //true


Но:
[null,null]==false //false
И при этом
[null,null]==true //false

Простите, нет.

Да. При чем тут сравнение с true/false, если речь про преобразование? if ([0]) { console.log('yoba'); } else { console.log('ne yoba'); } что выведет?

Это однозначно не сложные вопросы, максимум среднего уровня.
В чём разница между ES6-классами и конструкторами функций?

И главное как раз упущено- попробуйте унаследоваться от Array или Function с помощью конструктора, а не класса. Механика немножко там другая при инициализации цепочки прототипов.
Это однозначно не сложные вопросы, максимум среднего уровня.
В чём разница между ES6-классами и конструкторами функций?

И главное как раз упущено- попробуйте унаследоваться от Array или Function с помощью конструктора, а не класса. Механика немножко там другая при инициализации цепочки прототипов.
Это гайд: «Как не нужно задавать вопросы.»
Ибо я, зная ответы, не могу определить что мне отвечать на не конкретно заданный вопрос.
Любое значение, которое не входит в этот список, при его преобразовании к логическому типу, превращается в true (такие значения называют «истинными» — truthy). Например:

function foo() {… } (функции).


Вообще не пишу на JS, но почему функция равно true, разве функция не возвращает результат своего выполнения, который может быть разным? Или тут идет речь об определении?

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


!!function(){} === true
Отличная статья, было очень интересно скоротать вечерок за ней. Особенно про «глубокую заморозку»
Sign up to leave a comment.