Что лежит в основе этой идиомы JavaScript: var self = this?

357

В исходном тексте для WebKit HTML 5 SQL Storage Notes Demo я увидел следующее :

function Note() {
  var self = this;

  var note = document.createElement('div');
  note.className = 'note';
  note.addEventListener('mousedown', function(e) { return self.onMouseDown(e) }, false);
  note.addEventListener('click', function() { return self.onNoteClick() }, false);
  this.note = note;
  // ...
}

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

Thomas L Holaday
источник
4
Это особенность языка JS, называемая «лексическое замыкание».
subZero
2
Возможный дубликат: var self = this? ,
DavidRR
концепция ЭТОГО подробно объясняется здесь scotch.io/@alZami/understanding-this-in-javascript
AL-zami
Соответствующий пример в этом ответе stackoverflow.com/a/20279485/5610569 (к вопросу «Как получить доступ к правильному thisвнутри обратного вызова?»)
FluxLemur

Ответы:

431

Смотрите эту статью на alistapart.com . (Ред .: статья была обновлена ​​с момента ссылки)

selfиспользуется для сохранения ссылки на оригинал, thisдаже если контекст меняется. Эта техника часто используется в обработчиках событий (особенно в замыканиях).

Изменить: Обратите внимание, что использование selfтеперь не рекомендуется, поскольку window.selfсуществует и может привести к ошибкам, если вы не будете осторожны.

То, что вы называете переменной, не имеет особого значения. var that = this;хорошо, но в названии нет ничего волшебного.

Функции, объявленные внутри контекста (например, обратные вызовы, замыкания), будут иметь доступ к переменным / функциям, объявленным в той же области видимости или выше.

Например, простой обратный вызов события:

function MyConstructor(options) {
  let that = this;

  this.someprop = options.someprop || 'defaultprop';

  document.addEventListener('click', (event) => {
    alert(that.someprop);
  });
}

new MyConstructor({
  someprop: "Hello World"
});

Джонатан Финглэнд
источник
Похоже, что статья превратилась в использованиеvar that = this;
Боб Стейн
@BobStein Спасибо. Я обновлю ответ соответственно.
Джонатан Фингланд
96

Я думаю, что имя переменной «self» больше не следует использовать таким образом, поскольку современные браузеры предоставляют глобальную переменную,self указывающую на глобальный объект либо обычного окна, либо WebWorker.

Чтобы избежать путаницы и потенциальных конфликтов, вы можете написать var thiz = thisили var that = thisвместо.

Дуань Яо
источник
43
Я обычно использую_this
djheru
6
@djheru +1. намного лучше, чем " that" (к которому мой мозг никогда не привыкнет).
o_o_o--
9
Я начал использовать «я» :)
Мопарти Равиндранат,
3
Пока современные браузеры не начнут предоставлять глобальную переменную _this, that или me.
Beejor
10
Нет абсолютно никаких проблем с использованием имени, selfесли вы объявите его как variable, оно будет скрывать глобальное имя. Конечно, если вы забыли, varтогда это не будет работать с любым другим именем.
Берги
34

Да, вы увидите это повсюду. Это часто that = this;.

Посмотрите, как selfиспользуется внутри функций, вызываемых событиями? У тех был бы собственный контекст, поэтому selfон используется для удержания того, thisчто вошло Note().

Причина selfпо-прежнему доступна для функций, даже если они могут выполняться только послеNote() завершения выполнения функции, в том, что внутренние функции получают контекст внешней функции из-за закрытия .

Nosredna
источник
12
Для меня убедительным моментом является то, что selfне имеет особого значения. Лично я предпочитаю использовать переменную с именем, отличным от того, selfчто меня часто смущает, так как я ожидаю, что «я» будет зарезервированным словом. Поэтому мне нравится ваш ответ. И в примере с OP, я бы предпочел var thisNote = thisили подобное.
Стив
@steve согласился, хотя я стараюсь избегать использования этих ссылок / ссылок в целом, поскольку они очень хрупкие с точки зрения ремонтопригодности.
mattLummus
28

Следует также отметить, что существует альтернативный шаблон Proxy для сохранения ссылки на оригинал thisв обратном вызове, если вам не нравится эта var self = thisидиома.

Поскольку функцию можно вызывать с заданным контекстом с помощью function.applyили function.call, вы можете написать оболочку, которая возвращает функцию, которая вызывает вашу функцию с использованием applyили callс использованием данного контекста. См. proxyФункцию jQuery для реализации этого шаблона. Вот пример его использования:

