JS Object this.method() ломается через jQuery
Я уверен, что есть простой ответ на это, но сегодня пятница, и я устал.:(
Не знаю, как это объяснить, поэтому я просто опубликую пример кода...
Вот простой объект:
var Bob =
{ Stuff : ''
, init : function()
{
this.Stuff = arguments[0]
}
, doSomething : function()
{
console.log( this.Stuff );
}
}
И здесь это используется:
$j = jQuery.noConflict();
$j(document).ready( init );
function init()
{
Bob.init('hello');
Bob.doSomething();
$j('#MyButton').click( Bob.doSomething );
}
Все работает, кроме последней строки. Когда jQuery вызывает метод doSomething, он переопределяет this и останавливает его работу.
Пытаясь использовать просто Stuff
тоже не работает.
Итак, как я могу ссылаться на собственные свойства объекта таким образом, чтобы jQuery мог вызывать его, а также позволял объекту работать с вызывающим объектом jQuery?
т.е. я хотел бы иметь возможность делать такие вещи:
doSomething : function()
{
console.log( <CurrentObject>.Stuff + $j(<CallerElement>).attr('id') );
}
(Куда <CurrentObject>
а также <CallerElement>
заменены соответствующими именами.)
4 ответа
Личность this
это распространенная проблема в JavaScript Это также сломалось бы, если бы вы попытались создать ярлык для doSomething
:
var do = Bob.doSomething;
do(); // this is no longer pointing to Bob!
Это хорошая практика, чтобы не полагаться на личность this
, Вы можете сделать это различными способами, но самое простое - явно указать Bob
вместо this
Внутри doSomething
, Другой - использовать функцию конструктора (но тогда вы потеряете классный объектно-литеральный синтаксис):
var createBob = function() {
var that = {};
that.Stuff = '';
that.init = function() {
that.Stuff = arguments[0];
};
that.doSomething = function() {
console.log( that.Stuff );
};
return that;
}
var bob = createBob();
Это не ошибка jQuery, это неотъемлемая часть того, как JavaScript обрабатывает объекты.
В отличие от большинства других объектно-ориентированных языков, "this" не привязано к уровню метода в JavaScript; вместо этого он определяется исключительно тем, как вызывается функция:
Bob= {
toString: function() { return 'Bob!'; },
foo: function() { alert(this); }
};
Brian= {
toString: function() { return 'Brian!'; },
};
Bob.foo(); // Bob!
Bob['foo'](); // Bob!
Brian.foo= Bob.foo;
Brian.foo(); // Brian! NOT Bob
var fn= Bob.foo;
fn(); // window NOT Bob
Что вы делаете в случае:
$j('#MyButton').click( Bob.doSomething );
это как последний пример с fn
: вы тянете за функцию doSomething
от Боба и передачи его в установщик обработчика событий jQuery как чистую функцию: он больше не имеет никакого отношения к Бобу или любому другому объекту, поэтому JavaScript передается в глобальный window
объект вместо (Это одна из худших особенностей дизайна JavaScript, так как вы можете не сразу заметить, что window
не Bob
и начните обращаться к свойствам на нем, вызывая странные и запутанные взаимодействия и ошибки.)
Чтобы запомнить Боба, вы обычно делаете функцию, как в ответе Никита, чтобы сохранить копию "Боба" в замыкании, чтобы ее можно было запомнить во время обратного вызова и использовать для вызова реального метода. Однако теперь существует стандартизированный способ сделать это в пятом издании ECMAScript:
$j('#MyButton').click( Bob.doSomething.bind(Bob) );
(Вы также можете добавить дополнительные аргументы в bind
звонить звонить doSomething
обратно с ними, что удобно.)
Для браузеров, которые еще не поддерживают метод bind() в пятом издании (который на данный момент является большинством из них), вы можете взломать собственную реализацию bind
(библиотека Prototype также делает это), что-то вроде:
if (!Object.bind) {
Function.prototype.bind= function(owner) {
var that= this;
var args= Array.prototype.slice.call(arguments, 1);
return function() {
return that.apply(owner,
args.length===0? arguments : arguments.length===0? args :
args.concat(Array.prototype.slice.call(arguments, 0))
);
};
};
}
+ Изменить
$('#MyButton').click( Bob.doSomething );
в
$('#MyButton').click( function() { Bob.doSomething() } );
Вы также можете добавить к своему объекту Bob личное поле var that = this
и использовать это везде в членах вместо this
если вы действительно хотите избежать анонимной функции.
например
var Bob = new function() {
// Private Fields
var that = this;
// Public Members
this.Stuff = '';
this.init = function() {
that.Stuff = arguments[0];
}
this.doSomething = function() {
console.log( that.Stuff );
}
}
Вы всегда можете попробовать сделать что-то вроде этого:
$j('#MyButton').click(function(event) {
Bob.doSomething(event, this);
});
Сейчас doSomething
все еще будет Bob
для его this
и, используя аргументы функции, он будет иметь event
объект, и элемент, на котором он был запущен.