JavaScript中'this'关键字行为的基本原理是什么?

时间:2009-02-12 13:14:31

标签: javascript language-design

我从语言设计的角度来问这个问题。所以我想找出

  1. this的行为的理由是什么?
  2. this的行为在多大程度上是错误的,或者可以改进?

  3. 为了澄清为什么我对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的某些仇杀,或者想要开始辩论;我只是想更好地理解它。我很欣赏“这个”可能有用,但同时也认识到it can be confusing ......当然会让初学者感到困惑,也许对专家来说也会让人感到困惑。

    然而,在该语言的其他核心方面似乎是回避的公平游戏(即Crockford和with或{{}时,它仍然是一种使用频繁,看似备受尊重的语言部分。 1}})。那么我错过了什么,这使得new不可或缺?

7 个答案:

答案 0 :(得分:11)

您似乎期望它的行为与某些OO语言中的行为一样,它始终引用方法所属的对象。

但是在JavaScript中,函数可以附加到多个对象,或者根本不附加任何对象。在您的示例中,您已经编写了一个旨在用于一个特定对象的上下文中的函数...但是没有什么能阻止我使用该函数并将其附加到任何其他对象。这只是语言的本质 - 函数是一流的,对象成员资格是可选的。

因此, this 指的是调用函数的上下文。现在,这是一个任意对象(通过..apply.call()指定)或全局对象。在该语言的未来版本中,它将引用定义函数的上下文:全局函数的全局对象,内部函数的外部this;你可以将其视为设计缺陷的修正,因为实际上能够使用它来引用全局对象并不是特别有用。

答案 1 :(得分:8)

我不认为让“这个”不受约束是一个错误。起初它有时会让人感到困惑,但它有很好的理由。首先想到的是,由于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;
};

建议的语法artificialidiot会很方便,但使用apply将“this”绑定到特定对象非常容易:

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;
};

答案 2 :(得分:3)

  • 不是
  • 很多OO的东西
  • 它的方式很好

答案 3 :(得分:3)

我认为未绑定的“这个”是一个错误。否则它非常方便。未绑定的“this”打开了错误解释在浏览器事件处理中最突出的上下文的可能性。此外,javascript库对事件处理和许多回调结构(如map,filter)中“this”应该引用的内容有不同的看法。

删除未绑定的“此”可能不会让事情变得更加困难。

编辑:我想一个替代语法示例将使我的立场更加清晰。

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;};
}

答案 4 :(得分:1)

  • 它应该被称为'self'而不是
  • 引用当前对象状态的任何内容。
  • 选择'self'作为'this'的名称,并将其显式(作为第一个参数)传递给所有方法。通过这种方式,您可以轻松地从静态方法或函数中告诉实例方法。

抱歉,但我真的很喜欢Python; - )

答案 5 :(得分:1)

认为未绑定的'this'关键字是必要的,因为JavaScript是基于原型的语言。更好的消息可以在这里填写详细信息。

尽管如此,但事实上是非常无益的。特别是如果你想将一个对象的方法传递给一个更高阶的函数,事情开始变得丑陋(以下示例在MooTools的帮助下):

myArray.each(myObject.foo);

无效,因为myObject.foo中的'this'将引用myArray而不是myObject。代替:

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

这对我来说似乎很难看。这就是为什么我通常不在JavaScript中以面向对象的方式编程,但我严重依赖于闭包。

答案 6 :(得分:1)

将惯用语a.f()视为:

的简写
a.f.call(a);

根据定义,使用范围f调用函数a

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

如果thisa不是同一个对象,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()