in ECMAScript

Тонкости ECMA-262-3. Часть 3. This.

Read this article in: English.

Введение

В данной небольшой заметке мы рассмотрим ещё одну сущность, связанную с контекстами исполнения. Речь пойдёт о ключевом слове this.

Как показывает практика, данная тема является достаточно сложной и часто вызывает затруднения в определении значения this в том или ином контексте.

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

Давайте подробней разберём, что же такое this в ECMAScript.

Определение

Итак, this является свойством контекста исполнения:

activeExecutionContext = {
  VO: {...},
  this: thisValue
};

где VO — это объект переменных (variable object), который мы подробно разбирали в предыдущей статье.

Значение this напрямую связанно с типом исполняемого кода контекста. Определяется оно при входе в контекст и на протяжении исполнения кода контекста, является неизменным.

Рассмотрим эти случаи подробней.

This в глобальном коде

Здесь довольно всё просто. В коде глобального контекста, значением this всегда является сам глобальный объект (global); таким образом, можно косвенно к нему обратиться:

// явное объявление свойства
// глобального объекта
this.a = 10; // global.a = 10
alert(a); // 10

// косвенное, посредством присваивания
// неопределённому до этого идентификатору
b = 20;
alert(this.b); // 20

// также косвенное, посредством объявления
// переменной, поскольку объектом переменных
// в глобальном контексте является сам глобальный объект
var c = 30;
alert(this.c); // 30

This в коде функции

Со значением this в коде функции — интереснее. Именно в этом моменте возникает большинство затруднений.

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

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

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

var foo = {x: 10};

var bar = {
  x: 20,
  test: function () {

    alert(this === bar); // true
    alert(this.x); // 20
    
    this = foo; // ошибка, нельзя менять this
 
    alert(this.x); // если бы не было ошибки, было бы 10, а не 20

  }

};

// при входе в контекст, значение this
// определёно как объект "bar"; почему – будет
// подробно разобрано ниже

bar.test(); // true, 20

foo.test = bar.test;

// однако, здесь уже this указывает
// на "foo" - при вызове той же функции

foo.test(); // false, 10

Итак, от чего же зависит меняющееся значение this в коде функции? Здесь есть несколько факторов.

Во-первых, при обычном вызове функции, this определяется вызывающей стороной, которая активирует код контекста функции, — так называемый, caller, т.е. родительский контекст, который вызывает функцию. Определение значения this происходит по форме выражения вызова (иными словами, как синтаксически вызвана функция).

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

Так, например, в некоторых статьях и даже книгах по JavaScript, иногда можно видеть неверные утверждения, где описания вроде: this зависит от того, как описана функция: если это глобальная функция, то this будет указывать на глобальный объект, если же функция является методом объекта, то this всегда будет объектом, которому принадлежит вызываемый метод” — являются ошибочными. Забегая вперёд, покажем, что уже обычную глобальную функцию можно активировать разными формами вызова, которые и влияют на разное значение this:

function foo() {
  alert(this);
}

foo(); // global

alert(foo === foo.prototype.constructor); // true

// но при иной форме вызова той же
// функции, this будет иметь уже другое значение

foo.prototype.constructor(); // foo.prototype

Аналогично можно вызвать функцию, описанную как метод объекта, но значением this не будет являться этот объект:

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

foo.bar(); // foo, true

var exampleFunc = foo.bar;

alert(exampleFunc === foo.bar); // true

// опять же, при иной форме вызова той же
// функции, this уже установлен в другое значение

exampleFunc(); // global, false

Каким же образом форма выражения вызова влияет на значение this? Здесь всё завязано на один из внутренних типов реализации — тип Reference, который, для полного понимания определения значения this, нужно рассмотреть подробней.

Тип Reference

Псевдокодом, значение типа Reference можно представить в виде обычного объекта с двумя свойствами: база (base) (т.е. объект, которому принадлежит свойство) и имя свойства (property name) внутри базы:

var valueOfReferenceType = {
  base: <base object>,
  propertyName: <property name>
};

Значение типа Reference может быть получено только в двух случаях:

  1. когда мы имеем дело с идентификатором (identifier);
  2. либо же с выражением доступа к свойству (property accessor);

