Пожалуйста переверните

70 вопросов собеседования JavaScript.

Часть 3.

#translate#javascript

Оригинал статьи 70 JavaScript Interview Questions

21. Какие значения являются ложными (falsy) в JavaScript ?

const falsyValues = ["", 0, null, undefined, NaN, false]

Ложными являются такие значения, которые при конвертации в булево значение становятся false.

22. Как проверить, что значение является ложным?

Используйте функцию Boolean или оператор Двойное НЕ !!.

23. Что делает "use strict"?

use strict - это особенность стандарта ES5, позволяющая запускать код в строгом режиме как в отдельной функции, так и во всем скрипте. Данный режим позволяет нам избегать ошибок на раннем этапе разработки и добавлять ограничения в наш код.

Вот список ограничений:

  • Присвоение значений или доступ к необъявленным переменным.
function returnY() {
  "use strict"
  y = 123
  return y
}
  • Присвоение значений к глобальным переменным, предназначенным только для чтения.
"use strict"
var NaN = NaN
var undefined = undefined
var Infinity = "and beyond"
  • Удаление свойств, которые нельзя удалять.
"use strict"
const obj = {}

Object.defineProperty(obj, "x", {
  value: "1",
})

delete obj.x
  • Дублирование имен параметров.
"use strict"

function someFunc(a, b, b, c) {}
  • Создание переменных с помощью функции eval.
"use strict"

eval("var x = 1;")

console.log(x) // Вызовет Reference Error x is not defined
  • Значением по умолчанию для this будет undefined.
"use strict"

function showMeThis() {
  return this
}

showMeThis() // вернет undefined

24. Каково значение this в Javascript?

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

const carDetails = {
  name: "Ford Mustang",
  yearBought: 2005,
  getName() {
    return this.name
  },
  isRegistered: true,
}

console.log(carDetails.getName()) // выведет Ford Mustang

Это очевидно то, что мы ожидаем, потому что в методе getName мы возвращаем this.name, а this ссылается на объект carDetails, который в данный момент является "хозяином" выполнения функции.

Хорошо, давайте добавим немного кода, чтобы сделать его более странным. Ниже выражения console.log добавьте следующие три строчки кода

var name = "Ford Ranger"
var getCarName = carDetails.getName

console.log(getCarName()) // выведет Ford Ranger

Второй console.log выведет Ford Ranger, что странно, поскольку в нашем первом console.log выводилось Ford Mustang. Причина этого в том, что метод getCarName имеет отличный от window "хозяйский" объект Объявление переменных ключевым словом var в глобальном окружении добавляет свойства в объект window c теми же именами, что и переменные. Помните, что this в глобальном окружении ссылается на объект window в случае, если use strict не был использован.

console.log(getCarName === window.getCarName) // true
console.log(getCarName === this.getCarName) // true

В этом примере this и window ссылаются на один и тот же объект.

Единственным способом решить эту проблему является использование в функциях методов apply и call.

console.log(getCarName.apply(carDetails)) // Ford Mustang
console.log(getCarName.call(carDetails)) // Ford Mustang

Методы apply и call ожидают первым параметром объект, который будет иметь значение this внутри этой функции.

IIEF или Немедленно вызываемые функциональные выражения (Immediately Invoked Function Expression), функции, объявленные в глобальном окружении, анонимные функции и внутренние функции методов внутри объектов имеют в качестве значения по умолчанию для this ссылку на объект window.

;(function() {
  console.log(this)
})() // объект "window"

function iHateThis() {
  console.log(this)
}

iHateThis() // объект "window"

const myFavoriteObj = {
  guessThis() {
    function getName() {
      console.log(this.name)
    }
    getName()
  },
  name: "Marko Polo",
  thisIsAnnoying(callback) {
    callback()
  },
}

myFavoriteObj.guessThis() // объект "window"
myFavoriteObj.thisIsAnnoying(function() {
  console.log(this) // объект "window"
})

У нас есть два способа получить значение свойства name объекта myFavoriteObj равное Marko Polo.

Первый способ - сохранить значение this в переменной.

