В настоящее время я участвую в курсе 220: Front End Development with JavaScript в Launch School. Готовясь к оценке для этого курса, я просматривал проекты, которые я завершил, и был поражен одним заданием, в котором нам дали необычную структуру кода. Вот его упрощенная версия:

В строке 1 мы объявляем переменную inventory в глобальной области видимости. Таким образом, мы можем получить к нему доступ в консоли во время разработки. Затем мы используем выражение немедленного вызова функции (IIFE), чтобы добавить свойства к объекту инвентаризации. Мы используем IIFE, чтобы свойства были частными и инкапсулировались в объект инвентаризации.

Последняя строчка - это то место, где я запутался.

$(inventory.init.bind(inventory));

Я долго смотрел на эту строчку.

Я задавался вопросом, почему необходимо привязывать inventory.init method к контексту объекта inventory? И почему эта строка заключена в функцию jQuery?

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

Начнем с первого вопроса:

Почему необходимо привязывать метод inventory.init к контексту объекта inventory?

В моем понимании контекста в JavaScript до сих пор я знал, что если вы вызываете метод для объекта, контекст устанавливается для вызывающего его объекта. Так что, подумал я, не могли бы вы просто написать $(inventory.init)?

Я попробовал это и получил эту ошибку:

Uncaught TypeError: this.bindEvents is not a function

Какие? Но bindEvents определен в объекте inventory, а объект inventory это this правильно? … Неправильный. После использования точки останова в консоли я обнаружил, что this указывает на Window, неявный глобальный контекст. Но как это случилось? Почему был потерян inventory контекст?

Затем наступил мой первый момент а-ха. Я понял, что метод inventory.init вызывается не сразу. Если бы он был вызван, за ним должна была бы стоять такая скобка: inventory.init().

И в отличие от call или apply, bind не вызывает функцию. Вместо этого его цель - навсегда привязать контекст к переданному ему объекту.

Тут я вспомнил важную концепцию: контекст выполнения функции определяется не тогда, когда функция определена, а когда она выполняется.

Например:

var foo = {
  bar: function() {
    return this;
  }
};
var baz = foo.bar;
foo.bar(); // => returns Object { ... }
baz();     // => returns Window { ... }

foo.bar() устанавливает this в foo, потому что это вызов метода. Однако, когда мы назначаем foo.bar baz, а затем вызываем baz(), контекст foo теряется, потому что мы используем вызов функции, поэтому неявный контекст Window используется вместо this.

Чтобы исправить это, мы будем использовать bind так:

var foo = {
  bar: function() {
    return this;
  }
}
var baz = foo.bar.bind(foo);
foo.bar(); // => returns Object { ... }
baz();     // => returns Object { ... }

Когда первая часть головоломки была решена, пришло время перейти к следующему вопросу:

Почему строка заключена в функцию jQuery?

Вот эта строка снова для нашей справки:

$(inventory.init.bind(inventory));

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

inventory.init()

В любом случае, я привык видеть селекторы обертывания функций jQuery, такие как $('p'), что они делают, обертывая функцию?

Тут я вспомнил, что ярлык для записи

$(document).ready(function() { } )

is

$(function() { } );

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

Поскольку функция jQuery не вызывала сразу inventory.init функцию, а вместо этого сохраняла ее для вызова позже, когда документ был готов, контекст теряется, если вы не bind контекст для объекта инвентаризации.

Это связано с тем, что, когда функция фактически вызывается, она вызывается функцией jQuery, что делает ее вызовом функции, поэтому используется неявный контекст функции Window. Вот почему вместо этого мы пишем $(inventory.init.bind(inventory)), чтобы навсегда привязать контекст функции к объекту inventory, чтобы при последующем вызове функции jQuery контекст сохранялся.

Это также помогает объяснить, почему в строке 10

$(“#add_item”).on(“click”, this.newItem.bind(this)); 

мы должны снова связать контекст, потому что мы не сразу вызываем метод this.newItem, а вместо этого jQuery вызовет его позже, используя вызов функции, когда срабатывает событие щелчка.

Итак, вкратце, если бы мы объяснили, что происходит в последней строке кода,

$(inventory.init.bind(inventory));

мы бы сказали, что привязываем метод inventory.init к контексту объекта инвентаризации и передаем его функции jQuery в качестве обратного вызова, который будет вызываться как вызов функции после загрузки документа и готовности DOM.

Уф! Теперь я наконец понял, что происходит в этой последней строке кода, и почувствовал, что лучше понимаю контекст в JavaScript и то, как работает функция jQuery.

Для дальнейшего чтения я настоятельно рекомендую Нежное объяснение ключевого слова this в JavaScript.

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