- Привязка контекста (this) к функции в javascript и частичное применение функций
- Введение
- 1. jQuery.proxy
- 2. Function.prototype.bind
- Заключение
- jQuery’s this: demystified
- What is «this»?
- jQuery’s this
- Example of this as a DOM element
- Example of this as a jQuery object
- Finishing up
- jQuery get ‘this’ in a function referring to jQuery object
- Javascript Source Code
- Related
Привязка контекста (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);
Обратите внимание на то, что название метода передается в виде строки.
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, при этом я старался оставить только важные детали (с моей точки зрения).
Если вы заметили ошибки/неточности или хотите что-то уточнить/добавить — напишите в ЛС, поправлю
jQuery’s this: demystified
More often that not, in my early dabbling with jQuery and more advanced JavaScript, I found I would constantly get confused over the meaning of «this» in jQuery and my own new libraries.
Hopefully this quick guide can help clarify those confusing moments, because once you’ve got it, it’s simple as pie.
READER DISCOUNT Save $50 on terminal.training
I’ve published 38 videos for new developers, designers, UX, UI, product owners and anyone who needs to conquer the command line today.
$49 — only from this link
What is «this»?
In many object-oriented programming languages, this (or self) is a keyword which can be used in instance methods to refer to the object on which the currently executing method has been invoked.
jQuery’s this
There are really two main contexts of ‘this’ in jQuery. The first refers to a to a DOM element, and the second to a jQuery object.
Example of this as a DOM element
‘this’ is a DOM element when you are inside of a callback function (in the context of jQuery), for example, being called by the click, each, bind, etc. methods.
The following code searches for anchor links with the class of ‘newTarget’ and sets the ‘target’ attribute to ‘_new’ (which is a trick to create strict XHTML while still having some links open in a new window).
In this example we are also going to perform a double check to ensure links to the same domain don’t open in a new window using the this object.
Example of this as a jQuery object
‘this’ is a jQuery object when you are inside your own jQuery functions. Note that the result of a selector query (i.e. $(‘a’) ) is a jQuery object, which is an array of the matched DOM elements (imagine jQuery is an array with bells on).
jQuery.fn.newTarget = function() < // 'this' is a jQuery object at this point - with all the jQuery functions return this.each(function() < // return so we don't break the chain // now we are inside of a jQuery function, the DOM element is the context // so 'this' has changed to become a DOM element. if (this.host != window.location.host) < $(this).attr('target', '_new'); >>); >;
Finishing up
This is far from comprehensive, but equally there’s very little to the logic. So long as you remember the context of ‘this’ changes when moving in and out of object methods then you’re on your way.
If you’re still not sure, get your hands on Firebug and add ‘console.log(this)’ within your code to interrogate and understand what ‘this’ is at that point in your code.
If this still doesn’t make sense or I’ve got it terribly wrong — or you’re suddenly enlightened, please do let me know — and I’ll do my best to help.
Published 12-Apr 2007 under #explanation & #guide & #javascript & #jquery & #this & #code. Edit this post
jQuery get ‘this’ in a function referring to jQuery object
The following tutorial shows you how to do «jQuery get ‘this’ in a function referring to jQuery object».
The result is illustrated in the iframe.
You can check the full source code and open it in another tab using the links.
Javascript Source Code
The Javascript source code to do «jQuery get ‘this’ in a function referring to jQuery object» is
//sections.push($('#section' + p_sectionIndex)); sections = $.map($(".section"),function(obj)< return $(obj); >); this.showSection = function() < $(this).show(); >this.hideSection = function() < $(this).hide(); >sections[sections.length-1].on('show',this.showSection); sections[sections.length-1].on('hide',this.hideSection); sections[sections.length-1].trigger("show"); setTimeout(function()< sections[sections.length-1].trigger("hide"); >,2000)
html> head> meta name="viewport" content="width=device-width, initial-scale=1"> script type="text/javascript" src="https://code.jquery.com/jquery-1.10.1.js" > !-- w w w . d em o 2 s. c o m --> body> div >"section" style="display: none;">section script type='text/javascript'> //sections.push($('#section' + p_sectionIndex)); sections = $.map($(".section"),function(obj)< return $(obj); >); this.showSection = function() < $(this).show(); >this.hideSection = function() < $(this).hide(); >sections[sections.length-1].on('show',this.showSection); sections[sections.length-1].on('hide',this.hideSection); sections[sections.length-1].trigger("show"); setTimeout(function()< sections[sections.length-1].trigger("hide"); >,2000)
Related
- jQuery bring a jQuery object into a function
- jQuery bring a jQuery object into a function (Demo 2)
- jQuery create a jQuery function that returns another object
- jQuery get ‘this’ in a function referring to jQuery object
- jQuery map an object literal to be an instance of my constructor function
- jQuery pass a jquery object to function
- jQuery pass JS Object to jQuery function .queue()
demo2s.com | Email: | Demo Source and Support. All rights reserved.