Передать функции параметр this javascript

Привязка контекста (this) к функции в javascript и частичное применение функций

В предыдущем посте я описал, что this в javascript не привязывается к объекту, а зависит от контекста вызова. На практике же часто возникает необходимость в том, чтобы this внутри функции всегда ссылался на конкретный объект.
В данной статье мы рассмотрим два подхода для решения данной задачи.
1. jQuery.proxy — подход с использованием популярной библиотеки jQuery
2. Function.prototype.bind — подход, добавленный в JavaScript 1.8.5. Рассмотрим также его применение для карринга (частичного применения функции) и некоторые тонкости работы, о которых знают единицы.

Введение

Рассмотрим простой объект, содержащий свойство x и метод f, который выводит в консоль значение this.x

Как я указывал в предыдущем посте, при вызове object.f() в консоли будет выведено число 3. Предположим теперь, что нам нужно вывести данное число через 1 секунду.

setTimeout(object.f, 1000); // выведет undefined //простой способ это обойти — сделать вызов через обёртку: setTimeout(function() < object.f(); >, 1000); // выведет 3 

Каждый раз использовать функцию обертку — неудобно. Нужен способ привязать контекст функции, так, чтобы this внутри функции object.f всегда ссылался на object

1. jQuery.proxy

jQuery.proxy(function, context); jQuery.proxy(context, name); 

Ни для кого не секрет, что jQuery — очень популярная библиотека javascript, поэтому вначале мы рассмотрим применение jQuery.proxy для привязки контекста к функции.
jQuery.proxy возвращает новую функцию, которая при вызове вызывает оригинальную функцию function в контексте context. С использованием jQuery.proxy вышеописанную задачу можно решить так:

setTimeout($.proxy(object.f, object), 1000); // выведет 3 
setTimeout($.proxy(object.f, object), 1000); setTimeout($.proxy(object.f, object), 2000); setTimeout($.proxy(object.f, object), 3000); 
var fn = $.proxy(object.f, object); setTimeout(fn, 1000); setTimeout(fn, 2000); setTimeout(fn, 3000); 

Обратим теперь внимание на то, что мы дважды указали object внутри $.proxy (первый раз метод объекта — object.f, второй — передаваемй контекст — object). Может быть есть возможность избежать дублирования? Ответ — да. Для таких случаев в $.proxy добавлена альтернативная возможность передачи параметров — первым параметром должен быть объект, а вторым — название его метода. Пример:

var fn = $.proxy(object, "f"); setTimeout(fn, 1000); 

Обратите внимание на то, что название метода передается в виде строки.

Читайте также:  Create calendar in java

2. Function.prototype.bind

func.bind(context[, arg1[, arg2[, . ]]]) 

Перейдем к рассмотрению Function.prototype.bind. Данный метод был добавлен в JavaScript 1.8.5.

Function.prototype.bind = function (oThis) < if (typeof this !== "function") < // closest thing possible to the ECMAScript 5 internal IsCallable function 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; >; 

Function.prototype.bind имеет 2 назначения — статическая привязка контекста к функции и частичное применение функции.
По сути bind создаёт новую функцию, которая вызывает func в контексте context. Если указаны аргументы arg1, arg2… — они будут прибавлены к каждому вызову новой функции, причем встанут перед теми, которые указаны при вызове новой функции.

2.1. Привязка контекста
function f() < console.log(this.x); >var bound = f.bind(); // bound - новая функция - "обертка", у которой this ссылается на объект bound();// Выведет 3 

Таким образом пример из введения можно записать в следующем виде:

var object = < x: 3, f: function() < console.log(this.x); >> setTimeout(object.f.bind(object), 1000); // выведет 3 
2.2. Частичное применение функций
function f(x, y, z) < console.log(x + y + z); >var bound = f.bind(null, 3, 5); // напомню что первый параметр - это контекст для функции, поскольку мы не используем this в функции f, то контекст не имеет значения - поэтому в данном случае передан null bound(7); // распечатает 15 (3 + 5 + 7) bound(17); // распечатает 25 (3 + 5 + 17) 

