почему «это» функции стрелки не изменяется внутри литерала вложенного объекта?

Я обнаружил, что ключевое слово this всегда указывает на global при использовании функции стрелки внутри литерала вложенного объекта.

Согласно другим вопросам, следующий фрагмент можно объяснить как «это» функции стрелки определяется в лексическом контексте.

var c = 100;
var a = {c:5 , fn: () => {return this.c;} };
console.log(a.c); //100

Однако я не могу понять следующий код (литерал вложенного объекта):

var c = 100;

var a = {
    c: 5,
    b: {
        c: 10,
        fn: ()=> {return this.c;}
    }
}

console.log(a.b.fn());// still 100, why not 5?

Я имею в виду, если рассматривать с точки зрения лексического контекста, не должно ли «это» в a.b.fn указывать на a?

Почему, независимо от того, на скольких уровнях вложен объект, все экземпляры this указывают на оконные или глобальные?


person jt-wang    schedule 05.06.2016    source источник
comment
Он указывает на текущую область. Объекты не меняют область видимости, только функции изменяют область видимости. Таким образом, this внутри функции стрелки будет относиться к ближайшей функции, внутри которой она находится. В вашем случае это только верхний уровень.   -  person mash    schedule 05.06.2016
comment
@mash: this и область действия в значительной степени не связаны, а this лишь изредка относится к ... [a] функции. Но суть комментария, конечно, правильная.   -  person T.J. Crowder    schedule 05.06.2016
comment
Для справки: стрелочные функции меняют область действия точно так же, как и любая другая функция. Они просто связывают this, arguments, super и new.target лексически вместо того, чтобы определять свои собственные переменные, которые затеняют внешние.   -  person Johannes H.    schedule 05.06.2016
comment
@JohannesH.: Просто для протокола :-), стрелочные функции вообще не связывают this. (Или, точнее: запись среды, созданная, когда мы их вызываем, этого не делает.) Вот почему они наследуют this контекста выполнения, в котором они созданы: они закрываются поверх него, точно так же, как закрываются поверх всего остального в этом контексте. (например, переменные).   -  person T.J. Crowder    schedule 05.06.2016
comment
@TJCrowder Верно. Однако это поведение по-прежнему называется лексической привязкой почти во всех справочниках по JS, включая MDN: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ (первый абзац)   -  person Johannes H.    schedule 05.06.2016


Ответы (3)


Единственное выражение в JavaScript, которое изменяет область видимости, — это функция, а в ES6 — блоки (обратите внимание, что литерал объекта не является блоком, несмотря на то, что вокруг него заключены фигурные скобки). Это означает: все, что не находится внутри функции, находится в глобальной области видимости.

В глобальной области this относится к глобальному объекту (window в случае браузеров). Единственное, что изменяет область видимости, — это функция стрелки (да, они ДЕЙСТВИТЕЛЬНО меняют область видимости!), но она связывает this лексически (что означает, что она использует this из внешней области видимости), так что это по-прежнему глобальный объект.

Если вы хотите, чтобы this ссылался на объект a, используйте IIFE вместо литерала объекта:

var c = 100;

var a = new function () {
    this.c = 5;
    this.b = {
        c: 10,
        fn: ()=> {return this.c;}
    }
}()

alert(a.b.fn()) // 5;

Или, чтобы привязать bк this:

var c = 100;

var a = {
    c : 5,
    b : new function () {
        this.c = 10;
        this.fn = ()=> {return this.c;}
    }()
}

alert(a.b.fn()) // 10;

В качестве альтернативы, чтобы связать this с b, вы также можете использовать обычную функцию вместо функции стрелки:

var c = 100;

var a = {
    c: 5,
    b: {
        c: 10,
        fn: function () {return this.c;}
    }
}

alert(a.b.fn()) // 10;
person Johannes H.    schedule 05.06.2016
comment
Единственное выражение в JavaScript, которое изменяет область действия, — это функция — нет. Блоки тоже. - person Bergi; 05.06.2016
comment
@Bergi Берги, я согласен с вами, и поэтому я был сбит с толку this в литералах вложенных объектов. - person jt-wang; 05.06.2016
comment
@jt-wang: но литералы объектов не являются блоками :-) - person Bergi; 05.06.2016
comment
Они делают это с ES6, да. Однако область видимости блока влияет только на переменные, объявленные с помощью let, и не влияет на var, поэтому это половина. Тем не менее, я изменю свою формулировку, чтобы указать на это. - person Johannes H.; 05.06.2016
comment
Остерегайтесь смешивать область и контекст выполнения. Они родственники, но очень разные. this относится к контексту выполнения, а не к области действия. - person T.J. Crowder; 05.06.2016
comment
@Bergi Я не думаю, что это точно повторяющийся вопрос о методах в объектах ES6: использование функций стрелок, потому что этот вопрос фокусируется на поведении this во вложенном литерале объекта, и этот вопрос не объясняет это. - person jt-wang; 05.06.2016

Это то же самое, что и this, где находится инициализатор объекта. Итак, в обоих ваших примерах это то же самое, что и this, где находится ваша строка var a = .... this никогда не изменяется в данном контексте выполнения, и инициализаторы объектов не создают новый контекст выполнения; только функции и eval делают это. В ваших примерах единственный раз, когда создается новый контекст выполнения, — это когда вы вызываете fn, а поскольку fn является стрелочной функцией, она закрывается над this в контексте выполнения, где он был создан (который является глобальным контекстом выполнения в ваши примеры).

Причина, по которой вы видите 100 вместо this.c в вашем примере кода, заключается в том, что this в глобальной области (в свободном режиме) относится к глобальному объекту, а var переменные в глобальной области видимости становятся свойствами глобального объекта, и поэтому this.c является глобальной переменной c.

Если вы поместите весь этот код в функцию определения области видимости, например:

(function() { // Or `(() => {`, doesn't matter in this case
    var c = 100;

    var a = {
        c: 5,
        b: {
            c: 10,
            fn: ()=> {return this.c;}
        }
    }
    console.log(a.b.fn());// still 100, why not 5?
})();    

...this.c будет undefined, потому что, хотя this по-прежнему будет ссылаться на глобальный объект, c больше не будет глобальной переменной (и, следовательно, свойством глобального объекта).

Если вы хотите, чтобы this внутри fn ссылалось на b в выражении a.b.fn(), тогда вам не нужна стрелочная функция, вам нужна обычная функция; вы получите 10 (значение a.b.c), а не 5 (значение a.c).

Конечно, поскольку это одноразовый объект и fn закрывается a, вы также можете просто сделать тело fn return a.c; или return a.b.c; в зависимости от того, какой c вы хотите.

person T.J. Crowder    schedule 05.06.2016
comment
Что насчет второго фрагмента кода? b инициализируется внутри a, но почему a.b.c указывает на глобальное c вместо a.c? - person jt-wang; 05.06.2016
comment
@jt-wang: я говорил о втором фрагменте. :-) this.c относится к глобальному c в этом примере по той же причине, что и в первом: this остается тем же, что и в строке var a. this никогда не изменяется в контексте выполнения, а инициализатор объекта не создает новый контекст выполнения (это делают только функции и eval). - person T.J. Crowder; 05.06.2016

Другой способ думать об этом состоит в том, что внутри литерала нет понятия новой "этой" области видимости.

С другой стороны, функция вводит новую область видимости.

person Robert Moskal    schedule 05.06.2016