这个关键字在一个函数中如何工作?

我刚刚在JavaScript中遇到了一个有趣的情况。 我有一个类的方法,使用对象文字符号定义几个对象。 在这些对象内部,正在使用this指针。 从程序的行为中,我推断出this指针指的是调用方法的类,而不是由文字创build的对象。

这似乎是任意的,虽然这是我期望它的工作方式。 这是定义的行为? 它是跨浏览器安全的吗? 有没有任何推理说明为什么超出“规范说明”这样的方式(例如,这是否是一些更广泛的devise决策/理念的结果)? 削减代码示例:

 // 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 } 

从我的另一个post拆了,这里比你想知道的更多。

在我开始之前,要记住关于Javascript的最重要的事情,并在没有意义的时候重复自己。 Javascript没有类(ES6 class是语法糖 )。 如果某件事看起来像一个class级,这是一个聪明的把戏。 Javascript有对象function 。 (这不是100%准确的,函数只是对象,但有时可以把它们看作是独立的东西)

这个variables被附加到函数中。 无论何时你调用一个函数,都会给出一个特定的值,这取决于你如何调用函数。 这通常被称为调用模式。

有四种方法可以在javascript中调用函数。 你可以调用函数作为一个方法 ,作为一个函数 ,作为构造函数 ,并与应用程序

作为一种方法

方法是附加到对象的函数

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

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

作为一个function

如果你有一个独立的函数, 这个variables将被绑定到“全局”对象,几乎总是浏览器上下文中的窗口对象。

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

这可能是什么绊倒你 ,但不要感觉不好。 许多人认为这是一个糟糕的devise决定。 由于callback是作为函数调用的,而不是作为方法调用的,所以这就是为什么你看到什么似乎是不一致的行为。

很多人通过做这样的事情来解决问题

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

你定义一个指向这个的variables。 closures(一个主题都是它自己的)会保持这个状态 ,所以如果你把bar当作callback,它仍然有一个引用。

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

作为构造函数

你也可以调用一个函数作为构造函数。 基于你使用的命名约定(TestObject),这也可能是你在做什么,是什么让你绊倒

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

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

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

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

通过应用方法

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

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

函数调用

函数只是一种对象。

所有的函数对象都有调用和应用方法来执行它们被调用的函数对象。

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

因此,调用一个函数…

 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其他参数作为parameter passing给函数调用,而应用的单个附加参数可以将函数调用的参数指定为类似数组的对象。

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

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

当一个函数被执行时,它会创build一个新的作用域并且引用任何封闭的作用域。 当在上面的例子中创build匿名函数时,它会引用它创build的作用域,这是bind的作用域。 这被称为“closures”。

 [global scope (window)] - whereAmI, foo, obj, baz | [bind scope] - func, context | [anonymous scope] 

当你尝试访问一个variables的时候,这个“范围链”就会被寻找一个具有给定名称的variables – 如果当前范围不包含variables,则查看链中的下一个范围,依此类推,直到达到全球范围。 当匿名函数返回并且bind完成执行时,匿名函数仍然有一个bind的范围的引用,所以bind的范围不会“消失”。

鉴于以上所有,你现在应该能够理解范围如何工作在下面的例子,以及为什么传递一个函数周围的“预先约束”与这个特定的值时,它将被称为工程:

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

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

是。 是的。

有没有任何推理为什么它是这样的…

这个的含义很简单,可以推导出:

  1. 如果this是在构造函数中使用的,并且该函数是用new关键字调用的,则this指的是将要创build的对象。 即使在公共的方式下, this仍然意味着对象。
  2. 如果在其他地方使用它,包括嵌套的保护函数,它指的是全局作用域(在浏览器的情况下是窗口对象)。

第二种情况显然是一个devise缺陷,但通过使用闭包来解决这个问题是相当容易的。

在这种情况下,这个内部被绑定到全局对象而不是外部函数的thisvariables。 这是语言devise的方式。

请参阅Douglas Crockford的“JavaScript:好的部分”一个很好的解释。

我find了一个关于ECMAScript的好教程

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

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

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

这个特性非常重要,因为与variables相反,这个值从不参与标识符parsing过程。 也就是说,在代码中访问它时,它的值是直接从执行上下文中获取的,没有任何范围链查找。 进入上下文时,这个值只能确定一次。

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

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

参考Javascript-the-core和Chapter-3这个