JQuery и "this"? Как избежать переменных коллизий? - программирование
Подтвердить что ты не робот

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, какой лучший способ избежать этого типа переменного столкновения?


Надеюсь, это не ужасные вопросы. Заранее благодарим за то, что нашли время, чтобы помочь мне.:)

4b9b3361

Ответ 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)