2013-05-14 3 views
0

Кажется, что контекст $ (this) изменяется при определенных обстоятельствах для селектора классов. Я провел большое количество часов, пытаясь отладки ниже JavaScript, который я не какой-нибудь хорошо:

$.fn.specialbutton = function(callback) { 
    $(this).bind('click', function() { // $(this) is the a.button, as expected 
     $.ajax({ 
      url: 'ajax.php', 
      data: callback(); // context pitfall 
     }).done(function(response) { 
      $(this).html(response); // context changed to something unknown 
     }) 
    }); 
} 

$('a.button').specialbutton(function() { 
    // context pitfall 
    return { 
     id: $(this).data('id'); // $(this) is not what you expect 
    }; 
}); 

В конце концов, я полагаю, решение сохранить контекст и использовать явное призвание обратного вызова:

$.fn.specialbutton = function(callback) { 
    $(this).bind('click', function() { // $(this) is the a.button, as expected 

     var $this = $(this); // save the context; 

     $.ajax({ 
      url: 'ajax.php', 
      data: callback.call($this); // explicitly specify context 
     }).done(function(response) { 
      $this.html(response); // reuse the saved context 
     }) 
    }); 
} 

$('a.button').specialbutton(function() { 
    // context pitfall 
    return { 
     id: $(this).data('id'); // $(this) is what was specified in prototype 
    }; 
}); 

Каково правило и причина изменения контекста? Является ли это конструктивной особенностью или ограничением? Это, похоже, не влияет, если селектор был селектором HTML-идентификатора, то есть $('a#button'). Что было бы лучшим или распространенным способом кодирования вышеуказанного?

+0

Правило, контекст будет меняться с места на место. Это javascript, не связанная с jQuery, и зависит от того, как выполняется каждая функция и/или где она определена. Например, выполнение 'callback' напрямую будет выполнять его с контекстом, в котором он был определен (который будет' window'), но используя '.call()', конечно, изменяет контекст на то, что вы передаете в качестве первого параметра в ' .call() '. Внутри обработчика успеха ajax 'this' ссылается на свойство' context' вызова ajax, который по умолчанию является параметром, переданным в '$ .ajax'. Это происходит внутри jQuery, используя '.call()'. –

+0

Это не проблема с тем, как работает jQuery, это проблема с тем, как работает * JavaScript *. Каждая функция имеет свой собственный контекст ('this'). Если вы узнаете, как обрабатывается контекст в JavaScript, использование контекста с jQuery будет иметь больше смысла. – zzzzBov

+0

хорошая статья http://alistapart.com/article/getoutbindingsituations – shakib

ответ

1

Поскольку упомянутый article помог вам с ответом, больной попытаться упомянуть ключевые моменты из статьи,

в JavaScript связывание всегда явное, и может быть легко потерян, поэтому метод, использующий это не будет относиться к собственно о bject во всех ситуациях, если вы не заставляете это делать. В целом, привязка к JavaScript не является сложной концепцией, но она слишком часто игнорируется или замаскивается JavaScript-машинами, что приводит к путанице.

пример:

var john = { 
    name: 'John', 
    greet: function(person) { 
    alert("Hi " + person + ", my name is " + this.name); 
    } 
}; 
john.greet("Mark"); 

//=> "Hi Mark, my name is John" 

затем снова

var john = { 
    name: 'John', 
    greet: function(person) { 
    alert("Hi " + person + ", my name is " + this.name); 
    } 
}; 
var fx = john.greet; 
fx("Mark"); 
// => "Hi Mark, my name is " 

Это является наиболее важным вопросом с JavaScript связывания с чем-то я буду называть «потери связывания.«Это происходит всякий раз, когда вы обращаетесь к методу через ссылку вместо непосредственно через свой объект владельца. Метод теряет свою неявную привязку , и это перестает ссылаться на свой объект-владелец и возвращается к его значение по умолчанию, которое в этом случае является окном (поэтому, если к окну было добавлено свойство имени ), оно будет использоваться).

Распознавание связывания Чувствительного код модели

Binding-чувствительные кодовые шаблоны включают прохождение ссылки метод, , которые, как правило, происходят через два возможных способов: либо вы назначая метод как значение, или вы передаете метод как аргумент (что по сути то же самое, когда вы думаете об ).

Связывание явно

Так как же это исправить? Мы связываем явно, то есть мы явно указываем на то, что это укажет на метод, когда он будет вызван. И как мы это сделаем? JavaScript предоставляет нам два варианта: apply и call.

var fx = john.greet; 
fx.apply(john, ["Mark"]); 
// => "Hi Mark, my name is John" 

var fx = christophe.greet; 
fx.call(christophe, "Mark"); 
// => "Hi Mark, my name is John" 