Процесс разрешения имён идентификаторов (identifier resolution) подробно рассматривается в четвёртой части (Цепь областей видимости, Scope chain). Здесь же отметим, что на выходе данного алгоритма всегда будет значение типа Reference (это важно для значения this).

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

var foo = 10;
function bar() {}

в промежуточных операциях, будут получены соответствующие значения типа Reference:

var fooReference = {
  base: global,
  propertyName: 'foo'
};

var barReference = {
  base: global
  propertyName: 'bar'
};

Для получения реального значения объекта из значения типа Reference предусмотрен метод GetValue, который псевдокодом можно описать следующим образом:

function GetValue(value) {

  if (Type(value) != Reference) {
    return value;
  }

  var base = GetBase(value);

  if (base === null) {
    throw new ReferenceError;
  }

  return base.[[Get]](GetPropertyName(value));

}

где внутренний метод [[Get]] получает значение свойства объекта, учитывая также и наследуемые свойства из цепи прототипов:

GetValue(fooReference); // 10
GetValue(barReference); // function object "bar"

Выражение доступа к свойству (property accessor), так же многим известно. Осуществляется оно либо через точечную нотацию (когда имя свойства является правильным идентификатором и заранее известно), либо же — через скобочную:

foo.bar();
foo['bar']();

На выходе промежуточного вычисления также будет получено значение типа Reference:

var fooBarReference = {
  base: foo,
  propertyName: 'bar'
};

GetValue(fooBarReference); // function object "bar"

Итак, как же связано значение типа Reference со значением this контекста функции? — Самым главным образом. Данный момент является основным в этой статье. Правило определения this в контексте функции звучит следующим образом:

Значение this в контексте функции определяется вызывающей стороной (caller-ом) по форме вызова.

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

Во всех остальных случаях (т.е. при любом другом типе значения, отличном от типа Reference), значением this будет всегда являться null. Но, т.к. null особого смысла для значения this не несёт, автоматом подставляется глобальный объект.

Покажем на примерах:

function foo() {
  return this;
}

foo(); // global

Видим, что слева от скобок вызова стоит выражение типа Reference (поскольку foo — это идентификатор):

var fooReference = {
  base: global,
  propertyName: 'foo'
};

Соответственно, значение this определенно, как база этого значения типа Reference, т.е. глобальный объект. Аналогично с выражением доступа к свойству:

var foo = {
  bar: function () {
    return this;
  }
};

foo.bar(); // foo

Имеем, опять же, значение типа Reference, где базой является объект foo, который и будет использован в качестве значения this при активации функции bar:

var fooBarReference = {
  base: foo,
  propertyName: 'bar
};

Однако, активируя ту же самую функцию, но с другой формой вызова, мы имеем уже другое значение this:

var test = foo.bar;
test(); // global

поскольку test, являясь идентификатором, порождает другое значение типа Reference, то именно база этого нового значения типа Reference и будет использована в качестве значения this — т.е. глобальный объект:

var testReference = {
  base: global,
  propertyName: 'test'
};

Примечание: в строгом режиме ES5 значение this не преобразуется к глобальному объекту, но установлено вместо этого в значение undefined.

Теперь мы можем точно сказать, почему одна и та же функция, но активированная разными формами вызова, имеет и разные значения this — ответ заключён в разных промежуточных значения типа Reference:

function foo() {
  alert(this);
}

foo(); // global, т.к.

var fooReference = {
  base: global,
  propertyName: 'foo'
};

alert(foo === foo.prototype.constructor); // true

// другая форма вызова

foo.prototype.constructor(); // foo.prototype, т.к.

var fooPrototypeConstructorReference = {
  base: foo.prototype,
  propertyName: 'constructor'
};

Ещё (классический) пример динамического определения this по форме выражения вызова:

function foo() {
  alert(this.bar);
}

var x = {bar: 10};
var y = {bar: 20};

x.test = foo;
y.test = foo;

x.test(); // 10
y.test(); // 20

Вызов функции и НЕ тип Reference

