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

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

这似乎是武断的,尽管这是我期望它的工作方式。这是已定义的行为吗?跨浏览器安全吗?是否有任何理由可以解释为什么它超越了“规范这么说”(例如,它是一些更广泛的设计决策/哲学的结果)?精简代码示例:

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

答案 1

从我的另一个帖子中蚕食,这里比你想知道的更多。

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

变量附加到函数。每当您调用函数时,都会为其指定一个特定的值,具体取决于调用函数的方式。这通常称为调用模式。

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

作为一种方法

方法是附加到对象的函数

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

当作为方法调用时,它将绑定到函数/方法所属的对象。在此示例中,这将绑定到 foo。

作为功能

如果您有一个独立的函数,则 this 变量将绑定到“global”对象,几乎总是浏览器上下文中的窗口对象。

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

这可能是绊倒你的原因,但不要感到难过。许多人认为这是一个糟糕的设计决策。由于回调是作为函数而不是方法调用的,因此您会看到看似不一致的行为。

许多人通过做这样的事情来解决这个问题,嗯,这个

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

定义一个指向此值的变量。闭包(一个它自己的主题)保留了一点,所以如果你把bar作为回调,它仍然有一个引用。

注意:在模式下,如果用作函数,则不绑定到全局。(它是)。use strictthisundefined

作为构造函数

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

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

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

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

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

使用应用方法

最后,每个函数都有一个名为“apply”的方法(是的,函数是Javascript中的对象)。Apply 允许您确定值的值,还允许您传入一个参数数组。这是一个无用的示例。

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

答案 2

函数调用

函数只是对象的一种类型。

所有 Function 对象都有调用应用方法来执行调用它们的 Function 对象。

调用时,这些方法的第一个参数指定在函数执行期间关键字将引用的对象 - 如果它是 或 ,则全局对象 , 用于 。thisnullundefinedwindowthis

因此,调用函数...

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"

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

因此,等效于 或 。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

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

对函数引用的调用 ,没有为调用提供任何上下文,因此它实际上与 相同,因此最终会引用 。如果我们想知道它属于 ,我们需要在调用时以某种方式提供该信息,这是 或 和 闭包的第一个参数发挥作用的地方。bazbaz.call(undefined)thiswindowbazobjbazcallapply

范围链

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

执行函数时,它会创建一个新作用域,并具有对任何封闭作用域的引用。在上面的示例中创建匿名函数时,它具有对创建它的作用域的引用,即 的作用域。这称为“闭包”。bind

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

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

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

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