Как видно из примера — суть частичного применения функций проста — создание новой функции с уменьшенным количеством аргументов, за счет «фиксации» первых аргументов с помощью функции bind.
На этом можно было бы закончить статью, но… Функции, полученные с использованием метода bind имеют некоторые особенности в поведении

2.3. Особенности bind

В комментариях к предыдущей статье было приведено 2 примера, касающихся bind (раз, два).
Я решил сделать микс из этих примеров, попутно изменив строковые значения, чтобы было проще с ними разбираться.
Пример (попробуйте угадать ответы)

function ClassA() < console.log(this.x, arguments) >ClassA.prototype.x = "fromProtoA"; var ClassB = ClassA.bind(, "bindArg"); ClassB.prototype = ; new ClassA("callArg"); new ClassB("callArg"); ClassB("callArg"); ClassB.call(, 'callArg'); 

fromProtoA [«callArg»]
fromProtoA [«bindArg», «callArg»]
fromBind [«bindArg», «callArg»]
fromBind [«bindArg», «callArg»]

Прежде чем разобрать — я перечислю основные особенности bind в соответствии со стандартом.

2.3.1. Внутренние свойств

У объектов Function, созданных посредством Function.prototype.bind, отсутствует свойство prototype или внутренние свойства [[Code]], [[FormalParameters]] и [[Scope]].

Это ограничение отличает built-in реализацию bind от вручную определенных методов (например, вариант из MDN)

2.3.2. call и apply
boundFn.[[Call]] = function (thisValue, extraArgs): var boundArgs = boundFn.[[BoundArgs]], boundThis = boundFn.[[BoundThis]], targetFn = boundFn.[[TargetFunction]], args = boundArgs.concat(extraArgs); return targetFn.[[Call]](boundThis, args); 

В коде видно, что thisValue не используется нигде. Таким образом подменить контекст вызова для функций полученных с помощью Function.prototype.bind с использованием call и applyнельзя!

2.3.3. В конструкторе

В конструкторе this ссылается на новый (создаваемый) объект. Иначе говоря, контекст заданный при помощи bind, просто игнорируется. Конструктор вызывает обычный [[Call]] исходной функции.
Важно! Если в конструкторе отсутствует return this, то возвращаемое значение в общем случае неопределено и зависит от возвращаемого значения новой функции!

Разбор примера
function ClassA() < console.log(this.x, arguments) >ClassA.prototype.x = "fromProtoA"; var ClassB = ClassA.bind(, "bindArg"); // исходя из 2.3.1, эта строчка не делает ровным счетом ничего в built-in реализациях Function.prototype.bind // Но в ручной реализации bind (например, как в MDN) эта строчка сыграет роль ClassB.prototype = ; // Тут все просто - никакой bind мы еще не использовали // Результат: fromProtoA ["callArg"] new ClassA("callArg"); // Исходя из 2.3.3 - this ссылается на новый объект. поскольку в bind был задан параметр bindArg, то в выводе аргументов он займет первое место // Результат: fromProtoA ["bindArg", "callArg"] // При ручной реализации bind результат будет другой: fromBind ["bindArg", "callArg"]. new ClassB("callArg"); // Обычный вызов bind функции, поэтому в качестве контекста будет , первым параметром bindArg (заданный через bind), вторым - "callArg" // Результат: fromBind ["bindArg", "callArg"] ClassB("callArg"); // Из пункта 2.3.2. следует, что при вызове метода call на функции, полученной с использованием bind передаваемый контекст игнорируется. // Результат: fromBind ["bindArg", "callArg"] ClassB.call(, 'callArg'); 

Заключение

В данном посте я постарался описать основные методы привязывания контекста к функциям, а также описал некоторые особенности в работе Function.prototype.bind, при этом я старался оставить только важные детали (с моей точки зрения).
Если вы заметили ошибки/неточности или хотите что-то уточнить/добавить — напишите в ЛС, поправлю

Источник

Как работают this, call, apply и bind в Java Script: разбираемся на примерах

