【问题标题】:How does "this" keyword work within a function?“this”关键字在函数中如何工作?
【发布时间】:2010-09-13 03:14:30
【问题描述】:

我刚刚在 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
}

【问题讨论】:

标签: javascript language-features language-design


【解决方案1】:

从我的另一篇文章中被蚕食,这里比你想知道的更多这个

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

this 变量附加到函数。每当你调用一个函数时,this 都会被赋予一个特定的值,这取决于你调用函数的方式。这通常称为调用模式。

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

作为一种方法

方法是附加到对象的函数

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

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

作为一个函数

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

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

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

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

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

你定义了一个变量that,它指向this。闭包(它自己的一个主题)使 that 存在,所以如果你将 bar 作为回调调用,它仍然有一个引用。

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

作为构造函数

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

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

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

当作为构造函数调用时,将创建一个新对象,并且 this 将绑定到该对象。同样,如果您有内部函数并且它们被用作回调,那么您将作为函数调用它们,并且 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);

【讨论】:

  • 注意:在 strict mode 中,this 将是 undefined 用于函数调用。
  • 一个函数声明,例如。 function myfunction () {},是“作为方法”的一种特殊情况,其中“this”是全局范围(窗口)。
  • @richard:除了在严格模式下,this 与作用域无关。你的意思是全局object
  • @alan-storm 。在“作为构造函数”的情况下,this.confusing = 'hell yeah'; 是否与 var confusing = 'hell yeah'; 相同?所以两者都会允许myObject.confusing?如果不只是让您可以使用this 为内部工作创建属性和其他变量,那就太好了。
  • 但是我又猜想可以在函数之外完成工作并将值传递给构造函数:function Foo(thought){ this.confusing = thought; } 然后var myObject = new Foo("hell yeah");
【解决方案2】:

函数调用

函数只是一种对象。

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

当被调用时,这些方法的第一个参数指定在函数执行期间将被 this 关键字引用的对象 - 如果它是 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 的定义中看到的那样,由于函数只是一种对象,它们可以被引用(因此可以通过引用传递给函数调用或通过引用从函数调用返回)。当一个函数的引用被传递时,它不会携带关于它从哪里传递来自的额外信息,这就是为什么会发生以下情况:

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

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

作用域链

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。
【解决方案3】:

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

是的。是的。

是否有任何理由 就是这样……

this 的含义很容易推断:

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

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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

      我找到了一个很好的关于 ECMAScript this 的教程

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

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

      a 这个值是执行上下文的一个属性,但不是 变量对象的属性。

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

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

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

      参考Javascript-the-coreChapter-3-this

      【讨论】:

      • "在全局上下文中,一个this值就是全局对象本身(也就是说,这里的这个值等于变量对象)"。 全局对象 是全局执行上下文的一部分,(es4)“变量对象”和 ES5 环境记录也是如此。但它们是全局对象的不同实体(例如,环境记录不能直接引用,规范禁止,但全局对象可以)。
      【解决方案6】:

      这里的所有答案都非常有帮助,但我仍然很难弄清楚 this 在我的案例中指向什么,这涉及到对象解构。所以我想使用我的代码的简化版本再添加一个答案,

      let testThis = {
        x: 12,
        y: 20,
        add({ a, b, c }) {
          let d = a + b + c()
          console.log(d)
        },
        test() {
          //the result is NaN
          this.add({
            a: this.x,
            b: this.y,
            c: () => {
              //this here is testThis, NOT the object literal here
              return this.a + this.b 
            },
          })
        },
        test2() {
          //64 as expected
          this.add({
            a: this.x,
            b: this.y,
            c: () => {
              return this.x + this.y
            },
          })
        },
        test3() {
          //NaN
          this.add({
            a: this.x,
            b: this.y,
            c: function () {
              //this here is the global object
              return this.x + this.y 
            },
          })
        },
      }
      

      正如这里解释的Javascript - destructuring object - 'this' set to global or undefined, instead of object它实际上与对象解构无关,而是如何调用c(),但在这里并不容易看透。

      MDN 说“箭头函数表达式最适合非方法函数”,但箭头函数在这里有效。

      【讨论】:

        猜你喜欢
        • 2020-07-17
        相关资源
        最近更新 更多