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 объект, и элемент, на котором он был запущен.

Другие вопросы по тегам