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


248

Я только что натолкнулся на интересную ситуацию в 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
}

это случается, когда я делаю это тоже var signup = { onLoadHandler:function(){ console.log(this); return Type.createDelegate(this,this._onLoad); }, _onLoad: function (s, a) { console.log("this",this); }};
Deeptechtons


проверить этот пост . Имеет хорошее объяснение различного использования и поведения этого ключевого слова.
Любовь Хасия

Ответы:


558

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

Прежде чем я начну, вот самое важное, что нужно помнить о 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);

8
Примечание: В строгом режиме , thisбудет undefinedдля функции вызовов.
Miscreant

1
Объявление функции, например. function myfunction () {}, это особый случай «как метода», где «this» - глобальная область (окно).
Ричард

1
@ Richard: За исключением строгого режима, thisон не имеет ничего общего с областью действия. Вы имеете в виду глобальный объект .
TJ Crowder

@ Алан-шторм. В случае «Как конструктор» это this.confusing = 'hell yeah';то же самое, что и var confusing = 'hell yeah';? Так что оба позволят myObject.confusing? Было бы неплохо, если бы не только то, что вы можете использовать thisдля создания свойств и других переменных для внутренней работы.
wunth

Но опять же я думаю , что рабочий материал может быть сделан за пределами функции и значения , переданным через конструктору: function Foo(thought){ this.confusing = thought; }а затемvar myObject = new Foo("hell yeah");
wunth

35

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

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

Все объекты 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"

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

Таким образом, 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область действия не «исчезает».

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

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

«Когда передается ссылка на функцию, никакая дополнительная информация о том, откуда она была передана, не переносится с ней», спасибо @insin за это.
Алекс Марандон

9

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

Да. И да.

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

Смысл thisдовольно просто вывести:

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

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


4

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

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


4

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

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

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

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

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

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

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

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


« В глобальном контексте это значение является самим глобальным объектом (это означает, что это значение здесь равно переменному объекту) ». Глобальный объект является частью глобального контекста исполнения, как это (ES4) «изменяемый объект» и запись среды ES5. Но они являются различными объектами глобального объекта (например, на запись среды нельзя ссылаться напрямую, это запрещено спецификацией, но глобальный объект может быть).
RobG
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.