Каково обоснование поведения ключевого слова this в JavaScript? - PullRequest
8 голосов
/ 12 февраля 2009

Я спрашиваю это с точки зрения языкового дизайна. Поэтому я пытаюсь выяснить

  1. Каково обоснование поведения this?
  2. В какой степени поведение this было ошибкой или могло быть улучшено?

Чтобы прояснить, почему меня беспокоит this, рассмотрим следующий пример:

var a = {};
a.f = function(){ return this; }
var f = a.f;
// f() != a.f()

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

Теперь рассмотрим:

var newA = function(){
    var self = {};
    self.f = function(){ return self; }
    return self;
}

var a = newA();
var f = a.f;
// f() == a.f() !

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

Я не в какой-то вендетте против this или не собираюсь начинать спор; Я просто пытаюсь лучше понять это. Я действительно ценю, что «это» может быть полезным, но признаю, что это может сбивать с толку также… Конечно, сбивает с толку новичков, а, возможно, и экспертов в достаточно неясных случаях.

И, тем не менее, он остается широко используемой и, казалось бы, уважаемой частью языка, в то время как другие основные аспекты языка кажутся справедливой игрой для избегания (например, Крокфорд и with или new) , Что же я упускаю тогда, что делает this незаменимым?

Ответы [ 7 ]

11 голосов
/ 12 февраля 2009

Вы, похоже, ожидаете, что это будет вести себя так же, как и в некоторых языках OO, где оно всегда ссылается на объект, которому принадлежит метод.

Но в JavaScript функция может быть присоединена к нескольким объектам или вообще к объектам не может быть. В вашем примере вы написали функцию, предназначенную для использования в контексте одного конкретного объекта ... Но ничто не мешает мне взять эту функцию и прикрепить ее к любому другому объекту. Это просто природа языка - функции первоклассные, членство в объекте необязательно.

Следовательно, this относится к контексту, в котором вызывается функция. Прямо сейчас это либо произвольный объект (указанный через ., .apply, либо .call()), либо глобальный объект. В будущих версиях языка он будет ссылаться на контекст, в котором была определена функция: глобальный объект для глобальных функций, внешний this для внутренних функций; Вы можете рассматривать это как исправление недостатка проекта, поскольку на практике возможность ссылаться на глобальный объект с помощью этого не была особенно полезна.

8 голосов
/ 12 февраля 2009

Я не думаю, что делать «это» свободным было ошибкой. Сначала это может сбить с толку, но для этого есть веские причины. Первое, что приходит на ум, это то, что, поскольку JavaScript не является языком, основанным на классах, функции не связаны с каким-либо конкретным классом, поэтому нет единого способа автоматически связать «this» с правильным экземпляром объекта. Например,

function Person(first, last, age) {
    this.firstName = first;
    this.lastName = last;
    this.age = age;
}

Person.prototype.getFullName = function() {
    return this.firstName + " " + this.lastName;
};

«this» должен ссылаться на объект Person, но функция, назначенная Person.prototype.getName, не имеет никакого способа узнать, как он будет использоваться, поэтому «this» необходимо привязать к какому-либо объекту это называется.

Где это вызывает проблему, это когда у вас есть вложенные функции.

// This is a really contrived example, but I can't think of anything better
Person.prototype.getInfo = function() {
    // get name as "Last, First"
    function getNameLastFirst() {
        // oops. "this" is the global object, *not* the Person
        return this.lastName + ", " + this.firstName;
    }

    // expect something like "Crumley, Matthew: Age 25",
    // but you get "undefined, undefined: Age 25"
    return getNameLastFirst() + ": Age " + this.age;
};

Предлагаемый синтаксис Искусственный_лиот будет удобен, но довольно просто связать "это" с конкретным объектом, используя apply:

function bind(func, obj) {
    return function() {
        return func.apply(obj, arguments);
    };
}

Person.prototype.getInfo = function() {
    // get name as "Last, First"
    var getNameLastFirst = bind(function () {
        return this.lastName + ", " + this.firstName;
    }, this);

    return getNameLastFirst() + ": Age " + this.age;
};

или более «традиционный» метод с использованием замыканий:

Person.prototype.getInfo = function() {
    var self = this;

    // get name as "Last, First"
    function getNameLastFirst() {
        return self.lastName + ", " + self.firstName;
    }

    return getNameLastFirst() + ": Age " + this.age;
};
3 голосов
/ 12 февраля 2009

Я думаю, что несвязанное «это» является ошибкой. В противном случае это довольно удобно. Несвязанное «это» открывает возможность неверного толкования контекста, наиболее заметного в обработке событий браузерами. Также библиотеки javascript имеют разные мнения о том, на что должно ссылаться «this» в обработке событий и во многих конструкциях обратного вызова (таких как map, filter).

Удаление несвязанного слова "это", вероятно, не усложнит ситуацию.

Редактировать: Я думаю, альтернативный синтаксис прояснит мою позицию.

function Foo()
{
    //both this refer to the Foo instance
    this.blah=this.function(){this.bar;};

    //second this refers to baz
    this.blah=baz.function(){this.bar;};

    //second this refers to anonymous function itself
    this.blah=function(){this.bar;};
}
3 голосов
/ 12 февраля 2009
  • Это не было
  • Много ОО вещи
  • Это хорошо, как это
1 голос
/ 12 февраля 2009

Рассмотрим идиому a.f() как сокращение для:

a.f.call(a);

Это по определению вызов функции f, использующий область действия a.

var f = a.f;
f(); // f.call(this);
a.f(); // f.call(a);

Если this и a не являются одним и тем же объектом, f() и a.f() будут использовать разные области и, следовательно, могут вести себя по-разному. Рассмотрим различие между статическими и классовыми методами в других языках:

class Foo {
public:
    static void a(Foo *scope) {
        // do something with given scope
    };

    void b() {
        a(this); // do something with the scope of this object
    };
};

Foo foo;
Foo bar;

foo.a(&bar) != foo.b(); // just like f() != a.f()
foo.a(&foo) == foo.b(); // just like f.call(a) == a.f()
1 голос
/ 12 февраля 2009

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

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

myArray.each(myObject.foo);

Не будет работать, потому что «this» в myObject.foo будет ссылаться на myArray вместо myObject. Вместо этого:

myArray.each(myObject.foo.bind(myObject))

Что мне кажется очень уродливым. Вот почему я обычно не программирую объектно-ориентированным способом на JavaScript, но вместо этого я сильно полагаюсь на замыкания.

1 голос
/ 12 февраля 2009
  • Вместо этого он должен называться «я»
  • Все, что относится к текущему состоянию объектов.
  • Выбрав «self» в качестве имени для «this» и передав его явно (в качестве первого аргумента) всем методам. Таким образом, вы можете легко отличить метод экземпляра от статического метода или от функции.

Извините, но мне действительно нравится Python; -)

...