В настоящее время я участвую в курсе 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.
Примечание. Поскольку я относительно новичок в этих концепциях, вполне возможно, что в этом сообщении есть ошибки, которые необходимо исправить. Пожалуйста, помогите мне и внесите исправления в комментарии, и я отредактирую этот пост.