【问题标题】:What is the rationale for the behavior of the 'this' keyword in JavaScript?JavaScript 中“this”关键字行为的基本原理是什么?
【发布时间】:2010-10-07 04:06:00
【问题描述】:

我是从语言设计的角度来问这个问题的。所以我想知道

  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 的仇杀,也不是想要开始争论;我只是想更好地理解它。我确实很欣赏“this”可能很有用,但也认识到 it can be confusing... 肯定会让初学者感到困惑,在足够晦涩的情况下也可能让专家感到困惑。

然而,在该语言的其他核心方面似乎可以回避的时代(即 Crockford 和 withnew) .那我错过了什么,这使得this 不可或缺?

【问题讨论】:

  • 是什么让你觉得“这个”是个错误?
  • @Gamecat:你从来没有尝试过设计类似javascript的语言,是吗?
  • 我仍然认为这是有争议的。该语言暗示了一个问题,参见“‘这’多么令人惊奇(如果它令人惊奇的话)”
  • 这不是js本身的问题。许多人认为这是一个挑战,即如何在 js 中完成一些事情以弥补“this”的缺点,或者抱怨 js 的设计有多糟糕。我建议人们不要回答,除非他们从语言设计的角度考虑过。
  • 我试着让它看起来不那么有争议。我不认为这是作者的意图,但是寻找对语言特征的批判性评估总是有点雷区。 @mike g:如果您不喜欢我的更改,请随时回滚。

标签: javascript language-design


【解决方案1】:

我不认为将“this”解绑是错误的。起初有时可能会令人困惑,但它的方式有充分的理由。首先想到的是,由于 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;
};

【讨论】:

  • Matthew Crumley,(我不知道为什么“@”符号不起作用,但希望您能看到这篇文章)。我正在尝试在您的答案中遵循您的代码,但也许我在这里遗漏了一些东西?请告诉我,你在哪里定义函数getNameFirstLast?这是一个错字吗?为什么它的返回值是未定义的?谢谢。
  • @Chris22 糟糕,这是一个错字。它应该是getNameLastFirst()。在第一个示例中它返回undefined 的原因是因为它在没有上下文的情况下被调用,所以this 默认绑定到全局范围。因此(假设没有名为 lastNamefirstName 的全局变量)这两个属性都将是未定义的,而不是像您期望的那样从 Person 对象中获取值。
  • +1 Matthew Crumley 好的,谢谢。我理解“这个”的范围。错字只是分散注意力:-)
  • @Chris22 这是可以理解的。对此感到抱歉。
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:
    • 应该改为“self”
    • 任何与当前对象状态相关的内容。
    • 通过选择“self”作为“this”的名称,并将其显式(作为第一个参数)传递给所有方法。通过这种方式,您可以轻松地将实例方法与静态方法或函数区分开来。

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

    【讨论】:

    • 我觉得应该叫我;)
    • 哇,有人没有幽默感吗?否决“这个”?
    • 您忘了提及您希望将“self”作为第一个参数传递给方法,而不是隐式访问它。
    • @Ionug G. Stan:是的。更新我的答案以反映这一点
    • 我想你会发现自我被滥用的时机已经成熟。应该是“那个”
    【解决方案4】:

    将习语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()
    

    【讨论】:

      【解决方案5】:

      认为未绑定的“this”关键字是必要的,因为 JavaScript 是一种基于原型的语言。了解更多的人可能可以在此处填写详细信息。

      尽管如此,但它是非常无用的。尤其是如果你想将对象的方法传递给高阶函数,事情就会变得很糟糕(在 MooTools 的帮助下以下示例):

      myArray.each(myObject.foo);
      

      将不起作用,因为 myObject.foo 中的“this”将引用 myArray 而不是 myObject。而是:

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

      这对我来说似乎很丑陋。这就是为什么我通常不会在 JavaScript 中以面向对象的方式编程,而是大量依赖闭包。

      【讨论】:

        【解决方案6】:

        我认为未绑定的“this”是一个错误。否则它非常方便。未绑定的“this”打开了误解浏览器事件处理中最明显的上下文的可能性。此外,javascript 库对于“this”在事件处理和许多回调构造(如映射、过滤器)中应指的内容有不同的看法。

        删除未绑定的“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;};
        }
        

        【讨论】:

          【解决方案7】:
          • 不是
          • 很多面向对象的东西
          • 这样很好

          【讨论】:

          • 好的。您介意举例说明哪些 OO 东西是不可能的吗?
          • 这是一个非常无益的答案,因为作者将观点作为事实陈述,而没有提供推理或例子的支持。 -1
          • mike g:公开功能 rix0rr:很难用事实来支持,因为对方没有提供任何东西
          • @rixOrr:问题也将意见陈述为事实
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-03-09
          • 1970-01-01
          • 1970-01-01
          • 2010-10-01
          • 2014-12-05
          • 2016-12-05
          • 1970-01-01
          相关资源
          最近更新 更多