Как работает ключевое слово "this" в функции?

Я только что натолкнулся на интересную ситуацию в JavaScript. У меня есть класс с методом, который определяет несколько объектов, используя объектно-буквенную нотацию. Внутри этих объектов this указатель используется. Из поведения программы я сделал вывод, что this Указатель ссылается на класс, для которого был вызван метод, а не на объект, создаваемый литералом.

Это кажется произвольным, хотя я ожидаю, что это сработает. Это определенное поведение? Это кросс-браузер безопасно? Есть ли какое-либо объяснение, лежащее в основе того, почему оно выходит за рамки "спецификации так сказано" (например, является ли это следствием более широкого дизайнерского решения / философии)? Пример урезанного кода:

// inside class definition, itself an object literal, we have this function:
onRender: function() {

    this.menuItems = this.menuItems.concat([
        {
            text: 'Group by Module',
            rptletdiv: this
        },
        {
            text: 'Group by Status',
            rptletdiv: this
        }]);
    // etc
}

7 ответов

Решение

Каннибализированный из другого моего поста, вот больше, чем вы когда-либо хотели знать об этом.

Прежде чем я начну, вот самое важное, что нужно помнить о Javascript, и повторить для себя, когда это не имеет смысла. Javascript не имеет классов (ES6 class является синтаксическим сахаром). Если что-то похоже на класс, это умный трюк. Javascript имеет объекты и функции. (это не на 100% точно, функции - это просто объекты, но иногда полезно думать о них как об отдельных вещах)

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

Существует четыре способа вызова функций в JavaScript. Вы можете вызвать функцию как метод, как функцию, как конструктор и с помощью apply.

Как метод

Метод - это функция, которая прикреплена к объекту.

var foo = {};
foo.someMethod = function(){
    alert(this);
}

При вызове в качестве метода это будет связано с объектом, частью которого является функция / метод. В этом примере это будет связано с foo.

Как функция

Если у вас есть отдельная функция, переменная this будет связана с "глобальным" объектом, почти всегда объектом окна в контексте браузера.

 var foo = function(){
    alert(this);
 }
 foo();

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

Многие люди решают проблему, делая что-то вроде

var foo = {};
foo.someMethod = function (){
    var that=this;
    function bar(){
        alert(that);
    }
}

Вы определяете переменную , которая указывает на это. Закрытие (тема, которой он владеет) сохраняет это, поэтому, если вы вызываете bar как обратный вызов, у него все еще есть ссылка.

ПРИМЕЧАНИЕ: в use strict режим, если используется как функция, this не связан с глобальным. (Это undefined).

Как конструктор

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

Вы вызываете функцию как конструктор с новым ключевым словом.

function Foo(){
    this.confusing = 'hell yeah';
}
var myObject = new Foo();

При вызове в качестве конструктора будет создан новый объект, который будет привязан к этому объекту. Опять же, если у вас есть внутренние функции, и они используются как обратные вызовы, вы будете вызывать их как функции, и это будет связано с глобальным объектом. Используйте эту переменную, которая = этот трюк / шаблон.

Некоторые люди думают, что ключевое слово constructor/new было костью, брошенной Java/ традиционным программистам ООП, как способ создания чего-то похожего на классы.

С помощью метода Apply

Наконец, у каждой функции есть метод (да, функции - это объекты в Javascript) с именем "apply". Применить позволяет вам определить значение этого параметра, а также передать массив аргументов. Вот бесполезный пример.

function foo(a,b){
    alert(a);
    alert(b);
    alert(this);
}
var args = ['ah','be'];
foo.apply('omg',args);

Вызовы функций

Функции - это просто тип объекта.

Все объекты Function имеют методы вызова и применения, которые выполняют объект Function, к которому они обращаются.

При вызове первый аргумент этих методов указывает объект, на который будет ссылаться thisключевое слово во время выполнения функции - если это nullили же undefinedглобальный объект,window, используется дляthis,

Таким образом, вызов функции...

whereAmI = "window";

function foo()
{
    return "this is " + this.whereAmI + " with " + arguments.length + " + arguments";
}

... с круглыми скобками - foo()- эквивалентно foo.call(undefined)или жеfoo.apply(undefined), который фактически так же, какfoo.call(window)или жеfoo.apply(window),

>>> foo()
"this is window with 0 arguments"
>>> foo.call()
"this is window with 0 arguments"

Дополнительные аргументы кcallпередаются в качестве аргументов для вызова функции, тогда как один дополнительный аргумент дляapplyможет указать аргументы для вызова функции как объект типа Array.