Привет, Хабр! Эту статью написал Тарас Голомозый, fullstack web-разработчик и преподаватель в школе программирования Эльбрус Буткемп. В своей практике он часто сталкивается с кейсами выпускников, которых на собеседовании просят рассказать о роли ключевого слова this в JavaScript. Простого определения про ссылку на контекст часто оказывается недостаточно, требуется более глубокое погружение в тему. В этой статье он на нескольких примерах разбирает, в каких ситуациях может пригодиться это ключевое слово и как используется call, apply и bind.

В общем смысле this — это ссылка на определенный контекст внутри объекта. Рассмотрим, что это означает, на примере.

Создадим объект car с набором свойств: моделью и годом выпуска автомобиля.

Для того, чтобы обратиться к объекту извне, достаточно выбрать имя переменной и через точку выбрать любое свойство: например, model .

В консоли мы увидим модель автомобиля: Toyota.

Обратиться к объекту изнутри сложнее: находясь внутри фигурных скобок, мы не знаем, куда будет записан объект и будет ли записан вообще. Попробуем сделать это, создав внутри объекта функцию showModel . Ее единственная задача — показывать модель автомобиля:

Чтобы из этой функции получить доступ к model , пишем this.model . Благодаря ключевому слову this можно получить доступ к любому свойству объекта, находясь внутри него.

Теперь из объекта car можно вызвать showModel — метод покажет модель автомобиля, Toyota.

Допустим, у нас есть еще один автомобиль. Создадим новый объект и назовем его anotherCar . У этого объекта будут те же свойства: model и year .

Если скопировать функцию showModel с this внутрь нового объекта, то при вызове anotherCar.showModel , то в консоли мы увидим модель той машины, внутри которой вызывается этот метод. В данном случае — Benz.

Исходя из этих примеров, можно сказать, что this — это ссылка на объект, внутри которого находится это ключевое слово.

Важно отметить, что функции бывают обычными (как в этом примере) и стрелочными. Если в этом примере заменить функцию на стрелочную, в консоли мы увидим undefined. Дело в том, что стрелочные функции не имеют своего this.

Call, apply и bind

Что делать, если у второго автомобиля нет метода, который показывает его модель? Можем ли мы использовать функцию с вызовом модели первого автомобиля? Да, для этого в JavaScript есть специальные ключевые слова: call, apply и bind.

Начнем с call. Вызываем из первой машины метод car.showModel , используя call. В качестве аргумента ключевого слова указываем название объекта, в который записан второй автомобиль, anotherCar . В консоли мы увидим Benz.

То же самое можно сделать с помощью метода apply, просто меняя ключевое слово: car.showModel.apply(anotherCar); . Результат будет таким же: в консоли мы увидим Benz.

Возникает вопрос — чем call отличается от apply? Разница между ними в формате передачи параметров. Рассмотрим пример: функция showModel , кроме вывода модели автомобиля, может принимать еще какие-то параметры: например, цвет автомобиля и тип двигателя.

Используя apply, новые параметры можно передать в виде массива: например, автомобиль будет красным и с бензиновым двигателем:

// car.showModel.apply(anotherCar, ['red', 'diesel']);

В консоли мы увидим, что это красный дизельный Benz.

Call позволяет передать те же параметры не в виде массива, а простым перечислением: // car.showModel.call(anotherCar, ‘green’, ‘gas’) .

Третий метод, bind, позволяет создать новую функцию и записать ее в переменную. Выглядеть это будет так:

const modelShower = car.showModel.bind(anotherCar); modelShower('black', 'diesel');

Функция modelShower вызывает метод showModel, а bind привязывает к нему контекст другого автомобиля — в нашем случае это anotherCar.Таким образом, в переменной modelShower появилась функция, способная показывать контекст anotherCar так, будто она написана внутри самого anotherCar.

Передадим в функцию цвет и параметры двигателя:

В выводе мы, как и ожидалось, получим черный дизельный Benz.

Заключение

Рады, если теперь вам удалось разобраться, что такое this в JavaScript, в чем разница между call и apply и для чего нужен bind. Если у вас появились вопросы или уточнения, пишите их в комментариях!

Источник

Оцените статью