Итак, как мы уже отметили, в случае, когда слева от скобок вызова находится значение не типа Reference, а любого другого типа, значение this будет автоматически определенно как null, и, как следствие, global.

Рассмотрим примеры таких выражений:

(function () {
  alert(this); // null => global
})();

В данном случае мы имеем объект Function, но не объект типа Reference (это не идентификатор и не выражение доступа к свойству), соответственно, значение this в конечном итоге будет определенно, как глобальный объект.

Примеры сложнее:

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

foo.bar(); // Reference, OK => foo
(foo.bar)(); // Reference, OK => foo

(foo.bar = foo.bar)(); // global?
(false || foo.bar)(); // global?
(foo.bar, foo.bar)(); // global?

Почему же, используя выражение доступа к свойству (property accessor), промежуточным результатом которого должно являться значение типа Reference, мы, в определённых вызовах, получаем в качестве this не базовый объект (т.е. foo), а global?

Дело в том, что последние три вызова, после применения определённых операций, имеют слева от скобок вызова уже значение не типа Reference.

С первым случаем всё понятно – там однозначно тип Reference и, как следствие, this — это база, т.е. foo.

Во втором случае применяется оператор группировки, который не вызывает, рассмотренный выше, метод получения реального значения объекта из значения типа Reference, т.е. GetValue (см. примечание к 11.1.6). Соответственно, на выходе оператора группировки — всё ещё значение типа Reference, а потому, значение this снова определено, как база, т.е. foo.

В третьем случае, оператор присваивания, в отличие от оператора группировки, вызывает метод GetValue (см. шаг 3 11.13.1). В итоге на выходе уже будет значения типа Function, означающее, что в качестве this будет использован null и, как следствие, global.

Аналогично с четвертым и пятым случаями — оператор запятая и логическое ИЛИ вызывают GetValue, соответственно, мы теряем значение типа Reference и получаем значение типа Function; вновь, this определён как global.

Тип Reference и значение this равное null

Существует ситуация, когда выражение вызова определит слева от скобок вызова значение типа Reference, однако значение this будет определено как null и, как следствие, global.

Это относится к случаю, когда базовым объектом значения типа Reference, является объект активации.

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

function foo() {
  function bar() {
    alert(this); // global
  }
  bar(); // равносильно AO.bar()
}

Объект активации всегда возвращает в качестве значения thisnull (т.е. схематичная запись AO.b() равносильна null.b()). И здесь мы снова возвращаемся к вышеописанному случаю, и снова в качестве this подставляется глобальный объект.

Исключение может составить вызов внутренней функции в блоке оператора with, в случае, если объект with содержит свойство с именем функции. Оператор with в цепи областей видимости добавляет свой объект перед объектом активации. Соответственно, имея значения типа Reference (по идентификатору или выражению доступа к свойству), мы имеем базой не объект активации, а объект with. Кстати, это касается не только вложенной функции, но и глобальной — объект with “заслонит” вышестоящий объект (глобальный или объект активации) области видимости:

var x = 10;

with ({

  foo: function () {
    alert(this.x);
  },
  x: 20

}) {

  foo(); // 20

}

// поскольку

var  fooReference = {
  base: __withObject,
  propertyName: 'foo'
};

Аналогичная ситуация должна быть с активацией функции, являющейся параметром выражения catch: в данном случае объект catch также добавляется перед объектом активации, либо глобальным объектом. Однако, данное поведение было определенно как баг ECMA-262-3 и исправлено в новой версии стандарта ECMA-262-5; таким образом, значение this в данной активации должно быть global, но не объект catch:

try {
  throw function () {
    alert(this);
  };
} catch (e) {
  e(); // __catchObject - в ES3, global - исправлено в ES5
}

// по идее

var eReference = {
  base: __catchObject,
  propertyName: 'e'
};

// однако, это баг, и base
// принудительно определяется как
// null => global

var eReference = {
  base: global,
  propertyName: 'e'
};

Та же самая ситуация с рекурсивным вызовом именованной функции-выражения (подробней о функциях смотрите в пятой части). При первой активации функции, базой является родительский объект активации (или глобальный объект), при последующих — базой должен быть специальный объект хранящий имя функции-выражения. Однако, в данном случае в качестве this также всегда используется global:

