Как работает ключевое слово "this" в функции? - PullRequest
246 голосов
/ 25 сентября 2008

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

Ответы [ 5 ]

555 голосов
/ 25 сентября 2008

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

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

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

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

как метод

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

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

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

как функция

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

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

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

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

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

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

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

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

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

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

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

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

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

с применением метода

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

function foo(a,b){
    alert(a);
    alert(b);
    alert(this);
}
var args = ['ah','be'];
foo.apply('omg',args);
34 голосов
/ 26 сентября 2008

Функциональные вызовы

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

Все объекты Function имеют call и apply методы, которые выполняют объект 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 может указывать аргументы для вызова функции в виде объекта, подобного массиву.

Таким образом, 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"
9 голосов
/ 25 сентября 2008

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

Да. И да.

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

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

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

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

4 голосов
/ 13 марта 2014

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

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

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

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

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

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

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

Ссылка Javascript-the-core и Chapter-3-this

4 голосов
/ 25 сентября 2008

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

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

...