var wrappedFunc = $.proxy(this.myFunc, this);

wrappedFuncзатем может быть вызван и будет иметь вашу версию в thisкачестве контекста.

Максимум
источник
10

Как уже объяснили другие, var self = this;позволяет коду в замыкании возвращаться к родительской области.

Тем не менее, сейчас 2018 год, и ES6 широко поддерживается всеми основными веб-браузерами. var self = this; идиома не так важна, как раньше.

Теперь можно избежать var self = this;с помощью функций стрелок .

В случаях, когда мы бы использовали var self = this:

function test() {
    var self = this;
    this.hello = "world";
    document.getElementById("test_btn").addEventListener("click", function() {
        console.log(self.hello); // logs "world"
    });
};

Теперь мы можем использовать функцию стрелки без var self = this:

function test() {
    this.hello = "world";
    document.getElementById("test_btn").addEventListener("click", () => {
        console.log(this.hello); // logs "world"
    });
};

Функции со стрелками не имеют своих собственных thisи просто принимают вмещающую область.

Эллиот Б.
источник
Или - шок, ужас! Почему бы не передать фактическую релевантную вещь в качестве аргумента вашей функции (замыкание)? Почему, черт возьми, вы ссылаетесь за пределы области видимости, почему, черт возьми, кто-то программирует так? Там нет никогда никакой реальной причины , чтобы сделать это. Вместо этого .addEventListender("click", (x) => { console.log(x); });Вы очень четко объяснили, как и почему, и я согласен, что использование функций со стрелками имеет больше смысла, но все же ... это просто ужасное, ленивое, грязное программирование.
Бенджамин Р
2
В JavaScript обратная ссылка на родительскую область является чрезвычайно распространенной и необходимой. Это фундаментальная часть языка. Ваше предложение передать родительскую область видимости в качестве аргумента в обработчик событий фактически невозможно. Кроме того, в ES6 функции стрелок используют лексическую область видимости - «это» относится к текущей окружающей области, а не к дальнейшему - это не «ссылка вне состояния области» или что-то в этом роде.
Эллиот Б.
9

Переменная фиксируется встроенными функциями, определенными в методе. thisв функции будет ссылаться на другой объект. Таким образом, вы можете заставить функцию содержать ссылку на thisвнешнюю область видимости.

Мехрдад Афшари
источник
9

Это причуда JavaScript. Когда функция - это свойство объекта, более точно называемое методом, это относится к объекту. В примере обработчика события содержащий объект является элементом, который вызвал событие. Когда стандартная функция вызывается, это будет относиться к глобальному объекту. Когда у вас есть вложенные функции, как в вашем примере, это вообще не относится к контексту внешней функции. Внутренние функции делят область действия с содержащей функцией, поэтому разработчики будут использовать варианты var that = this, чтобы сохранить то, что им нужно во внутренней функции.

Комбат
источник
5

На самом деле self - это ссылка на window ( window.self), поэтому, когда вы говорите, var self = 'something'вы переопределяете ссылку на себя, потому что self существует в объекте window.

Вот почему большинство разработчиков предпочитают var that = thisболееvar self = this;

Тем не мение; var that = this;не соответствует хорошей практике ... предполагая, что ваш код будет пересмотрен / изменен позже другими разработчиками, вы должны использовать самые распространенные стандарты программирования в отношении сообщества разработчиков

Поэтому вы должны использовать что-то вроде var oldThis/ var oThis/ etc - чтобы быть понятным в вашей области действия // .. это не так уж много, но сэкономит несколько секунд и несколько циклов мозга

SorinN
источник
2
@ prior Я думаю, что это имеет смысл до последнего параграфа.
Miphe
0

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

Сиприян
источник
@JohnPaul ... это дает ответ. Возможно, это не правильный ответ, но как вы можете сказать, что «он привык к…» - это не ответ «почему он это сделал»?
GreenAsJade
-1
function Person(firstname, lastname) {
  this.firstname = firstname;

  this.lastname = lastname;
  this.getfullname = function () {
    return `${this.firstname}   ${this.lastname}`;
  };

  let that = this;
  this.sayHi = function() {
    console.log(`i am this , ${this.firstname}`);
    console.log(`i am that , ${that.firstname}`);
  };
}

let thisss = new Person('thatbetty', 'thatzhao');

let thatt = {firstname: 'thisbetty', lastname: 'thiszhao'};

thisss.sayHi.call (thatt);

хт чжао
источник
1
Вы должны добавить некоторые объяснения с кодом, что то, что вы сделали особенным.
Фархана