函数中的“ this”关键字如何工作?


248

我刚刚遇到了一个有趣的JavaScript情况。我有一个类,该类的方法使用对象文字表示法定义了多个对象。在这些对象内部,this正在使用指针。从程序的行为,我推断出this指针是指向在其上调用方法的类,而不是由文字创建的对象。

尽管这是我期望的工作方式,但这似乎是任意的。这是定义的行为吗?跨浏览器安全吗?是否有任何推理依据说明它超出了“规格说明”的范围(例如,这是某些更广泛的设计决策/理念的结果)吗?简化的代码示例:

// inside class definition, itself an object literal, we have this function:
onRender: function() {

    this.menuItems = this.menuItems.concat([
        {
            text: 'Group by Module',
            rptletdiv: this
        },
        {
            text: 'Group by Status',
            rptletdiv: this
        }]);
    // etc
}

当我也这样做时,它确实会发生 var signup = { onLoadHandler:function(){ console.log(this); return Type.createDelegate(this,this._onLoad); }, _onLoad: function (s, a) { console.log("this",this); }};
Deeptechtons,2011年


查看这篇文章。对这个关键字的各种用法和行为有一些很好的解释。
Love Hasija '17

Answers:


558

从我的另一篇文章拆解,这里有更多的比你想要知道这个

在开始之前,这里要牢记关于Javascript的最重要的事情,并在没有意义的情况下对自己重复一遍。Javascript没有类(ES6 class语法糖)。如果某个东西看起来像一堂课,那是个聪明的把戏。Javascript具有对象功能。(这不是100%准确的,函数只是对象,但有时将它们视为独立的东西会有所帮助)

这个变量被附连到功能。当你调用一个函数,这个被赋予了一定的价值,这取决于你如何调用该函数。这通常称为调用模式。

有四种方法可以调用javascript中的函数。您可以将函数作为方法函数构造函数以及apply调用。

作为一种方法

方法是附加到对象的函数

var foo = {};
foo.someMethod = function(){
    alert(this);
}

当作为一种方法被调用,将被绑定到对象的功能/方法是的一部分。在此示例中,它将绑定到foo。

作为功​​能

如果您具有独立功能,则变量将绑定到“全局”对象,几乎总是在浏览器上下文中的window对象。

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

这可能是使您绊倒的原因,但并不难过。许多人认为这是一个错误的设计决定。由于回调是作为函数而不是方法调用的,因此这就是为什么您看到的行为似乎不一致。

很多人通过做类似的事情来解决这个问题

var foo = {};
foo.someMethod = function (){
    var that=this;
    function bar(){
        alert(that);
    }
}

你定义一个变量指向这个。闭包(它本身就是一个话题)可以解决这个问题,因此,如果您将bar称为回调,它仍然具有引用。

注意:在use strict模式下,如果用作功能,this则不绑定到全局。(是undefined)。

作为建设者

您还可以将函数作为构造函数调用。根据您正在使用的命名约定(TestObject),这也可能是您正在做的事情,也是使您绊倒的原因

您可以使用new关键字将函数作为构造函数调用。

function Foo(){
    this.confusing = 'hell yeah';
}
var myObject = new Foo();

当作为一个构造函数调用时,一个新的对象将被创建,并且将被绑定到该对象。同样,如果您具有内部函数并将它们用作回调,则将它们作为函数调用,并且将绑定到全局对象。使用那个=这个技巧/模式的变量。

有人认为,构造函数/新关键字是Java /传统OOP程序员的骨干,是创建类似于类的东西的一种方法。

使用Apply方法

最后,每个函数都有一个名为“ apply”的方法(是的,函数是Javascript中的对象)。应用可以让你决定的价值来决定将是,也可以让你在参数数组传递。这是一个无用的示例。

function foo(a,b){
    alert(a);
    alert(b);
    alert(this);
}
var args = ['ah','be'];
foo.apply('omg',args);

8
注意:在严格模式下thisundefined用于函数调用。
错误的

1
函数声明,例如 函数myfunction(){},是“作为方法”的特例,其中“ this”是全局作用域(窗口)。
理查德

1
@richard:严格模式下除外,this与作用域无关。您的意思是全局对象
TJ Crowder

@ alan-storm。在“作为构造函数”的情况下,this.confusing = 'hell yeah';var confusing = 'hell yeah';?那么两者都允许myObject.confusing吗?不仅如此,这还不错,您可以使用它this来为内部工作创建属性和其他变量。
wunth

但话又说回来,我认为可以在函数之外完成工作,并将值传递给构造函数:function Foo(thought){ this.confusing = thought; }然后var myObject = new Foo("hell yeah");
2009年

35

函数调用

函数只是对象的一种。

所有Function对象都有调用Apply方法,这些方法执行被调用的Function对象。

