- JavaScript Function bind()
- Example
- Preserving this
- Example
- Example
- Example
- What is this?
- Привязка контекста к функции
- Потеря «this»
- Решение 1: сделать функцию-обёртку
- Решение 2: привязать контекст с помощью bind
- Частичное применение
- Частичное применение без контекста
- Итого
- The data-bind syntax
- Binding syntax
- Binding values
- Whitespace
- Comments
- Skipping the binding value
JavaScript Function bind()
With the bind() method, an object can borrow a method from another object.
The example below creates 2 objects (person and member).
The member object borrows the fullname method from the person object:
Example
const person = <
firstName:»John»,
lastName: «Doe»,
fullName: function () <
return this.firstName + » » + this.lastName;
>
>
const member = firstName:»Hege»,
lastName: «Nilsen»,
>
let fullName = person.fullName.bind(member);
Preserving this
Sometimes the bind() method has to be used to prevent losing this.
In the following example, the person object has a display method. In the display method, this refers to the person object:
Example
const person = <
firstName:»John»,
lastName: «Doe»,
display: function () <
let x = document.getElementById(«demo»);
x.innerHTML = this.firstName + » » + this.lastName;
>
>
When a function is used as a callback, this is lost.
This example will try to display the person name after 3 seconds, but it will display undefined instead:
Example
const person = <
firstName:»John»,
lastName: «Doe»,
display: function () <
let x = document.getElementById(«demo»);
x.innerHTML = this.firstName + » » + this.lastName;
>
>
The bind() method solves this problem.
In the following example, the bind() method is used to bind person.display to person.
This example will display the person name after 3 seconds:
Example
const person = <
firstName:»John»,
lastName: «Doe»,
display: function () <
let x = document.getElementById(«demo»);
x.innerHTML = this.firstName + » » + this.lastName;
>
>
let display = person.display.bind(person);
setTimeout(display, 3000);
What is this?
In JavaScript, the this keyword refers to an object.
Which object depends on how this is being invoked (used or called).
The this keyword refers to different objects depending on how it is used:
In an object method, this refers to the object. |
Alone, this refers to the global object. |
In a function, this refers to the global object. |
In a function, in strict mode, this is undefined . |
In an event, this refers to the element that received the event. |
Methods like call() , apply() , and bind() can refer this to any object. |
Привязка контекста к функции
При передаче методов объекта в качестве колбэков, например для setTimeout , возникает известная проблема – потеря this .
В этой главе мы посмотрим, как её можно решить.
Потеря «this»
Мы уже видели примеры потери this . Как только метод передаётся отдельно от объекта – this теряется.
Вот как это может произойти в случае с setTimeout :
let user = < firstName: "Вася", sayHi() < alert(`Привет, $!`); > >; setTimeout(user.sayHi, 1000); // Привет, undefined!
При запуске этого кода мы видим, что вызов this.firstName возвращает не «Вася», а undefined !
Это произошло потому, что setTimeout получил функцию sayHi отдельно от объекта user (именно здесь функция и потеряла контекст). То есть последняя строка может быть переписана как:
let f = user.sayHi; setTimeout(f, 1000); // контекст user потеряли
Метод setTimeout в браузере имеет особенность: он устанавливает this=window для вызова функции (в Node.js this становится объектом таймера, но здесь это не имеет значения). Таким образом, для this.firstName он пытается получить window.firstName , которого не существует. В других подобных случаях this обычно просто становится undefined .
Задача довольно типичная – мы хотим передать метод объекта куда-то ещё (в этом конкретном случае – в планировщик), где он будет вызван. Как бы сделать так, чтобы он вызывался в правильном контексте?
Решение 1: сделать функцию-обёртку
Самый простой вариант решения – это обернуть вызов в анонимную функцию, создав замыкание:
let user = < firstName: "Вася", sayHi() < alert(`Привет, $!`); > >; setTimeout(function() < user.sayHi(); // Привет, Вася! >, 1000);
Теперь код работает корректно, так как объект user достаётся из замыкания, а затем вызывается его метод sayHi .
То же самое, только короче:
setTimeout(() => user.sayHi(), 1000); // Привет, Вася!
Выглядит хорошо, но теперь в нашем коде появилась небольшая уязвимость.
Что произойдёт, если до момента срабатывания setTimeout (ведь задержка составляет целую секунду!) в переменную user будет записано другое значение? Тогда вызов неожиданно будет совсем не тот!
let user = < firstName: "Вася", sayHi() < alert(`Привет, $!`); > >; setTimeout(() => user.sayHi(), 1000); // . в течение 1 секунды user = < sayHi() < alert("Другой пользователь в 'setTimeout'!"); >>; // Другой пользователь в 'setTimeout'!
Следующее решение гарантирует, что такого не случится.
Решение 2: привязать контекст с помощью bind
В современном JavaScript у функций есть встроенный метод bind, который позволяет зафиксировать this .
// полный синтаксис будет представлен немного позже let boundFunc = func.bind(context);
Результатом вызова func.bind(context) является особый «экзотический объект» (термин взят из спецификации), который вызывается как функция и прозрачно передаёт вызов в func , при этом устанавливая this=context .
Другими словами, вызов boundFunc подобен вызову func с фиксированным this .
Например, здесь funcUser передаёт вызов в func , фиксируя this=user :
let user = < firstName: "Вася" >; function func() < alert(this.firstName); >let funcUser = func.bind(user); funcUser(); // Вася
Здесь func.bind(user) – это «связанный вариант» func , с фиксированным this=user .
Все аргументы передаются исходному методу func как есть, например:
let user = < firstName: "Вася" >; function func(phrase) < alert(phrase + ', ' + this.firstName); >// привязка this к user let funcUser = func.bind(user); funcUser("Привет"); // Привет, Вася (аргумент "Привет" передан, при этом this = user)
Теперь давайте попробуем с методом объекта:
let user = < firstName: "Вася", sayHi() < alert(`Привет, $!`); > >; let sayHi = user.sayHi.bind(user); // (*) sayHi(); // Привет, Вася! setTimeout(sayHi, 1000); // Привет, Вася!
В строке (*) мы берём метод user.sayHi и привязываем его к user . Теперь sayHi – это «связанная» функция, которая может быть вызвана отдельно или передана в setTimeout (контекст всегда будет правильным).
Здесь мы можем увидеть, что bind исправляет только this , а аргументы передаются как есть:
let user = < firstName: "Вася", say(phrase) < alert(`$, $!`); > >; let say = user.say.bind(user); say("Привет"); // Привет, Вася (аргумент "Привет" передан в функцию "say") say("Пока"); // Пока, Вася (аргумент "Пока" передан в функцию "say")
Если у объекта много методов и мы планируем их активно передавать, то можно привязать контекст для них всех в цикле:
Некоторые JS-библиотеки предоставляют встроенные функции для удобной массовой привязки контекста, например _.bindAll(obj) в lodash.
Частичное применение
До сих пор мы говорили только о привязывании this . Давайте шагнём дальше.
Мы можем привязать не только this , но и аргументы. Это делается редко, но иногда может быть полезно.
let bound = func.bind(context, [arg1], [arg2], . );
Это позволяет привязать контекст this и начальные аргументы функции.
Например, у нас есть функция умножения mul(a, b) :
Давайте воспользуемся bind , чтобы создать функцию double на её основе:
function mul(a, b) < return a * b; >let double = mul.bind(null, 2); alert( double(3) ); // = mul(2, 3) = 6 alert( double(4) ); // = mul(2, 4) = 8 alert( double(5) ); // = mul(2, 5) = 10
Вызов mul.bind(null, 2) создаёт новую функцию double , которая передаёт вызов mul , фиксируя null как контекст, и 2 – как первый аргумент. Следующие аргументы передаются как есть.
Это называется частичное применение – мы создаём новую функцию, фиксируя некоторые из существующих параметров.
Обратите внимание, что в данном случае мы на самом деле не используем this . Но для bind это обязательный параметр, так что мы должны передать туда что-нибудь вроде null .
В следующем коде функция triple умножает значение на три:
function mul(a, b) < return a * b; >let triple = mul.bind(null, 3); alert( triple(3) ); // = mul(3, 3) = 9 alert( triple(4) ); // = mul(3, 4) = 12 alert( triple(5) ); // = mul(3, 5) = 15
Для чего мы обычно создаём частично применённую функцию?
Польза от этого в том, что возможно создать независимую функцию с понятным названием ( double , triple ). Мы можем использовать её и не передавать каждый раз первый аргумент, т.к. он зафиксирован с помощью bind .
В других случаях частичное применение полезно, когда у нас есть очень общая функция и для удобства мы хотим создать её более специализированный вариант.
Например, у нас есть функция send(from, to, text) . Потом внутри объекта user мы можем захотеть использовать её частный вариант: sendTo(to, text) , который отправляет текст от имени текущего пользователя.
Частичное применение без контекста
Что если мы хотим зафиксировать некоторые аргументы, но не контекст this ? Например, для метода объекта.
Встроенный bind не позволяет этого. Мы не можем просто опустить контекст и перейти к аргументам.
К счастью, легко создать вспомогательную функцию partial , которая привязывает только аргументы.
function partial(func, . argsBound) < return function(. args) < // (*) return func.call(this, . argsBound, . args); >> // использование: let user = < firstName: "John", say(time, phrase) < alert(`[$] $: $!`); > >; // добавляем частично применённый метод с фиксированным временем user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes()); user.sayNow("Hello"); // Что-то вроде этого: // [10:00] John: Hello!
Результатом вызова partial(func[, arg1, arg2. ]) будет обёртка (*) , которая вызывает func с:
- Тем же this , который она получает (для вызова user.sayNow – это будет user )
- Затем передаёт ей . argsBound – аргументы из вызова partial ( «10:00» )
- Затем передаёт ей . args – аргументы, полученные обёрткой ( «Hello» )
Благодаря оператору расширения . реализовать это очень легко, не правда ли?
Также есть готовый вариант _.partial из библиотеки lodash.
Итого
Метод bind возвращает «привязанный вариант» функции func , фиксируя контекст this и первые аргументы arg1 , arg2 …, если они заданы.
Обычно bind применяется для фиксации this в методе объекта, чтобы передать его в качестве колбэка. Например, для setTimeout .
Когда мы привязываем аргументы, такая функция называется «частично применённой» или «частичной».
Частичное применение удобно, когда мы не хотим повторять один и тот же аргумент много раз. Например, если у нас есть функция send(from, to) и from всё время будет одинаков для нашей задачи, то мы можем создать частично применённую функцию и дальше работать с ней.
The data-bind syntax
Knockout’s declarative binding system provides a concise and powerful way to link data to the UI. It’s generally easy and obvious to bind to simple data properties or to use a single binding. For more complex bindings, it helps to better understand the behavior and syntax of Knockout’s binding system.
Binding syntax
A binding consists of two items, the binding name and value, separated by a colon. Here is an example of a single, simple binding:
An element can include multiple bindings (related or unrelated), with each binding separated by a comma. Here are some examples:
The binding name should generally match a registered binding (either built-in or custom) or be a parameter for another binding. If the name matches neither of those, Knockout will ignore it (without any error or warning). So if a binding doesn’t appear to work, first check that the name is correct.
Binding values
The binding value can be a single value, variable, or literal or almost any valid JavaScript expression. Here are examples of various binding values:
. The item is . . .
These examples show that the value can be just about any JavaScript expression. Even the comma is fine when it’s enclosed in braces, brackets, or parentheses. When the value is an object literal, the object’s property names must be valid JavaScript identifiers or be enclosed in quotes. If the binding value is an invalid expression or references an unknown variable, Knockout will output an error and stop processing bindings.
Whitespace
Bindings can include any amount of whitespace (spaces, tab, and newlines), so you’re free to use it to arrange your bindings as you like. The following examples are all equivalent:
Comments
Bindings can include JavaScript-style comments ( //. and /*. */ ). For example:
Skipping the binding value
If you specify bindings without a value, Knockout will give the binding an undefined value. For example:
Text that will be cleared when bindings are applied.
This ability is especially useful when paired with binding preprocessing, which can assign a default value for a binding.