Как ключевое слово "this" в Javascript действует в объектном литерале?

Я видел Как работает "his" ключевое слово в функции?, но я не вижу ответа на следующее.

С учетом этого кода:

var MyDate = function(date) {
    this.date = date;
};

var obj1 = {
    foo: new Date(),
    bar: new MyDate(this.foo)  //  this.foo is undefined
};

var obj2 = {};
obj2.foo = new Date();
obj2.bar = new MyDate(this.foo);  //  this.foo is undefined

var obj3 = {
    foo: new Date(),
    bar: new MyDate(obj3.foo)
};

var obj4 = {};
obj4.foo = new Date();
obj4.bar = new MyDate(obj4.foo);

Почему первые две попытки терпят неудачу, но последние две работы? Если this не привязан к текущему литералу объекта, к чему он привязан?

Ответ 1

Javascript - это язык позднего связывания. На самом деле, это очень поздно связывать. Мало того, что this не связано во время компиляции, он даже не обязан во время исполнения (как и большинство других языков позднего связывания). В JavaScript this связано во время разговора.

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

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

Правила таковы:

1 - Когда функция вызывается как конструктор, новый объект создается и this связано с этим объектом. Например:

function Foo () {
    this.bar = 1; // when called with the new keyword
                  // this refers to the object just created
}
new Foo().bar;

2 - При вызове в качестве метода объекта this относится к объекту, которому принадлежит метод. В основном имя перед последней точкой. Например:

foo.bar = 1;
foo.baz = function () {
    alert(this.bar); // this refers to foo when called as foo.baz()
}
foo.baz();

3 - Если используется вне какой-либо функции или если функция не вызывается как метод, this относится к глобальному объекту. Спецификация javascript не дает имени глобальному объекту, кроме того, что он существует, но для браузеров он традиционно называется window. Например:

bar = 1;
alert(this.bar); // this refers to the global object
foo = {
    bar: this.bar // also global object
}
function foofoo () {
    alert(this.bar); // also refers to the global object
}
foofoo();

4 - В обработчике события (например, onclick и т.д.) this относится к элементу DOM, который вызвал событие. Или для событий, не связанных с DOM, таких как setTimeout или XMLHTTPRequest, this относится к глобальному объекту. Например:

foo.bar = 1;
foo.baz = function () {
    alert(this.bar); // this would normally be foo but if this
                     // function is assigned to an event it would
                     // point to the element that triggered the event
}
somediv.bar = 2;
somediv.onclick = foo.baz; // clicking on somedive alerts 2 instead of 1

5. Наконец, когда функция вызывается с использованием методов call() или apply() this можно переназначить на что угодно (google "mdn function.prototype.call"). Таким образом, любой объект в javascript может позаимствовать/украсть методы других объектов. Например:

cat = {
    type: "cat",
    explain: function () {
        return "I am a " + this.type;
    }
}
dog = {
    type: "dog"
}
cat.explain.call(dog); // returns "I am a dog"

С Function.bind() в современных реализациях javascript у нас теперь есть другое правило:

6 - Функции также могут явно связать this с объектом, используя метод bind(). Метод bind возвращает новый экземпляр функции, где this связано с аргументом, переданным для bind. Например:

function explain () {
    return "I am a " + this.type;
}
dog = {
    type: "dog"
}
var dog_explain = explain.bind(dog);
dog_explain(); // returns "I am a dog"

В ECMAscript 5 введен строгий режим, который меняет значение этого в функциях, которые не вызываются как методы, не вызываются с помощью call или apply, поэтому мы должны добавить новое правило:

7 - Когда в строгом режиме, this не может ссылаться на глобальный объект (окно в браузере). Поэтому, когда функция не вызывается как метод или this не связано ни к чему вручную с помощью call или apply или bind то this становится undefined:

"use strict";
function foo () {
    return this;
}
foo(); // returns undefined instead of the global object

В ECMAscript 6 введены функции стрелок. Функции стрелок меняют поведение при раннем связывании.

8 - В функциях стрелок this связано в то время, когда функция объявлена. Так this в следующем коде:

var x = () => {return this};

ведет себя так, как будто функция объявлена как следующий код:

var x = function () {return this}.bind(this);

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

Ответ 2

Я думаю, что вам может не хватать ключевое различие между функциями и литералами объектов:

Тело функции не оценивается до тех пор, пока функция не будет вызвана.

Тело литерала объекта оценивается сразу.

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

Ответ 3

В Javascript только вызовы функций устанавливают новый this контекст. Когда вы вызываете foo.bar(), в функции bar this привязывается к foo; когда вы вызываете foo(), внутри него this будет привязан к window. Конструктор литерала объекта не является вызовом метода, поэтому он никак не влияет на this; он все равно будет ссылаться на то, что он имел в виду вне литерала объекта.

Ответ 4

this.foo undefined, потому что во всех ваших примерах this ссылается на глобальный объект window. Кроме того, даже если вы попробовали obj1.foo, он все равно вернет undefined, потому что свойство не было создано до тех пор, пока не будет оценено все выражение. Вместо этого попробуйте:

var obj1 = {
    foo: new Date(),
    bar: function() {
        return new MyDate( this.foo ); // will work
    }
};

Это работает, потому что к тому времени, когда вы вызовете obj1.bar(), объект будет создан к тому времени; и поскольку вы находитесь в функции, объект this будет ссылаться на текущий объект.