(function foo(bar) {

  alert(this);

  !bar && foo(1); // "должен" быть спец.объект, но всегда global

})(); // global

This при вызове функции в качестве конструктора

Есть ещё одна ситуация, связанная со значением this в контексте функции — это вызов функции в качестве конструктора:

function A() {
  alert(this); // вновь сознанный объект, ниже - объект "a"
  this.x = 10;
}

var a = new A();
alert(a.x); // 10

В данном случае, оператор new вызовет внутренний метод [[Construct]] функции A, который, в свою очередь, после создания объекта, вызовет внутренний метод [[Call]], всё той же функции А, передав в качестве значения this вновь созданный объект.

Явная установка значения this при вызове функций

Существуют два метода, описанные в Function.prototype (а, соответственно, они доступны всем функциям), позволяющие явно указать значение this при вызове функции. Это методы apply и call.

Оба они принимают в качестве первого параметра значение this, которое будет использовано в контексте вызова. Разница между этими методами несущественная: для первого из них вторым параметром обязательно должен быть массив (либо массиво-подобный объект, например, arguments), в свою очередь, call может принимать любые параметры. Обязательным параметром для обоих методов является лишь первый — значение this.

Примеры:

var b = 10;

function a(c) {
  alert(this.b);
  alert(c);
}

a(20); // this === Global, this.b == 10, c == 20

a.call({b: 20}, 30); // this === {b: 20}, this.b == 20, c == 30
a.apply({b: 30}, [40]) // this === {b: 30}, this.b == 30, c == 40

Заключение

В данной заметке мы разобрали особенности ключевого слова this в ECMAScript (и они, действительно — особенности, в отличие, скажем, от C++ или Java). Надеюсь, статья помогла более чётко представить, как работает эта сущность в JavaScript. Как всегда, буду рад ответить на ваши вопросы в комментариях.

Дополнительная литература

10.1.7 – This;
11.1.1 – Ключевое слово this;
11.2.2 – Оператор new;
11.2.3 – Вызовы функций.


Автор: Dmitry A. Soshnikov
Дополнения и корректировки: Zeroglif

Дата публикации: 29.06.2009; обновление: 07.03.2010;

Write a Comment

Comment