const myFavoriteObj = {
  guessThis() {
    const self = this // сохраняет значение this в переменной self
    function getName() {
      console.log(self.name)
    }
    getName()
  },
  name: "Marko Polo",
  thisIsAnnoying(callback) {
    callback()
  },
}

Мы переопредели значение this на myFavoriteObj и, таким образом, получили доступ к внутренней функции getName.

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

const myFavoriteObj = {
  guessThis() {
    const getName = () => {
      // копирует значение "this" за пределы этой стрелочной функции
      console.log(this.name)
    }
    getName()
  },
  name: "Marko Polo",
  thisIsAnnoying(callback) {
    callback()
  },
}

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

25. Что такое прототип объекта?

Проще говоря, прототип - это схема объекта. Он используется как запасной вариант для свойств и методов объекта, если их нет в текущем объекте. Это способ распространения свойств и функциональности между объектами. Это основная концепция прототипного наследования JavaScript.

const o = {}
console.log(o.toString()) // [object Object]

Даже при том, что метода o.toString не существует, это не вызывает ошибки, а возвращает [object Object]. Когда свойства объекта не существует, начинается поиск этого свойства у прототипа, у простотипа прототипа и так далее по цепочке прототипов. Финалом этой цепочки является Object.prototype.

console.log(o.toString === Object.prototype.toString) // выводит true
// Это значит, что мы произвели поиск по цепочке прототипов,
// достигли Object.prototype и использовали метод "toString"

26. Что такое IIFE и для чего оно применяется?

IIEF или Немедленно вызываемое функциональное выражение (Immediately Invoked Function Expression) - это функция, которая вызывается или исполняется сразу же после создания или объявления. Сиснтаксис для IIEF такой - мы оборачиваем функцию круглыми скобками и сразу же вызываем ее при помощи круглых скобок (function(){...})().

(function () {
...
}());

(function () {
...
})();

(function named(params) {
...
})();

(() => {
...
})();

(function (global) {
...
})(window);

const utility = (function () {
   return {
      // utilities
   };
})();

Это все валидные примеры использования IIEF. Со второго по последний примеры демонстрируют как мы можем передавать параметры в функцию. В последнем примере продемонстрировано, как мы можем сохранить результат вызова IIEF в переменной для последующего использования.

Лучшее использование IIFE - это создание функциональных возможностей настройки инициализации и предотвращение конфликтов имен с другими переменными в глобальной области видимости или загрязнения глобального пространства имен. Давайте взглянем на пример.

<script src="https://cdnurl.com/somelibrary.js"></script>

Предположим, что у нас есть ссылка на библиотеку somelibrary.js в которой находятся некоторые глобальные функции, которые мы можем использовать в нашем коде. Но так же в библиотеке находятся два метода, которые мы не используем - createGraph и drawGraph, потому что в этих методах есть баги. И мы хотим реализовать наши собственные методы createGraph и drawGraph.

  • Одним из способов решения проблемы является изменение структуры наших скриптов.
<script src="https://cdnurl.com/somelibrary.js"></script>
<script>
  function createGraph() {
    // createGraph логика
  }
  function drawGraph() {
    // drawGraph логика
  }
</script>

Тут мы переопределяем эти два метода, предоставляемые библиотекой.

  • Следующий способ - изменение имен наших собственных функций.
<script src="https://cdnurl.com/somelibrary.js"></script>
<script>
  function myCreateGraph() {
    // createGraph логика
  }
  function myDrawGraph() {
    // drawGraph логика
  }
</script>

При этом решении мы меняем вызовы функций на новые имена функций.

  • И наконец, мы можем использовать IIEF.
<script src="https://cdnurl.com/somelibrary.js"></script>
<script>
  const graphUtility = function() {
    function createGraph() {
      // createGraph логика
    }
    function drawGraph() {
      // drawGraph логика
    }
    return {
      createGraph,
      drawGraph,
    }
  }
</script>

В этом решении мы создаем служебную переменную, которая является результатом IIFE и возвращает объект, который содержит два метода - createGraph и drawGraph.

В следующем примере показана другая проблема, с которой помогает IIEF.

