Объект JS 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> заменены соответствующими именами.)

Ответ 1

Личность 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();

Ответ 2

Это не ошибка jQuery, она неотъемлема от того, как JavaScript обрабатывает объекты.

В отличие от большинства других объектно-ориентированных языков, "это не связано на уровне каждого метода в 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 и начинает доступ к свойствам на нем, вызывая странные и запутывающие взаимодействия и ошибки.)

Чтобы помнить Боба, вы обычно выполняете функцию как в запросе nickyt, чтобы сохранить копию "Боба в закрытии", чтобы его можно было вспомнить во время обратного вызова и использовать для вызова реального метода. Однако теперь есть стандартизованный способ сделать это в пятом выпуске ECMAScript:

$j('#MyButton').click( Bob.doSomething.bind(Bob) );

(Вы также можете добавить дополнительные аргументы в вызов bind для вызова doSomething назад с ними, что удобно.)

Для браузеров, которые еще не поддерживают метод Fifth Edition 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))
            );
        };
    };
}

Ответ 3

Изменить

$('#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 );
        }
}

Ответ 4

Вы всегда можете попробовать сделать что-то вроде этого:

$j('#MyButton').click(function(event) {
  Bob.doSomething(event, this);
});

Теперь doSomething будет по-прежнему иметь Bob для своего this, и используя аргументы функции, он будет иметь объект event и элемент, на который он был запущен.