JQuery и "this"? Как избежать переменных коллизий?
Когда вы пишете сложный jQuery/javascript, как вам управлять с помощью this
без переопределения переменных this
, которые были определены ранее? У вас есть эмпирическое правило или личное предпочтение для обозначения ваших переменных this
(по мере того, как вложенность становится глубже)?
Есть моменты, когда я хочу, чтобы переменные из более высокой области были доступны для вложенных функций/обратных вызовов, но иногда бывают случаи, когда я хочу иметь чистый сланец/область; есть ли хороший способ вызвать функции/обратные вызовы, не беспокоясь о переменных столкновениях? Если да, то какой метод вы используете?
Какой-то супер глупый тестовый код:
$(document).ready(function() {
console.warn('start');
var $this = $(this),
$dog = $('#dog'),
billy = function() {
console.log('BILLY!', 'THIS:', $this, ' | ', 'DOG:', $dog);
var $this = $(this);
console.log('BILLY!', 'THIS:', $this, ' | ', 'DOG:', $dog);
};
// (#1)
billy(); // BILLY! THIS: undefined | DOG: jQuery(p#dog)
// BILLY! THIS: jQuery(Window /demos/this/) | DOG: jQuery(p#dog)
console.log('THIS:', $this, ' | ', 'DOG:', $dog); // THIS: jQuery(Document /demos/this/) | DOG: jQuery(p#dog)
// (#2)
billy(); // BILLY! THIS: undefined | DOG: jQuery(p#dog)
// BILLY! THIS: jQuery(Window /demos/this/) | DOG: jQuery(p#dog)
$('#foo').slideUp(function() {
// (#3)
console.log('THIS:', $this, ' | ', 'DOG:', $dog); // BILLY! THIS: undefined | DOG: jQuery(p#dog)
var $this = $(this); // (#10)
// (#4)
console.log('THIS:', $this, ' | ', 'DOG:', $dog); // BILLY! THIS: jQuery(Window /demos/this/) | DOG: jQuery(p#dog)
});
$('#clickme').click(function() {
// (#5)
console.log('THIS:', $this, ' | ', 'DOG:', $dog); // THIS: undefined | DOG: jQuery(p#dog)
var $this = $(this);
// (#6)
console.log('THIS:', $this, ' | ', 'DOG:', $dog); // THIS: jQuery(button#clickme) | DOG: jQuery(p#dog)
$('#what').animate({
opacity : 0.25,
left : '+=50',
height : 'toggle'
}, 500, function() {
// (#7)
console.log('THIS:', $this, ' | ', 'DOG:', $dog); // THIS: undefined | DOG: jQuery(p#dog)
var $this = $(this);
// (#8)
console.log('THIS:', $this, ' | ', 'DOG:', $dog); // THIS: jQuery(div#what) | DOG: jQuery(p#dog)
});
});
// (#9)
billy(); // THIS: undefined | DOG: jQuery(p#dog)
// THIS: jQuery(div#foo) | DOG: jQuery(p#dog)
console.warn('finish');
});
Здесь можно найти полную демо-страницу (jsbin.com).
Примечание.. Как вы можете видеть, я "помечен" комментариями номерами (#XX) для удобства.
Наблюдение 1:
Маркер (# 1)
BILLY! THIS: undefined | DOG: jQuery(p#dog)
Риторический вопрос: Почему $this
undefined, но $dog
доступен?
Ответ: Поскольку var
внутри этой области переопределяет $this
; это просто, что я пытаюсь log $this
, прежде чем он будет определен в этой области.
Если я прокомментирую var $this = $(this);
, то маркер (# 1) возвращает:
BILLY! THIS: jQuery(Document index2.html) | DOG: jQuery(p#dog)
BILLY! THIS: jQuery(Document index2.html) | DOG: jQuery(p#dog)
То же самое относится к марке (# 2), (# 3), (# 4), (# 5), (# 6), (# 7) и (# 8).
Основываясь на этом наблюдении (и, пожалуйста, исправьте меня, если я ошибаюсь здесь) Я предполагаю, что могу положить var $this = $(this);
в нижней части функции, и текущая область будет знать, что я хочу использовать текущую scope $this
(хотя он еще не определен), а не родительский $this
(хотя он определен как IS).
ВОЗМОЖНОЕ РЕШЕНИЕ ДЛЯ ИЗБЕЖАНИЯ $this
КОНФЛИКТЫ:
Если вы хотите кэшировать $(this)
вне/внутри других закрытий/функций/обратных вызовов и избегать коллизий, тогда следует использовать разные метки переменных, например, такие как:
var $$ = $(this);
var $this2 = $(this);
var $t = $(this);
var $that = $(this);
Вопрос:
Является ли решение выше, как вы могли бы избежать столкновений $this
? Если нет, то какая у вас техника?
Наблюдение 2:
Маркер (# 9)
THIS: undefined | DOG: jQuery(p#dog)
... $this
имеет значение undefined по указанным выше причинам, но:
THIS: jQuery(div#foo) | DOG: jQuery(p#dog)
... $this
теперь $('#foo')
!
Вопрос (ы):
Почему именно это произошло?
Это потому, что $this
был переопределен с помощью маркера (# 10)?
(Хм, мне кажется, что мне нужно "сбор мусора в Google" в javascript).
Опять же, при написании сложного jquery/javascript, какой лучший способ избежать этого типа переменного столкновения?
Надеюсь, это не ужасные вопросы. Заранее благодарим за то, что нашли время, чтобы помочь мне.:)
Ответы
Ответ 1
Поведение, которое вы видите при обновлении var $this
во внутренней области, называется hoisting. Это одна из причин, почему вы всегда должны иметь один оператор var
в начале каждой области действия для любых переменных, которые вам нужны в этой области - не это необходимо, это просто яснее, потому что он делает то же самое и имена переменных легче найти.
Что касается использования переменной в глубоко вложенных областях, это может быть признаком того, что что-то не так (слишком много гнездования, единственная ответственность и т.д.). Если вам нужно много использовать некоторые конструкции, подумайте о том, чтобы добавить их в область действия объекта, а не дать им имена, заканчивающиеся на 2
. По крайней мере, вы должны дать им описательные имена (обычно я использую только $this = $(this)
в пределах одной области действия, и чтобы не называть $()
несколько раз). self
часто используется для ссылки на текущий объект в определениях функций внутри своей области.
var Dog = function (boy, dog) {
this.boy = boy;
this.dog = dog;
this.$clickme = $("#clickme");
}
Dog.prototype.bind = function () {
var self = this;
self.$clickme.on('click', function() {
var $this = $(this);
console.log('THIS:', $this, ' | ', 'DOG:', self.dog);
$('#what').animate({
opacity : 0.25,
left : '+=50',
height : 'toggle'
}, 500, function() {
var $this = $(this);
console.log('CLICKME:', self.$clickme, ' | ', 'DOG:', self.dog);
});
});
}
Ответ 2
На самом деле вы столкнулись с проблемой переменной подъема:
billy = function() {
console.log('BILLY!', 'THIS:', $this, ' | ', 'DOG:', $dog);
var $this = $(this);
console.log('BILLY!', 'THIS:', $this, ' | ', 'DOG:', $dog);
};
фактически интерпретируется во время выполнения следующим образом:
billy = function() {
var $this;
console.log('BILLY!', 'THIS:', $this, ' | ', 'DOG:', $dog); // $this hasn't been set to anything yet...
$this = $(this);
console.log('BILLY!', 'THIS:', $this, ' | ', 'DOG:', $dog);
};
JavaScript поднимает объявления переменных и функций вверху блока области видимости, поэтому вы можете ссылаться на них, прежде чем их вручную объявлять. Это вызывает у людей проблемы, когда они ссылаются на одноименные вары из-за пределов блока видимости, как вы можете ясно видеть в своем примере.
Ответ 3
Вы только что столкнулись с радостями масштаба и JavaScript. Одна вещь, которую многие любят делать, - если вам нужен доступ к текущей области в обратном вызове, определите это как "я", например:
var $ = require('jquery')
, database = require('database')
$.on('click', function(action) {
var self = this;
database.connect(function() {
database.putSync(self.action.button);
});
});
Обратите внимание, что при вызове "connect" область действия "this" равна reset по области обратного вызова, а не по клику, поэтому вы сохранили область видимости в доступной переменной.
Ответ 4
Другие уже указали на проблемы с грузом, которые вы испытали в своем примере. Тем не менее, я думаю, что стоит упомянуть прокси-функциональность jQuery.
Это супер-удобно! Вам не нужно делать такие вещи, как var self = this;
, что дает вам проблемы при вложении нескольких областей.
Документация прокси-сервера jQuery
С прокси-сервером вы можете делегировать this
в свою функцию, что очень полезно, если вы используете объектно-ориентированный javascript в сочетании с jQuery:
$(document).ready(function () {
var $dog = $('#dog');
// without proxy
$('.billy').click(function () {
// $(this) refers to the node that has been clicked
$(this).css('color', 'green');
});
// with proxy
$('.billy').click($.proxy(function () {
// $(this) refers to $dog
$(this).css('backgroundColor', 'red');
}, $dog));
});
Рабочий пример здесь: jsFiddle использование прокси-сервера jQuery
Если вам действительно нужны ваши ссылки this
бок о бок в функции, я предлагаю сохранить их загруженными в массив, так что вам не нужно беспокоиться о их именовании, и вы также держите оценку ваш уровень гнездования:
$(document).ready(function () {
var thisStack = [];
thisStack.push($('#dog'));
$('.billy').click(function () {
thisStack.push($this);
// thisStack[0] refers to $('#dog')
// thisStack[1] refers to $(this)
... more code ...
thisStack.pop(); // get rid of $(this), leaving scope
});
});
Ответ 5
Как указано выше. Одним из популярных способов является назначение переменной self 'this'
Другой способ - отправить область, в которую вы хотите включить функцию, в качестве последнего параметра:
this.on('change', function() {
//this is now the scope outside the callback function.
}, this)