Область видимости и контекст в JavaScript уникальны для этого языка, отчасти благодаря гибкости, которую они приносят. Каждая функция имеет различный контекст и область видимости переменных. Эти концепции лежат в основе некоторых мощных шаблонов проектирования в JavaScript. Однако это также вносит большую путаницу в разработчиков. Ниже подробно описаны различия между контекстом и областью действия в JavaScript, а также то, как их используют различные шаблоны проектирования.
контекст против области действия
Первое, что необходимо уточнить, это то, что контекст и область действия — это разные понятия. За прошедшие годы я заметил, что многие разработчики часто путают эти два термина, неправильно называя один другим. Честно говоря, эти термины стали очень запутанными.
Каждый вызов функции имеет связанную с ним область действия и контекст. По сути, область действия основана на функциях, а контекст — на объектах. Другими словами, область действия связана с доступом к переменным при каждом вызове функции, и каждый вызов независим. Контекстом всегда является значение ключевого слова this, которое является ссылкой на объект, вызывающий текущий исполняемый код.
область видимости переменной
Переменные могут быть определены в локальных или глобальных областях, что приводит к доступу к переменным во время выполнения из разных областей. Глобальные переменные должны быть объявлены вне тела функции, существовать на протяжении всего выполняемого процесса и могут быть доступны и изменены в любой области. Локальные переменные определяются только внутри тела функции и имеют разную область действия для каждого вызова функции. В этой теме присвоение, оценка и работа со значениями только внутри вызова, а к значениям вне области доступа доступ невозможен.
В настоящее время JavaScript не поддерживает область действия на уровне блока. Область действия на уровне блока относится к определению переменных в блоках операторов, таких как операторы if, операторы переключения, операторы цикла и т. д. Это означает, что доступ к переменным за пределами блока операторов невозможен. В настоящее время к любым переменным, определенным в блоке операторов, можно получить доступ за пределами блока операторов. Однако вскоре ситуация изменится, поскольку ключевое слово let было официально добавлено в спецификацию ES6. Используйте его вместо ключевого слова var, чтобы объявить локальные переменные как область уровня блока.
«этот» контекст
Контекст обычно зависит от того, как вызывается функция. Когда функция вызывается как метод объекта, ей присваивается объект, для которого вызывается метод:
Скопируйте код кода следующим образом:
вар объект = {
фу: функция(){
предупреждение (этот === объект);
}
};
объект.foo(); // правда
Тот же принцип применяется при вызове функции для создания экземпляра объекта с использованием нового оператора. При таком вызове значение this будет установлено для вновь созданного экземпляра:
Скопируйте код кода следующим образом:
функция фу(){
предупреждение (это);
}
foo() // окно
новый foo() // foo
При вызове несвязанной функции по умолчанию будет установлен глобальный контекст или объект окна (если в браузере). Однако, если функция выполняется в строгом режиме («использовать строгий»), значение этого параметра по умолчанию будет установлено на неопределенное.
Контекст выполнения и цепочка областей действия
JavaScript — это однопоточный язык, а это означает, что он может одновременно выполнять в браузере только одно действие. Когда интерпретатор JavaScript первоначально выполняет код, он сначала по умолчанию использует глобальный контекст. Каждый вызов функции создает новый контекст выполнения.
Здесь часто возникает путаница. Термин «контекст выполнения» здесь означает область действия, а не контекст, как обсуждалось выше. Это неудачное название, но этот термин определен спецификацией ECMAScript, и у него нет другого выбора, кроме как соблюдать ее.
Каждый раз, когда создается новый контекст выполнения, он добавляется в начало цепочки областей действия и становится стеком выполнения или вызова. Браузер всегда запускается в текущем контексте выполнения в верхней части цепочки областей действия. После завершения он (текущий контекст выполнения) удаляется из вершины стека, и управление возвращается к предыдущему контексту выполнения. Например:
Скопируйте код кода следующим образом:
функция первая(){
второй();
функция вторая(){
третий();
функция третья(){
четвертый();
функция четвертая(){
// сделать что-то
}
}
}
}
первый();
Выполнение предыдущего кода приведет к выполнению вложенных функций сверху вниз до четвертой функции. На этот раз цепочка областей действия сверху вниз будет следующей: четвертая, третья, вторая, первая, глобальная. Четвертая функция может обращаться к глобальным переменным и любым переменным, определенным в первой, второй и третьей функциях, точно так же, как и к ее собственным переменным. Как только четвертая функция завершит выполнение, четвертый контекст будет удален из вершины цепочки областей, и выполнение вернется к функции thrid. Этот процесс продолжается до тех пор, пока весь код не завершит выполнение.
Конфликты имен переменных между различными контекстами выполнения разрешаются путем восхождения по цепочке областей действия от локального к глобальному. Это означает, что локальные переменные с одинаковым именем имеют более высокий приоритет в цепочке областей видимости.
Проще говоря, каждый раз, когда вы пытаетесь получить доступ к переменной в контексте выполнения функции, процесс поиска всегда начинается с собственного объекта переменной. Если переменная, которую вы ищете, не найдена в вашем собственном объекте переменной, продолжайте поиск в цепочке областей. Он будет подниматься по цепочке областей и проверять каждый объект переменной контекста выполнения, чтобы найти значение, соответствующее имени переменной.
закрытие
Замыкание формируется, когда к вложенной функции осуществляется доступ за пределами ее определения (области действия), чтобы ее можно было выполнить после возврата внешней функции. Оно (замыкание) поддерживает (во внутренней функции) доступ к локальным переменным, аргументам и объявлениям функций во внешней функции. Инкапсуляция позволяет нам скрыть и защитить контекст выполнения от внешней области, открывая при этом общедоступный интерфейс, через который можно выполнять дальнейшие операции. Простой пример выглядит так:
Скопируйте код кода следующим образом:
функция фу(){
var local = 'частная переменная';
вернуть функцию bar(){
вернуть местный;
}
}
вар getLocalVariable = foo();
getLocalVariable() // частная переменная
Одним из самых популярных видов застежек является всем известный модульный узор. Он позволяет вам издеваться над публичными, частными и привилегированными членами:
Скопируйте код кода следующим образом:
вар Модуль = (функция(){
вар PrivateProperty = 'foo';
функция PrivateMethod(args){
//сделать что-нибудь
}
возвращаться {
publicProperty: "",
publicMethod: функция (аргументы) {
//сделать что-нибудь
},
привилегированныйМетод: функция (аргументы) {
частныйМетод (аргументы);
}
}
})();
Модули на самом деле чем-то похожи на синглтоны, добавляющие в конце пару круглых скобок и выполняющие их сразу после того, как интерпретатор заканчивает их интерпретацию (выполняйте функцию немедленно). Единственными доступными внешними членами контекста выполнения замыкания являются общедоступные методы и свойства возвращаемого объекта (например, Module.publicMethod). Однако все приватные свойства и методы будут существовать на протяжении всего жизненного цикла программы, поскольку контекст выполнения защищен (замыкания), а взаимодействие с переменными происходит через публичные методы.
Другой тип замыкания называется немедленно вызываемым функциональным выражением IIFE, которое представляет собой не что иное, как самовызываемую анонимную функцию в контексте окна.
Скопируйте код кода следующим образом:
функция(окно){
вар а = 'foo', b = 'бар';
функция частная(){
// сделать что-то
}
окно.Модуль = {
общественность: функция () {
// сделать что-то
}
};
})(этот);
Это выражение очень полезно для защиты глобального пространства имен. Все переменные, объявленные в теле функции, являются локальными переменными и сохраняются во всей среде выполнения посредством замыканий. Этот способ инкапсуляции исходного кода очень популярен как для программ, так и для фреймворков, обычно предоставляя единый глобальный интерфейс для взаимодействия с внешним миром.
Позвоните и подайте заявку
Эти два простых метода, встроенные во все функции, позволяют выполнять функции в пользовательском контексте. Для функции вызова требуется список параметров, а функция применения позволяет передавать параметры в виде массива:
Скопируйте код кода следующим образом:
пользователь функции (первый, последний, возраст) {
// сделать что-то
}
user.call(окно, «Джон», «Доу», 30);
user.apply(window, ['Джон', 'Доу', 30]);
Результат выполнения тот же: пользовательская функция вызывается в контексте окна и предоставляются те же три параметра.
ECMAScript 5 (ES5) представил метод Function.prototype.bind для управления контекстом, который возвращает новую функцию, которая постоянно привязана к первому параметру метода привязки, независимо от того, как функция вызывается. Он корректирует контекст функции посредством замыканий. Вот решение для браузеров, которые его не поддерживают:
Скопируйте код кода следующим образом:
if(!('bind' в Function.prototype)){
Function.prototype.bind = функция(){
var fn = this, context = аргументы[0], args = Array.prototype.slice.call(arguments, 1);
функция возврата(){
вернуть fn.apply(контекст, аргументы);
}
}
}
Он обычно используется при потере контекста: объектно-ориентированной и обработки событий. Это необходимо, поскольку метод узла addEventListener всегда поддерживает контекст выполнения функции как узел, к которому привязан обработчик событий, что важно. Однако если вы используете расширенные объектно-ориентированные методы и вам необходимо поддерживать контекст функции обратного вызова как экземпляр метода, вам необходимо вручную настроить контекст. В этом удобство привязки:
Скопируйте код кода следующим образом:
функция MyClass(){
this.element = document.createElement('div');
this.element.addEventListener('click', this.onClick.bind(this), false);
}
MyClass.prototype.onClick = функция(e){
// сделать что-то
};
Просматривая исходный код функции привязки, вы можете заметить следующую относительно простую строку кода, вызывающую метод массива:
Скопируйте код кода следующим образом:
Array.prototype.slice.call(аргументы, 1);
Интересно, что здесь важно отметить, что объект аргументов на самом деле не является массивом, однако его часто описывают как объект, подобный массиву, во многом похожий на список узлов (результат, возвращаемый методом document.getElementsByTagName()). Они содержат свойства длины, и значения можно индексировать, но они по-прежнему не являются массивами, поскольку не поддерживают собственные методы работы с массивами, такие как срез и push. Однако, поскольку они ведут себя аналогично массивам, методы массива можно вызывать и перехватывать. Если вы хотите выполнять методы массива в контексте, подобном массиву, следуйте приведенному выше примеру.
Этот прием вызова методов других объектов применяется и в объектно-ориентированном, при эмуляции классического наследования (наследования классов) в JavaScript:
Скопируйте код кода следующим образом:
MyClass.prototype.init = функция(){
// вызываем метод инициализации суперкласса в контексте экземпляра "MyClass"
MySuperClass.prototype.init.apply(это, аргументы);
}
Мы можем воспроизвести этот мощный шаблон проектирования, вызывая методы суперкласса (MySuperClass) в экземплярах подкласса (MyClass).
в заключение
Очень важно понять эти концепции, прежде чем приступить к изучению расширенных шаблонов проектирования, поскольку область видимости и контекст играют важную и фундаментальную роль в современном JavaScript. Говорим ли мы о замыканиях, объектно-ориентированном подходе и наследовании или о различных собственных реализациях, контекст и область видимости играют важную роль. Если ваша цель — овладеть языком JavaScript и получить глубокое понимание его компонентов, вашей отправной точкой должны быть область применения и контекст.
Приложение переводчика
Функция связывания, реализованная автором, является неполной. Параметры не могут быть переданы при вызове функции, возвращаемой привязкой. Следующий код устраняет эту проблему:
Скопируйте код кода следующим образом:
if(!('bind' в Function.prototype)){
Function.prototype.bind = функция(){
var fn = this, context = аргументы[0], args = Array.prototype.slice.call(arguments, 1);
функция возврата(){
return fn.apply(context, args.concat(arguments));//исправлено
}
}
}