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

时间:2008-09-25 15:25:51

标签: javascript language-features language-design

我刚刚在JavaScript中遇到了一个有趣的情况。我有一个类,其方法使用object-literal表示法定义多个对象。在这些对象中,正在使用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 个答案:

答案 0 :(得分:555)

从我的另一篇文章中删除,这里比你想知道这个更多。

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

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

有四种方法可以在javascript中调用函数。您可以将该函数作为方法,作为函数,作为构造函数调用,并使用 apply 。< / p>

作为方法

方法是附加到对象

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

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

作为一种功能

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

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

这可能会让你沮丧,但不要感觉不好。很多人认为这是一个糟糕的设计决定。由于回调是作为函数而不是作为方法调用的,因此您可能会看到看似不一致的行为。

很多人通过这样做来解决这个问题,嗯,这个

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

您定义一个 的变量,指向 this 。 Closure(它自己的主题)保持那个,所以如果你把bar称为回调,它仍然有一个引用。

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

作为构造函数

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

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

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

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

有些人认为构造函数/ new关键字是Java /传统OOP程序员抛出的一个骨骼,可以创建类似于类的东西。

使用“应用方法”

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

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

答案 1 :(得分:34)

函数调用

函数只是一种Object。

所有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)或{相同{1}}。

foo.apply(window)

>>> foo() "this is window with 0 arguments" >>> foo.call() "this is window with 0 arguments" 的附加参数作为函数调用的参数传递,而call的一个附加参数可以将函数调用的参数指定为类似Array的对象。

因此,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)的定义中所看到的,由于函数只是一种Object类型,因此可以引用它们(因此可以通过引用传递给函数调用或通过函数调用的引用返回)。当传递对函数的引用时,没有关于它从传递的位置的附加信息随之携带,这就是为什么会发生以下情况:

obj

对我们的函数参考>>> baz = obj.foo; >>> baz(); "this is window with 0 arguments" 的调用不会为调用提供任何上下文,因此它与baz实际上相同,因此baz.call(undefined)最终引用{{1} }}。如果我们希望this知道它属于window,我们需要在调用baz时以某种方式提供该信息,这是obj或{的第一个参数的位置{1}}和闭包发挥作用。

范围链

baz

执行Function时,它会创建一个新范围并引用任何封闭范围。在上面的示例中创建匿名函数时,它会引用它所创建的作用域,即call的作用域。这被称为“封闭”。

apply

当您尝试访问变量时,将使用“范围链”查找具有给定名称的变量 - 如果当前范围不包含变量,则查看链中的下一个范围,依此类推直到达到全球范围。当返回匿名函数并且function bind(func, context) { return function() { func.apply(context, arguments); }; } 完成执行时,匿名函数仍然引用bind的范围,因此[global scope (window)] - whereAmI, foo, obj, baz | [bind scope] - func, context | [anonymous scope] 的范围不会“消失”。 / p>

鉴于上述所有内容,您现在应该能够理解范围在以下示例中的工作方式,以及为什么使用特定值bind将“预绑定”函数传递给它的技术它被称为作品:

bind

答案 2 :(得分:9)

  

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

是。是的。

  

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

this的含义很容易推断:

  1. 如果在构造函数内部使用this,并且使用new关键字调用该函数,则this引用将要创建的对象。 this即使在公共方法中也会继续表示对象。
  2. 如果在其他任何地方使用this,包括嵌套的 protected 函数,则它引用全局范围(在浏览器的情况下是窗口对象)。
  3. 第二种情况显然是一个设计缺陷,但通过使用闭包来解决它很容易。

答案 3 :(得分:4)

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

请参阅Douglas Crockford的“JavaScript:The Good Parts”作为一个很好的解释。

答案 4 :(得分:4)

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

  

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

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

  

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

此功能非常重要,因为与变量相反,此值从不参与标识符解析过程。即在代码中访问它时,它的值直接来自执行上下文,没有任何作用域链查找。输入上下文时,此值仅确定一次。

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

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

参考Javascript-the-coreChapter-3-this

答案 5 :(得分: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 
      },
    })
  },
}