Таким образом, foo(1, 2, 3)эквивалентноfoo.call(null, 1, 2, 3)или жеfoo.apply(null, [1, 2, 3]),

>>> foo(1, 2, 3)
"this is window with 3 arguments"
>>> foo.apply(null, [1, 2, 3])
"this is window with 3 arguments"

Если функция является свойством объекта...

var obj =
{
    whereAmI: "obj",
    foo: foo
};

... получить доступ к ссылке на функцию через объект и вызвать ее в скобках - obj.foo()- эквивалентноfoo.call(obj)или жеfoo.apply(obj),

Однако функции, содержащиеся в свойствах объектов, не "привязаны" к этим объектам. Как вы можете видеть в определенииobj выше, поскольку функции являются просто типом объекта, на них можно ссылаться (и, таким образом, их можно передавать по ссылке на вызов функции или возвращать по ссылке из вызова функции). Когда передается ссылка на функцию, никакая дополнительная информация о том, откуда она была передана, не переносится, поэтому происходит следующее:

>>> baz = obj.foo;
>>> baz();
"this is window with 0 arguments"

Призыв к нашей функции ссылки, baz, не предоставляет никакого контекста для вызова, поэтому он фактически такой же, как baz.call(undefined), так this заканчивается ссылками window, Если мы хотим baz знать, что это принадлежит objнам нужно как-то предоставить эту информацию, когда baz называется, где первый аргумент call или же apply и замыкания вступают в игру.

Цепи прицела

function bind(func, context)
{
    return function()
    {
        func.apply(context, arguments);
    };
}

Когда функция выполняется, она создает новую область и имеет ссылку на любую вмещающую область. Когда анонимная функция создается в приведенном выше примере, она имеет ссылку на область, в которой была создана, bindСфера. Это известно как "закрытие".

[global scope (window)] - whereAmI, foo, obj, baz
    |
    [bind scope] - func, context
        |
        [anonymous scope]

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

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

>>> baz = bind(obj.foo, obj);
>>> baz(1, 2);
"this is obj with 2 arguments"

Это определенное поведение? Это кросс-браузер безопасно?

Да. И да.

Есть ли какое-то обоснование, почему так оно и есть...

Значение this довольно просто вывести:

  1. Если this используется внутри функции конструктора, и функция была вызвана с new ключевое слово, this относится к объекту, который будет создан. this будет продолжать означать объект даже в публичных методах.
  2. Если this используется где-либо еще, включая вложенные защищенные функции, это относится к глобальной области действия (которая в случае браузера является объектом окна).

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

В этом случае внутренний this привязан к глобальному объекту, а не к this переменная внешней функции. Это способ, которым разработан язык.

См. "JavaScript: Хорошие части" Дугласа Крокфорда для хорошего объяснения.

Я нашел хороший учебник по ECMAScript это

Значение this - это специальный объект, связанный с контекстом выполнения. Следовательно, он может быть назван как объект контекста (то есть объект, в контексте которого активирован контекст выполнения).

Любой объект может быть использован в качестве этого значения контекста.

Это значение является свойством контекста выполнения, но не свойством объекта переменной.

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

В глобальном контексте это значение является самим глобальным объектом (это означает, что это значение здесь равно переменному объекту)

В случае контекста функции это значение в каждом вызове функции может отличаться

Ссылка на Javascript-ядро и главу-3-это

в JS:

Есть 3 типа функций, которые имеют разное значение. Их лучше всего объяснить на примере:

  1. Конструктор

  1. Прослушиватель событий

Внутри функции обработчика событийthisбудет ссылаться на элемент DOM, обнаруживший событие. См. этот вопрос: Использование этого внутри обработчика событий

Все ответы здесь очень полезны, но мне все еще было трудно понять, что thisв моем случае это связано с деструктуризацией объекта. Поэтому я хотел бы добавить еще один ответ, используя упрощенную версию моего кода,

let testThis = {
  x: 12,
  y: 20,
  add({ a, b, c }) {
    let d = a + b + c()
    console.log(d)
  },
  test() {
    //the result is NaN
    this.add({
      a: this.x,
      b: this.y,
      c: () => {
        //this here is testThis, NOT the object literal here
        return this.a + this.b 
      },
    })
  },
  test2() {
    //64 as expected
    this.add({
      a: this.x,
      b: this.y,
      c: () => {
        return this.x + this.y
      },
    })
  },
  test3() {
    //NaN
    this.add({
      a: this.x,
      b: this.y,
      c: function () {
        ////this here is the global object
        return this.x + this.y 
      },
    })
  },
}
Другие вопросы по тегам