252

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

6 回答 6

564

从我的另一篇文章中蚕食,这里比你想知道的还要

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

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

在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);
    }
}

您定义一个指向this变量。闭包(它自己的一个主题)保留了它,所以如果你调用 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);
于 2008-09-25T15:52:39.677 回答
35

函数调用

函数只是一种对象。

所有 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"

附加参数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)or foo.apply(obj)

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

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

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

范围链

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

当一个函数被执行时,它会创建一个新的作用域并引用任何封闭的作用域。在上面的例子中创建匿名函数时,它有一个对创建它的作用域的引用,也就是bind's 作用域。这被称为“关闭”。

[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"
于 2008-09-25T22:24:16.897 回答
9

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

是的。是的。

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

的含义this很容易推断:

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

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

于 2008-09-25T15:38:25.577 回答
4

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

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

于 2008-09-25T15:43:41.120 回答
4

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

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

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

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

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

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

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

参考Javascript-the-coreChapter-3-this

于 2014-03-13T12:40:55.007 回答
0

这里的所有答案都非常有帮助,但我仍然很难弄清楚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 - 解构对象 - 'this' 设置为全局或未定义,而不是 object它实际上与对象解构无关,而是如何调用 c(),但在这里不容易看穿它。

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

于 2020-09-29T06:05:40.023 回答