Так что мы хотим, чтобы это способ настойчиво привязать метод, так что мы получаем связанное эталонный метод, так сказать. Единственный способ достичь этого требует, чтобы мы обернули наш оригинальный метод в другом, который будет выполнять вызов. Вот удар у него:

function createBoundedWrapper(object, method) { 
    return function() { 
    return method.apply(object, arguments); 
    }; 
} 

var john = { 
    name: 'John', 
    greet: function(person) { 
    alert("Hi " + person + ", my name is " + this.name); 
    } 
}; 
var fx = createBoundedWrapper(john, john.greet); 

fx("Mark"); 
// => "Hi Mark, my name is John" 

Если вы не слишком заинтересованы в JavaScript, приведенный выше код может запутать вас немного. Идея здесь заключается в том, что вызов createBoundedWrapper с заданным объектом и методом (который предположительно принадлежит указанному объекту) будет создать совершенно новую функцию (анонимную, которую мы возвращаем).

Следует ли связывать?

Теперь, когда мы прошли через детали привязки, справедливо только подчеркнуть, что иногда привязка переполнена. В частности, существует шаблон кода , в котором привязка может быть заменена, при этом значительная прибыль от , используя лексическое закрытие. (Если вы не ясно на то, что закрытие, не паникуйте.)

Вот шаблон: некоторый код внутри метода полагается на анонимную функции передается по ссылке на работу. Эта анонимная функция должна получить доступ к этому ключевому слову окружающего метода. Например, если предположить, на минуту мы имеем каждый итератор в пределах массивов, рассмотрим следующий код еще раз:

// ... 
    processItems: function() { 
    this.items.each(function(item) { 
     // Process item… 
     this.markItemAsProcessed(item); 
    }); 
    }, 
// ... 

анонимный метод может быть обернута вокруг createBoundedWrapper и внутренний this затем указать на наружной scope this.

Однако такой код не так хорош, как может показаться. Мы видели, что , достигающий такой «связанной ссылки», требует, чтобы мы обернули исходный метод в анонимной функции, что означает вызов ссылочных результатов связанного метода двумя способами: нашей анонимной оберткой и исходным методом. И если есть что-то справедливое только для на любом языке, это вызовы метода дорогостоящие.

В этой ситуации, мы имеем доступ к оригиналу, желательно это ключевого слова в том же месте, где кода мы определяем и называем неисправную функции (анонимный методом мы передаем в качестве аргумента в каждый). Мы можем просто сохранить правильную в этой ссылке в локальном переменном, и использовании, что внутри нашей итерации функции:

// ... 
    processItems: function() { 
    var that = this; 
    this.items.each(function(item) { 
     // Process item 
     that.markItemAsProcessed(item); 
    }); 
    }, 
// ... 

TAKEAWAY точки

Резюмируя:

Any member access must be qualified with the object it pertains to, even when it is this. 
Any sort of function reference (assigning as a value, passing as an argument) loses the function’s original binding. 
JavaScript provides two equivalent ways of explicitly specifying a function’s binding when calling it: apply and call. 
Creating a “bound method reference” requires an anonymous wrapper function, and a calling cost. In specific situations, leveraging closures may be a better alternative. 

надеюсь, что это поможет.

2

Простой ответ заключается в том, что он не меняет контекста. Вернее, он согласуется с тем, как он меняет свой контекст. Когда вы находитесь в методе done, этот метод специфичен для вызываемого вами звонка ajax. Таким образом, в этом случае определенно означает что-то отличное от this только внутри обработчика submit.

Если вы сделали это ajax call за пределами от отправителей, что бы вы ожидали this будет?

Подводя итог: this по определению является контекстуальным. При изменении контекстов (например, при создании вызова ajax и внутри методов/обратных вызовов этого объекта) значение this определенно изменится. Если вам нужны предыдущие значения , вам нужно отслеживать предыдущие значения this от более высоких закрытий, что именно вы делаете при установке $this.

1

Контекст (this) изменится в зависимости от того, как функция определена и/или выполнена.

Лучший способ написать это будет:

$.fn.specialbutton = function(callback) { 
    this.bind('submit', function() { // $(this) is the a.button, as expected 
     $.ajax({ 
      url: 'ajax.php', 
      data: callback.call(this), // explicitly specify context 
      context: this, // explicitly specify context 
     }).done(function(response) { 
      this.html(response); // the context is now correct 
     }) 
    }); 
} 

$('a.button').specialbutton(function() { 
    // context pitfall 
    return { 
     id: this.data('id'); // this is the element that was submitted 
    }; 
}); 

Примечание анкерные метки не имеют представить событие, так что ваш код не имеет особого смысла ...

Смежные вопросы