Многоликий this в JS
В этом посте я сделаю все возможное, чтобы объяснить одну из самых фундаментальных частей JavaScript: контекст выполнения. Если вы много используете JS-фреймворки, понимание «this» может сначала показаться приятным дополнением. Однако, если вы собираетесь серьезно относиться к программированию, понимание контекста абсолютно важно для того, чтобы быть JavaScript программистом.
Мы используем this во многом так же, как мы используем его на естественном языке. Мы предпочли бы написать «Моя мама посинела, это очень тревожно», вместо «Моя мама посинела. Становление моей мамы синей очень беспокоит». Зная контекст this, мы можем понять, что нас так беспокоит.
Попробуем соединить его с языком программирования. В JavaScript мы используем this как ярлык, ссылку. Он относится к объектам, переменным, и мы используем их в контексте.
Это очень тревожно, но не бойтесь. Через минуту все станет ясно.
Глобальный контекст
Это происходит в браузере постоянно. Сотни тысяч разработчиков используют this без контекста. Наш бедный браузер делает все возможное, чтобы соотнести this применительно к глобальному объекту, window в этом конкретном примере.
Вне любой функции в глобальном контексте выполнения this относится к глобальному контексту (объекту window).
Контекст функции
Контекст в JavaScript связан с объектами. Он ссылается на объект внутри выполняемой функции. this относится к объекту, в котором выполняется функция.
Когда функция вызывается как метод объекта, она ссылается на объект, который вызывал метод.
True — Мы все еще находимся в глобальном контексте.
False — функция вызывается как метод объекта.
True — функция вызывается как метод объекта.
False — Функция вызывается как метод объекта y_obj, поэтому this его контекст.
Example 4
В строгом режиме (strict mode) правила разнятся. Контекст остается таким, каким он был установлен. В этом конкретном примере this
Example 5
Как и в предыдущем примере, функция вызывается как метод объекта, независимо от того, как он был определен.
Example 6
this является динамическим, то есть он может меняться от одного объекта к другому
Example 7
Мы можем назвать фрукты this по имени объекта.
Example 8
Итак, new измененяет правила. Оператор new создает экземпляр объекта. Контекст функции будет ссылаться на созданный экземпляр объекта.
Call, apply, bind
Эти методы позволяют нам выполнять любую функцию в любом желаемом контексте. Посмотрим, как они работают, на примерах.
Example 1
xo xo - Мы назвали тест в глобальном контексте.
lorem ipsum - Используя вызов, мы вызываем тест в контексте foo.
lorem ipsum - Используя apply, мы вызываем тест в контексте foo.
Эти два метода позволяют выполнять функцию в любом желаемом контексте.
apply позволяет вызывать функцию с аргументами в виде массива, тогда как call требует, чтобы параметры были явно указаны.
Example 2
Undefined - В объекте документа нет переменной.
15 - Мы создали новый объект {a: 15} и вызвали test в этом контексте.
Метод bind устанавливает постоянный контекст в требуемое значение.
После использования bind this неизменяемо даже при вызове call, apply или bind.
Стрелочные функции (ES6)
Стрелочные функции были введены как фича ES6. Их можно рассматривать как очень удобный инструмент. Однако, вы должны знать, что стрелочные функции работают иначе, чем обычные функции с точки зрения контекста. Посмотрим.
Example 1
Когда мы используем стрелочные функции, this сохраняет значение охватывающего лексического контекста.
Example 2
Обратите внимание на разницу между стрелочной и обычной функцией. Сo стрелочной функцией мы находимся в контексте window.
Можно сказать, что:
x => this.y равно function(x) {return this.y}.bind(this)
Стрелочная функция всегда имеет значение this и поэтому не может использоваться как конструктор. Этот последний пример иллюстрирует разницу.
Example 3
Что почитать
http://www.joshuakehn.com/2011/10/20/Understanding-JavaScript-Context.html
http://ryanmorr.com/understanding-scope-and-context-in-javascript/
https://hackernoon.com/execution-context-in-javascript-319dd72e8e2c
http://2ality.com/2012/04/arrow-functions.html
Присоединяйтесь к нашим каналам FrontEndDev и Web Stack в Telegram, чтобы не пропустить самое интересное!
Оригинал статьи The many faces of this in javascript
you-dont-know-js-ru/ch3.md at master · azat-io/you-dont-know-js-ru · GitHub
В главе 1 мы отбросили различные ложные представления о this
и взамен изучили, что привязка this
происходит при каждом вызове функции, целиком на основании ее
Точка вызова
Чтобы понять привязку this
, мы должны понять что такое точка вызова: это место в коде, где была вызвана функция (не там, где она объявлена). Мы должны исследовать точку вызова, чтобы ответить на вопрос: на что же этот this
указывает?
В общем поиск точки вызова выглядит так: «найти откуда вызывается функция», но это не всегда так уж легко, поскольку определенные шаблоны кодирования могут ввести в заблуждение относительно
Важно поразмышлять над стеком вызовов (стеком функций, которые были вызваны, чтобы привести нас к текущей точке исполнения кода). Точка вызова, которая нас интересует, находится в вызове перед текущей выполняемой функцией.
Продемонстрируем стек вызовов и точку вызова:
function baz() { // стек вызовов: `baz` // поэтому наша точка вызова — глобальная область видимости console.log( "baz" ); bar(); // <-- точка вызова для `bar` } function bar() { // стек вызовов: `baz` -> `bar` // поэтому наша точка вызова в `baz` console.log( "bar" ); foo(); // <-- точка вызова для `foo` } function foo() { // стек вызовов: `baz` -> `bar` -> `foo` // поэтому наша точка вызова в `bar` console.log( "foo" ); } baz(); // <-- точка вызова для `baz`
Позаботьтесь при анализе кода о том, чтобы найти настоящую точку вызова (из стека вызовов), поскольку это единственная вещь, которая имеет значение для привязки this
.
Примечание: Вы можете мысленно визуализировать стек вызовов посмотрев цепочку вызовов функций в том порядке, в котором мы это делали в комментариях в коде выше. Но это утомительно и чревато ошибками. Другой путь посмотреть стек вызовов — это использование инструмента отладки в вашем браузере. Во многих современных настольных браузерах есть встроенные инструменты разработчика, включающие JS-отладчик. В вышеприведенном коде вы могли бы поставить точку остановки в такой утилите на первой строке функции
или просто вставить оператор debugger;
в первую строку. Как только вы запустите страницу, отладчик остановится в этом месте и покажет вам список функций, которые были вызваны, чтобы добраться до этой строки, каковые и будут являться необходимым стеком вызовов. Таким образом, если вы пытаетесь выяснить привязку this
, используйте инструменты разработчика для получения стека вызовов, затем найдите второй элемент стека от его вершины и это и будет реальная точка вызова.
Ничего кроме правил
Теперь обратим наш взор на то, как точка вызова определяет на что будет указывать this
во время выполнения функции.
Вам нужно изучить точку вызова и определить какое из 4 правил применяется. Сначала разъясним каждое из 4 правил по отдельности, а затем проиллюстрируем их порядок приоритета, для случаев когда к точке вызова могут применяться несколько правил сразу.
Привязка по умолчанию
Первое правило, которое мы изучим, исходит из самого распространенного случая вызовов функции: отдельный вызов функции. Представьте себе это правило this
как правило, действующее по умолчанию когда остальные правила не применяются.
Рассмотрим такой код:
function foo() { console. log( this.a ); } var a = 2; foo(); // 2
Первая вещь, которую можно отметить, если вы еще не сделали этого, то, что переменные, объявленные в глобальной области видимости, как например var a = 2
, являются синонимами глобальных свойств-объектов с таким же именем. Они не являются копиями друг друга, они и есть одно и то же. Представляйте их как две стороны одной монеты.
Во-вторых, видно, что когда вызывается foo()
this.a
указывает на нашу глобальную переменную a
. Почему? Потому что в этом случае, для this
применяется привязка по умолчанию при вызове функции и поэтому this
указывает на глобальный объект.
Откуда мы знаем, что здесь применяется привязка по умолчанию? Мы исследуем точку вызова, чтобы выяснить как вызывается foo()
. В нашем примере кода foo()
вызывается по прямой, необернутой ссылке на функцию. Ни одного из демонстрируемых далее правил тут не будет применено, поэтому вместо них применяется привязка по умолчанию.
Когда включен strict mode
, объект ‘global’ не подпадает под действие привязки по умолчанию, поэтому в противоположность обычному режиму this
устанавливается в undefined
.
function foo() { "use strict"; console.log( this.a ); } var a = 2; foo(); // TypeError: `this` is `undefined`
Едва уловимая, но важная деталь: даже если все правила привязки this
целиком основываются на точке вызова, глобальный объект подпадает под привязку по умолчанию только если содержимое foo()
не выполняется в режиме strict mode
; Состояние strict mode
в точке вызова foo()
не имеет значения.
function foo() { console.log( this.a ); } var a = 2; (function(){ "use strict"; foo(); // 2 })();
Примечание: К намеренному смешиванию включения и выключения strict mode
в коде обычно относятся неодобрительно. Вся программа пожалуй должна быть либо строгой, либо нестрогой. Однако, иногда вы подключаете сторонние библиотеки, в которых этот режим строгости отличается от вашего, поэтому нужно отнестись с вниманием к таким едва уловимым деталям совместимости.
Неявная привязка
Рассмотрим еще одно правило: есть ли у точки вызова объект контекста, также называемый как владеющий или содержащий объект, хотя эти альтернативные термины могут немного вводить в заблуждение.
Рассмотрим:
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; obj.foo(); // 2
Во-первых, отметим способ, которым была объявлена foo()
, а затем позже добавлена как ссылочное свойство в obj
. Независимо от того была ли foo()
изначально объявлена в obj
или добавлена позднее как ссылка (как в вышеприведенном коде), ни в том, ни в другом случае функция на самом деле не «принадлежит» или «содержится» в объекте obj
.
Однако, точка вызова использует контекст obj
, чтобы ссылаться на функцию, поэтому можно сказать, что объект obj
«владеет» или «содержит» ссылку на функцию в момент вызова функции.
Какое название вы бы ни выбрали для этого шаблона, в момент когда вызывается foo()
, ей предшествует объектная ссылка на obj
. Когда есть объект контекста для ссылки на функцию, правило неявной привязки говорит о том, что именно этот объект и следует использовать для привязки this
к вызову функции.
Поскольку obj
является this
для вызова foo()
, this.a
— синоним obj.a
.
Только верхний/последний уровень ссылки на свойство объекта в цепочке имеет значение для точки вызова. Например:
function foo() { console.log( this.a ); } var obj2 = { a: 42, foo: foo }; var obj1 = { a: 2, obj2: obj2 }; obj1. obj2.foo(); // 42
Неявно потерянный
Одним из самых распространенных недовольств, которые вызывает привязка this
— когда неявно привязанная функция теряет эту привязку, что обычно означает что она вернется к привязке по умолчанию, либо объекта global
, либо undefined
, в зависимости от режима strict mode
.
Представим такой код:
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var bar = obj.foo; // ссылка/алиас на функцию! var a = "ой, глобальная"; // `a` также и свойство глобального объекта bar(); // "ой, глобальная"
Несмотря на то, что bar
по всей видимости ссылка на obj.foo
, фактически, это на самом деле другая ссылка на саму foo
. Более того, именно точка вызова тут имеет значение, а точкой вызова является bar()
, который является прямым непривязанным вызовом, а следовательно применяется привязка по умолчанию.
Более неочевидный, более распространенный и более неожиданный путь получить такую ситуацию когда мы предполагаем передать функцию обратного вызова:
function foo() { console.log( this.a ); } function doFoo(fn) { // `fn` — просто еще одна ссылка на `foo` fn(); // <-- точка вызова! } var obj = { a: 2, foo: foo }; var a = "ой, глобальная"; // `a` еще и переменная в глобальном объекте doFoo( obj.foo ); // "ой, глобальная"
Передаваемый параметр — всего лишь неявное присваивание, а поскольку мы передаем функцию, это неявное присваивание ссылки, поэтому окончательный результат будет таким же как в предыдущем случае.
Что если функция, в которую вы передаете функцию обратного вызова, не ваша собственная, а встроенная в язык? Никакой разницы, такой же результат.
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var a = "ой, глобальная"; // `a` еще и переменная в глобальном объекте setTimeout( obj.foo, 100 ); // "ой, глобальная"
Поразмышляйте над этой грубой теоретической псевдо-реализацией setTimeout()
, которая есть в качестве встроенной в JavaScript-среде:
function setTimeout(fn,delay) { // подождать (так или иначе) `delay` миллисекунд fn(); // <-- точка вызова! }
Достаточно распространенная ситуация, когда функции обратного вызова теряют свою привязку this
, как мы только что видели. Но еще один способ, которым this
может удивить нас, когда функция, которой мы передаем нашу функцию обратного вызова, намеренно меняет this
для этого вызова. Обработчики событий в популярных JavaScript-библиотеках часто любят, чтобы в вашей функции обратного вызова this
принудительно указывал, например, на DOM-элемент, который вызвал это событие. Несмотря на то, что иногда это бывает полезно, в другое время это может прямо таки выводить из себя. К сожалению, эти инструменты редко дают возможность выбирать.
Каким бы путем ни менялся неожиданно this
, у вас в действительности нет контроля над тем как будет вызвана ваша функция обратного вызова, таким образом у вас нет возможности контролировать точку вызова, чтобы получить заданную привязку. Мы кратко рассмотрим способ «починки» этой проблемы починив this
.
Явная привязка
В случае неявной привязки, как мы только что видели, нам требуется менять объект, о котором идет речь, чтобы включить в него функцию и использовать эту ссылку на свойство-функцию, чтобы опосредованно (неявно) привязать this
к этому объекту.
Но, что если вам надо явно использовать при вызове функции указанный объект для привязки this
, без помещения ссылки на свойство-функцию в объект?
У «всех» функций в языке есть несколько инструментов, доступных для них (через их [[Прототип]]
, о котором подробности будут позже), которые могут оказаться полезными в решении этой задачи. Говоря конкретнее, у функций есть методы call(..)
и apply(..)
. Технически, управляющие среды JavaScript иногда обеспечивают функции, которые настолько специфичны, что у них нет такой функциональности. Но таких мало. Абсолютное большинство предоставляемых функций и конечно все функции, которые создаете вы сами, безусловно имеют доступ к call(..)
и apply(..)
.
Как работают эти инструменты? Они оба принимают в качестве первого параметра объект, который будет использоваться в качестве this
, а затем вызывают функцию с указанным this
. Поскольку вы явно указываете какой this
вы хотите использовать, мы называем такой способ явной привязкой.
Представим такой код:
function foo() { console.log( this.a ); } var obj = { a: 2 }; foo.call( obj ); // 2
Вызов foo
с явной привязкой посредством foo.call(..)
позволяет нам указать, что this
будет obj
.
Если в качестве привязки this
вы передадите примитивное значение (типа string
, boolean
или number
), то это примитивное значение будет обернуто в свою объектную форму (new String(..)
, new Boolean(..)
или new Number(..)
соответственно). Часто это называют «обертка».
*Примечание: * В отношении привязки this
call(..)
и apply(..)
идентичны. Они по-разному ведут себя с дополнительными параметрами, но мы не будем сейчас на этом останавливаться.
К сожалению, явная привязка сама по себе все-таки не предлагает никакого решения для указанной ранее проблемы «потери» функцией ее привязки this
, либо оставляет это на усмотрение фреймворка.
Жесткая привязка
Но поиграв с вариациями на тему явной привязки на самом деле можно получить желаемое. Пример:
function foo() { console.log( this.a ); } var obj = { a: 2 }; var bar = function() { foo.call( obj ); }; bar(); // 2 setTimeout( bar, 100 ); // 2 // `bar` жестко привязывает `this` в `foo` к `obj` // поэтому его нельзя перекрыть bar.call( window ); // 2
Давайте изучим как работает этот вариант. Мы создаем функцию bar()
, которая внутри вручную вызывает foo.call(obj)
, таким образом принудительно вызывая foo
с привязкой obj
для this
. Неважно как вы потом вызовете функцию bar
, она всегда будет вручную вызывать foo
с obj
. Такая привязка одновременно явная и сильная, поэтому мы называем ее жесткой привязкой.
Самый типичный способ обернуть функцию с жесткой привязкой — создать сквозную обертку, передающую все параметры и возвращающую полученное значение:
function foo(something) { console. log( this.a, something ); return this.a + something; } var obj = { a: 2 }; var bar = function() { return foo.apply( obj, arguments ); }; var b = bar( 3 ); // 2 3 console.log( b ); // 5
Еще один способ выразить этот шаблон — создать переиспользуемую вспомогательную функцию:
function foo(something) { console.log( this.a, something ); return this.a + something; } // простая вспомогательная функция `bind` function bind(fn, obj) { return function() { return fn.apply( obj, arguments ); }; } var obj = { a: 2 }; var bar = bind( foo, obj ); var b = bar( 3 ); // 2 3 console.log( b ); // 5
Поскольку жесткая привязка — очень распространеный шаблон, он есть как встроенный инструмент в ES5: Function.prototype.bind
, а используется вот так:
function foo(something) { console.log( this.a, something ); return this.a + something; } var obj = { a: 2 }; var bar = foo.bind( obj ); var b = bar( 3 ); // 2 3 console.log( b ); // 5
bind(. .)
возвращает новую функцию, в которой жестко задан вызов оригинальной функции с именно тем контекстом this
, который вы указываете.
Примечание: Начиная с ES6, в функции жесткой привязки, выдаваемой bind(..)
, есть свойство .name
, наследуемое от исходной функции. Например: у bar = foo.bind(..)
должно быть в bar.name
значение "bound foo"
, которое является названием вызова функции, которое должно отражаться в стеке вызовов.
«Контексты» в вызовах API
Функции многих библиотек, и разумеется многие встроенные в язык JavaScript и во внешнее окружение функции, предоставляют необязательный параметр, обычно называемый «контекст», который спроектирован как обходной вариант для вас, чтобы не пользоваться bind(..)
, чтобы гарантировать, что ваша функция обратного вызова использует данный this
.
Например:
function foo(el) { console. log( el, this.id ); } var obj = { id: "awesome" }; // используем `obj` как `this` для вызовов `foo(..)` [1, 2, 3].forEach( foo, obj ); // 1 awesome 2 awesome 3 awesome
Внутренне эти различные функции почти наверняка используют явную привязку через call(..)
или apply(..)
, избавляя вас от хлопот.
Привязка
new
Четвертое и последнее правило привязки this
потребует от нас переосмысления самого распространенного заблуждения о функциях и объектах в JavaScript.
В традиционных классо-ориентированных языках, «конструкторы» — это особые методы, связанные с классами, таким образом, что когда создается экземпляр класса с помощью операции new
, вызывается конструктор этого класса. Обычно это выглядит как-то так:
something = new MyClass(..);
В JavaScript есть операция new
и шаблон кода, который используется для этого, выглядит в основном идентично такой же операции в класс-ориентированных языках; многие разработчики полагают, что механизм JavaScript выполняет что-то похожее. Однако, на самом деле нет никакой связи с классо-ориентированной функциональностью у той, что предполагает использование new
в JS.
Во-первых, давайте еще раз посмотрим что такое «конструктор» в JavaScript. В JS конструкторы — это всего лишь функции, которые, так уж получилось, были вызваны с операцией new
перед ними. Они ни связаны с классами, ни создают экземпляров классов. Они — даже не особые типы функций. Они — всего лишь обычные функции, которые, по своей сути, «украдены» операцией new
при их вызове.
Например, функция Number(..)
действует как конструктор, цитируя спецификацию ES5.1:
15.7.2 Конструктор Number
Когда Number вызывается как часть выражения new, оно является конструктором: оно инициализирует только что созданный объект.
Так что, практически любая старенькая функция, включая встроенные объектные функции, такие как Number(..)
(см. главу 3), могут вызываться с new
перед ними и это превратит такой вызов функции в вызов конструктора. Это важное, но едва уловимое различие: нет такой вещи как «функции-конструкторы», а скорее есть вызовы, конструирующие из функций.
Когда функция вызывается с указанием перед ней new
, также известный как вызов конструктора, автоматически выполняются следующие вещи:
- Создается новенький объект (т.е. конструируется) прямо из воздуха
- Только что сконструированный объект связывается с
[[Прототипом]]
- Только что сконструированный объект устанавливается как привязка
this
для этого вызова функции - За исключением тех случаев, когда функция возвращает свой собственный альтернативный объект, вызов функции с
new
автоматически вернет только что сконструированный объект.
Пункты 1, 3 и 4 применимы к нашему текущему обсуждению. Сейчас мы пропустим пункт 2 и вернемся к нему в главе 5.
Взглянем на такой код:
function foo(a) { this.a = a; } var bar = new foo( 2 ); console.log( bar.a ); // 2
Вызывая foo(..)
с new
впереди нее, мы конструируем новый объект и устанавливаем этот новый объект как this
для вызова foo(..)
. Таким образом new
— единственный путь, которым this
при вызове функции может быть привязан. Мы называем это привязкой new.
Всё по порядку
Итак, теперь мы раскрыли 4 правила привязки this
в вызовах функций. Всё, что вам нужно сделать — это найти точку вызова и исследовать ее, чтобы понять какое правило применяется. Но что если к точке вызова можно применить несколько соответствующих правил? Должен быть порядок очередности применения этих правил, а потому далее мы покажем в каком порядке применяются эти правила.
Думаю, совершенно ясно, что привязка по умолчанию имеет самый низкий приоритет из четырех. Поэтому мы отложим ее в сторону.
Что должно идти раньше: неявная привязка или явная привязка? Давайте проверим:
function foo() { console.log( this.a ); } var obj1 = { a: 2, foo: foo }; var obj2 = { a: 3, foo: foo }; obj1.foo(); // 2 obj2.foo(); // 3 obj1.foo.call( obj2 ); // 3 obj2.foo.call( obj1 ); // 2
Итак, явная привязка имеет приоритет над неявной привязкой, что означает, что вы должны спросить себя применима ли сначала явная привязка до проверки на неявную привязку.
Теперь, нам нужно всего лишь указать куда подходит по приоритету привязка new.
function foo(something) { this.a = something; } var obj1 = { foo: foo }; var obj2 = {}; obj1.foo( 2 ); console.log( obj1.a ); // 2 obj1.foo.call( obj2, 3 ); console.log( obj2.a ); // 3 var bar = new obj1.foo( 4 ); console.log( obj1.a ); // 2 console.log( bar.a ); // 4
Хорошо, привязка new более приоритетна, чем неявная привязка. Но как вы думаете: привязка new более или менее приоритетна, чем явная привязка?
Примечание: new
и call
/apply
не могут использоваться вместе, поэтому new foo.call(obj1)
не корректно, чтобы сравнить напрямую привязку new с явной привязкой. Но мы все-таки можем использовать жесткую привязку, чтобы проверить приоритет этих двух правил.
До того, как мы начнем исследовать всё это на примере кода, постарайтесь вспомнить как физически работает жесткая привязка, которая есть в Function.prototype.bind(..)
, которая создает новую функцию-обертку, и в ней жестко задано игнорировать ее собственную привязку this
(какой бы она ни была) и использовать указанную вручную нами.
По этой причине, кажется очевидным предполагать, что жесткая привязка (которая является формой явной привязки) более приоритетна, чем привязка new, а потому и не может быть перекрыта действием new
.
Давайте проверим:
function foo(something) { this.a = something; } var obj1 = {}; var bar = foo.bind( obj1 ); bar( 2 ); console.log( obj1.a ); // 2 var baz = new bar( 3 ); console.log( obj1.a ); // 2 console.log( baz.a ); // 3
Ого! bar
жестко связан с obj1
, но new bar(3)
не меняет obj1.a
на значение 3
что было бы ожидаемо нами. Вместо этого жестко связанный (с obj1
) вызов bar(..)
может быть перекрыт с new
. Поскольку был применен new
, обратно мы получили новый созданный объект, который мы назвали baz
, и в результате видно, что в baz.a
значение 3
.
Это должно быть удивительно с учетом ранее рассмотренной «фальшивой» вспомогательной функции привязки:
function bind(fn, obj) { return function() { fn.apply( obj, arguments ); }; }
Если вы порассуждаете о том, как работает код этой вспомогательной функции, в нем нет способа для перекрытия жесткой привязки к obj
операцией new
как мы только что выяснили.
Но встроенная Function.prototype.bind(..)
из ES5 — более сложная, даже очень на самом деле. Вот (немного отформатированный) полифиллинг кода, предоставленный со страницы MDN для функции bind(..)
:
if (!Function.prototype.bind) { Function.prototype.bind = function(oThis) { if (typeof this !== "function") { // наиболее подходящая вещь в ECMAScript 5 // внутренняя функция IsCallable throw new TypeError( "Function.prototype.bind - what " + "is trying to be bound is not callable" ); } var aArgs = Array.prototype.slice.call( arguments, 1 ), fToBind = this, fNOP = function(){}, fBound = function(){ return fToBind.apply( ( this instanceof fNOP && oThis ? this : oThis ), aArgs.concat( Array.prototype.slice.call( arguments ) ) ); } ; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; }; }
Примечание: Полифиллинг bind(. .)
, показанный выше, отличается от встроенной bind(..)
в ES5, учитывающей функции жесткой привязки, которые используются с new
(см. ниже почему это может быть полезно). Поскольку полифиллинг не может создавать функцию без .prototype
так, как это делает встроенная утилита, есть едва уловимый окольный путь, чтобы приблизиться к такому же поведению. Двигайтесь осторожно, если планируете использовать new
вместе с функцией жесткой привязки и полагаетесь на этот полифиллинг.
Часть, которая позволяет перекрыть new
:
this instanceof fNOP && oThis ? this : oThis // ... and: fNOP.prototype = this.prototype; fBound.prototype = new fNOP();
Мы не будем на самом деле углубляться в объяснения того, как работает эта хитрость (это сложно и выходит за рамки нашего обсуждения), но по сути утилита определяет была ли вызвана или нет функция жесткой привязки с new
(в результате получая новый сконструированный объект в качестве ее this
), и если так, то она использует этот свежесозданный this
вместо ранее указанной жесткой привязки для this
.
Почему перекрытие операцией new
жесткой привязки может быть полезным?
Основная причина такого поведения — чтобы создать функцию (которую можно использовать вместе с new
для конструирования объектов), которая фактически игнорирует жесткую привязку this
, но которая инициализирует некоторые или все аргументы функции. Одной из возможностей bind(..)
является умение сделать аргументы, переданные после после аргумента, привязки this
, стандартными аргументами по умолчанию для предшествующей функции (технически называемое «частичным применением», которое является подмножеством «карринга»).
Пример:
function foo(p1,p2) { this.val = p1 + p2; } // используем здесь `null`, т.к. нам нет дела до // жесткой привязки `this` в этом сценарии, и она // будет переопределена вызовом с операцией `new` в любом случае! var bar = foo.bind( null, "p1" ); var baz = new bar( "p2" ); baz.val; // p1p2
Определяем
this
Теперь можно кратко сформулировать правила для определения this
по точке вызова функции, в порядке их приоритета. Зададим вопросы в том же порядке и остановимся как только будет применено первое же правило.
Функция вызвана с
new
(привязка new)? Раз так, тоthis
— новый сконструированный объект.var bar = new foo()
Функция вызвана с
call
илиapply
(явная привязка), даже скрыто внутри жесткой привязки вbind
? Раз так,this
— явно указанный объект.var bar = foo.call( obj2 )
Функция вызвана с контекстом (неявная привязка), иначе называемым как владеющий или содержащий объект? Раз так,
this
является тем самым объектом контекста.var bar = obj1.foo()
В противном случае, будет
this
по умолчанию (привязка по умолчанию). В режимеstrict mode
, это будетundefined
, иначе будет объектglobal
.var bar = foo()
Вот и всё. Вот всё, что нужно, чтобы понимать правила привязки this
для обычных вызовов функций. Ну… почти.
Исключения привязок
Как обычно, из «правил» есть несколько исключений.
Поведение привязки this
в некоторых сценариях может быть неожиданным, там где вы подразумеваете одну привязку, а получаете в итоге поведение привязки по правилу привязки по умолчанию (см. ранее).
Проигнорированный
this
Если вы передаете null
или undefined
в качестве параметра привязки this
в call
, apply
или bind
, то эти значения фактически игнорируются, а взамен к вызову применяется правило привязки по умолчанию.
function foo() { console.log( this.a ); } var a = 2; foo.call( null ); // 2
Зачем вам бы понадобилось намеренно передавать что-то подобное null
в качестве привязки this
?
Довольно распространено использовать apply(. .)
для распаковки массива значений в качестве параметров вызова функции. Аналогично и bind(..)
может каррировать параметры (предварительно заданные значения), что может быть очень полезно.
function foo(a,b) { console.log( "a:" + a + ", b:" + b ); } // распакуем массив как параметры foo.apply( null, [2, 3] ); // a:2, b:3 // каррируем с помощью `bind(..)` var bar = foo.bind( null, 2 ); bar( 3 ); // a:2, b:3
Обa этих инструмента требуют указания привязки this
в качестве первого параметра. Если рассматриваемым функциям не важен this
, то вам нужно -значение-заменитель, и null
— это похоже разумный выбор, как мы видели выше.
Примечание: В этой книге мы не уделим этому внимания, но в ES6 есть операция расширения ...
, которая дает возможности синтаксически «развернуть» массив как параметры без необходимости использования apply(..)
, например как в foo(...[1,2])
, что равносильно foo(1,2)
— синтаксически избегая привязки this
, раз она не нужна. К сожалению, в ES6 нет синтаксической замены каррингу, поэтому параметр this
вызова bind(..)
все еще требует внимания.
Однако, есть некоторая скрытая «опасность» в том, чтобы всегда использовать null
, когда вам не нужна привязка this
. Если вы когда-нибудь воспользуетесь этим при вызове функции (например, функции сторонней библиотеки, которой вы не управляете) и эта функция все-таки воспользуется ссылкой на this
, сработает правило привязки по умолчанию, что повлечет за собой ненамеренно ссылку (или еще хуже, мутацию!) на объект global
(window
в браузере).
Очевидно, что такая ловушка может привести к ряду очень трудно диагностируемых/отслеживаемых ошибок.
Более безопасный
this
Пожалуй в некоторой степени «более безопасная» практика — передавать особым образом настроенный объект для this
, который гарантирует отсутствие побочных эффектов в вашей программе. Заимствуя терминологию из сетевых (и военных) технологий, мы можем создать объект «DMZ» (демилитаризованной зоны (de-militarized zone)) — не более чем полностью пустой, неделегированный (см. главы 5 и 6) объект.
Если всегда передавать DMZ-объект для привязок this
, которые не требуются, то мы можем быть уверены в том, что любое скрытое/неожидаемое использование this
будет ограничено пустым объектом, который защитит объект global
нашей программы от побочных эффектов.
Поскольку этот объект совершенно пустой, лично я люблю давать его переменной имя ø
(математический символ пустого множества в нижнем регистре). На многих клавиатурах (как например US-раскладка на Mac), этот символ легко можно ввести с помощью ⌥
+o
(option+o
). В некоторых системах есть возможность назначать горячие клавиши на определенные символы. Если вам не нравится символ ø
или на вашей клавиатуре сложно набрать такой символ, вы конечно же можете назвать переменную как вам угодно.
Как бы вы ни назвали ее, самый простой путь получить абсолютно пустой объект — это Object.create(null)
(см. главу 5). Object.create(null)
— похож на { }
, но без передачи Object.prototype
, поэтому он «более пустой», чем просто { }
.
function foo(a,b) { console.log( "a:" + a + ", b:" + b ); } // наш пустой DMZ-объект var ø = Object.create( null ); // распаковываем массив как параметры foo.apply( ø, [2, 3] ); // a:2, b:3 // каррируем с помощью `bind(..)` var bar = foo.bind( ø, 2 ); bar( 3 ); // a:2, b:3
Не только функционально «безопаснее», но еще и стилистически выгоднее использовать ø
, что семантически отражает желаение «Я хочу, чтобы this
был пустым» немного точнее, чем null
. Но опять таки, называйте свой DMZ-объект как хотите.
Косвенность
Еще одной вещью, которую нужно опасаться, является создание (намеренно или нет) «косвенных ссылок» на функции, и в этих случаях, когда такая ссылка на функцию вызывается, то также применяется правило привязки по умолчанию.
Самый распространенный путь появления косвенных ссылок — при присваивании:
function foo() { console.log( this.a ); } var a = 2; var o = { a: 3, foo: foo }; var p = { a: 4 }; o.foo(); // 3 (p.foo = o.foo)(); // 2
Результатом выражения присваивания p.foo = o.foo
будет всего лишь ссылка на внутренний объект функции. В силу этого, настоящая точка вызова — это просто foo()
, а не p.foo()
или o.foo()
как вы могли бы предположить. Согласно вышеприведенным правилам будет применено правило привязки по умолчанию.
Напоминание: независимо от того как вы добрались до вызова функции используя правило привязки по умолчанию, статус содержимого вызванной функции в режиме strict mode
, использующего ссылку на this
, а не точка вызова функции, определяет значение привязки по умолчанию: либо объект global
если не в strict mode
или undefined
в strict mode
.
Смягчение привязки
Ранее мы отметили, что жесткая привязка была одной из стратегий для предотвращения случайного действия правила привязки по умолчанию при вызове функции, заставив ее привязаться к указанному this
(до тех пор, пока вы не используете new
, чтобы переопределить это поведение!). Проблема в том, что жесткая приязка значительно уменьшает гибкость функции, не давая указывать this
вручную, чтобы перекрыть неявную привязку или даже последующие попытки явной привязки.
Было бы неплохо, если бы был путь указать другое умолчание для привязки по умолчанию (не global
или undefined
), но при этом оставив возможность для функции вручную привязать this
через технику неявной или явной привязки.
Можно собрать инструмент так называемой мягкой привязки, который эмулирует желаемое поведение.
if (!Function. prototype.softBind) { Function.prototype.softBind = function(obj) { var fn = this, curried = [].slice.call( arguments, 1 ), bound = function bound() { return fn.apply( (!this || (typeof window !== "undefined" && this === window) || (typeof global !== "undefined" && this === global) ) ? obj : this, curried.concat.apply( curried, arguments ) ); }; bound.prototype = Object.create( fn.prototype ); return bound; }; }
Инструмент softBind(..)
, представленный здесь, работает подобно встроенному в ES5 инструменту bind(..)
, за исключением нашего поведения мягкой привязки. Он делает обертку указанной функции с логикой, которая проверяет this
в момент вызова и если это global
или undefined
, использует указанное заранее альтернативное умолчание (obj
). В противном случае this
остается как есть. Также этот инструмент дает возможность опционального карринга (см. ранее обсуждениеbind(..)
).
Продемонстрируем его в действии:
function foo() { console.log("name: " + this.name); } var obj = { name: "obj" }, obj2 = { name: "obj2" }, obj3 = { name: "obj3" }; var fooOBJ = foo.softBind( obj ); fooOBJ(); // name: obj obj2.foo = foo.softBind(obj); obj2.foo(); // name: obj2 <---- смотрите!!! fooOBJ.call( obj3 ); // name: obj3 <---- смотрите! setTimeout( obj2.foo, 10 ); // name: obj <---- возврат к мягкой привяке
Для мягкопривязанной версии функции foo()
можно вручную привязать this
к obj2
или obj3
как показано выше, но он возвращается к obj
в случае применения привязки по умолчанию.
Лексический
this
В обычных функциях строго соблюдаются 4 правила, которые мы только что рассмотрели. Но в ES6 представлен особый вид функции, которая не использует эти правила: стрелочная функция.
Стрелочные функции обозначаются не ключевым словом function
, а операцией =>
, так называемой «жирной стрелкой». Вместо использования четырех стандартных this
-правил, стрелочные функции заимствуют привязку this
из окружающей (функции или глобальной) области видимости.
Проиллюстрируем лексическую область видимости стрелочной функции:
function foo() { // возвращаем стрелочную функцию return (a) => { // Здесь `this` лексически заимствован из `foo()` console.log( this.a ); }; } var obj1 = { a: 2 }; var obj2 = { a: 3 }; var bar = foo.call( obj1 ); bar.call( obj2 ); // 2, а не 3!
Стрелочная функция, созданная в foo()
, лексически захватывает любой this
в foo()
во время ее вызова. Поскольку в foo()
this
был привязан к obj1
, bar
(ссылка на возвращаемую стрелочную функцию) также будет с привязкой this
к obj1
. Лексическая привязка стрелочной функции не может быть перекрыта (даже с помощью new
!).
Самый распространенный вариант использования стрелочной функции — обычно при использовании функций обратного вызова, таких как обработчики событий или таймеры:
function foo() { setTimeout(() => { // Здесь `this` лексически заимствован из `foo()` console. log( this.a ); },100); } var obj = { a: 2 }; foo.call( obj ); // 2
Несмотря на то, что стрелочные функции предоставляют альтернативу применению bind(..)
к функции, чтобы гарантировать определенный this
, что может выглядеть весьма привлекательно, важно отметить, что они фактически запрещают традиционный механизм this
в пользу более понятной лексической области видимости. До ES6, у нас уже был довольно распространенный шаблон для выполнения такой задачи, который по сути почти неотличим от сущности стрелочных функций ES6:
function foo() { var self = this; // лексический захват `this` setTimeout( function(){ console.log( self.a ); }, 100 ); } var obj = { a: 2 }; foo.call( obj ); // 2
В том время как self = this
и стрелочные функции обе кажутся хорошим «решением» при нежелании использовать bind(..)
, они фактически убегают от this
вместо того, чтобы понять и научиться использовать его.
Если вы застали себя пишущим код в стиле this
, но большую часть или всё время вы сводите на нет механизм this
с помощью трюков лексической конструкции self = this
или стрелочной функции, возможно вам следует сделать что-то одно из этого:
Использовать только лексическую область видимости и забыть о фальшивости кода в стиле
this
.Полностью научиться использовать механизмы
this
-стиля, включая применениеbind(..)
, где необходимо, и попытаться избегать трюков «лексического this» с помощьюself = this
и стрелочной функции.
Программа может эффективно использовать оба стиля кодирования (лексический и this
), но внутри одной и той же функции и, разумеется, при одних и тех же видах поисков переменных, смешивание двух этих механизмов обычно приводит к менее обслуживаемому коду, и возможно будет слишком перегруженным, чтобы выглядеть умным.
Обзор
Определение привязки this
для вызова функции требует поиска непосредственной точки вызова этой функции. Как уже выяснилось, к точке вызова могут быть применены четыре правила, в именно таком порядке приоритета:
Вызвана с
new
? Используем только что созданный объект.Вызвана с помощью
call
илиapply
(илиbind
)? Используем указанный объект.Вызвана с объектом контекста, владеющего вызовом функции? Используем этот объект контекста.
По умолчанию:
undefined
в режимеstrict mode
, в противном случае объект global.
Остерегайтесь случайного/неумышленного вызова с применением правила привязки по умолчанию. В случаях, когда вам нужно «безопасно» игнорировать привязку this
, «DMZ»-объект, подобный ø = Object.create(null)
, — хорошая замена, защищающая объект global
от непредусмотренных побочных эффектов.
Вместо четырех стандартных правил привязки стрелочные функции ES6 используют лексическую область видимости для привязки this
, что означает, что они заимствуют привязку this
(какой бы она ни была) от вызова своей окружающей функции. Они по существу являются синтаксической заменой self = this
в до-ES6 коде.
Поднятие или hoisting в JavaScript
Поднятие (hoisting)
Поднятие предполагает, что объявления переменных var
и функций function
физически перемещаются в начало кода, но, на самом деле это не так.
По сути, когда Javascript компилирует весь код, все объявления переменных, использующие var
, поднимаются/hoisted в верхнюю часть их функциональной/локальной области видимости (если объявляется внутри функции) или в глобальную область видимости (если объявляется вне функции) независимо от того, где была сделана фактическая декларация.
Объявления переменных и функций помещаются в память на этапе компиляции, но они остаются именно там, где мы ввели их в свой код.
Итак, под капотом происходит следующее: на этапе создания, движок JavaScript просматривает код и, как только он видит ключевое слово var
или ключевое слово function
, он выделяет некоторую память для них.
Поднятие функций
Одним из преимуществ JavaScript является помещение объявления функций в память, перед выполнением любого сегмента кода. Объявления функций поднимаются, но они идут на самый верх, поэтому они будут находиться над всеми объявлениями переменных. Это позволяет нам использовать функцию до того, как мы объявим её в своем коде. Например:
function printName(name) {
console.log("Hello, my name is " + name);
}
printName("Alex");
Теперь, давайте посмотрим, что произойдёт, когда мы вызовем функцию printName
, прежде чем напишем её:
printName("Alex");
function printName(name) {
console.log("Hello, my name is " + name);
}
Несмотря на то, что мы вызвали функцию printName
, перед тем, как написать её, код всё ещё работает. Это происходит из-за того, как контекстное выполнение работает в JavaScript.
Поднятие хорошо сотрудничает с другими типами данных. Переменные можно инициализировать и использовать до их объявления.
JavaScript поднимает только объявления, а не инициализации. Если переменная объявлена и инициализирована после её использования, значение будет неопределенным (undefined). Например:
console.log(name);
var name;
name = "Alex";
Если мы объявляем переменную после её использования, но предварительно инициализируем её, она вернет значение:
name = "Alex";
console.log(name);
var name;
Таким образом переменные частично подняты. Поднимая переменную, но не правую сторону (не фактическое значение), мы просто присваиваем ему undefined
.
Функции полностью подняты. Означает, что объявлению функции, на этапе создания, было назначено место в памяти.
Поднятие const, let и var
var
— это традиционный способ объявления переменных в JavaScript.
ES6 (ECMAScript 6) представил два новых способа объявления переменных: const
и let
, и, как правило, они рекомендуются во избежание неожиданных осложнений при подъеме.
Ключевое слово var
var
имеет область действия функции;- объявления
var
поднимаются, но не инициализируются.
console.log(name);
var name = "Alex";
Приведенный выше код, из-за поднятия эквивалентен приведенному коду ниже.
var name;
console.log(name);
name = "Alex";
Ключевые слова const / let
const
иlet
имеют область видимости блока.
На самом деле объявления var
, let
, const
, function
и class
поднимаются; но, мы должны помнить, что концепция поднятия не является буквальным процессом (т. е. сами объявления не перемещаются в начало файла — это просто процесс компилятора JavaScript, который сначала читает их, чтобы освободить для них место в памяти).
Разница между объявлениями var / function
и объявлениями let / const / class
заключается в инициализации.
Первые инициализируются с неопределенным значением undefined
. Однако, вторые, лексически объявленные переменные, остаются не инициализированными. Это означает, что ReferenceError
выбрасывается при попытке доступа к ним. Они будут инициализированы только после того, как операторы let / const / class
будут определены. Всё что до, называется временной мертвой зоной.
Временная мертвая зона — это не синтаксическое местоположение, а время между созданием переменной (области) и инициализацией. Ссылка на переменную в коде над объявлением не является ошибкой, если этот код не выполняется (например, тело функции или просто мертвый код), но ошибка будет выдана, если мы запросим доступ к переменной до её инициализации.
Разница между объявлениями var
, let
и const
заключается в их инициализации.
Экземпляры var
и let
могут быть инициализированы без значения, в то время как const
выдаст ошибку ReferenceError
, если ты попытаешься объявить её без одновременного присвоения ей значения. Так что const myName = 'Alex'
будет работать, но const myName; myName = 'Alex';
не будет. С помощью var
и let
ты можешь попробовать использовать значение var
до того, как оно будет присвоено, и оно вернет undefined
. Однако, если ты сделаешь то же самое с let
— получишь ReferenceError
.
console.log(a);
var a = 1;
console.log(b);
let b = 2;
console.log(c);
const c = 5;
Также, если ты создашь var
на верхнем уровне (глобальный уровень), создастся свойство для глобального объекта; в случае с браузером — это объект window
. Поэтому на создание var myName = 'Alex';
можно ссылаться также путем вызова window.myName
.
Однако, если ты напишешь let newName = 'Alex';
это не будет доступно в глобальном объекте window
— следовательно, ты не сможешь использовать window.newName
в качестве ссылки на 'Alex'
,
К объявлениям, сделанным с помощью var
, можно получить доступ за пределами их первоначальной области видимости, тогда как к объявлениям, сделанным с помощью let
и const
, нельзя.
console.log('1 myName1', myName1);
if (1) {
console.log('2 myName1', myName1);
var myName1 = 'Alex';
}
console.log('1 myName2', myName2);
if (1) {
console.log('1 myName2', myName2);
let myName2 = 'Alex';
}
console.log('1 myName3', myName3);
if (1) {
console.log('2 myName3', myName3);
const myName3 = 'Alex';
}
Зачем учить JavaScript и где он пригодится
JavaScript полностью интегрирован с HTML, он способен как угодно менять веб-страницу. В ответ на событие программист может:
- на лету вставить в HTML-код любые теги;
- задать внешний вид элементов через класс и атрибуты HTML;
- переместить любой элемент;
- запросить у пользователя данные;
- отправить запрос на сервер (технология AJAX).
Это только то, что сразу пришло в голову. JavaScript может намного больше, в пределах своей страницы он Бог.
JavaScript — подходящий язык для изучения программирования. Он достаточно прост, но содержит все фундаментальные вещи: алгоритмы, объектно-ориентированную модель, структуры данных. Если традиционные языки для обучения — Pascal и Basic — несут мало практической пользы, то JavaScript — рабочая лошадка.
Начинать с JavaScript хорошо и потому, что синтаксически он похож на великий и ужасный язык С. Изучив JavaScript, получишь базовое представление обо всех «сиобразных» языках: С++, C#, Java, PHP. Они задают тренд в своих областях и весьма популярны, поэтому для новичка важно познакомиться с синтаксисом С.
Программа на JavaScript — это простой текст. Писать на JavaScript можно в любом текстовом редакторе.
В пределах своей страницы JavaScript — Бог
Классический JavaScript — это язык программирования для интернета, он бессилен за пределами браузера. С помощью JavaScript нельзя запустить программу на компьютере или записать файл в нужную папку.
Из-за правил безопасности браузеры ограничивают мощь JavaScript и за пределами «родной» страницы. Управлять вкладками можно при определенных условиях или же вовсе нельзя. Например, JavaScript может закрыть только ту вкладку, которую создал сам.
Год-два назад появились платформы Node.js и React Native, с ними на JavaScript пишут не только для браузера, но и для компьютеров со смартфонами. Это модные и трендовые технологии, но глобально JavaScript — язык программирования для интернета.
На JavaScript пишут для интернета и браузеров
Сейчас в веб-программировании нет ничего, что способно пошатнуть позиции JavaScript. Язык настолько удачен, что нет причин изобретать что-то другое.
С чистым JavaScript конкурируют только надстройки над ним: CoffeeScript, TypeScript, Dart. Код надстроек порой компактнее, его легче читать и отлавливать ошибки, но перед выполнением он все равно преобразуется в JavaScript.
Главная сила JavaScript — вечная молодость. Он вышел 21 год назад, но не устарел, а развивался и развивается вслед за HTML.
Серьезных конкурентов у JavaScript нет
Можно приступать к JavaScript, вообще не имея представления о программировании. JavaScript — удачный выбор для первого языка, особенно если связываешь будущее с веб-разработкой. При этом любые знания в сфере программирования будет плюсом.
Если есть опыт HTML и CSS, совсем хорошо. Создание сайта логично начать со статичных страниц на HTML и CSS, а потом оживить их при помощи JavaScript. Плюс HTML и CSS дают базовое понимание того, как устроен интернет и работают сайты.
JavaScript — подходящий первый язык, если связываешь будущее с веб-разработкой
Изучив основы JavaScript, можно копать так глубоко, как хочется.
Хорошо освоить библиотеки и фреймворки для JavaScript — наборы готовых классов с функциями. Некоторые из них настолько мощные, что полностью меняют сценарии программирования. Для JavaScript самые популярные фреймворки и библиотеки — React, jQuery и Angular2.
Код в обеих строках делает одно и то же — скрывает элемент с идентификатором «helloDiv»Кроме фреймворков полезно изучить надстройки над JavaScript: CoffeeScript, TypeScript и Dart. Одни надстройки сделают ваш код чище и компактнее, другие — строже.
Наконец, можно взяться за серверное программирование и Node.js. Это трендовая технология, которую используют BMW, Amazon, Apple и другие серьезные компании. Так вы расширите область своих знаний JavaScript за пределы управления веб-страницей.
Для JavaScript-программиста нет потолка развития
Хотите написать колонку для Нетологии? Читайте наши условия публикации. Чтобы быть в курсе всех новостей и читать новые статьи, присоединяйтесь к Телеграм-каналу Нетологии
функции будущего в JS уже сегодня
Каждый разработчик ценит свое время и всегда ищет способы избежать дублирования кода и написания одинаковых реализаций под различные проекты. Lodash — библиотека с огромным количеством функций для работы с различными структурами данных. Опытный программист всегда найдет способ не писать велосипеды с костылями, а использовать готовые, проверенные решения. Тем более если эти решения хорошо структурированы и отлично задокументированы.
Lodash — это библиотека JavaScript, которая помогает программистам писать более компактный и простой в обслуживании JavaScript код.
Его можно разбить на несколько основных направлений:
- Utilities — для упрощения общих задач программирования, таких как определение типа, а также упрощение математических операций.;
- Function — упрощение связывания, декорирование, сдерживание, дросселирование, debouncing, currying и изменение указателя;
- String — функции преобразования для выполнения основных операций с строками, такие как обрезка, преобразование в верхний регистр, случай верблюда и т.д.;
- Array — создание, разбиение, объединение, изменение и сжатие;
- Collection — итерация, сортировка, фильтрация, расщепление и строительство;
- Object — доступ, расширение, слияние, дефолты и преобразование;
- Seq — caching, упаковка, фильтрация и тестирование.
Библиотека доступна для установки с CDN без использования сборщиков, доступна на npm, есть поддержка TypeScript. Начать пользоваться Lodash очень просто, поэтому мы не будем уделять внимание установке. Если у Вас возникнут сложности — можете обратиться к документации, здесь подробно рассмотрены все варианты подключения библиотеки.
С появлением ES6 репутация Lodash слегка пошатнулась для некоторых разработчиков. Причиной этому стало появление большого количество функций для работы с структурами и добавление новых типов данных. В ES6 появилось большое количество функций для работы с массивами:
- forEach — используется для перебора массива;
- filter — используется для фильтрации массива через callback функцию;
- map — используется для трансформации массива;
- every/some — методы используются для проверки массива;
- reduce/reduceRight — используется для последовательной обработки каждого элемента массива с сохранением промежуточного результата.
Более подробную информацию по этим функциям Вы можете найти здесь.
Так же появились такие нововведения как:
Но несмотря на это польза библиотеки все равно не вызывает ни капли сомнения, JavaScript еще не скоро «впитает» или повторит все возможности Lodash.
Для того что бы понять удобство использования Lodash, давайте попробуем его в деле.
Предположим что мы делаем интернет-магазин и что бы снизить нагрузку на сервер мы решили переложить сложную логику сортировки и фильтрации на JavaScript. Логика будет следующая:
1) Пользователь открывает страницу — подгружаются все товары магазина и кладутся в переменную;
2) При взаимодействии с фильтрами и сортировками — JavaScript выполняет манипуляции с данными и выводит их пользователю;
В терминологии lodash коллекция — это набор объектов(сущностей), который заключен в массив. В данном случае наши товары являются сущностями, а массив с товарами — коллекцией. Таким образом мы можем применять все методы для коллекций из существующих в Lodash. Наши товары будут представлены в виде объектов и собраны в коллекцию вида:
var products = [
{
id: 0,
name: "Товар 1",
price: 1200,
color: "Красный",
размер: "XL"
},
....
]
Lodash хранится в переменной _
. С технической стороны: переменная _
— это объект Lodash, который содержит в себе множество методов (свойств). Из этого следует что к переменной Lodash можно обращаться как к обычному объекту, используя «_.<method name>
«, таким образом для вызова функции «filter
» нам потребуется написать следующее «_.filter(...)
«. Это важно для понимания и правильного восприятия структуры библиотек, во избежание эффекта «абракадабры» для разработчика (я написал вот так, а оно там как-то само все сделало).
Для реализации задумки необходимо вывести два фильтра и одну сортировку:
- по цвету;
- по размеру;
- сортировка по цене.
Дабы сильно не заморачиваться, я сделал простые <select>
и повесил на них обработчики события «change
«. При смене значения, мы будем производить фильтрацию и выводить новый список товаров.
Так как нам нужно что-то выводить, давайте напишем функцию «render
«(название не принципиально). Работает эта функция до слез в глазах примитивно и не оптимизировано, но сейчас не об этом. Принцип ее работы следующий: принимаем агрументом массив с товарами, очищаем «<div>
» в который выводятся товары и в цикле выводим все товары из массива используя шаблонные строки ES6:
// Функция вывода товаров
function render(data = []) {
$("#listing").empty() // Очищаем контейнер
for(let product of data) {
$("#listing").append($(`
<div>
<p><b>${product.name} (${product.id})</b></p>
<p>${product.color}, ${product.size}</p>
<p>Цена: ${product. price}</p>
</div>
`)) // Добавляем товары по шаблону
}
$("#total").text(`Найдено ${data.length} товаров`) // Выводим общее количество товаров
}
Вручную формировать список товаров — не самое интересное занятие, он будет генерироваться случайным образом. Для этого можно использовать функции random и uniqueId.
Кульминация события — функция filter. Функция работает с объектом filterData
, и формирует новый список отсортированных и фильтрованных товаров, но основе базового списка. Для фильтрации товаров по свойствам цвета и размера можно использовать функции Lodash — filter и orderBy.
Вот что в итоге получилось:
See the Pen Lodash demo by Konstantin (@ssstelllo) on CodePen.
Результат на лицо — мы сократили достаточно сложные операции с данными до 1 строки кода. Библиотека безусловно заслуживает внимания и полагаясь на выбор миллионов разработчиков, можно смело использовать ее в своих проектах. Нет сомнения что со временем библиотека уйдет в бытие, когда JavaScript «из коробки» будет поддерживать все эти функции, но это не самая близкая перспектива, так что радуемся и пользуемся на здоровье!
Ниже представлен список наиболее популярных функций с описанием, который я позаимствовал у zag2art, за что ему большое спасибо.
Источник
Массивы
compact | Убираем из массива все пустые элементы (0, «», null, undefined) |
difference | Создаем новый массив, как разницу, где из первого массива исключили все значения второго (А—Б) |
findIndex | Возвращает индекс первого элемента, по которому callback вернул true |
findLastIndex | Возвращает индекс первого с конца элемента, по которому callback вернул true |
first | Возвращает первый или несколько первых элементов массива |
flatten | Извлекает, в виде массива, элементы из объектов, содержащихся в передаваемом массиве |
indexOf | Возвращает индекс первого совпадающего (===) элемента, если массив сортирован можно ускорить поиск |
initial | Возвращает начальную часть массива (кроме n последних) |
intersection | Возвращает пересечение массивов (нескольких) |
last | Возвращает последний или несколько последних элементов массива |
lastIndexOf | Возвращает индекс последнего совпадающего (===) элемента. (можно искать не с самого конца) |
pull | Удаляет переданные элементы из массива |
range | Создает массив с числами от start до end (можно выбрать шаг) |
remove | Удаляет из массива элементы по заданному правилу и возвращает массив удаленных элементов |
rest | Возвращает все кроме первого (нескольких первых) элементов массива |
sortedIndex | Возвращает индекс вставки текущего значения в сортированном массиве |
union | Возвращает массив уникальных значений — результат объединения нескольких массивов |
uniq | Создает копию массива без дубликатов |
without | Создает новый массив из существующего исключая некоторые значения |
xor | Создает новый массив — «семантическую разницу» между переданными массивами |
zip | Создает массив сгруппированных в подмассивы элементов. На вход принимает несколько массивов. Собирает так: первые элементы всех входных массивов попадают в первую группу и т.д. |
zipObject | Собирает объект из двух массивов (с ключами и со значениями) |
at | Создает новую коллекцию, только из перечисленных элементов |
contains | Проверяет, содержится (содержатся) ли определенный элемент в коллекции |
countBy | Создает объект, ключами которого будут значения, возвращаемые функцией обратного вызова, а значениями — количество соответствующих возвратов |
every | Проверка на то, что все элементы коллекции истины (или удовлетворяют условию). |
filter | Пробегает по всем элементам коллекции и возвращает МАССИВ из элементов, удовлетворяющих условию |
find | Возвращает первый элемент коллекции, отвечающий заданным критериям |
findLast | То же самое, что и find, только с конца |
forEach | Пробегается по всем элементам коллекции, запуская функцию обратного вызова для каждого элемента |
forEachRight | То же самое, что и forEach, только с конца |
groupBy | Создает коллекцию, ключами которой являются значения, возвращенные функцией обратного вызова, а значениями — массивы из первоначальных элементов коллекции |
indexBy | Создает коллекцию, ключами которой являются значения, возвращенные функцией обратного вызова, а значениями — последние элементы первоначальной коллекции с соответствующим ключом |
invoke | Выполняет определенный метод для каждого элемента коллекции и возвращает массив из результатов этого выполнения |
map | Создает массив элементов, прогоняя каждый элемент коллекции через функцию обратного вызова |
max | Возвращает максимальное значение коллекции |
min | Возвращает минимальное значение коллекции |
pluck | Возвращает все значения определенного свойства коллекции |
reduce | Уменьшаем коллекцию до значения, получаемое вызовом функций обратного вызова для каждого значения. В эту функцию, кроме самого значения, передается также результат предыдущего вызова. |
reduceRight | То же самое, что и reduce, только с конца |
reject | В противоположность filter — эта функция возвращает массив элементов коллекции, которые не удовлетворяют условию |
sample | Возвращает случайный элемент коллекции |
shuffle | Возвращает массив из перемешанных в случайном порядке элементов коллекции |
size | Возвращает размер коллекции |
some | Проверка на то, что хотя бы один элемент коллекции истинен (или удовлетворяет условию). |
sortBy | Возвращает массив сортированных по возрастанию элементов коллекции. Для сортировки используются значения получаемые функцией обратного вызова по каждому элементу. |
toArray | Преобразует коллекцию к массиву. Полезно для работы с arguments |
where | Проводит глубокое сравнение каждого элемента с определенным объектом. Возвращает массив, удовлетворяющий сравнению. |
after | Возвращает функцию, которая вызовет переданный колбек только после n-ого вызова |
bind | Возвращает функцию, которая при вызове будет привязана к текущему this, к привязанным аргументам и аргументам, предающимся в саму вызванную функцию. |
bindAll | Привязывает все методы объекта к самому этому объекту (this в методах всегда будет сам объект) |
bindKey | Привязывает метод объекта к самому объетку (на момент привязки, метод может еще не существовать) |
compose | Возвращает функцию — композицию из переданных функций. Передаем f,g,h — возвращает функцию = f(g(h())) |
curry | Принимает на вход функцию с n параметрами, а возвращает функцию, которая в случае если параметро достаточно — вызовет входную, если нет, возвратит другую функцию, передав оставшиеся параметры которой — вызовется первоначальная. |
debounce | Возвращает функцию, которая запустит входную функцию только выдержав паузу после своего последнего запуска. (устранение дребезжания) |
defer | Вызовет входную функцию тогда, когда освободится текущий стек вызова (в начале следующего цикла событий) |
delay | Вызовет входную функцию через n миллисекунд |
memoize | Возвращает функцию, которая кэширует результаты своего выполнения, и не выполняется, если результат есть в кэше. |
once | Возвратит функцию, которая вызовет входную только один раз. В последующие разы будет возвращаться полученный результат. |
partial | То же самое, что и bind, но не привязывает this |
partialRight | То же самое, что и partial, но привязывается к параметрам справа |
throttle | Возвращает функцию, которая вызывает исходную не чаще чем один раз в n миллисекунд |
wrap | Создает функцию, в первый параметр которой будет передан первый параметр врапера. |
assign | Дополняет объект отсутствующими (не просто undefined) свойствами из другого объекта |
clone | Делает копию объекта (вложенные объекты копируются по ссылке) |
cloneDeep | Глубокое копирование объекта (вложенные объекты копируются по содержанию) |
create | Создает объект по переданному прототипу и свойствам |
defaults | Дополняет объект отсутствующими (=== undefined) свойствами из другого объекта — задает умолчания |
findKey | Ищет первый объект удовлетворяющий условиям — возвращает ключ |
findLastKey | Ищет последний объект удовлетворяющий условиям — возвращает ключ |
forIn | Обходит все свойства объекта (включая внутренние), вызывая для каждого из них функцию обратного вызова |
forInRight | То же самое что и forIn только с конца |
forOwn | Обходит все собственные свойства объекта, вызывая для каждого из них функцию обратного вызова |
forOwnRight | То же самое что и forOwn только с конца |
functions | Возвращает отсортированный массив имен всех свойств объекта, значениями которых ялвяются функции |
has | Проверяет, является ли указанное свойство собственным свойством объекта |
invert | Создает объект у которого ключи и значения поменяны местами |
isArguments | Проверяет, является ли значение — объектом arguments |
isArray | Проверяет, является ли значение массивом |
isBoolean | Проверяет, является ли значение булевой переменной |
isDate | Проверяет, является ли значение массивом |
isElement | Проверяет, является ли значение DOM-элементом |
isEmpty | Проверяет, является ли значение пустым. Массивы, строки, arguments-объекты с нулевой длинной считаются пустыми |
isEqual | Проводит глубокое сравнение двух значений |
isFinite | Проверяет, является ли данное значение конечным числом, или же оно может быть приобразовано к нему |
isFunction | Проверяет, ялвяется ли данное значение функцией |
isNaN | Проверяет значение на === NaN (это не то же самое, что стандартная isNuN, которая возвращает true для undefined и не числовых значений) |
isNull | Проверяет значение на === null |
isNumber | Проверяет, является ли значение числом (NaN тоже считается числом) |
isObject | Проверяет, является ли значение объектом |
isPlainObject | Проверяет, является ли значение чистым объектом (созданным конструктором Object) |
isRegExp | Проверяет, является ли значение регулярным выражение |
isString | Проверяет, является ли значение строкой |
isUndefined | Проверяет значение на === undefined |
keys | Возвращает массив ключей объекта |
mapValues | Созает новый объект с такими же ключами, как у исходного, значения получаются вызовом callback-функции к каждому элементу |
merge | Рекурсивно добавляет переданные объекты в объект назначения |
omit | Возвращает объект, у которого убраны некоторые свойства |
pairs | Создает из объекта двумерный массив, типа [[key1, value1], [key2, value2]]. |
pick | Создает из объекта другой объект со свойствами из списка |
transform | Более простая альтернатива reduce — позволяет трансформировать входящий объект в другой, посредством функции обратного вызова, в которую передается кроме элементов еще и результирующий объект |
values | Возвращает массив значений объекта |
now | Возвращает текущий Unix-time в миллисекундах |
constant | Создает функцию, возвращающую переданное значение |
createCallback | Создает функцию, коллбек — используется для внутренних целей lodash |
escape | Экранирует символы &, <, >, «, и ‘ соответсующими html-сущностями |
identity | Функция возвращает первый переданный в нее аргумент |
mixin | Добавляет в объект (или в сам lodash) элементы преданного объекта. Если объект-приемник — функция, добавляет свойства в прототип. |
noConflict | Делает _ равным старому значению (до запуска lodash), и возвращает указатель на lodash |
noop | Пустая функция. Возвращает undefined |
parseInt | Извлекает число из строки, по умолчанию работает всегда с 10-ой системой счисления (в отличии от стандартной) |
property | Возвращает функцию в стиле pluck, вызов которой с объектом в качестве параметра, вернет значение определенного свойства. |
random | Вернет случайное число из диапазона. Может дробное. |
result | Вернет значение свойства в объекте (если значением будет функция, она будет вызвана и возвращен ее результат) |
runInContext | Вернет новую lodash-функцию привязанную к заданному контексту |
template | Микрошаблонизатор |
times | Выполняет указанный callback n-раз, возвращая массив результатов |
unescape | Функция, обратная escape |
uniqueId | Возвращает уникальный числовой ID (число, каждый раз на единицу больше. можно передавать префикс) |
это в JavaScript
это ключевое слово
может иметь разные значения в зависимости от того, где оно используется.
Незнание этой крошечной детали JavaScript может вызвать много головной боли, поэтому стоит потратить 5 минут на изучение всех приемов.
это
в строгом режиме Вне любого объекта это
в строгом режиме всегда undefined
.
Обратите внимание, я упомянул строгий режим. Если строгий режим отключен (состояние по умолчанию, если вы явно не добавляете 'use strict'
поверх вашего файла), вы находитесь в так называемом неаккуратном режиме , а это
— за исключением некоторых особых случаев упомянутый ниже — имеет значение глобального объекта.
Что означает окно
в контексте браузера.
это
в методахМетод — это функция, прикрепленная к объекту.
Вы можете увидеть это в различных формах.
Вот один:
const car = {
производитель: 'Ford',
модель: 'Fiesta',
водить машину() {
console.log (`За рулем автомобиля $ {this.maker} $ {this.model}!`)
}
}
car.drive ()
// За рулем автомобиля Ford Fiesta!
В этом случае при использовании обычной функции этот
автоматически привязывается к объекту.
Примечание: приведенное выше объявление метода аналогично drive: function () {
…, но короче:
const car = {
производитель: 'Ford',
модель: 'Fiesta',
drive: function () {
console.log (`За рулем автомобиля $ {this.maker} $ {this.model}!`)
}
}
То же самое работает в этом примере:
const car = {
производитель: 'Ford',
модель: 'Fiesta'
}
car.drive = function () {
console.log (`За рулем автомобиля $ {this.maker} $ {this.model}!`)
}
car. drive ()
// За рулем автомобиля Ford Fiesta!
Стрелочная функция не работает так же, как она лексически связана:
const car = {
производитель: 'Ford',
модель: 'Fiesta',
диск: () => {
консоль.log (`За рулем автомобиля $ {this.maker} $ {this.model}!`)
}
}
car.drive ()
// Вождение неопределенной неопределенной машины!
Связывание стрелочных функций
Невозможно привязать значение к функции стрелки, как это делается с обычными функциями.
Это невозможно из-за их работы. это
является лексически связанным , что означает, что его значение получено из контекста, в котором они определены.
Явно передать объект, который будет использоваться как
, это
JavaScript предлагает несколько способов сопоставить и
любому объекту, который вы хотите.
Использование bind ()
, при объявлении функции Шаг :
const car = {
производитель: 'Ford',
модель: 'Fiesta'
}
const drive = function () {
console. log (`За рулем автомобиля $ {this.maker} $ {this.model}!`)
} .bind (автомобиль)
водить машину()
// За рулем автомобиля Ford Fiesta!
Вы также можете привязать существующий метод объекта, чтобы переназначить его на это значение
:
const car = {
производитель: 'Ford',
модель: 'Fiesta',
водить машину() {
консоль.log (`За рулем автомобиля $ {this.maker} $ {this.model}!`)
}
}
const anotherCar = {
производитель: 'Audi',
модель: 'A4'
}
car.drive.bind (другой автомобиль) ()
// За рулем Audi A4!
Использование call ()
или apply ()
, при вызове функции Шаг :
const car = {
производитель: 'Ford',
модель: 'Fiesta'
}
const drive = function (kmh) {
console.log (`Вождение автомобиля $ {this.maker} $ {this.model} со скоростью $ {kmh} км / ч!`)
}
drive.call (автомобиль, 100)
// За рулем Ford Fiesta со скоростью 100 км / ч!
водить машину. применить (автомобиль, [100])
// За рулем Ford Fiesta со скоростью 100 км / ч!
Первый параметр, который вы передаете call ()
или apply ()
, всегда привязан к this
.
Разница между call () и apply () заключается только в том, что второй хочет массив в качестве списка аргументов, а первый принимает переменное количество параметров, которые передаются как аргументы функции.
Частный случай обработчиков событий браузера
В обратных вызовах обработчиков событий, это
относится к элементу HTML, получившему событие:
документ.querySelector ('# button'). addEventListener ('click', function (e) {
console.log (это) // HTMLElement
}
Вы можете привязать его с помощью
document.querySelector ('# button'). AddEventListener (
'щелкнуть',
function (e) {
console.log (this) // Окно, если оно глобальное, или ваш контекст
} . bind (это)
)
Другие руководства по js:
- Чего следует избегать в JavaScript (плохие части)
- Отсрочки и обещания в JavaScript (+ Ember.js пример)
- Как загружать файлы на сервер с помощью JavaScript
- Стиль кодирования JavaScript
- Введение в массивы JavaScript
- Введение в язык программирования JavaScript
- Полное руководство ECMAScript 2015-2019
- Понимание обещаний JavaScript
- Лексическая структура JavaScript
- Типы JavaScript
- Переменные JavaScript
- Список примеров идей для веб-приложений
- Введение в функциональное программирование с помощью JavaScript
- Современный асинхронный JavaScript с Async и ожиданием
- Циклы и область действия JavaScript
- Структура данных JavaScript карты
- Заданная структура данных JavaScript
- Руководство по литералам шаблонов JavaScript
- Дорожная карта для изучения JavaScript
- Выражения JavaScript
- Откройте для себя таймеры JavaScript
- Объяснение событий JavaScript
- Циклы JavaScript
- Написание циклов JavaScript с использованием карты, фильтрации, сокращения и поиска
- Цикл событий JavaScript
- Функции JavaScript
- Глоссарий JavaScript
- Объяснение закрытий JavaScript
- Учебное пособие по функциям стрелок в JavaScript
- Руководство по регулярным выражениям JavaScript
- Как проверить, содержит ли строка подстроку в JavaScript
- Как удалить элемент из массива в JavaScript
- Как глубоко клонировать объект JavaScript
- Введение в Unicode и UTF-8
- Юникод в JavaScript
- Как вводить первую букву строки в верхний регистр в JavaScript
- Как отформатировать число как денежное значение в JavaScript
- Как преобразовать строку в число в JavaScript
- это в JavaScript
- Как получить текущую метку времени в JavaScript
- Строгий режим JavaScript
- Выражения функции немедленного вызова JavaScript (IIFE)
- Как перенаправить на другую веб-страницу с помощью JavaScript
- Как удалить свойство из объекта JavaScript
- Как добавить элемент в массив в JavaScript
- Как проверить, не определено ли свойство объекта JavaScript
- Введение в модули ES
- Введение в CommonJS
- Асинхронное программирование JavaScript и обратные вызовы
- Как заменить все вхождения строки в JavaScript
- Краткое руководство по синтаксису современного JavaScript
- Как обрезать ведущий ноль в числе в JavaScript
- Как проверить объект JavaScript
- Полное руководство по датам JavaScript
- Момент. js учебник
- Точка с запятой в JavaScript
- Арифметические операторы JavaScript
- Объект JavaScript Math
- Создание случайных и уникальных строк в JavaScript
- Как заставить ваши функции JavaScript «спать»
- Прототипное наследование JavaScript
- Исключения JavaScript
- Как использовать классы JavaScript
- Поваренная книга JavaScript
- Цитаты в JavaScript
- Как проверить адрес электронной почты в JavaScript
- Как получить уникальные свойства набора объектов в массиве JavaScript
- Как проверить, начинается ли строка с другой в JavaScript
- Как создать многострочную строку в JavaScript
- Руководство ES6
- Как получить текущий URL в JavaScript
- Руководство ES2016
- Как инициализировать новый массив значениями в JavaScript
- Руководство ES2017
- Руководство ES2018
- Как использовать Async и Await с массивом. prototype.map ()
- Асинхронный код против кода синхронизации
- Как сгенерировать случайное число между двумя числами в JavaScript
- Учебное пособие по HTML Canvas API
- Как получить индекс итерации в цикле for-of в JavaScript
- Что такое одностраничное приложение?
- Введение в WebAssembly
- Введение в JSON
- Руководство JSONP
- Стоит ли использовать или изучать jQuery в 2020 году?
- Как скрыть элемент DOM с помощью простого JavaScript
- Как объединить два объекта в JavaScript
- Как очистить массив JavaScript
- Как кодировать URL с помощью JavaScript
- Как установить значения параметров по умолчанию в JavaScript
- Как отсортировать массив объектов по значению свойства в JavaScript
- Как подсчитать количество свойств в объекте JavaScript
- call () и apply () в JavaScript
- Введение в PeerJS, библиотеку WebRTC
- Работа с объектами и массивами с помощью Rest and Spread
- Разрушение объектов и массивов в JavaScript
- Полное руководство по отладке JavaScript
- Руководство по TypeScript
- Динамически выбрать метод объекта в JavaScript
- Передача undefined в JavaScript Выражения немедленного вызова функций
- Свободно типизированные языки против строго типизированных языков
- Как стилизовать элементы DOM с помощью JavaScript
- Приведение в JavaScript
- Учебное пособие по генераторам JavaScript
- Размер папки node_modules не является проблемой. Это привилегия
- Как устранить непредвиденную ошибку идентификатора при импорте модулей в JavaScript
- Как перечислить все методы объекта в JavaScript
- Метод replace () строки
- Метод String search ()
- Как я запускаю небольшие фрагменты кода JavaScript
- Руководство ES2019
- Метод String charAt ()
- Метод String charCodeAt ()
- Метод String codePointAt ()
- Метод String concat ()
- Метод String EndWith ()
- Строка включает метод ()
- Метод String indexOf ()
- Метод String lastIndexOf ()
- Метод String localeCompare ()
- Метод String match ()
- Метод String normalize ()
- Метод String padEnd ()
- Метод String padStart ()
- Метод String repeat ()
- Метод String slice ()
- Метод String split ()
- Метод String StartWith ()
- Метод String substring ()
- Метод String toLocaleLowerCase ()
- Метод String toLocaleUpperCase ()
- Метод String toLowerCase ()
- Метод String toString ()
- Метод String toUpperCase ()
- Метод String trim ()
- Метод String trimEnd ()
- Метод trimStart () String
- Мемоизация в JavaScript
- Метод String valueOf ()
- Ссылка на JavaScript: строка
- Метод Number isInteger ()
- Метод Number isNaN ()
- Метод Number isSafeInteger ()
- Метод Number parseFloat ()
- Метод Number parseInt ()
- Метод Number toString ()
- Метод Number valueOf ()
- Метод Number toPrecision ()
- Метод Number toExponential ()
- Метод Number toLocaleString ()
- Метод Number toFixed ()
- Метод Number isFinite ()
- Ссылка на JavaScript: номер
- Дескрипторы свойств JavaScript
- Метод Object assign ()
- Метод создания объекта ()
- Метод Object defineProperties ()
- Метод Object defineProperty ()
- Метод записи объекта ()
- Метод Object freeze ()
- Метод Object getOwnPropertyDescriptor ()
- Метод Object getOwnPropertyDescriptors ()
- Метод Object getOwnPropertyNames ()
- Метод Object getOwnPropertySymbols ()
- Метод Object getPrototypeOf ()
- Метод Object is ()
- Метод Object isExtensible ()
- Метод Object isFrozen ()
- Метод Object isSealed ()
- Метод Object keys ()
- Метод Object preventExtensions ()
- Метод объекта seal ()
- Метод Object setPrototypeOf ()
- Метод значений объекта ()
- Метод Object hasOwnProperty ()
- Метод Object isPrototypeOf ()
- Метод Object propertyIsEnumerable ()
- Метод Object toLocaleString ()
- Метод Object toString ()
- Метод Object valueOf ()
- Ссылка на JavaScript: объект
- Оператор присваивания JavaScript
- Интернационализация JavaScript
- Тип JavaScript оператора
- Новый оператор JavaScript
- Операторы сравнения JavaScript
- Правила приоритета операторов JavaScript
- Экземпляр JavaScript оператора
- Операторы JavaScript
- Область действия JavaScript
- Преобразование типов JavaScript (приведение)
- Операторы равенства JavaScript
- Условное выражение if / else в JavaScript
- Условный переключатель JavaScript
- Оператор удаления JavaScript
- Параметры функции JavaScript
- Оператор распространения JavaScript
- Возвращаемые значения JavaScript
- Логические операторы JavaScript
- Тернарный оператор JavaScript
- Рекурсия JavaScript
- Свойства объекта JavaScript
- Объекты ошибок JavaScript
- Глобальный объект JavaScript
- Функция JavaScript filter ()
- Функция JavaScript map ()
- Функция JavaScript reduce ()
- Оператор in в JavaScript
- Операторы JavaScript
- Как получить значение свойства CSS в JavaScript
- Как добавить прослушиватель событий к нескольким элементам в JavaScript
- Поля частного класса JavaScript
- Как отсортировать массив по значению даты в JavaScript
- Поля открытого класса JavaScript
- Символы JavaScript
- Как использовать библиотеку JavaScript bcrypt
- Как переименовать поля при использовании деструктуризации объекта
- Как проверять типы в JavaScript без использования TypeScript
- Как проверить, содержит ли массив JavaScript определенное значение
- Что означает оператор двойного отрицания !! делать в JavaScript?
- Какой оператор равенства следует использовать при сравнении JavaScript? == против ===
- Стоит ли изучать JavaScript?
- Как вернуть результат асинхронной функции в JavaScript
- Как проверить, пуст ли объект в JavaScript
- Как выйти из цикла for в JavaScript
- Как добавить элемент в массив по определенному индексу в JavaScript
- Почему не следует изменять прототип объекта JavaScript
- В чем разница между использованием let и var в JavaScript?
- Ссылки, используемые для активации функций JavaScript
- Как соединить две строки в JavaScript
- Как объединить два массива в JavaScript
- Как проверить, является ли значение JavaScript массивом?
- Как получить последний элемент массива в JavaScript?
- Как отправлять данные в кодировке urlencop с помощью Axios
- Как узнать дату завтрашнего дня с помощью JavaScript
- Как получить вчерашнюю дату с помощью JavaScript
- Как получить название месяца из даты JavaScript
- Как проверить, совпадают ли две даты в один и тот же день в JavaScript
- Как проверить, относится ли дата к дню в прошлом в JavaScript
- Операторы, помеченные JavaScript
- Как дождаться разрешения 2 или более обещаний в JavaScript
- Как получить дни между двумя датами в JavaScript
- Как загрузить файл с помощью Fetch
- Как отформатировать дату в JavaScript
- Как перебирать свойства объекта в JavaScript
- Как рассчитать количество дней между двумя датами в JavaScript
- Как использовать ожидание верхнего уровня в модулях ES
- Динамический импорт JavaScript
- Необязательное связывание JavaScript
- Как заменить пробел внутри строки в JavaScript
- Нулевое объединение JavaScript
- Как сгладить массив в JavaScript
- Это десятилетие в JavaScript
- Как отправить заголовок авторизации с помощью Axios
- Список ключевых и зарезервированных слов в JavaScript
- Как преобразовать массив в строку в JavaScript
- Как удалить все содержимое папок node_modules
- Как удалить дубликаты из массива JavaScript
- Let vs Const в JavaScript
- Один и тот же вызов POST API в различных библиотеках JavaScript
- Как получить первые n элементов в массиве в JS
- Как разделить массив на несколько равных частей в JS
- Как замедлить цикл в JavaScript
- Как загрузить изображение на холст HTML
- Как разрезать строку на слова в JavaScript
- Как разделить массив пополам в JavaScript
- Как написать текст на холсте HTML
- Как удалить последний символ строки в JavaScript
- Как удалить первый символ строки в JavaScript
- Как исправить ошибку TypeError: не удается назначить свойство только для чтения «exports» объекта «#
- Как создать всплывающее окно с намерением выхода
- Как проверить, является ли элемент потомком другого
- Как принудительно вводить учетные данные для каждого запроса Axios
- Как устранить ошибку «не функция» в JavaScript
- Гэтсби, как изменить фавикон
- Загрузка внешнего файла JS с помощью Gatsby
- Как определить темный режим с помощью JavaScript
- Посылка, как исправить ошибку `регенератор Время выполнения не определено`
- Как определить, используется ли Adblocker с JavaScript
- Деструктуризация объектов с помощью типов в TypeScript
- The Deno Handbook: краткое введение в Deno 🦕
- Как получить последний сегмент пути или URL с помощью JavaScript
- Как перемешать элементы в массиве JavaScript
- Как проверить, существует ли ключ в объекте JavaScript
- Возбуждение событий и захват событий
- событие. stopPropagation против event.preventDefault () против return false в событиях DOM
- Примитивные типы и объекты в JavaScript
- Как узнать, к какому типу относится значение в JavaScript?
- Как вернуть несколько значений из функции в JavaScript
- Стрелочные функции и обычные функции в JavaScript
- Как мы можем получить доступ к значению свойства объекта?
- В чем разница между null и undefined в JavaScript?
- В чем разница между методом и функцией?
- Какими способами мы можем выйти из цикла в JavaScript?
- JavaScript для..из петли
- Что такое деструктуризация объектов в JavaScript?
- Что такое подъем в JavaScript?
- Как заменять запятые на точки с помощью JavaScript
- Важность тайминга при работе с DOM
- Как перевернуть массив JavaScript
- Как проверить, является ли значение числом в JavaScript
- Как принять неограниченное количество параметров в функции JavaScript
- Прокси-объекты JavaScript
- Делегирование событий в браузере с использованием ванильного JavaScript
- Супер-ключевое слово JavaScript
- Введение в XState
- Значения передаются в JavaScript по ссылке или по значению?
- Пользовательские события в JavaScript
- Пользовательские ошибки в JavaScript
- Пространства имен в JavaScript
- Любопытное использование запятых в JavaScript
- Цепочка вызовов методов в JavaScript
- Как справиться с отклонением обещаний
- Как поменять местами два элемента массива в JavaScript
- Как я исправил ошибку «cb. «применить не является функцией» ошибка при использовании Gitbook
- Как добавить элемент в начало массива в JavaScript
- Gatsby, исправьте ошибку «не удается найти модуль gatsby-cli / lib / reporter»
- Как получить индекс элемента в массиве JavaScript
- Как проверить пустой объект в JavaScript
- Как преобразовать объект в существующие переменные в JavaScript
- Структура данных JavaScript в виде массива
- Структура данных JavaScript стека
- Структуры данных JavaScript: очередь
- Структуры данных JavaScript: установить
- Структуры данных JavaScript: словари
- Структуры данных JavaScript: связанные списки
- JavaScript, как экспортировать функцию
- JavaScript, как экспортировать несколько функций
- JavaScript, как выйти из функции
- JavaScript, как найти символ в строке
- JavaScript, как фильтровать массив
- JavaScript, как расширить класс
- JavaScript, как найти дубликаты в массиве
- JavaScript, как заменить элемент массива
- Алгоритмы JavaScript: линейный поиск
- Алгоритмы JavaScript: двоичный поиск
- Алгоритмы JavaScript: сортировка выбора
- Алгоритмы JavaScript: быстрая сортировка
- Алгоритмы JavaScript: сортировка слиянием
- Алгоритмы JavaScript: пузырьковая сортировка
- Дождитесь разрешения всех обещаний в JavaScript
Почему «Это» в JavaScript — Скотч.
ioХотя JavaScript — увлекательный и мощный язык, он может быть сложным и требует правильного понимания лежащих в его основе принципов для уменьшения распространенных ошибок.
В этом посте мы познакомим вас с ключевым словом и
, его поведением и трудностями , лежащими в основе его . Они будут подробно описаны с использованием соответствующих примеров, чтобы лучше понять суть.
Разработчики ошибаются, полагая, что это
относится к «области действия» функции, в которой оно встречается.Это неверно, потому что всякий раз, когда функция вызывается, она выполняется в новом контексте выполнения, пока ее выполнение не будет завершено. Каждый контекст выполнения обычно ссылается на объект, и значение этого объекта преобразуется в значение , это
.
Чтобы правильно понять, что такое и
, нам сначала нужно рассмотреть два важных понятия:
Контекст
Обычно в английском языке, когда существительное определено в предложении, последующие ссылки на подлежащее существительное будут делаться с использованием местоимения. Понимаю?
Первое заявление: Брэд — хороший мальчик. Второе утверждение: Он очень умный мальчик!
Кто такой гениальный мальчик? Брэд … очевидно!
Это то же самое в JavaScript, первый оператор создает контекст, в то время как этот
функционирует как «он» во втором операторе и используется для ссылки на субъект в первом операторе.
этот
относится к контексту выполняемой функции.
Это будет означать, что во время каждого выполнения функции фокус будет на новом контексте выполнения, который переопределяется всякий раз, когда вызывается функция, и, поскольку контекст содержит ссылку на объект, значение этого
обновляется соответствующим образом.
Непрактично предполагать, что значение , это
фиксировано и всегда относится к области действия функции, значение , это
, фактически, динамическое и определяется тем, как функция вызывается и где она вызывается:
Различные разделы в программе JavaScript запускаются в контексте выполнения и поддерживают этот контекст до тех пор, пока не произойдет переключение на новый контекст. Контекст выполняющейся функции ссылается на объект, который ее вызвал, поэтому вызов и
внутри функции относится к вызывающему объекту.
Поскольку контекст действительно важен для нашего понимания , это
, , нам нужно знать, как / почему контекст переключается во время выполнения:
Как меняется контекст?
Текущий контекст выполнения в программе JavaScript обычно указывает на объект, который вызывает функцию, и его можно переключить на другой объект, вызвав функцию с другим объектом.
Call-Site
Место, где функция вызывается в программе JavaScript, называется сайтом вызова .
Это также важно при определении значения и
, потому что, если функция не вызывается каким-либо видимым объектом, текущий контекст сайта вызова
будет отвечать за установку нового контекста выполнения функции.
Проще говоря, значение , это
представляет контекст начальной выполняемой функции на сайте вызова и остается неизменным в любом другом сайте вызова, вызывающая функция не предоставляет контекст.
Мы наконец можем определить это
на основе всех объяснений, представленных выше:
Значение
этого ключевого слова
зависит от объекта, для которого оно вызывается, а не от самой функции или ее области видимости.
Пункт вызова
функции также важен для определения того, к чему относится этот
.
Есть четыре привязки, которые важны для определения значения и
в программе JavaScript.Мы рассмотрим их в этом разделе.
Обвязка по умолчанию
Допустим, мы писали программу и определили функцию, в которой мы использовали ключевое слово this
, затем мы вызвали функцию в глобальной области (вне любой функции) программы. Как вы думаете, какой объект это ключевое слово
будет ссылаться на среду выполнения? Посмотрим:
let randomFunction = () => {
this.attribute = 'Это новый атрибут, который будет создан для вызывающего объекта';
консоль. журнал (это);
}
randomFunction ();
Хорошая мысль! Этот
ссылается на глобальный объект в выводе ниже:
В приведенном выше коде мы создали случайную функцию, которая присоединяет новое свойство «атрибут» к любому объекту, который его вызывает. Поскольку сайт вызова
вызова функции имел контекст, ссылающийся на глобальный объект (это значение по умолчанию), мы видим, что в выходных данных консоль регистрирует множество свойств, которые прикреплены к глобальному объекту (объекту окна), включая свойство, созданное случайной функцией.
Это привязка по умолчанию к
в JavaScript. Когда функция вызывается без объекта, , этот
по умолчанию привязан к текущему контексту выполнения, мы рассмотрим другие привязки ниже.
По умолчанию
этот
привязан к глобальному объекту при вызове глобальной функцией.
Примечательно, что когда мы кодируем в строгом режиме , , этот
содержит значение undefined в глобальных функциях и в анонимных функциях, которые не привязаны к какому-либо объекту.
Явная привязка
Помните, как мы говорили, что контекст выполнения можно изменить, вызвав функцию на объекте? Что ж, есть явные способы легко достичь того же результата, давайте кратко их рассмотрим:
- Bind: Этот метод создает новую функцию и при вызове устанавливает для ее ключевого слова this заданное значение (переданное в качестве аргумента).
- Call — этот метод вызывает функцию и позволяет нам указать контекст, в котором его
это значение
должно быть привязано в качестве аргумента.Этот метод принимает необязательные аргументы. - Применить — эта функция похожа на
call ()
с той разницей, что позволяет нам передавать аргументы в виде массива.
Рассмотрим пример:
function randomFunction () {
console.log (это);
}
let newObj = {
описание: «Это новый объект»
}
console.log (randomFunction.bind (newObj) ());
console. log (randomFunction.call (newObj));
console.log (randomFunction.apply (newObj));
В приведенном выше коде мы явно связали , это
в случайной функции с переменной newObj
, и мы видим, что вызывает
, связывает
и применяет методы
доступны в прототипе функции в JavaScript.Все они правильно связаны в выводе ниже:
Неявное связывание
Когда объект определяет метод (который вызывает , этот
) как свойство и вызывает этот метод где-то в программе, , этот
внутри метода будет неявно привязан к этому объекту. Давайте посмотрим на этот пример:
пусть newObj = {
описание: «Это новый объект»,
randomFunction () {
console.log (это);
}
}
newObj.randomFunction ();
Это простой пример, который соответствует нашему определению и
выше; вызывающий объект здесь неявно привязан к методу и регистрирует объект при вызове:
Новый переплет
Если у нас есть конструктор объекта и мы создаем с ним новый объект, новый объект будет иметь значение и
как ссылку на конструктор, из которого он был создан. Это привязка и
, которая возникает при создании нового объекта с помощью конструктора. Давайте посмотрим на пример:
function newObj () {
this.description = "Это конструктор объекта"
this.randomFunction = () => {
console.log (это);
}
}
пусть anotherFunction = new newObj ()
anotherFunction.randomFunction ()
В приведенном выше фрагменте мы определили новую функцию-конструктор и дали ей description
и randomFunction
свойство и метод соответственно.Мы создали экземпляр с переменной anotherFunction
и вызвали его метод randomFunction
, вот результат:
Здесь зарегистрированный объект имеет свойство description
, которое было определено в конструкторе объекта, чтобы доказать, что this
ссылается на функцию конструктора.
Вывод из всего, что было показано выше:
Везде, где
этот
определен в функции, ему обычно не присваивается значение, пока объект не вызовет содержащую функцию.
Вы можете подумать, что приведенный выше абзац — это все, что вам когда-либо понадобится при работе с ,
, но есть сценарии, в которых , этот
ведет себя странным образом (cus JavaScript).
Давайте посмотрим на фрагмент кода, в котором мы определяем замыкание, которое ссылается на на
в JavaScript:
пусть newObj = {
описание: «Это новый объект»,
randomFunction () {
пусть a = 1
return function () {
console.log (это);
}
}
}
newObj.randomFunction () ();
Можно было бы разумно ожидать, что замыкание вернет значение , это
как объект, который вызвал randomFunction
, но давайте посмотрим, что говорит результат:
Подождите, что? Как здесь и
относятся к глобальному объекту?
Разве внешняя функция не вызывалась объектом newObj
?
Разве это не должно было изменить контекст выполнения и обновить на эту ссылку
?
Это все правильные вопросы, однако вот что нужно отметить:
Замыканияне могут получить доступ к значениям
этого
своей внешней функции, вызываяэто
внутри себя.
Это связано с тем, что это значение функции
доступно только той функции, в которой оно встречается, а не ее внутренним функциям. Следовательно, и
анонимного закрытия будут привязаны к глобальному объекту, где строгий режим не используется.
Как добиться желаемого поведения?
Вот уловка. Рассмотрим этот фрагмент кода:
var newObj = {
описание: «Это новый объект»,
randomFunction () {
var that = this;
return function () {
консоль.журнал (что);
}
}
}
newObj.randomFunction () ();
Здесь, в randomFunction
мы объявляем новую переменную с именем , которая
, и присваиваем ей значение , это
. Таким образом, мы можем ссылаться на на значение
внешней функции внутри замыкания, поскольку область внешней функции всегда доступна для ее внутренних функций.
Вот результат:
Отлично! Мы сослались на , это
значение внешней функции в замыкании, создав переменную , что
.
Следует отметить, что мы могли назвать переменную как угодно, но выбрали , а не
, потому что многие разработчики JavaScript уже считают удобным называть ее , а не
. Я иногда выбираю cat
или thanos
хе-хе.
Теперь вы знаете, что это
… и это
.
Лучший способ справиться с этим без объявления новой переменной — использовать стрелочную функцию. У нас:
var newObj = {
описание: «Это новый объект»,
randomFunction () {
return () => консоль.журнал (это)
}
}
newObj.randomFunction () ();
Это дает тот же результат, что и объявление , что
.
Мы исследовали ключевое слово и
в JavaScript, прояснив его поведение и использование в процессе. Мы также рассмотрели сценарий замыканий, в котором логика и
может быть неуловимой, и мы рассмотрели подходящее решение, однако есть ситуации, когда и
все еще могут оказаться сложными, лучший способ выяснить это — определить, какие привязок работает во время выполнения функции и тщательно отслеживает контекст. Не стесняйтесь оставлять свои отзывы и комментарии. Удачного кодирования !!
Понравилась эта статья? Подпишитесь на @iChuloo в Twitter
Понимание ключевого слова this, вызов, применение и привязка в JavaScript
Прежде чем углубляться в специфику ключевого слова this
в JavaScript, важно сделать шаг назад и сначала понять, почему ключевое слово this
вообще существует. Ключевое слово и
позволяет многократно использовать функции в разных контекстах.Иными словами, ключевое слово «this» позволяет вам решить, какой объект должен быть в фокусе при вызове функции или метода. Все, о чем мы будем говорить после этого, будет основываться на этой идее. Мы хотим иметь возможность повторно использовать функции или методы в разных контекстах или с разными объектами.
Первое, что мы рассмотрим, это как определить, на что ссылается это ключевое слово
. Первый и самый важный вопрос, который вам нужно задать себе, когда вы пытаетесь ответить на этот вопрос, — это: « Где вызывается эта функция? ». only способ узнать, на что ссылается ключевое слово this
, посмотрев, где была вызвана функция, использующая ключевое слово this
.
Чтобы продемонстрировать это на примере, с которым вы уже знакомы, скажем, у нас есть функция greet
, которая принимает имя, предупреждающее и приветственное сообщение.
function greet (name) {
alert (`Здравствуйте, меня зовут $ {name}`)
}
Если бы я спросил вас, что именно greet
будет предупреждать, что бы вы ответили? Учитывая только определение функции, узнать невозможно.Чтобы узнать, что такое name
, вам нужно посмотреть на вызов функции greet
.
То же самое и с выяснением, на что ссылается это ключевое слово
. Вы даже можете думать о ключевом слове и
как о обычном аргументе функции — оно будет меняться в зависимости от того, как функция вызывается.
Теперь, когда вы знаете, что первый шаг к выяснению того, на что ссылается это ключевое слово
, — это посмотреть, где вызывается функция, что дальше? Чтобы помочь нам в следующем шаге, мы собираемся установить 5 правил или рекомендаций.
- Неявная привязка
- Явная привязка
- Переплет новый
- Лексическая привязка
- переплет окна
Неявное связывание
Помните, цель здесь — иметь возможность взглянуть на определение функции с помощью ключевого слова this
и сказать, на что указывает этот
. Первое и наиболее распространенное правило для этого называется Implicit Binding
. Я бы сказал, он расскажет вам, на что ссылается ключевое слово и
примерно в 80% случаев.
Допустим, у нас есть объект, который выглядел так
const user = {
имя: 'Тайлер',
возраст: 27,
greet () {
alert (`Здравствуйте, меня зовут $ {this.name}`)
}
}
Теперь, если бы вы вызывали метод greet
для объекта user
, вы бы использовали точечную нотацию.
Это подводит нас к основному ключевому моменту правила неявной привязки. Чтобы выяснить, на что ссылается это ключевое слово
, сначала посмотрите налево от точки при вызове функции .Если есть «точка», посмотрите слева от нее, чтобы найти объект, на который ссылается это ключевое слово
.
В приведенном выше примере пользователь
находится «слева от точки», что означает, что это ключевое слово
ссылается на объект пользователя
. Итак, это , как если бы , внутри метода greet
интерпретатор JavaScript изменяет этого
на пользователя
.
greet () {
alert (`Здравствуйте, меня зовут $ {user.name} `)
}
Давайте посмотрим на похожий, но немного более сложный пример. Теперь, вместо того, чтобы просто иметь name
, age
и greet
property, давайте также дадим нашему объекту пользователя свойство mother
, которое также имеет свойство name
и greet
.
const user = {
имя: 'Тайлер',
возраст: 27,
greet () {
alert (`Здравствуйте, меня зовут $ {this.name}`)
},
мама: {
имя: 'Стейси',
greet () {
alert (`Здравствуйте, меня зовут $ {this.name} `)
}
}
}
Теперь возникает вопрос, о чем будет предупреждать каждый вызов ниже?
user.greet ()
user.mother.greet ()
Всякий раз, когда мы пытаемся выяснить, на что ссылается это ключевое слово
, нам нужно посмотреть на вызов и увидеть, что находится «слева от точки». В первом вызове пользователь
находится слева от точки, что означает, что этот
будет ссылаться на пользователя
. Во втором вызове mother
находится слева от точки, что означает, что , этот
будет ссылаться на mother
.
user.greet ()
user.mother.greet ()
Как упоминалось ранее, примерно в 80% случаев объект будет «слева от точки». Вот почему первый шаг, который вам следует предпринять при выяснении того, на что ссылается ключевое слово и это ключевое слово
, — это «посмотреть налево от точки». Но что, если точки нет? Это подводит нас к следующему правилу —
Явная привязка
А что, если бы наша функция greet
не была методом объекта user
, она была бы просто отдельной автономной функцией.
function greet () {
alert (`Здравствуйте, меня зовут $ {this.name}`)
}
const user = {
имя: 'Тайлер',
возраст: 27,
}
Мы знаем, что для того, чтобы определить, на что ссылается это ключевое слово
, мы сначала должны посмотреть, где вызывается функция. Теперь возникает вопрос, как мы можем вызвать greet
, но при этом вызвать его с ключевым словом this
, ссылающимся на объект user
. Мы не можем просто выполнить user.greet ()
, как это делали раньше, потому что user
не имеет метода greet
.В JavaScript каждая функция содержит метод, который позволяет вам делать именно это, и этот метод называется call
.
«call» — это метод для каждой функции, который позволяет вам вызывать функцию, определяющую, в каком контексте функция будет вызываться.
Имея это в виду, мы можем вызвать greet
в контексте пользователя
со следующим кодом —
Опять же, вызов
— это свойство каждой функции, и первый аргумент, который вы передадите ему, будет контекстом (или фокальным объектом), в котором функция вызывается.Другими словами, первым аргументом, который вы передадите для вызова, будет то, на что ссылается ключевое слово и
внутри этой функции.
Это основа правила № 2 (явное связывание), поскольку мы явно (с использованием .call
) указываем, на что ссылается это ключевое слово
.
Теперь давайте немного изменим нашу функцию greet
. Что, если бы мы также хотели передать некоторые аргументы? Скажите вместе с их именем, мы также хотели предупредить, какие языки они знают.Примерно так
function greet (l1, l2, l3) {
предупреждение (
`Здравствуйте, меня зовут $ {this.name}, и я знаю $ {l1}, $ {l2} и $ {l3}`
)
}
Теперь, чтобы передать аргументы функции, вызываемой с помощью .call
, вы передаете их один за другим после того, как вы укажете первый аргумент, который является контекстом.
function greet (l1, l2, l3) {
предупреждение (
`Здравствуйте, меня зовут $ {this.name}, и я знаю $ {l1}, $ {l2} и $ {l3}`
)
}
const user = {
имя: 'Тайлер',
возраст: 27,
}
const languages = ['JavaScript', 'Ruby', 'Python']
приветствую. вызов (пользователь, языки [0], языки [1], языки [2])
Это работает и показывает, как можно передавать аргументы функции, вызываемой с помощью .call
. Однако, как вы могли заметить, довольно неприятно передавать аргументы один за другим из нашего массива languages
. Было бы неплохо, если бы мы могли просто передать весь массив в качестве второго аргумента, и JavaScript разложил бы их для нас. Что ж, хорошие новости для нас, это именно то, что делает .apply
. .apply
— это то же самое, что и .call
, но вместо того, чтобы передавать аргументы один за другим, вы можете передать один массив, и он будет распространять каждый элемент в массиве для вас как аргументы функции.
Итак, теперь, используя .apply
, наш код может измениться на этот (ниже), при этом все остальное останется прежним.
const languages = ['JavaScript', 'Ruby', 'Python']
greet.apply (пользователь, языки)
До сих пор в соответствии с нашим правилом «Явное связывание» мы узнали о . вызовите
, а также .apply
, которые позволяют вызывать функцию, указывая, на что это ключевое слово
будет ссылаться внутри этой функции. Последняя часть этого правила — .bind
. .bind
— это то же самое, что .call
, но вместо немедленного вызова функции он вернет новую функцию, которую вы можете вызвать позже. Итак, если мы посмотрим на наш предыдущий код с использованием .bind
, он будет выглядеть так:
function greet (l1, l2, l3) {
предупреждение (
`Здравствуйте, меня зовут $ {this.name} и я знаю $ {l1}, $ {l2} и $ {l3} `
)
}
const user = {
имя: 'Тайлер',
возраст: 27,
}
const languages = ['JavaScript', 'Ruby', 'Python']
const newFn = greet.bind (пользователь, языки [0], языки [1], языки [2])
newFn ()
переплет новый
Третье правило для определения того, на что ссылается это ключевое слово
, называется привязкой new
. Если вы не знакомы с ключевым словом new
в JavaScript, всякий раз, когда вы вызываете функцию с ключевым словом new
, под капотом интерпретатор JavaScript создаст для вас совершенно новый объект и назовет его this
.Итак, естественно, если функция была вызвана с new
, ключевое слово this
ссылается на этот новый объект, созданный интерпретатором.
функция Пользователь (имя, возраст) {
this.name = имя
this.age = возраст
}
const me = новый пользователь ('Тайлер', 27)
Лексическая привязка
На данный момент мы придерживаемся нашего 4-го правила, и вы, возможно, чувствуете себя немного подавленным. Это честно. Ключевое слово и
в JavaScript, возможно, сложнее, чем должно быть.Хорошие новости: это следующее правило является наиболее интуитивным.
Скорее всего, вы уже слышали и использовали стрелочную функцию раньше. Они новы в ES6. Они позволяют писать функции в более кратком формате.
friends.map ((friend) => friend.name)
Даже больше, чем краткость, стрелочные функции имеют гораздо более интуитивный подход, когда дело доходит до этого ключевого слова
. В отличие от обычных функций, стрелочные функции не имеют собственных , а не
.Вместо это
определяется лексически как
. Это причудливый способ сказать, что , это
определяется так, как вы ожидаете, в соответствии с обычными правилами поиска переменных. Продолжим пример, который мы использовали ранее. Теперь, вместо того, чтобы иметь языков,
и приветствуют
отдельно от объекта, давайте объединим их.
const user = {
имя: 'Тайлер',
возраст: 27,
языки: ['JavaScript', 'Ruby', 'Python'],
привет () {}
}
Ранее мы предполагали, что массив languages
всегда будет иметь длину 3.Таким образом мы смогли использовать жестко запрограммированные переменные, такие как l1
, l2
и l3
. Давайте сделаем приветствовать
немного умнее и предположим, что языков
могут быть любой длины. Для этого мы будем использовать .reduce
, чтобы создать нашу строку.
const user = {
имя: 'Тайлер',
возраст: 27,
языки: ['JavaScript', 'Ruby', 'Python'],
greet () {
const hello = `Привет, меня зовут $ {this.name} и я знаю`
const langs = this.languages.reduce (function (str, lang, i) {
if (i === this.languages.length - 1) {
вернуть `$ {str} и $ {lang} '.
}
return `$ {str} $ {lang},`
}, "")
оповещение (привет + языки)
}
}
Это намного больше кода, но конечный результат должен быть таким же. Когда мы вызываем user.greet ()
, мы ожидаем увидеть Привет, меня зовут Тайлер, и я знаю JavaScript, Ruby и Python.
. К сожалению, произошла ошибка. Вы можете это заметить? Возьмите приведенный выше код и запустите его в консоли.Вы заметите, что выдает ошибку Uncaught TypeError: Cannot read property 'length' of undefined
. Валовой. Единственное место, где мы используем .length
, — это строка 9, поэтому мы знаем, что наша ошибка там.
if (i === this.languages.length - 1) {}
Согласно нашей ошибке, this.languages
не определен. Давайте пройдемся по нашим шагам, чтобы понять, что это за , это ключевое слово
явно ссылается на причину, а не на пользователя
, как должно быть.Во-первых, нам нужно посмотреть, где вызывается функция. Подождите? Где вызывается функция? Функция передается на .reduce
, поэтому мы понятия не имеем. На самом деле мы никогда не видим вызова нашей анонимной функции, поскольку JavaScript делает это сам в реализации .reduce
. Это проблема. Нам нужно указать, что мы хотим, чтобы анонимная функция, которую мы передаем в .reduce
, вызывалась в контексте пользователя
. Вот так это.languages
будет ссылаться на user.languages
. Как мы узнали выше, мы можем использовать .bind
.
const user = {
имя: 'Тайлер',
возраст: 27,
языки: ['JavaScript', 'Ruby', 'Python'],
greet () {
const hello = `Привет, меня зовут $ {this.name} и я знаю`
const langs = this.languages.reduce (function (str, lang, i) {
if (i === this.languages.length - 1) {
вернуть `$ {str} и $ {lang} '.
}
return `$ {str} $ {lang},`
} .bind (это), "")
оповещение (привет + языки)
}
}
Итак, мы видели, как .bind
решает проблему, но при чем здесь стрелочные функции. Ранее я сказал, что со стрелочными функциями это
определяется лексически
. Это причудливый способ сказать, что , это
определяется так, как вы ожидаете, в соответствии с обычными правилами поиска переменных «.
В приведенном выше коде, следуя вашей естественной интуиции, что будет с этой ссылкой на ключевое слово
внутри анонимной функции? Для меня это должно ссылаться на пользователя
.Нет причин создавать новый контекст только потому, что мне пришлось передать новую функцию .reduce
. И с этой интуицией приходит часто упускаемое из виду значение стрелочных функций. Если мы переписываем приведенный выше код и ничего не делаем, кроме использования анонимной стрелочной функции вместо объявления анонимной функции, все «просто работает».
const user = {
имя: 'Тайлер',
возраст: 27,
языки: ['JavaScript', 'Ruby', 'Python'],
greet () {
const hello = `Привет, меня зовут $ {this.name} и я знаю`
const langs = this.languages.reduce ((str, lang, i) => {
if (i === this.languages.length - 1) {
вернуть `$ {str} и $ {lang} '.
}
return `$ {str} $ {lang},`
}, "")
оповещение (привет + языки)
}
}
Опять же, причина этого в том, что со стрелочными функциями это
определяется «лексически». У стрелочных функций нет собственных , это
. Вместо этого, как и в случае поиска переменных, интерпретатор JavaScript будет смотреть на охватывающую (родительскую) область видимости, чтобы определить, на что ссылается этот
.
переплет окна
И, наконец, универсальный случай — привязка окна. Допустим, у нас был следующий код
function sayAge () {
console.log (`Мой возраст $ {this.age}`)
}
const user = {
имя: 'Тайлер',
возраст: 27
}
Как мы уже говорили, если вы хотите вызвать sayAge
в контексте пользователя
, вы можете использовать .call
, .apply
или .bind
. Что бы произошло, если бы мы не использовали ни один из них, а вместо этого вызвали бы sayAge
, как обычно,
Неудивительно, что вы получите Мой возраст не определен
, потому что это .возраст
не определен. Здесь все становится немного странно. На самом деле здесь происходит то, что слева от точки нет ничего, мы не используем .call
, .apply
, .bind
или новое ключевое слово
, JavaScript по умолчанию это
для ссылки объект окно
. Это означает, что если мы добавим свойство age
к объекту window
, то, когда мы снова вызовем нашу функцию sayAge
, this.age
больше не будет неопределенным, а вместо этого будет иметь значение age
для объекта window. Не верите мне? Запустите этот код,
window.age = 27
function sayAge () {
console.log (`Мой возраст $ {this.age}`)
}
Довольно коряво, правда? Вот почему пятое правило - привязка окна
. Если ни одно из других правил не выполняется, то JavaScript по умолчанию будет использовать ключевое слово this
для ссылки на объект window
.
Начиная с ES5, если у вас включен «строгий режим», JavaScript будет поступать правильно и вместо значения по умолчанию для объекта окна просто сохранит «this» как неопределенное.
'строгое использование'
window.age = 27
function sayAge () {
console.log (`Мой возраст $ {this.age}`)
}
sayAge ()
Итак, применяя все наши правила на практике, всякий раз, когда я вижу ключевое слово this
внутри функции, я предпринимаю следующие шаги, чтобы выяснить, на что оно ссылается.
- Посмотрите, где была вызвана функция.
- Есть ли объект слева от точки? Если да, то это то, на что указывает ключевое слово this. Если нет, переходите к №3.
- Была ли функция вызвана с помощью «call», «apply» или «bind»? Если да, то здесь будет явно указано, на что ссылается ключевое слово this. Если нет, переходите к №4.
- Была ли функция вызвана с использованием ключевого слова «new»? Если да, то ключевое слово this ссылается на вновь созданный объект, созданный интерпретатором JavaScript.Если нет, переходите к №5.
- Находится ли «это» внутри функции стрелки? Если это так, его ссылка может быть найдена лексически во включающей (родительской) области. Если нет, переходите к №6.
- Вы в «строгом режиме»? Если да, то ключевое слово «это» не определено. Если нет, переходите к №7.
- JavaScript странный. «This» относится к объекту «окно».
Это в JavaScript | Zell Liew
21 июня 2017 г. Вас смущает ключевое слово и
в JavaScript? Вначале это всех сбивает с толку, так что не беспокойтесь об этом.Ты не одинок.
Но это не значит, что вы можете продолжать, не понимая и
вечно. Он так часто используется в JavaScript и во всех учебных пособиях, что вам рано или поздно нужно понять, что такое и
. Как только вы поймете и
, вы поймете, что это намного проще, чем вы думаете.
К концу этой статьи вы демистифицировали и
для себя. Вы будете знать, что это такое, для чего он нужен и как его использовать.
Итак, что это?
это
ключевое слово, значение которого изменяется в зависимости от того, как вызывается функция. Есть шесть различных способов, которыми и
могут принимать новые значения. Их:
-
это
в глобальном контексте -
это
в строительстве объекта -
это
в методе объекта -
это
в простой функции -
это
в виде стрелки -
это
в прослушивателе событий
Вы можете задаться вопросом, что такое ,
в каждом контексте и почему вообще необходимо менять на
.Чтобы ответить на ваш вопрос, давайте посмотрим, как это
изменяется в каждом из шести контекстов.
Это в глобальном контексте
Когда этот
вызывается вне какой-либо функции в глобальном контексте, по умолчанию используется объект Window
в браузере.
console.log (это) // Окно
По умолчанию это объект окна в браузерах. Обычно вы не будете использовать , это
в глобальном контексте, поэтому значение , это
здесь не имеет значения.Перейдем к следующему контексту.
Это в строительстве объекта
Когда вы создаете новый экземпляр объекта с ключевым словом new
, это
относится к экземпляру.
function Human (age) {
this.age = возраст
}
let greg = новый человек (22)
пусть thomas = новый человек (24)
console.log (greg) // this.age = 22
console.log (thomas) // this.age = 24
Это относится к экземпляру. Вы можете видеть, что greg
— это экземпляр Human
в приведенном выше коде.Теперь, когда вы ссылаетесь на greg
, вы не получите случайно thomas
. Таким образом, установка и
в качестве экземпляра имеет смысл.
Давайте теперь посмотрим на тесно связанный контекст — , это
в методе объекта.
Это в методе объекта
Методы — это причудливые слова для функций, связанных с объектом, например:
(Примечание: здесь методы определены с помощью сокращенного литерала объекта ES6. Если вы не знаете, что он делает, прочтите эту статью).
let o = {
// Метод
метод () {}
}
это
в любом методе относится к самому объекту.
let o = {
скажи это () {
console.log (это)
}
}
o.sayThis () // о
this относится к объекту Поскольку это
относится к объекту, вы можете использовать методы для получения экземпляра объекта, например:
function Human (имя) {
вернуть {
имя,
getName () {
верни это.имя
}
}
}
const zell = новый человек ('Zell')
const vincy = новый человек ('Винси')
console.log (zell.getName ()) // Zell
В этих двух объектных контекстах вы можете видеть, что измененное значение это
позволяет вам получить правильный экземпляр, который является основой для объектно-ориентированного программирования. Но это тема для другого дня.
Перейдем к следующему контексту.
Это в простой функции
Простые функции — это функции, которые вам очень хорошо известны; как показано ниже.Анонимные функции, написанные в той же форме, также считаются простыми функциями.
function hello () {
// скажи привет!
}
В браузерах это
всегда устанавливается на Window
в простой функции. То же самое верно, даже если вы вызываете простую функцию в методе объекта.
function simpleFunction () {
console.log (это)
}
const o = {
скажи это () {
simpleFunction ()
}
}
simpleFunction () // Окно
o.sayThis () // Окно
К сожалению, изменение на
неожиданно для новичков.Они ожидают, что и
останутся неизменными в методах объекта. Я тоже попался в это.
Чтобы понять, почему, рассмотрим следующий код. Здесь функция this.speakLeet
выполняется позже в рамках функции setTimeout
.
const o = {
doSomethingLater () {
setTimeout (function () {
this.speakLeet () // Ошибка
}, 1000)
},
SpeakLeet () {
console.log (`1337 15 4W350M3`)
}
}
К сожалению, приведенный выше код приводит к ошибке.Ошибка возникает из-за того, что этот
установлен на Window
в функции setTimeout
. Window
не имеет метода speakLeet
.
Одно из быстрых исправлений — создать переменную, которая хранит ссылку на и на
. Эта переменная часто называется self
или , что
.
const o = {
doSomethingLater () {
const self = это
setTimeout (function () {
self.speakLeet ()
}, 1000)
},
SpeakLeet () {
консоль.журнал (`1337 15 4W350M3`)
}
}
Второй способ решить эту проблему — использовать новые стрелочные функции ES6, которые подводят нас к следующему контексту.
Это стрелочные функции
этот
в стрелочной функции всегда совпадает с , это
вокруг него (в его непосредственной области действия). Итак, если вы используете стрелочные функции в методе объекта, контекст this
останется как объект, а не Window
.
При использовании стрелочных функций пример speakLeet
можно записать следующим образом:
const o = {
doSomethingLater () {
setTimeout (() => это.SpeakLeet (), 1000)
},
SpeakLeet () {
console.log (`1337 15 4W350M3`)
}
}
(Подробнее о функциях стрелок читайте в этой статье)
Третий способ изменить значение этого
в любой функции — использовать либо bind
, либо call
, либо apply
. Мы рассмотрим привязку
позже в этой статье, а вызовите
, а примените
в другой раз. Но сначала давайте рассмотрим последний контекст — слушателей событий.
Это в слушателях событий
этот
установлен для элемента, который инициировал событие в прослушивателе событий:
let button = document.querySelector ('кнопка')
button.addEventListener ('щелчок', function () {
console.log (this) // кнопка
})
При создании более сложных компонентов вы можете создать прослушиватели событий внутри методов.
function LeetSpeaker (elem) {
вернуть {
listenClick () {
элем.addEventListener ('щелчок', function () {
// Здесь что-нибудь делаем
})
}
}
}
Начиная с этот
относится к элементу в прослушивателе событий, если вам нужно активировать другой метод, вам необходимо предоставить ссылку на объект с помощью.
function LeetSpeaker (elem) {
вернуть {
listenClick () {
const self = это
elem.addEventListener ('щелчок', function () {
self.speakLeet ()
})
},
SpeakLeet () {console.log (`1337 15 4W350M3`)}
}
}
В качестве альтернативы вы можете использовать функцию стрелки.Если вы это сделаете, вы все равно можете получить элемент с event.currentTarget
.
function LeetSpeaker (elem) {
вернуть {
listenClick () {
elem.addEventListener ('щелчок', (e) => {
console.log (e.currentTarget) // элемент
this.speakLeet ()
})
},
SpeakLeet () {console.log (`1337 15 4W350M3`)}
}
}
Но обоих методов недостаточно, чтобы помочь вам удалить прослушиватели событий, когда возникнет необходимость, оба являются анонимными функциями.
Чтобы удалить прослушиватель событий, обратный вызов, переданный в качестве второго значения, должен быть именованной функцией:
function someFunction () {
console.log ('сделай что-нибудь')
// Удаляет прослушиватель событий.
document.removeEventListener ('щелчок', someFunction)
}
document.addEventListener ('щелчок', someFunction)
(подробнее об обратных вызовах, если вам нужна помощь).
Если вам нужно это
для ссылки на объект в приемнике событий, вам нужно использовать bind
, чтобы вручную создать контекст this
.
function LeetSpeaker (elem) {
вернуть {
listenClick () {
this.listener = this.speakLeet.bind (это)
elem.addEventListener ('щелкните', this.listener)
},
speakLeet (e) {
const elem = e.currentTarget
console.log (`1337 15 4W350M3`)
elem.removeEventListener ('щелкните', this.listener)
}
}
}
Приведенный выше код может запутать вас, если вы не понимаете bind
. Итак, прежде чем я покажу вам, что происходит, давайте сделаем крюк и разберемся, что делает bind
.
Изменение с помощью привязки
bind
— это метод, который присутствует в каждой функции. Это позволяет вам изменить контекст на этот
. Этот метод принимает любое количество аргументов и возвращает связанную функцию.
function sayThis () {
console.log (это)
}
const boundFunc = sayThis.bind (/ * аргументы ... * /)
Первый параметр, который вы передаете в привязку ,
становится , это
в связанной функции. Создав связанную функцию, вы можете вызывать ее в любое время:
function sayThis () {
консоль.журнал (это)
}
const boundFunc = sayThis.bind ({хиппи: 'хипстер'})
boundFunc ()
Связывание помогает изменить это Остальные параметры, которые вы передаете в , привязка
будет передана в качестве аргументов исходной функции
function sayParams (... args) {
console.log (... аргументы)
}
const boundFunc = sayParams.bind (нуль, 1, 2, 3, 4, 5)
boundFunc ()
Другие параметры, переданные для связывания, становятся аргументами в функции Примечание. bind
не работает со стрелочными функциями.
Это действительно все, что вам нужно знать о bind
.
Теперь давайте вернемся к коду, чтобы удалить прослушиватели событий и проанализировать, что происходит:
function LeetSpeaker (elem) {
вернуть {
listenClick () {
// Связывает this.speakLeet со ссылкой на экземпляр.
// Устанавливает привязанную функцию к this.listener, чтобы мы могли удалить ее позже.
this.listener = this.speakLeet.bind (это)
elem.addEventListener ('щелкните', this.listener)
},
speakLeet (e) {
консоль.журнал (`1337 15 4W350M3`)
// Получает элемент, чтобы мы могли удалить прослушиватель событий.
const elem = e.currentTarget
// Удаляет прослушиватель событий.
elem.removeEventListener ('щелкните', this.listener)
}
}
}
Вот Codepen, чтобы вы могли увидеть код в действии.
Видите перо SpeakLeetOnce? пользователя Zell Liew (@zellwk) на CodePen.
И это все, что вам нужно знать о и
.
Подведем итоги.
Подведение итогов
это
— ключевое слово в JavaScript.Он присутствует во многих фреймворках JavaScript, поэтому вы должны знать, что он делает.
Из этой статьи вы узнали о шести различных контекстах, где и
принимают разные значения. Вы также узнали, как изменить контекст на этот
с помощью таких функций, как bind
. Кроме того, вы также научились правильно удалять прослушиватели событий.
Это все, что вам нужно знать об этом. Просто усвойте концепции, изложенные в этой статье, и вы больше никогда не запутаетесь.
Прежде чем мы закончим статью, вы, возможно, слышали, что люди предлагают не использовать вместо
, потому что это сбивает с толку. Я умоляю вас подумать о прочтении этой статьи. Это может изменить ваш взгляд на изучение JavaScript.
Если у вас есть вопросы, напишите мне в комментариях 🙂
Если вам понравилась эта статья, расскажите о ней другу! Поделитесь этим в Twitter. Если вы заметили опечатку, я буду признателен, если вы сможете исправить ее на GitHub. Спасибо!
Пожалуйста, прекратите использовать классы в JavaScript
В течение многих лет ООП (объектно-ориентированное программирование) было стандартом де-факто в разработке программного обеспечения.Концепции классов, полиморфизма, наследования и инкапсуляции доминировали и произвели революцию в процессе разработки. Но у всего есть срок годности, включая парадигмы программирования. В этой статье я расскажу о том, почему вообще были введены классы, почему использовать классы в JavaScript — плохая идея и каковы некоторые альтернативы.
Я не собираюсь говорить о том, почему ООП в целом исчезает, но вы можете прочитать эту замечательную статью для получения дополнительной информации.
Pre-ES6 классы
Несмотря на то, что ключевое слово class было добавлено в JavaScript начиная с ES6 (ECMAScript 2015), люди использовали классы раньше. Для этого использовались функции конструктора и делегирование прототипа. Чтобы показать вам, что я имею в виду, я собираюсь реализовать тот же класс в средах ES5 и ES6. Рассмотрим класс Car
и SportsCar
, который наследует Car
. Оба они имеют свойства make
и model
и start
method, но SportsCar
также имеет свойство с турбонаддувом
и отменяет метод start
:
Как вы, наверное, догадались, функции Car
(строка 2) и SportsCar
(строка 18) являются функциями-конструкторами.Свойства определяются с использованием и этого ключевого слова
, а сами объекты создаются с новыми
. Если вы не знакомы с прототипом
, это особое свойство, которое каждый объект JS должен делегировать для общего поведения. Например, прототип для объектов массива имеет функции, которые вы, вероятно, хорошо знаете: map
, forEach
, find
и т. Д. Прототип для строк имеет функции replace
, substr
и т. Д.
После создания объекта Car в строке 33 вы можете получить доступ к его свойствам и методам. Вызов для запуска в строке 34 приводит к следующим действиям:
- Механизм JS запрашивает у объекта
car
значение с ключомstart
. - Объект отвечает, что у него нет такого значения
- Механизм JS запрашивает у объекта
car.prototype
значение с ключомstart
. -
car.prototype
возвращает функциюstart
, которую JS-движок выполняет немедленно.
Доступ к свойствам марки и модели выполняется аналогично, за исключением того, что они определяются непосредственно в объекте автомобиля, а не в прототипе.
Наследование немного сложнее. Он обрабатывается в строках 24-25. Самой важной функцией здесь является функция Object.create
. Он принимает объект и возвращает новый, в качестве прототипа которого задано значение, переданное в качестве аргумента. Теперь, если движок JS не находит значение в объекте sportsCar
или sportsCar.prototype
, он будет обращаться к sportsCar.prototype.prototype
, который является прототипом объекта Car
.
Ключевое слово класса ES6
С выпуском ES6 в 2015 году долгожданное ключевое слово class
появилось в JavaScript. Это было сделано по многочисленным просьбам сообщества, потому что люди чувствовали себя некомфортно, переходя от объектно-ориентированных языков. Но упустили один важный момент.
JavaScript не знает, что это за классы
JavaScript не является объектно-ориентированным языком, он не задумывался как таковой, понятие классов к нему абсолютно неприменимо.Хотя все в JS действительно является объектом, эти объекты отличаются от объектов в Java или C #. В JS объект — это просто структура данных карты с довольно сложной процедурой поиска. Это действительно так. И когда я говорю, что все является объектом, я имею в виду: даже функции являются объектами. Вы можете проверить это с помощью этого фрагмента:
Хорошо, это все хорошо, но как тогда работает ключевое слово class
? Рад, что ты спросил. Вы помните примеры Car
и SportsCar
ранее? Что ж, ключевое слово class — это просто синтаксический сахар поверх этого.Другими словами, класс концептуально создает один и тот же код и служит только эстетическим целям и целям удобочитаемости. Как я и обещал ранее, вот пример тех же классов в ES6:
Эти примеры идентичны и дают одинаковый результат. Что интересно, они производят (почти) один и тот же код под капотом. Я не буду описывать это здесь, но если вам интересно, зайдите в онлайн-транспилятор Babel и посмотрите на результат.
Почему нет?
Теперь вы должны понимать, что такое классы в JS и как они работают.Теперь, обладая всеми этими знаниями, я могу объяснить, почему использование классов в JS — плохая идея.
- Проблемы с привязкой . Поскольку функции конструктора класса тесно связаны с
и этим ключевым словом
, это может вызвать потенциальные проблемы с привязкой, особенно если вы попытаетесь передать свой метод класса в качестве обратного вызова внешней подпрограмме (привет, разработчики React 👋) - Проблемы с производительностью . Из-за реализации классов их, как известно, сложно оптимизировать во время выполнения. Пока нам нравятся высокопроизводительные машины, тот факт, что закон Мура исчезает, может все это изменить.
- Частные переменные . Одно из больших преимуществ и основных причин для классов, в первую очередь, частных переменных, просто не существует в JS.
- Строгие иерархии . Классы вводят прямой порядок сверху вниз и усложняют реализацию изменений, что неприемлемо для большинства приложений JS.
- Потому что команда React не говорит вам . Хотя они еще явно не отказались от классовых компонентов, скорее всего, в ближайшем будущем.
Все эти проблемы можно устранить с помощью объектов JS и делегирования прототипа. JS предлагает гораздо больше, чем могут когда-либо делать классы, но большинство разработчиков не замечают этого. Если вы хотите по-настоящему овладеть JS, вам нужно принять его философию и отойти от догматического классового мышления.
Наглядное руководство по ссылкам в JavaScript
, также известные как «указатели для разработчиков JavaScript»
В первый день обучения программированию кто-то говорит вам: «Переменная похожа на ящик.Запись thing = 5
помещает 5
в ячейку thing
». И это не на самом деле , как работают переменные, но этого достаточно, чтобы вы начали. Это как на уроке математики, когда вам лгут о полной картине, потому что полная картина взорвет ваш мозг прямо сейчас.
Однако через некоторое время вы начинаете замечать странные проблемы. Переменные меняются, если вы их не меняли. Призраки в машине.
«Я думал, что сделал копию! Почему это изменилось? » <- вот тут ссылка на ошибку!
К концу этой публикации вы поймете, почему это происходит и как это исправить.
Что такое ссылка?
Ссылки есть везде в JS, но они невидимы. Они просто выглядят как переменные. Некоторые языки, такие как C, явно называют эти вещи указателями со своим собственным синтаксисом для загрузки. Но в JS нет указателей, по крайней мере, с таким названием. И в JS для них тоже нет специального синтаксиса.
Возьмем, к примеру, эту строку JavaScript: она создает переменную с именем word
, в которой хранится строка «hello».
Обратите внимание, как word
указывает на прямоугольник со словом «hello». Здесь есть некоторая степень косвенности. Переменная — это не в коробке. Переменная указывает на коробку. Позвольте этому осознать, пока вы продолжаете читать.
Теперь дадим этой переменной новое значение с помощью оператора присваивания =
:
На самом деле здесь происходит не то, что «привет» заменяется словом «мир» — это больше похоже на создание совершенно нового поля, а слово
переназначено, чтобы указывать на новое поле.(и в какой-то момент «привет» очищается сборщиком мусора, поскольку его ничто не использует)
Если вы когда-либо пытались присвоить значение параметру функции, вы, вероятно, понимали, что это ничего не меняет вне функции.
Причина, по которой это происходит, заключается в том, что переназначение параметра функции повлияет только на локальную переменную, а не на исходную, которая была передана. Вот пример:
function reassignFail (word) {
// это присвоение не просачивается
word = "мир"
}
пусть тест = "привет"
reassignFail (тест)
консоль.log (test) // выводит "привет"
Изначально только test
указывает на значение «hello».
Однако, как только мы попадаем в функцию, оба test
и word
указывают на одно и то же поле.
После присвоения ( word = "world"
) переменная word
указывает на свое новое значение «world». Но у нас не изменили test
. Переменная test
все еще указывает на свое старое значение.
Вот как присваивание работает в JavaScript. Переназначение переменной изменяет только эту одну переменную. Никакие другие переменные, которые также указывали на это значение, не меняются. Это верно независимо от того, является ли значение строкой, логическим значением, числом, объектом, массивом, функцией … каждая переменная верхнего уровня работает таким образом.
Два типа типов
JavaScript имеет две широкие категории типов, и у них разные правила присваивания и ссылочного равенства. Давай поговорим об этом.
Примитивные типы в JavaScript
Существуют примитивные типы , такие как строка, число, логическое значение (а также символ, неопределенное значение и ноль). Это неизменяемые . он же только для чтения , нельзя изменить .
Когда переменная содержит один из этих примитивных типов, вы не можете изменять само значение. Вы можете только переназначить этой переменной новое значение.
Разница небольшая, но важная!
Другими словами, когда значение внутри поля представляет собой строку / число / логическое значение / символ / undefined / null, вы не можете изменить значение.Вы можете создавать только новые коробки.
Это не , а не работает так …
Вот почему, например, все методы для строк возвращают новую строку вместо изменения строки, и если вам нужно это новое значение, вы должны где-то его сохранить.
let name = "Дэйв"
name.toLowerCase ();
console.log (имя) // по-прежнему заглавная буква D "Дэйв"
name = name.toLowerCase ()
console.log (name) // теперь это "dave"
Все остальные типы: объекты, массивы и т. Д.
Другая категория — это объект типа . Сюда входят объекты, массивы, функции и другие структуры данных, такие как Map и Set. Все они объекты.
Большое отличие от примитивных типов в том, что объекты изменяемы ! Вы можете изменить значение в поле.
Неизменяемый предсказуем
Если вы передадите в функцию примитивное значение, исходная переменная, которую вы передали, гарантированно останется в покое. Функция не может изменять то, что внутри нее.Вы можете быть уверены, что после вызова функции переменная всегда будет одинаковой — любой функции.
Но с объектами и массивами (и другими типами объектов) у вас нет такой гарантии. Если вы передадите объект в функцию, эта функция может изменить ваш объект. Если вы передадите массив, функция может добавить в него новые элементы или полностью его очистить.
Итак, это одна из причин, по которой многие люди в сообществе JS пытаются писать код по принципу неизменяемого : легче понять, что делает код, если вы уверены, что ваши переменные не изменятся неожиданно.Если каждая функция по соглашению написана как неизменяемая, вам никогда не придется гадать, что произойдет.
Функция, которая не изменяет свои аргументы или что-либо вне себя, называется чистой функцией . Если ему нужно что-то изменить в одном из своих аргументов, он сделает это, вместо этого вернув новое значение. Это более гибко, потому что это означает, что вызывающий код должен решать, что делать с этим новым значением.
Резюме: переменные указывают на боксы, а примитивы неизменяемы
Мы говорили о том, как присвоение или переназначение переменной эффективно «указывает на поле», содержащее значение.И как присвоение буквального значения (в отличие от переменной) создает новый блок и указывает на него переменную.
пусть число = 42
let name = "Дэйв"
пусть да = правда
пусть нет = ложь
let person = {
firstName: "Дэйв",
lastName: "Ceddia"
}
пусть числа = [4, 8, 12, 37]
Это верно для примитивных и объектных типов, независимо от того, является ли это первым назначением или переназначением.
Мы говорили о том, что примитивные типы неизменяемы.Вы не можете их изменить, вы можете только переназначить переменную на что-то другое.
Теперь давайте посмотрим, что происходит, когда вы изменяете свойство объекта.
Изменение содержимого коробки
Начнем с объекта book
, представляющего книгу в библиотеке, которую можно извлечь. Он имеет заголовок
и автор
и флаг isCheckedOut
.
let book = {
title: "Маленькие привычки",
автор: "Би Джей Фогг",
isCheckedOut: false
}
Вот наш объект и его значения в прямоугольниках:
А затем представим, что мы запускаем этот код:
Вот что это делает с объектом:
Обратите внимание, что переменная book
никогда не меняется.Он продолжает указывать на тот же ящик, удерживая тот же объект. Изменилось только одно из свойств этого объекта.
Обратите внимание, что это тоже следует тем же правилам, что и раньше. Единственная разница в том, что переменные теперь находятся внутри объекта. Вместо переменной верхнего уровня isCheckedOut
мы обращаемся к ней как book.isCheckedOut
, но ее переназначение работает точно так же.
Важно понять, что объект не изменился как .Фактически, даже если мы сделаем «копию» книги, сохранив ее в другой переменной перед ее изменением, мы все равно все равно не будем создавать новый объект.
let book = {
title: "Маленькие привычки",
автор: "Би Джей Фогг",
isCheckedOut: false
}
пусть резервное копирование = книга
book.isCheckedOut = true
console.log (backup === book) // правда!
Строка let backup = book
укажет переменную backup
на существующий объект книги. (на самом деле это не копия!)
Вот как это будет выглядеть:
Консоль .log
в конце еще раз подтверждает эту точку зрения: book
все еще равно backup
, потому что они указывают на один и тот же объект, и поскольку изменение свойства в book
не изменило оболочку объекта, он изменил только внутренности.
Переменные всегда указывают на блоки, а не на другие переменные. Когда мы назначаем backup = book
, JS немедленно выполняет работу по поиску того, на что указывает book
, и указывает на то же самое backup
.Это не указывает на резервная копия
на книгу
.
Это хорошо: это означает, что каждая переменная независима, и нам не нужно держать в голове обширную карту того, какие переменные указывают на какие другие. За этим было бы очень сложно уследить!
Изменение объекта в функции
Верно, во вступлении я упоминал об изменении переменной внутри функции, и о том, как это иногда «остается внутри функции», а иногда просачивается в вызывающий код и за его пределы.
Мы уже говорили о том, что переназначение переменной внутри функции не просочится наружу, если это переменная верхнего уровня, такая как book
или house
, а не подсвойство, например book.isCheckedOut
или house. адрес. город
.
функция doesNotLeak (word) {
// это присвоение не просачивается
word = "мир"
}
пусть тест = "привет"
reassignFail (тест)
console.log (test) // выводит "привет"
В любом случае, в этом примере использовалась строка, поэтому мы не смогли бы ее изменить, даже если бы попытались.(помните, потому что строки неизменяемы)
Но что, если бы у нас была функция, которая получила объект в качестве аргумента? А потом поменяли на нем собственность?
функция checkoutBook (книга) {
// это изменение просочится!
book.isCheckedOut = true
}
let book = {
title: "Маленькие привычки",
автор: "Би Джей Фогг",
isCheckedOut: false
}
checkoutBook (книга);
Вот что происходит:
Знакомо? Это та же анимация, что и раньше, потому что конечный результат точно такой же! Неважно, заказывает ли .isCheckedOut = true
происходит внутри функции или снаружи, потому что это присвоение в любом случае изменит внутреннюю структуру объекта book
.
Если вы хотите, чтобы не допускал этого, необходимо сделать копию, а затем изменить копию.
function pureCheckoutBook (book) {
let copy = {... book}
// это изменение повлияет только на копию
copy.isCheckedOut = true
// необходимо вернуть, иначе изменение будет потеряно
вернуть копию
}
let book = {
title: "Маленькие привычки",
автор: "Би Джей Фогг",
isCheckedOut: false
}
// Эта функция возвращает новую книгу,
// вместо изменения существующего,
// поэтому замените `book` на новую извлеченную
book = pureCheckoutBook (книга);
Если вы хотите узнать больше о написании таких неизменяемых функций, прочтите мое руководство по неизменяемости.Он написан с учетом React и Redux, но большинство примеров представляют собой простой JavaScript.
Ссылки в реальном мире
С учетом ваших новых знаний о ссылках давайте рассмотрим несколько примеров, которые могут вызвать проблемы. Прежде чем читать решение, посмотрите, сможете ли вы определить проблему.
Слушатели событий DOM
Краткая справка о том, как работают функции прослушивателя событий: чтобы добавить прослушиватель событий, вызовите addEventListener
с именем события и функцией.Чтобы удалить прослушиватель событий, вызовите removeEventListener
с тем же именем события и той же функцией , что и в той же ссылке на функцию. (иначе браузер не сможет узнать, какую функцию удалить, поскольку к событию может быть прикреплено несколько функций)
Взгляните на этот код. Правильно ли используются функции добавления / удаления?
document.addEventListener ('щелкнуть', () => console.log ('щелкнуть'));
документ.removeEventListener ('щелкнуть', () => console.log ('щелкнуть'));
…
…
…
…
Разобрался?
Этот код никогда не удалит прослушиватель событий, потому что эти две стрелочные функции не равны по ссылкам. Это разные функции, хотя они идентичны по синтаксису.
Каждый раз, когда вы пишете стрелочную функцию () => {...}
или обычную функцию function something () {...}
, что создает новый объект (функции — это объекты, запомните).
Докажите! Попробуйте в консоли:
пусть a = () => {}
let b = () => {}
console.log (a === b)
Он напечатает false
! Каждый новый объект (массив, функция, набор, карта и т. Д.) Находится в совершенно новом блоке, а не в любом другом блоке.
Непреднамеренная мутация
Давайте посмотрим на еще один. Вот функция, которая находит наименьший элемент в массиве, сначала сортируя его и беря первый элемент.
минимум функции (массив) {
array.sort ();
вернуть массив [0]
}
const items = [7, 1, 9, 4];
const min = минимум (элементы);
console.log (мин.)
console.log (элементы)
Что это за печать?
…
…
…
…
Если вы сказали 1
и [7, 1, 9, 4]
, вы правы только наполовину;)
Метод .sort ()
для массивов сортирует массив на место , что означает, что он изменяет порядок в исходном массиве без его копирования.
В этом примере печатаются 1
и [1, 4, 7, 9]
.
Теперь, этот может быть тем, что вы хотели. Но, наверное, нет, правда? Когда вы вызываете функцию минимум
, вы не ожидаете, что она изменит порядок элементов в вашем массиве.
Такое поведение может быть особенно запутанным, когда функция находится в другом файле или в библиотеке, где код находится не прямо перед вами.
Идти дальше и опорная скважина
Эта штука постоянно появляется , но это также одна из тех вещей, с которыми вы можете как бы путаться, даже не зная, как это работает.
Может потребоваться некоторое время, чтобы осмыслить концепцию «указателей», переменных, указывающих на значения, и сохранения прямых ссылок. Если вам кажется, что ваш мозг сейчас в тумане, добавьте эту статью в закладки и вернитесь через неделю.
Как только вы это получите, вы получите это, и это сделает всю вашу JS-разработку более плавной.
Эта статья — первая из серии статей о структурах данных и алгоритмах в JS. Далее идут связанные списки в JavaScript! Теперь, когда вы знаете, как работают ссылки, связанные списки станут намного проще для понимания.
Я работаю над следующей статьей из этой серии, о бинарных деревьях. Оставьте свой адрес электронной почты в поле, если хотите, чтобы вас уведомили, когда он выйдет!
Успех! Теперь проверьте свою электронную почту.
‘this’ Ключевые слова и ссылки на функции
# В чем проблема?
Вы когда-нибудь видели строку кода JavaScript, которая выглядела бы примерно так?
кнопка.addEventListener ('щелкните', this.addItem.bind (это));
Что это за штука , привязка (эта)
? А почему скобки функции отсутствуют?
Почему линия не выглядит так?
button.addEventListener ('щелкните', this.addItem ());
Что ж, эта альтернатива не работает. И вот почему.
Не любите читать статьи? Посмотрите видео вверху этой страницы!
# Вызов функций и использование ссылок на функции
Очевидно, вы вызываете такую функцию в JavaScript:
function someFunction () {
}
someFunction ();
И внутри объекта / класса вы вызываете метод аналогичным образом:
class MyClass {
constructor () {
этот.myMethod ();
}
myMethod () {
}
}
Использование приведенного выше кода приведет к немедленному выполнению методов при первом запуске кода. В классе метод myMethod
выполняется при вызове конструктора, то есть при создании экземпляра класса.
Иногда вам не нужно сразу выполнять функцию / метод.
Рассмотрим прослушиватель событий на кнопке:
function someFunction () {...}
кнопка const = документ.querySelector ('кнопка');
button.addEventListener ('щелчок', someFunction ());
В этом фрагменте someFunction
на самом деле , а не , будет ждать щелчка, а вместо этого также будет выполняться сразу, когда код сначала анализируется / выполняется.
Но это не то, что нам нужно. Мы просто хотим «сообщить JavaScript / браузеру», что он должен выполнить за нас someFunction
при нажатии кнопки. Это также гарантирует, что функция может запускаться несколько раз — по одному разу при каждом нажатии кнопки.
Что вы делаете, когда хотите, чтобы ваш друг мог навестить ваших родителей, когда он закончит свою работу в течение дня?
Вы не отправляете его сразу, а говорите ему, где живут ваши родители. Это позволит вашему другу посетить их, когда у него будет время. По сути, вы даете другу адрес своих родителей, вместо того, чтобы брать его с собой.
Та же концепция может использоваться в JavaScript. Вы можете «дать JavaScript / браузеру» адрес чего-либо (=> функции) вместо того, чтобы сразу выполнять это вручную.
Это делается путем передачи так называемой «ссылки» .
Для прослушивателя событий следующий код передает ссылку на «подлежащую выполнению» функцию в прослушиватель событий (то есть на кнопку в данном случае).
function someFunction () {...};
const button = ...;
button.addEventListener ('щелчок', someFunction);
Обратите внимание, что в скобках отсутствуют после someFunction
. Поэтому мы не вызываем функцию — вместо этого мы просто передаем указатель на функцию (так называемую ссылку) прослушивателю событий (и, следовательно, объекту кнопки).
В контексте класса JavaScript код выглядит примерно так же:
class MyClass {
constructor () {
const button = ...;
button.addEventListener ('щелкните', this.myMethod);
}
myMethod () {...}
}
Ключевое слово и
здесь важно. Он в основном указывает на объект, созданный на основе класса. А поскольку myMethod
является методом класса / объекта, доступ к нему можно получить только через и
.
Вот как вы передаете ссылку на функцию, а не сразу вызываете ее. Вот почему вы должны использовать этот синтаксис без скобок.
# Когда «это» ведет себя странно
этот
требуется для доступа к методам или свойствам класса из любого места внутри этого класса / объекта.
Если занятия для вас впервые, подумайте о том, чтобы пройти мой курс ES6, поскольку я углубляюсь в занятия там.
Давайте перейдем к реальному примеру — к примеру, где и
на самом деле приведут к странному поведению.
class NameGenerator {
constructor () {
const btn = document.querySelector ('кнопка');
this.names = ['Макс', 'Ману', 'Анна'];
this.currentName = 0;
btn.addEventListener ('щелкните', this.addName);
}
addName () {
const name = new NameField (this.names [this.currentName]);
this.currentName ++;
if (this.currentName> = this.names.length) {
this.currentName = 0;
}
}
}
Давайте не будем беспокоиться о том, что делает new NameField (...)
— вы можете посмотреть видео (вверху этой страницы), чтобы увидеть полный пример.По сути, он просто отображает новый
с именем в виде текста в DOM.
Но давайте беспокоиться о том, удастся ли это или нет. Потому что на самом деле мы получим ошибку: Эта строка вызывает ошибку:
const name = new NameField (this.names [this.currentName]);
Каким-то образом доступ к this.names
и this.currentName
здесь не выполняется.
Но почему? Разве не
относится к объекту / классу?
Ну, на самом деле это не так. это
не определено для ссылки на объект, который включает его, когда вы пишете свой код.
Вместо это
относится к «тому, кто назвал код, в котором он используется».
И в этом случае кнопка
отвечает за выполнение addName
.
Мы можем увидеть это, если зарегистрируем значение , это
внутри addName
:
addName () {
console.log (это);
...
}
Это напечатает:
Итак, этот
теперь относится к элементу
, к которому мы прикрепили прослушиватель событий click
.
На самом деле это поведение JavaScript по умолчанию.
этот
относится к тому, кто вызвал метод, который использует это
.
Очевидно, это не то поведение, которое мы хотим здесь, и, к счастью, вы можете его изменить.
Вы можете привязать этот
внутри addName
к чему-то другому, кроме кнопки. Вы можете привязать его к окружающему классу / объекту:
btn.addEventListener ('щелкните', this.addName.bind (this));
bind ()
— это метод JavaScript по умолчанию, который вы можете вызывать для функций / методов.Это позволяет вам привязать , это
внутри «функции / метода к выполнению» к любому значению по вашему выбору.
В приведенном выше фрагменте мы привязываем , этот
внутри addName
к тому же значению , на которое ссылается этот
в конструкторе.
В этом конструкторе этот
будет ссылаться на класс / объект, потому что мы выполняем этот код самостоятельно. По сути, конструктор всегда выполняется самим объектом, можно сказать, поэтому и
внутри конструктора также относятся к этому объекту.
bind
также позволит вам передавать аргументы функции, которую вы в конечном итоге вызовете, но вы можете узнать об этом больше здесь.
# Сводка
Вот и все!
Это, надеюсь, иллюстрирует, почему у вас может быть код, в котором вы «вызываете функции» (не совсем) без добавления круглых скобок, и почему вам, возможно, придется использовать bind (this)
, чтобы заставить и
правильно работать в этой функции / методе.