Ключевое слово this и контекст в javascript
Содержание:
- super
- JS Учебник
- JS Tutorial
- Цикл «for»
- Что такое this?
- Поведение примитивных типов данных
- С помощью bind() мы можем каррировать функцию
- Методы Apply и Call
- Методы объекта
- Объект события
- Вызов функции как функции
- «Одалживание метода»
- Идентификаторы и зарезервированные слова
- Пробельные символы
- Стрелочные функции
- «this» не является фиксированным
- Приоритет способов привязки this
- Фиксированная привязка контекста
- Флаги свойств
- Неявный контекст
- Итого
super
В ES-2015 появилось новое ключевое слово . Оно предназначено только для использования в методах объекта.
Вызов позволяет из метода объекта получить свойство его прототипа.
Например, в коде ниже наследует от .
Вызов из метода объекта обращается к :
Как правило, это используется в классах, которые мы рассмотрим в следующем разделе, но важно понимать, что «классы» здесь на самом деле ни при чём. Свойство работает через прототип, на уровне методов объекта
При обращении через используется текущего метода, и от него берётся . Поэтому работает только внутри методов.
В частности, если переписать этот код, оформив как обычное свойство-функцию, то будет ошибка:
Ошибка возникнет, так как теперь обычная функция и не имеет . Поэтому в ней не работает .
Исключением из этого правила являются функции-стрелки. В них используется внешней функции. Например, здесь функция-стрелка в берёт внешний :
Ранее мы говорили о том, что у функций-стрелок нет своего , : они используют те, которые во внешней функции. Теперь к этому списку добавился ещё и .
Свойство – не изменяемое
При создании метода – он привязан к своему объекту навсегда. Технически можно даже скопировать его и запустить отдельно, и продолжит работать:
В примере выше метод запускается отдельно от объекта, но всё равно, благодаря , сохраняется доступ к его прототипу через .
Это – скорее технический момент, так как методы объекта, всё же, предназначены для вызова в контексте этого объекта. В частности, правила для в методах – те же, что и для обычных функций. В примере выше при вызове без объекта будет .
JS Учебник
JS ГлавнаяJS ВведениеJS УстановкаJS ВыводJS СинтаксисJS ЗаявленияJS КомментарииJS ПеременныеJS ОператорыJS АрифметикаJS ПрисваиванияJS Типы данныхJS ФункцииJS ОбъектыJS ОбластьJS СобытияJS СтрокиJS Методы строкJS ЧислаJS Методы чиселJS МассивыJS Методы массиваJS Сортировка массиваJS Итерация массиваJS ДатыJS Формат датыJS Метод получения датJS Методы набора…JS Математические…JS Случайные числаJS БулевыJS Сравнение…JS Заявления if…elseJS Заявление switchJS Цикл forJS Цикл whileJS Заявление break…JS Преобразование…JS Битовые…JS Регулярные выраженияJS ОшибкиJS ОтладчикJS ПодъемныйJS СтрогийJS Ключевое слово thisJS Руководство стиляJS ПрактикаJS Распространенные ошибкиJS ЭффективностьJS Зарезервированные словаJS ВерсииJS Версия ES5JS Версия ES6JS JSON
JS Tutorial
JS HOMEJS IntroductionJS Where ToJS OutputJS StatementsJS SyntaxJS CommentsJS VariablesJS OperatorsJS ArithmeticJS AssignmentJS Data TypesJS FunctionsJS ObjectsJS EventsJS StringsJS String MethodsJS NumbersJS Number MethodsJS ArraysJS Array MethodsJS Array SortJS Array IterationJS DatesJS Date FormatsJS Date Get MethodsJS Date Set MethodsJS MathJS RandomJS BooleansJS ComparisonsJS ConditionsJS SwitchJS Loop ForJS Loop WhileJS BreakJS Type ConversionJS BitwiseJS RegExpJS ErrorsJS ScopeJS HoistingJS Strict ModeJS this KeywordJS LetJS ConstJS Arrow FunctionJS DebuggingJS Style GuideJS Best PracticesJS MistakesJS PerformanceJS Reserved WordsJS VersionsJS Version ES5JS Version ES6JS JSON
Цикл «for»
Более сложный, но при этом самый распространённый цикл — цикл .
Выглядит он так:
Давайте разберёмся, что означает каждая часть, на примере. Цикл ниже выполняет для от до (но не включая) :
Рассмотрим конструкцию подробней:
часть | ||
---|---|---|
начало | Выполняется один раз при входе в цикл | |
условие | Проверяется перед каждой итерацией цикла. Если оно вычислится в , цикл остановится. | |
шаг | Выполняется после тела цикла на каждой итерации перед проверкой условия. | |
тело | Выполняется снова и снова, пока условие вычисляется в . |
В целом, алгоритм работы цикла выглядит следующим образом:
То есть, начало выполняется один раз, а затем каждая итерация заключается в проверке условия, после которой выполняется тело и шаг.
Если тема циклов для вас нова, может быть полезным вернуться к примеру выше и воспроизвести его работу на листе бумаги, шаг за шагом.
Вот в точности то, что происходит в нашем случае:
Встроенное объявление переменной
В примере переменная счётчика была объявлена прямо в цикле. Это так называемое «встроенное» объявление переменной. Такие переменные существуют только внутри цикла.
Вместо объявления новой переменной мы можем использовать уже существующую:
Любая часть может быть пропущена.
Для примера, мы можем пропустить если нам ничего не нужно делать перед стартом цикла.
Вот так:
Можно убрать и :
Это сделает цикл аналогичным .
А можно и вообще убрать всё, получив бесконечный цикл:
При этом сами точки с запятой обязательно должны присутствовать, иначе будет ошибка синтаксиса.
Что такое this?
указывает на объект, который выполняет текущий кусок JavaScript-кода.
Другими словами, – это ссылка на текущий контекст выполнения. У каждой функции есть этот контекст. Он указывает, где и как эта функция вызывается.
В случае с контекстом выполнения не имеет никакого значения, где и когда функция была объявлена.
В отличие от замыканий (еще одной базовой концепции языка)
Им как раз очень важно, где была объявлена функция
Если интересно, загляните сюда:
- Пора понять замыкания в JavaScript! Часть 1. Готовим фундамент
- Пора понять замыкания в JavaScript! Часть 2. Переходим к делу
Пример:
JavaScript
function bike() {
console.log(this.name);
}
var name = «Ninja»;
var obj1 = { name: «Pulsar», bike: bike };
var obj2 = { name: «Gixxer», bike: bike };
bike(); // «Ninja» / в строгом режиме ошибка
obj1.bike(); // «Pulsar»
obj2.bike(); // «Gixxer»
1 2 3 4 5 6 7 8 9 10 11 12 13 |
functionbike(){ console.log(this.name); } varname=»Ninja»; varobj1={name»Pulsar»,bikebike}; varobj2={name»Gixxer»,bikebike}; bike();// «Ninja» / в строгом режиме ошибка obj1.bike();// «Pulsar» obj2.bike();// «Gixxer» |
Функция обращается к объекту , пытаясь получить его свойство и вывести его в консоль. Другими словами, она пытается получить свойство текущего контекста выполнения.
Если вызвать функцию просто как функцию (), ее контекстом выполнения будет глобальный контекст ( в браузере). Тогда обращение будет равносильно , что в данном коде равно , так как переменные объявленные через записываются в свойства глобального объекта.
Внимание! Внимание!
В строгом режиме ( — который всегда нужно использовать!) функции вызываемые обычным способом не имеют контекста выполнения (). Поэтому в коде будет ошибка, так как функция не может получить поле объекта, которого не существует.
Если же вызвать функцию как метод объекта (через точку или квадратные скобки — ), то контекстом выполнения этой функции станет сам объект . Аналогично при вызове .
В этом случае ссылается на объекты и соответственно, и поле запрашивает у них.
Вот и все, что нужно знать о контексте выполнения. Ничего сложного.
Поведение примитивных типов данных
В JavaScript строки, числа и
булевые переменные:
String, Number, Boolean
относятся к примитивным
типам и, строго говоря, не являются объектами. Однако, для них также можно
вызывать методы базового объекта, например:
console.log("Hello World".toString());
Конечно, эта
запись не имеет особого смысла, т.к. она просто возвратит ту же самую строку,
но она показывает, что строка, литерал, здесь превращается в объект. Как такое
может быть? В действительности, все очень просто. Если виртуальная машина JavaScript «видит», что к
литералу идет обращение как к объекту, то она временно создает объект
соответствующего типа, для этого объекта вызывает указанный метод и, затем,
временный объект уничтожается. В частности, строка обертывается в объект типа String и мы можем для
литерала вызвать, например, метод toUpperCase:
console.log("Hello World".toUpperCase());
Раз это так, то
для любого такого объекта, в нашем случае строки, можно менять и добавлять
новые свойства и методы, используя объект prototype. Как мы
отмечали на предыдущем занятии, если добавить в prototype свойство (или
переопределить существующее), то новый созданный объект будет его содержать.
Например, мы легко можем переопределить метод toUpperCase:
String.prototype.toUpperCase = function() { return this; }
В результате, он
попросту вернет исходную строку. Или, можно добавить новый метод:
String.prototype.len = function() { return this.length; } console.log("Hello World".len());
И так далее.
Однако, этим функционалом следует пользоваться с большой осмотрительностью,
т.к. если в разных программных модулях будут переопределяться одинаковые
методы, но с разной логикой работы, то в основной программе работать будет тот,
что импортируется в последнюю очередь. Это может привести к непредвиденным
ошибкам. В современном программировании есть только один случай, в котором
одобряется изменение встроенных прототипов. Это создание полифилов.
Полифил – это эмуляция метода, который существует в спецификации JavaScript, но ещё не
поддерживается текущим движком JavaScript.
Во всех
остальных случаях лучше не прибегать к этому механизму.
С помощью bind() мы можем каррировать функцию
Каррирование функции, также известное, как частичное применение функции — это использование функции, которая возвращает новую функцию с уже частично выставленными аргументами. Эта функция имеет доступ к хранящимся аргументам и переменным внешней функции. В общем, это звучит куда сложнее, чем есть на самом деле.
Давайте уже применим метод и каррирование вместе. Для начала, у нас есть простенькая функция , которая принимает 3 параметра:
function greet (gender, age, name) {// Если мужчина, то используем Mr., если нет то Ms.. var salutation = gender === "male" ? "Mr. " : "Ms. "; if (age > 25) { return "Hello, " + salutation + name + "."; } else { return "Hey, " + name + "."; } }
И мы используем метод, чтобы каррировать нашу функцию . Как мы говорили ранее, первый аргумент метода будет иметь значение .
// В общем, мы передаем null, так как мы не используем this в функции var greetAnAdultMale = greet.bind (null, "male", 45); greetAnAdultMale ("John Hartlove"); // "Hello, Mr. John Hartlove." var greetAYoungster = greet.bind (null, "", 16); greetAYoungster ("Alex"); // "Hey, Alex." greetAYoungster ("Emma Waterloo"); // "Hey, Emma Waterloo."
Используя метод для каррировния, все наши параметры , кроме последнего аргумента, подставляются автоматически. Таким образом этот аргумент, который мы меняем, используется при вызове новых функций, каррированных с .
В общем, с помощью метода , мы можем выставлять значение для вызова методов на объекте, заимствовать и копировать методы, а также назначать методы переменным, которые выполнятся как функции.
Методы Apply и Call
Два этих метода чуть ли не самые используемые в JavaScript и это неспроста: с их помощью мы можем заимствовать функции и выставлять значение в вызове функции. Более того, функция в буквальном смысле позволяет нам выполнять функцию в массиве параметров, как-будто каждый параметр передаётся функции индивидуально, при её выполнении — отличное решение для вариативных функций; такие функции берут варьирующееся число аргументов, а не заданное количество, как делается в большинстве функций.
Методы объекта
Долгое время в JavaScript термин «метод объекта» был просто альтернативным названием для свойства-функции.
Теперь это уже не так. Добавлены именно «методы объекта», которые, по сути, являются свойствами-функциями, привязанными к объекту.
Их особенности:
- Более короткий синтаксис объявления.
- Наличие в методах специального внутреннего свойства («домашний объект»), ссылающегося на объект, которому метод принадлежит. Мы посмотрим его использование чуть дальше.
Для объявления метода вместо записи нужно написать просто .
Например:
Как видно, для создания метода нужно писать меньше букв. Что же касается вызова – он ничем не отличается от обычной функции. На данном этапе можно считать, что «метод» – это просто сокращённый синтаксис для свойства-функции. Дополнительные возможности, которые даёт такое объявление, мы рассмотрим позже.
Также методами станут объявления геттеров и сеттеров :
Можно задать и метод с вычисляемым названием:
Итак, мы рассмотрели синтаксические улучшения. Если коротко, то не надо писать слово «function». Теперь перейдём к другим отличиям.
Объект события
Чтобы хорошо обработать событие, могут понадобиться детали того, что произошло. Не просто «клик» или «нажатие клавиши», а также – какие координаты указателя мыши, какая клавиша нажата и так далее.
Когда происходит событие, браузер создаёт объект события, записывает в него детали и передаёт его в качестве аргумента функции-обработчику.
Пример ниже демонстрирует получение координат мыши из объекта события:
Некоторые свойства объекта :
- Тип события, в данном случае .
- Элемент, на котором сработал обработчик. Значение – обычно такое же, как и у , но если обработчик является функцией-стрелкой или при помощи привязан другой объект в качестве , то мы можем получить элемент из .
- Координаты курсора в момент клика относительно окна, для событий мыши.
Есть также и ряд других свойств, в зависимости от типа событий, которые мы разберём в дальнейших главах.
Объект события доступен и в HTML
При назначении обработчика в HTML, тоже можно использовать объект , вот так:
Это возможно потому, что когда браузер из атрибута создаёт функцию-обработчик, то она выглядит так: . То есть, её первый аргумент называется , а тело взято из атрибута.
Вызов функции как функции
Пример
function myFunction(a, b) {
return a * b;
}
myFunction(10, 2); // Будем возвращать 20
Функция выше не принадлежит ни одному объекту. Но в JavaScript всегда есть глобальный объект по умолчанию.
В HTML глобальным объектом по умолчанию является сама HTML страница, поэтому функция выше «принадлежит» HTML странице.
В браузере объектом страницы является окно браузера. Функция выше автоматически становится функцией окна.
myFunction() и window.myFunction() это та же функция:
Пример
function myFunction(a, b) {
return a * b;
}window.myFunction(10, 2); // Будут возвращать 20
Это распространенный способ вызова функции JavaScript, но не очень хорошая практика.
Глобальные переменные, методы или функции могут легко создавать конфликты имен и ошибки в глобальном объекте.
«Одалживание метода»
При помощи можно легко взять метод одного объекта, в том числе встроенного, и вызвать в контексте другого.
Это называется «одалживание метода» (на англ. method borrowing).
Используем эту технику для упрощения манипуляций с .
Как мы знаем, не массив, а обычный объект, поэтому таких полезных методов как , , и других у него нет. Но иногда так хочется, чтобы были…
Нет ничего проще! Давайте скопируем метод из обычного массива:
В строке объявлен пустой массив и скопирован его метод
Обратим внимание, мы не вызываем его, а просто копируем. Функция, в том числе встроенная – обычное значение, мы можем скопировать любое свойство любого объекта, и здесь не исключение.
В строке запустили в контексте , как будто он всегда там был.
Почему вызов сработает?
Здесь метод join массива скопирован и вызван в контексте . Не произойдёт ли что-то плохое от того, что – не массив? Почему он, вообще, сработал?
Ответ на эти вопросы простой. В соответствии , внутри реализован примерно так:
Как видно, используется , числовые индексы и свойство . Если эти свойства есть, то все в порядке. А больше ничего и не нужно.
В качестве подойдёт даже обычный объект:
…Однако, копирование метода из одного объекта в другой не всегда приемлемо!
Представим на минуту, что вместо у нас – произвольный объект. У него тоже есть числовые индексы, и мы хотим вызвать в его контексте метод . То есть, ситуация похожа на , но (!) вполне возможно, что у объекта есть свой метод .
Поэтому копировать , как сделано выше, нельзя: если он перезапишет собственный объекта, то будет страшный бардак и путаница.
Безопасно вызвать метод нам поможет :
Мы вызвали метод без копирования. Чисто, безопасно.
Идентификаторы и зарезервированные слова
Идентификатор — это просто имя. В JavaScript идентификаторы выступают в качестве имен переменных и функций, а также меток некоторых циклов. Идентификаторы в JavaScript должны начинаться с буквы, с символа подчеркивания (_) или знака доллара ($). Далее могут следовать любые буквы, цифры, символы подчеркивания или знаки доллара. (Цифра не может быть первым символом, так как тогда интерпретатору трудно будет отличать идентификаторы от чисел.) Примеры допустимых идентификаторов:
Для совместимости и простоты редактирования для составления идентификаторов обычно используются только символы ASCII и цифры. Однако JavaScript допускает возможность использования в идентификаторах букв и цифр из полного набора символов Юникода. Это позволяет программистам давать переменным имена на своих родных языках и использовать в них математические символы:
JavaScript резервирует ряд идентификаторов, которые играют роль ключевых слов самого языка. Эти ключевые слова не могут служить идентификаторами в программах. JavaScript также резервирует некоторые ключевые слова, которые в настоящее время не являются частью языка, но которые могут войти в его состав в будущих версиях. В приведенной таблице перечислены все ключевые слова по категориям:
Категория | Ключевые слова |
---|---|
Базовые идентификаторы | break delete function return typeof case do if switch var catch else in this void continue false instanceof throw while debugger finally new true with default for null try |
Новые ключевые слова в стандарте EcmaScript 5 | class const enum export extends import super |
Зарезервированные слова в строгом режиме (в обычном они доступны) | implements let private public yield interface package protected static arguments eval |
Ключевые слова языка Java (зарезервированы в EcmaScript 3) | abstract double goto native static boolean enum implements package super byte export import private synchronized char extends int protected throws class final interface public transient const float long short volatile |
Предопределенные глобальные переменные и функции | arguments encodeURI Infinity Number RegExp Array encodeURIComponent isFinite Object String Boolean Error isNaN parseFloat SyntaxError Date eval JSON parseInt TypeError decodeURI EvalError Math RangeError undefined decodeURIComponent Function NaN ReferenceError URIError |
Имейте в виду, что конкретные реализации JavaScript могут содержать свои предопределенные глобальные переменные и функции. Кроме того, каждая конкретная платформа JavaScript (клиентская, серверная и прочие) может иметь свой собственный список глобальных свойств.
Пробельные символы
Пробельные символы JavaScript – это пробелы, табы и перевод строки (это действие выполняет клавиша Enter). Как было показано ранее, избыточные пробелы вне строки, пробелы между операторами и другими символами игнорируются интерпретатором JavaScript. Это означает, что следующие три примера присвоения переменной будут иметь одинаковый результат:
Переменная userLocation будет иметь значение «New York City, NY» вне зависимости от стиля присвоения этого значения. Для JavaScript не имеет никакого значения, какие пробельные символы используются.
В написании программ есть одно проверенное и надежное правило: расставляя пробельные символы, следуйте тем же правилам, которыми вы пользуетесь в математике или грамматике. К примеру, строка:
читается проще, чем:
Важным исключением из этого правила является назначение нескольких переменных
Обратите внимание на позицию = в следующем примере:. Все операторы присваивания (=) выравниваются в одну линию с помощью пробелов
Этот тип структуры не используется всеми базами кода, но может повысить удобочитаемость
Все операторы присваивания (=) выравниваются в одну линию с помощью пробелов. Этот тип структуры не используется всеми базами кода, но может повысить удобочитаемость.
Лишние переходы строки тоже игнорируются в JavaScript. Как правило, дополнительные пустые строки вставляются над комментарием и после блока кода.
Стрелочные функции
Стрелочные функции не имеют привязки this. Вместо этого они переходят на следующий уровень исполнения.
Стрелочные функции полезно использовать в тех случаях, когда ключевое слово this должно ссылаться на внешний контекст. Например, если внутри класса у вас есть прослушиватель событий, вы, вероятно, захотите, чтобы this ссылалось на какое-то значение в классе.
В этом примере мы создадим и добавим кнопку в DOM, как мы делали раньше, но у класса будет прослушиватель событий, который будет изменять текстовое значение кнопки при нажатии.
Если вы нажмете кнопку, текстовое содержимое изменится на значение buttonText. Если бы вы не использовали здесь стрелочную функцию, this было бы равно event.currentTarget, и вы не смогли бы использовать его для доступа к значению в классе без явного его связывания. Эта тактика часто используется в методах классов в таких средах, как React.
«this» не является фиксированным
В JavaScript ключевое слово «this» ведёт себя иначе, чем в большинстве других языков программирования. Оно может использоваться в любой функции.
В этом коде нет синтаксической ошибки:
Значение вычисляется во время выполнения кода и зависит от контекста.
Например, здесь одна и та же функция назначена двум разным объектам и имеет различное значение «this» при вызовах:
Правило простое: при вызове значение внутри равно . Так что, в приведённом примере это или .
Вызов без объекта:
Мы даже можем вызвать функцию вовсе без использования объекта:
В строгом режиме () в таком коде значением будет являться . Если мы попытаемся получить доступ к , используя – это вызовет ошибку.
В нестрогом режиме значением в таком случае будет глобальный объект ( для браузера, мы вернёмся к этому позже в главе Глобальный объект). Это – исторически сложившееся поведение , которое исправляется использованием строгого режима ().
Обычно подобный вызов является ошибкой программирования. Если внутри функции используется , тогда ожидается, что она будет вызываться в контексте какого-либо объекта.
Последствия свободного
Если вы до этого изучали другие языки программирования, тогда вы, скорее всего, привыкли к идее «фиксированного » – когда методы, определённые внутри объекта, всегда сохраняют в качестве значения ссылку на свой объект (в котором был определён метод).
В JavaScript является «свободным», его значение вычисляется в момент вызова метода и не зависит от того, где этот метод был объявлен, а зависит от того, какой объект вызывает метод (какой объект стоит «перед точкой»).
Эта идея вычисления в момент исполнения имеет как свои плюсы, так и минусы. С одной стороны, функция может быть повторно использована в качестве метода у различных объектов (что повышает гибкость). С другой стороны, большая гибкость увеличивает вероятность ошибок.
Здесь мы не будем судить о том, является ли это решение в языке хорошим или плохим. Мы должны понимать, как с этим работать, чтобы получать выгоды и избегать проблем.
Приоритет способов привязки this
Когда интерпретатор пытается найти подходящий контекст выполнения для вызываемой функции, он проделывает следующую последовательность действий:
- Сначала проверяет, не вызвана ли функция как конструктор с ключевым словом . В этом случае контекстом всегда будет пустой объект;
- Затем смотрит на фиксированные привязки ();
- Потом на явные привязки или ;
- После этого пытается неявно привязать к объекту, если функция вызвана как метод (через точку или квадратные скобки);
- Если и это не удается (обычный вызов функции), то в качестве контекста выполнения устанавливается глобальный объект () или .
Фиксированная привязка контекста
и вызывают функцию с новым контекстом сразу же — на месте. Но иногда требуется просто привязать нужный контекст, а саму функцию вызвать позже или даже передать ее в другое место программы. Для этого существует метод .
let bike = function() {
console.log(this.name);
}
let obj1 = { name: «Pulsar» };
let obj2 = { name: «Gixxer» };
let bindBike = bike.bind(obj1);
bindBike(); // «Pulsar»
bindBike.call(obj2); // «Pulsar»
1 2 3 4 5 6 7 8 9 10 11 12 13 |
let bike=function(){ console.log(this.name); } let obj1={name»Pulsar»}; let obj2={name»Gixxer»}; let bindBike=bike.bind(obj1); bindBike();// «Pulsar» bindBike.call(obj2);// «Pulsar» |
К функции навсегда привязан контекст . Это значит, что при вызове не будет происходить неявная привязка. Не имеет значения, вызовете ли вы эту функцию напрямую или как метод другого объекта, у нее всегда будет один и тот же .
Флаги свойств
Помимо значения , свойства объекта имеют три специальных атрибута (так называемые «флаги»).
- – если , свойство можно изменить, иначе оно только для чтения.
- – если , свойство перечисляется в циклах, в противном случае циклы его игнорируют.
- – если , свойство можно удалить, а эти атрибуты можно изменять, иначе этого делать нельзя.
Мы ещё не встречали эти атрибуты, потому что обычно они скрыты. Когда мы создаём свойство «обычным способом», все они имеют значение . Но мы можем изменить их в любое время.
Сначала посмотрим, как получить их текущие значения.
Метод Object.getOwnPropertyDescriptor позволяет получить полную информацию о свойстве.
Его синтаксис:
- Объект, из которого мы получаем информацию.
- Имя свойства.
Возвращаемое значение – это объект, так называемый «дескриптор свойства»: он содержит значение свойства и все его флаги.
Например:
Чтобы изменить флаги, мы можем использовать метод Object.defineProperty.
Его синтаксис:
- ,
- Объект и его свойство, для которого нужно применить дескриптор.
- Применяемый дескриптор.
Если свойство существует, обновит его флаги. В противном случае метод создаёт новое свойство с указанным значением и флагами; если какой-либо флаг не указан явно, ему присваивается значение .
Например, здесь создаётся свойство , все флаги которого имеют значение :
Сравните это с предыдущим примером, в котором мы создали свойство «обычным способом»: в этот раз все флаги имеют значение . Если это не то, что нам нужно, надо присвоить им значения в параметре .
Теперь давайте рассмотрим на примерах, что нам даёт использование флагов.
Неявный контекст
Существует четыре основных контекста, в которых можно неявно определить значение ключевого слова this:
- глобальный контекст
- как метод внутри объекта
- как конструктор в функции или классе
- как обработчик событий DOM
Глобальный контекст
В глобальном контексте this ссылается на глобальный объект. Когда вы работаете в браузере, глобальный контекст – это окно. Когда вы работаете в Node.js, глобальный контекст – это global.
Примечание: Если вы еще не знакомы с понятием области видимости в JavaScript, ознакомьтесь с нашим мануалом Переменные, области и поднятие переменных в JavaScript.
В качестве примеров мы будем использовать код в консоли браузера Developer Tools.
Если вы зарегистрируете значение this без какого-либо другого кода, вы увидите, к какому объекту относится this.
Вы увидите, что this – это окно, которое является глобальным объектом браузера.
В статье Переменные, области и поднятие переменных в JavaScript мы говорили о том, что функции имеют собственный контекст для переменных. Сейчас можно подумать, что this будет следовать тем же правилам внутри функции, но это не так. Функция верхнего уровня сохранит ссылку this на глобальный объект.
Для примера давайте напишем функцию верхнего уровня или функцию, которая не связана ни с одним объектом:
Даже внутри функции this все равно относится к window, или к глобальному объекту.
Однако при использовании строгого режима контекст this внутри функции в глобальном контексте будет undefined.
Как правило, строгий режим использовать безопаснее, так как он позволяет уменьшить вероятность непредвиденной области применения ключевого слова this. Вряд ли кто-то захочет обратиться к объекту window, используя this.
За дополнительной информацией о строгом режиме и о том, какие изменения он вносит в отношении ошибок и безопасности, обращайтесь к документации на MDN.
Метод объекта
Метод – это функция объекта или задача, которую может выполнить объект. Метод использует this для ссылки на свойства объекта.
В этом примере this – america.
Во вложенном объекте this ссылается на текущую область метода. В следующем примере this.symbol в объекте details ссылается на details.symbol.
Проще говоря, this ссылается на объект с левой стороны от точки при вызове метода.
Конструктор функций
Когда вы используете ключевое слово new, оно создает экземпляр функции или класса конструктора. Конструкторы функций были стандартным способом инициализации пользовательского объекта до того, как в 2015 вместе с обновлением ECMAScript для JavaScript появился синтаксис класса.
В этом контексте this ссылается на экземпляр Country, который содержится в константе america.
Конструктор класса
Конструктор в классе действует так же, как в функции.
Ключевое слово this в методе describe относится к экземпляру Country, которым является america.
Обработчик событий DOM
В браузере есть специальный контекст this для обработчиков событий. В обработчике событий, вызываемом addEventListener ключевое слово this будет ссылаться на event.currentTarget
Чаще всего по мере необходимости разработчики просто используют event.target или event.currentTarget для доступа к элементам в DOM, но так как ссылка this изменяется в этом контексте, это важно знать
В следующем примере мы создадим кнопку, добавим к ней текст и поместим ее в DOM. Когда мы записываем значение this в обработчик события, он выводит цель.
Как только вы вставите этот код в свой браузер, на странице появится кнопка с надписью «Click me». Если вы нажмете кнопку, вы увидите <button>Click me</button> в консоли, так как нажатие кнопки регистрирует элемент, который является самой кнопкой. Как видите, this ссылается на целевой элемент, который является элементом, к которому мы добавили прослушиватель событий.
Итого
Есть три способа назначения обработчиков событий:
- Атрибут HTML: .
- DOM-свойство: .
- Специальные методы: для добавления, для удаления.
HTML-атрибуты используются редко потому, что JavaScript в HTML-теге выглядит немного странно. К тому же много кода там не напишешь.
DOM-свойства вполне можно использовать, но мы не можем назначить больше одного обработчика на один тип события. Во многих случаях с этим ограничением можно мириться.
Последний способ самый гибкий, однако нужно писать больше всего кода. Есть несколько типов событий, которые работают только через него, к примеру и . Также поддерживает объекты в качестве обработчиков событий. В этом случае вызывается метод объекта .
Не важно, как вы назначаете обработчик – он получает объект события первым аргументом. Этот объект содержит подробности о том, что произошло
Мы изучим больше о событиях и их типах в следующих главах.