Задача привязки функции к объекту в JavaScript, скорее всего, не вызовет у вас практически никаких трудностей. Но когда вам нужно решить проблему использования контекста объекта this
внутри другой функции, начинаются настоящие сложности. Вы не можете однозначно определить, что же необходимо делать, и вот тут на помощь приходит подход с использованием Function.prototype.bind()
.
Результатом первоначального анализа проблемы, вероятнее всего, станет попытка идентифицировать this
, как переменную, на которую можно ссылаться при изменении контекста. Многие люди выбирают в качестве имени переменной обозначения типа self
, _this
, а иногда и context
. В принципе, все эти варианты верны и ничего плохого не случится. Однако существует более правильный подход.
Джек Арчибальд пишет в Твиттере о кэшировании объекта this
:
Я готов сделать все что угодно для нашей сферы деятельности, но я не буду использовать такую операцию, как that = this
— Джек Арчибальд (Jake Archibald) (@jaffathecake) 20 февраля 2013 года
Некоторые вещи стали для меня более очевидными, когда Сайндер Сорхус дал четкое определение:
@benhowdle $ this для jQuery, а для простого JS я не использую .bind()
— Сайндер Сорхус (Sindre Sorhus) (@ sindresorhus) 22 февраля 2013 года
В течение многих месяцев я игнорировал этот мудрый совет.
Какую же проблему мы пытаемся решить?
Вот пример кода, в котором можно проследить за кэшированием контекста в переменную:
var myObj = {
specialFunction: function () {
},
anotherSpecialFunction: function () {
},
getAsyncData: function (cb) {
cb();
},
render: function () {
var that = this;
this.getAsyncData(function () {
that.specialFunction();
that.anotherSpecialFunction();
});
}
};
myObj.render();
Если бы мы оставили структуру вызова функции как this.specialFunction()
, то было бы получено следующее сообщение об ошибке:
Uncaught TypeError: Object [object global] has no method 'specialFunction'
Мы должны обеспечить правильную работу объекта myObj
при инициировании функции обратного вызова. Вызов that.specialFunction()
позволяет предположить о правильности выполнения нашей функции. Тем не менее, весь этот программный код мог бы быть несколько упрощен с помощью Function.prototype.bind()
.
Давайте перепишем наш пример:
render: function () {
this.getAsyncData(function () {
this.specialFunction();
this.anotherSpecialFunction();
}.bind(this));
}
Что мы только что сделали?
Итак, .bind()
просто создает новую функцию, которая при вызове устанавливает указанное значение для ключевого слова this
. Мы передаем наш желаемый контекст в функцию .bind()
. Затем, когда происходит инициирование функции обратного вызова, this
ссылается на myObj
.
Если вы заинтересованы в том, как выглядит синтаксис функции Function.prototype.bind()
, то можете рассмотреть следующий, достаточно простой пример:
Function.prototype.bind = function (scope) {
var fn = this;
return function () {
return fn.apply(scope);
};
}
А вот очень простой вариант использования:
var foo = {
x: 3
}
var bar = function(){
console.log(this.x);
}
bar(); // undefined
var boundFunc = bar.bind(foo);
boundFunc(); // 3
Мы создали новую функцию, которая, при выполнении устанавливает this
в foo.
Обратите внимание, что это не глобальная область, как в том примере, где мы вызывали функцию bar();
.
поддержка браузерами.
Браузер | Какая версия поддерживает |
---|---|
Chrome | 7 |
Firefox (Gecko) | 4,0 (2) |
Internet Explorer | 9 |
Opera | 11.60 |
Safari | 5.1.4 |
Как вы наверное уже успели заметить, к сожалению, Function.prototype.bind
не поддерживается в Internet Explorer 8 и более ранних версиях. Таким образом вы столкнетесь с рядом проблем, если попытаетесь воспользоваться этой функцией без обратного вызов.
К счастью, такой прекрасный ресурс, как Mozilla Developer Network предоставляет надежную альтернативу для тех случаев, когда браузер не поддерживает родной .bind()
метод:
if (!Function.prototype.bind) {
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();
ret ...
Если вы хотите прочитать полностью статью, посетите сайт наших спонсоров