20 Comments

  1. Большое спасибо за статью, очень актуальный вопрос.

  2. Дмитрий спасибо за замечательную статью!
    Объясните пожалуйста чем отличается (foo.bar = foo.bar)()
    от foo.bar = foo.bar; foo.bar();

    И ещё вопрос о том же, я знаю что чтобы вызвать eval (да и в принципе любую функцию) в глобальном контексте можно написать что то вроде (eval=eval)("/*global code*/"); или (1,eval)("");
    Такая техника называется indirect call.

    Могли бы вы пояснить логику выполнения этого выражения?

    Спасибо.

  3. @ Sergey

    чем отличается (foo.bar = foo.bar)()
    от foo.bar = foo.bar; foo.bar();

    В первом случае, как было отмечено в этой статье, оператор присваивания вызывает внутренний метод GetValue. Данный метод в свою очередь “портит” значение типа Reference, получая истинное значение (саму функцию foo.bar).

    Потеря значения типа Reference приводит к глобальному this в ES3 (в ES5 в strict-mode будет undefined в качестве this).

    Обратите еще раз внимание, что просто оператор группировки (обрамляющие скобки), не вызывает GetValue, и таким образом вызов функции получает this как объект foo: (foo.bar)();.

    Во втором же случае у Вас просто два независимых действия. Первое никак не относится к определению this; Вы просто “переприсвоили” функции значение самой себя. А вот дальше уже идет активация функции, и именно в этот момент определяется this, как объект foo, поскольку слева от скобок вызова — Reference, т.е. аккессор — доступ к свойству через точку.

    (eval=eval)("/*global code*/"); или (1,eval)("");
    Такая техника называется indirect call.

    Да, есть такой вызов eval‘a. Подробней я его описывал в статье про strict-mode.

    Суть его опять же сводится к Reference типу. Проще — только eval записанный в данной синтаксической форме, является явным (direct):

    eval(...);

    Все остальные синтаксические формы — это уже косвенные (indirect) вызовы и исполняются в глобальном контексте. Причины этого связаны с особенностями реализации eval‘a и с безопасностью.

  4. Дмитрий, у Вас отличные и главное – интересные статьи! Спасибо за это! Объясните, пожалуйста, подробнее, какое же все-таки значение будет принимать ключевое слово this в тот момент, когда мы только входим в контекст исполнения. Предположим, у нас есть только глобальный контекст исполнения программы. У него определяется свойство this. Какое значение оно будет принимать до интерпретации программы? В Вашем примере использовалось thisValue. Что оно представляет из себя? Заранее огромное спасибо!

  5. @Aleksey

    Объясните, пожалуйста, подробнее, какое же все-таки значение будет принимать ключевое слово this в тот момент, когда мы только входим в контекст исполнения.

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

    Предположим, у нас есть только глобальный контекст исполнения программы. У него определяется свойство this. Какое значение оно будет принимать до интерпретации программы?

    Да, верно, и это значение — сам глобальный объект, как было отмечено выше.

    В Вашем примере использовалось thisValue. Что оно представляет из себя?

    Это схематического обозначение значения this в определенном контексте исполнения.

    Структура контекста следующая (всего три основных свойства):

    executionContext = {
      VO: { хранилище_переменных }, // variable object
      ScopeChain: VO + все родительские VO,
      this: значение this // thisValue
    };

    Если есть необходимость, я рекомендую начать с описания самого контекста исполнения, и дальше уже его компонентов: VO, ScopeChain и this. Или же прочитать обзорную лекцию JavaScript. Ядро, где все эти моменты так же затрагиваются.

  6. Дмитрий, спасибо за Ваши пояснения! Продолжаю дальше читать Ваши статьи.

  7. в примере “Вызов функции и НЕ тип Reference” команда (false || foo.bar)(); возрашает true

  8. @max

    Если вы говорите про этот пример:

    var foo = {
      bar: function () {
        alert(this);
      }
    };
    (false || foo.bar)();

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

    Надеюсь я правильно понял ваш вопрос, и смог на него ответить.

  9. Добрый день!

    В FireFox (версии 16.0.2) код

    var f = {
        b: function() {
            console.log(this);
        }
    }
    f.b();
    

    пишет в консоль

    Object { b=function()}
    

    У вас же в примере указано, что будет "f":

    var foo = {
      bar: function () {
        alert(this);
      }
    };
    foo.bar(); // Reference, OK => foo
    
  10. @Иван Иван

    Я думою, дело в том, что при использовании консоли браузера, код будет выполнен с помощью функции eval, это и приведет к изменению значения this.

  11. А что Вам консоль должна выдать?
    Все правильно, Вы получили свой объект f, с методом b.

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

  12. Почему в данном коде this – global?

    var someObj = {
       someProp : function() {
                    outerFunc();
       };
    function outerFunc() { alert(this); }
    
    someObj.someProp();          
    
  13. Дмитрий подскажите пожалуйста, я правильно мыслю?

    'use strict';
     
    var test = {
        aa : function () {
            var bb = {
                j : function () {
                    console.log(this);
                }
            };
             
            function bar () {
                return bb.j;
            }
             
            return bar()();
        }
    };
     
    test.aa(); // this === undefined;
    
    test.aa === Reference // потому, что "."
    test.aa ->; bar === Reference // потому, что FD
    test.aa ->; bar ->; bb.j === Reference // потому, что "."
    var jReference = {
      base: bb,
      propertyName: 'j'
    };

    НО при выполнении bar() вернётся ссылка на функцию jFunc j : func...

    Соответственно, когда:

    jFunc()

    мы попадаем под правило

    НЕ тип Reference

    т.к.

    jFunc() !== identifier

    и

    jFunc ->; console.log(this); // undefined т.к. 'use strict';

    Всё верно ?

  14. Konstantin :
    Почему в данном коде this – global?

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

    То есть не “откуда”, а именно “как”!
    outerFunc(); -> Вызывается из AO родительской somePropFE, а доступа к AO в нормальных браузерах (текущий костяк) нет. Соответственно this === AOsomePropFunctionExpression === global // или undefined в 'use strict';

    Можно ещё так сказать:
    [ТО ЧТО СТОИТ ЗДЕСЬ, ВЛИЯЕТ НА this ВНУТРИ ->] outerFunc [ЕСЛИ ДАЛЬШЕ ИДЁТ ВЫЗОВ ->] ()

  15. @Alexander

    НО при выполнении bar() вернётся ссылка на функцию jFunc

    Пример получился несколько запутанный, но в целом, да, все верно 😉

  16. @Alexander
    Не очень понял Ваш ответ по поводу моего примера выше.
    Вообщем, как я вижу ситуацию:
    Ф-ция outerFunc создаётся в глобальном контексте, то есть входит в AO глобального объекта.В теле свойства someProp она вызывается как обычная ф-ия, следовательно её промежуточный Reference объект в качестве базы имеет global, обращение к этой функции идёт через scope chain. Поэтому и получаем global в качестве this…
    Надеюсь на Ваш ответ 🙂

  17. Поясните, пожалуйста.

    Во-первых, при обычном вызове функции, this определяется вызывающей стороной, которая активирует код контекста функции, — так называемый, caller, т.е. родительский контекст, который вызывает функцию.

    И вот:

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

    Однако ниже говорится, что:

    Существует ситуация, когда выражение вызова определит слева от скобок вызова значение типа Reference, однако значение this будет определено как null и, как следствие, global.

    Это относится к случаю, когда базовым объектом значения типа Reference, является объект активации.

    Следовательно немного не сходится пример:

    function foo() {
      function bar() {
        alert(this); // global
      }
      bar(); // равносильно AO.bar()
    }
    

    Как base может быть AO(объектом активации), если в определении сказано, что base – родительский контекст исполнения, а VO/AO – является именно СВОЙСТВОМ контекста исполнения. (Взято из второго урока про контесты исполнения).

    Понятно, что пример расскладывается так:

    var fooReference = {
      base: global,
      propertyName: 'foo'
    };
    var barReference = {
      base: AO,
      propertyName: 'bar'
    };
    

    Однако следуя логике определий про то, что base является именно родитеським контекстом исполнения (как я понял, не свойство родитеского контекста исполнения VO/AO, а им самим), то должно быть так:

    var fooReference = {
      base: global,
      propertyName: 'foo'
    };
    var barReference = {
      base: foo functionContext,
      propertyName: 'bar'
    };
    
  18. @Кирилл

    Вероятно я несколько путано написал первое предложение. Сам контекст исполнения никогда не является значением свойства base объекта типа Reference. Соответственно, правильный вариант — первый:

    var fooReference = {
      base: global,
      propertyName: 'foo'
    };
    var barReference = {
      base: AO,
      propertyName: 'bar'
    };
    

    Если перефразировать проще первое предложение, то в нем говорится, что значение this не зависит от того, как функция была создана, а от того, как функция (и где) будет вызвана, т.е. от caller’a:

    var foo = {
      x: 10,
      bar: function() {
        return this.x;
      },
    };
    
    var bar = foo.bar;
    
    var x = 100;
    
    bar(); // 100, global.bar();
    
    function baz() {
      bar(); // 100, AO.bar() -> null.bar() -> global.bar();
    }
    
    bar();
    

    Видим, что функция создана как метод объекта foo, но на определение значения this это никак не повлияло, поскольку caller (глобальный контекст) определил его при вызове — передав глобальный объект в качестве this (Reference: {base: global, propertyName: 'bar'}. В случае вызова из внутренней функции, base будет объект активации, и дальше также global.

  19. var user = {
      firstName: "Василий",
    
      export: this
    };
    
    alert( user.export.firstName );
    

    не подскажите а почему алерт выдает undefined, а вот здесь ожидаемо var name = "";

    var user = {
      name: "Василий",
    
      export: function() {
        return {
          value: this
        };
      }
    
    };
    
    alert( user.export().value.name );
  20. @Dmitriy

    не подскажите а почему алерт выдает undefined

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