var li = document.querySelectorAll(".list-group > li")
for (var i = 0, len = li.length; i < len; i++) {
  li[i].addEventListener("click", function(e) {
    console.log(i)
  })
}

Предположим у нас есть элемент ul класса list-group, который содержит 5 дочерних элементов li. И мы хотим вывести в консоль значение переменной i при клике на отдельный элемент li.

Но код ведет себя иначе - выводит 5-ку при клике на любой элемент li. Эта проблема связана с работой замыканий (Closures). Замыкания - это просто способность функций запоминать ссылки на переменные в текущей области, в области родительских функций и в глобальной области. Когда мы объявляем переменную с помощью ключевого слова var в глобальном окружении, очевидно, что мы создаем глобальную переменную i. Таким образом, кликая на элементе i, мы получаем в консоли 5 так как это и есть значение переменной i, когда мы позже ссылаемся на него в функции обратного вызова.

  • Решением этой проблемы является использование IIEF.
var li = document.querySelectorAll(".list-group > li")
for (var i = 0, len = li.length; i < len; i++) {
  ;(function(currentIndex) {
    li[currentIndex].addEventListener("click", function(e) {
      console.log(currentIndex)
    })
  })(i)
}

Это решение работает, потому что IIEF создает отдельное окружение для каждой итерации, значение переменной i сохраняется в параментр currentIndex и при исполнении IIEF значение currentIndex для каждой итерации становится разным.

27. Каково предназначение метода Function.prototype.apply?

apply вызывает функцию, указывающую объект this или «владельца» этой функции в момент вызова.

const details = {
  message: "Hello World!",
}

function getMessage() {
  return this.message
}

getMessage.apply(details) // возвращает 'Hello World!'

Данный метод работает также, как и Function.prototype.call. Единственная разница в том, как мы передаем аргументы. В методе apply мы передаем аргументы массивом.

const person = {
  name: "Marko Polo",
}

function greeting(greetingMessage) {
  return `${greetingMessage} ${this.name}`
}

greeting.apply(person, ["Hello"]) // возвращает "Hello Marko Polo!"

28. Каково предназначение метода Function.prototype.call?

call вызывает функцию, указывающую объект this или «владельца» этой функции в момент вызова.

const details = {
  message: "Hello World!",
}

function getMessage() {
  return this.message
}

getMessage.call(details) // возвращает 'Hello World!'

Данный метод работает также, как и Function.prototype.apply. Единственная разница в том, как мы передаем аргументы. В методе call мы передаем аргументы напрямую, разделяя запятой.

const person = {
  name: "Marko Polo",
}

function greeting(greetingMessage) {
  return `${greetingMessage} ${this.name}`
}

greeting.call(person, "Hello") // возвращает "Hello Marko Polo!"

29. В чем разница между методами Function.prototype.apply и Function.prototype.call?

Единственная разница между методами apply и call заключается в способе передачи аргументов. В apply мы передаем аргументы массивом, а в call - напрямую, разделяя их запятыми.

const obj1 = {
  result: 0,
}

const obj2 = {
  result: 0,
}

function reduceAdd() {
  let result = 0
  for (let i = 0, len = arguments.length; i < len; i++) {
    result += arguments[i]
  }
  this.result = result
}

reduceAdd.apply(obj1, [1, 2, 3, 4, 5]) // возвращает 15
reduceAdd.call(obj2, 1, 2, 3, 4, 5) // возвращает 15

30. Каково предназначение метода Function.prototype.bind?

Метод bind возвращает новую функцию, которая связывается со специфическим значением this или объектом-"хозяином". И мы можем использовать это в нашем коде. Методы call и apply вызывают функцию немедленно вместо возвращения новой функции, так как это делает метод bind.

import React from "react"

class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      value: "",
    }
    this.handleChange = this.handleChange.bind(this)
    // Связывает метод "handleChange" с компонентом "MyComponent"
  }

  handleChange(e) {
    // какое-то действие
  }

  render() {
    return (
      <>
        <input
          type={this.props.type}
          value={this.state.value}
          onChange={this.handleChange}
        />
      </>
    )
  }
}

Предыдущая статья Часть 2

Следующая статья Часть 4