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

IT技术 javascript language-features language-design
2021-02-01 03:33:43

我刚刚在 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
}
6个回答

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

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

这个变量被附连到功能。每当您调用一个函数时,它都会被赋予一个特定的值,具体取决于您调用该函数的方式。这通常称为调用模式。

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

作为一种方法

方法是附加到对象的函数

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

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

作为函数

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

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

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

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

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

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

注意:在use strict模式中,如果用作函数,this则不会绑定到全局。(它是undefined)。

作为构造函数

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

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

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

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

有些人认为,constructor/new 关键字是扔给 Java/传统 OOP 程序员的一种方法,用于创建类似于类的东西。

使用 Apply 方法

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

function foo(a,b){
    alert(a);
    alert(b);
    alert(this);
}
var args = ['ah','be'];
foo.apply('omg',args);
@艾伦风暴。在“作为构造函数”的情况下this.confusing = 'hell yeah';var confusing = 'hell yeah';? 所以两者都允许myObject.confusing如果不仅如此,您还可以用来this为内部工作创建属性和其他变量,那就太好了。
2021-03-15 03:33:43
函数声明,例如。function myfunction () {},是“作为方法”的特例,其中“this”是全局范围(窗口)。
2021-03-18 03:33:43
@richard:严格模式除外,this与范围无关。你的意思是全局对象
2021-03-31 03:33:43
但话又说回来,我猜工作的东西可以在函数之外完成,并将值传递给构造函数:function Foo(thought){ this.confusing = thought; }然后var myObject = new Foo("hell yeah");
2021-04-01 03:33:43
注意:在严格模式下thisundefined用于函数调用。
2021-04-04 03:33:43

函数调用

函数只是一种对象。

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

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

因此,调用函数...

whereAmI = "window";

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

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

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

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

因此,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)or foo.apply(obj)

然而,作为对象属性持有的函数并没有“绑定”到这些对象。正如你在obj上面的定义中看到的,由于函数只是一种对象,它们可以被引用(因此可以通过引用传递给函数调用或从函数调用引用返回)。传递对 Function 的引用时,不会携带有关它何处传递的其他信息,这就是发生以下情况的原因:

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

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

范围链

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

当一个函数被执行时,它会创建一个新的作用域并引用任何封闭的作用域。当在上面的例子中创建匿名函数时,它有一个对创建它的作用域的引用,也就是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。
2021-03-27 03:33:43

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

是的。是的。

有什么理由可以解释为什么它是这样的......

的含义this很容易推断:

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

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

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

请参阅 Douglas Crockford 的“JavaScript: The Good Parts”以获得很好的解释。

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

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

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

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

这个特性非常重要,因为与变量相反,这个值从不参与标识符解析过程。即在代码中访问 this 时,它的值直接从执行上下文中获取,无需任何作用域链查找。进入上下文时, this 的值仅确定一次。

在全局上下文中, this 值是全局对象本身(也就是说,这里的 this 值等于变量对象)

在函数上下文的情况下,每个函数调用中的这个值可能不同

参考Javascript-the-coreChapter-3-this

在全局上下文中,this 值是全局对象本身(也就是说,这里的 this 值等于变量对象) ”。所述全局对象是全局执行上下文的一部分,由于是(ES4)“可变对象”和ES5环境的记录。但是它们与全局对象是不同的实体(例如,环境记录不能被直接引用,规范禁止,但全局对象可以)。
2021-03-27 03:33:43