调用这些方法时,这些方法的第一个参数指定对象,该对象将this在执行Function时由关键字引用-如果它是nullundefined全局对象window,则用于this

因此,调用一个函数...

whereAmI = "window";

function foo()
{
    return "this is " + this.whereAmI + " with " + arguments.length + " + arguments";
}

...带括号-- foo()等效于foo.call(undefined)foo.apply(undefined)实际上foo.call(window)或相同foo.apply(window)

>>> foo()
"this is window with 0 arguments"
>>> foo.call()
"this is window with 0 arguments"

将的其他参数call作为函数调用的参数传递,而的一个附加参数apply可以将函数调用的参数指定为类似数组的对象。

因此,foo(1, 2, 3)等于foo.call(null, 1, 2, 3)foo.apply(null, [1, 2, 3])

>>> foo(1, 2, 3)
"this is window with 3 arguments"
>>> foo.apply(null, [1, 2, 3])
"this is window with 3 arguments"

如果函数是对象的属性...

var obj =
{
    whereAmI: "obj",
    foo: foo
};

...通过对象访问对该函数的引用并用括号-对其进行调用obj.foo()等效于foo.call(obj)foo.apply(obj)

但是,作为对象属性保留的功能未“绑定”到那些对象。在obj上面的定义中可以看到,由于函数只是对象的一种,因此可以对其进行引用(因此可以通过引用传递给Function调用,也可以通过引用从Function调用返回)。当对函数的引用传递,它通过了关于没有额外的信息与它进行,这就是为什么会发生以下情况:

>>> baz = obj.foo;
>>> baz();
"this is window with 0 arguments"

调用我们的Function引用baz不会提供任何上下文,因此它实际上与相同baz.call(undefined),因此this最终引用window。如果我们想baz知道它属于obj,那么我们需要以某种方式在baz调用时提供该信息,这是callor apply和闭包的第一个参数起作用的地方。

范围链

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

执行Function时,它将创建一个新作用域,并引用任何封闭的作用域。在上述示例中创建匿名函数时,它会引用创建该函数bind的作用域,即作用域。这称为“关闭”。

[global scope (window)] - whereAmI, foo, obj, baz
    |
    [bind scope] - func, context
        |
        [anonymous scope]

当您尝试访问变量时,将沿着“作用域链”查找具有给定名称的变量-如果当前作用域不包含该变量,则请查看链中的下一个作用域,依此类推,直到到达全球范围。返回匿名函数并bind完成执行后,匿名函数仍具有对bind范围的引用,因此bind范围不会“消失”。

鉴于以上所有内容,您现在应该能够理解以下示例中的作用域如何工作,以及为什么在调用带有特定值的“预绑定”周围传递函数的技术将起作用this时,将具有以下作用:

>>> baz = bind(obj.foo, obj);
>>> baz(1, 2);
"this is obj with 2 arguments"

“在传递对函数的引用时,不会附带有关传递它的位置的其他信息”,谢谢@insin。
亚历克斯·马兰登

9

这是定义的行为吗?跨浏览器安全吗?

是。是的。

是否有任何推理依据说明为什么是这样...

this推导的含义非常简单:

  1. 如果this在构造函数中使用if ,并且使用new关键字调用了该函数,则引用this将要创建的对象。this即使在公共方法中也将继续表示对象。
  2. 如果this在其他任何地方(包括嵌套的受保护函数)使用它,则引用全局范围(在浏览器的情况下为窗口对象)。

第二种情况显然是设计缺陷,但是通过使用闭包可以很容易地解决它。


4

在这种情况下,内部this绑定到全局对象,而不是this外部函数的变量。这是语言的设计方式。

有关详细说明,请参见Douglas Crockford撰写的“ JavaScript:好的部分”。


4

我找到了一个关于ECMAScript的不错的教程

这个值是一个特殊的对象,它与执行上下文有关。因此,可以将其命名为上下文对象(即,在其中激活了执行上下文的对象)。

任何对象都可以用作上下文的此值。

此值是执行上下文的属性,而不是变量对象的属性。

此功能非常重要,因为与变量相反,此值永远不会参与标识符解析过程。即,在代码中访问它时,其值直接从执行上下文中获取,而无需任何范围链查找。进入上下文时,此值仅确定一次。

在全局上下文中,this值是全局对象本身(这意味着此值等于变量对象)

在函数上下文中,每个单个函数调用中的该值可能都不同

参考Javascript-the-core第3章-this


在全局上下文中,this值是全局对象本身(这意味着此值等于变量对象) ”。所述全局对象是全局执行上下文的一部分,由于是(ES4)“可变对象”和ES5环境的记录。但是它们是全局对象的不同实体(例如,不能直接引用环境记录,规范禁止,但可以是全局对象)。
RobG
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.