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

时间:2020-03-06 14:43:37  来源:igfitidea点击:

我刚刚遇到了一个有趣的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
}

解决方案

Is this defined behavior? Is it
  cross-browser safe?

是的。是的。

Is there any reasoning underlying why
  it is the way it is...

可以很容易地推断出" this"的含义:

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

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

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

有关详细说明,请参见道格拉斯·克罗克福德(Douglas Crockford)撰写的" JavaScript:好的部分"。

在我的另一个职位上被吞噬了,这比我们想了解的更多。

在开始之前,请记住关于Javascript的最重要的内容,并在没有意义的情况下对自己重复。 Javascript没有类(ES6"类"是语法糖)。如果某个东西看起来像一堂课,那是个聪明的把戏。 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作为回调调用,它仍然具有引用。

注意:在"使用严格"模式下(如果用作功能),"此"未绑定到全局。 (它是未定义的)。

作为建设者

我们也可以将函数作为构造函数调用。根据我们正在使用的命名约定(TestObject),这也可能是我们正在做的事情,并且是绊脚石。

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

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

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

有人认为,构造器/新关键字是Java /传统OOP程序员的骨干,可以用来创建类似于类的东西。

使用Apply方法

最后,每个函数都有一个名为" apply"的方法(是的,函数是Javascript中的对象)。应用可以确定其值,也可以传递参数数组。这是一个无用的示例。

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

函数调用

函数只是对象的一种。

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

当调用这些方法时,这些方法的第一个参数指定对象,如果该函数为null或者undefined,则在执行函数时将由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相同(窗口)

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

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

因此," 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"

对我们的函数引用" baz"的调用未提供任何调用上下文,因此它实际上与" baz.call(undefined)"相同,因此" this"最终引用" window"。如果我们希望baz知道它属于obj,我们需要以某种方式在调用baz时提供该信息,这是call或者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"的特定值的" pre-bound"周围传递函数的技术有效的原因:

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