" this"关键字如何在函数中工作?
我刚刚遇到了一个有趣的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 }
解决方案
Is this defined behavior? Is it cross-browser safe?
是的。是的。
Is there any reasoning underlying why it is the way it is...
可以很容易地推断出" this"的含义:
- 如果在构造函数中使用了" this",并且使用" new"关键字调用了该函数,则" this"是指将要创建的对象。即使在公共方法中," this"也将继续表示对象。
- 如果" this"在其他任何地方都使用,包括嵌套的受保护函数,则它指的是全局范围(在浏览器中是窗口对象)。
第二种情况显然是设计缺陷,但是使用闭包可以很容易地解决它。
在这种情况下,内部的" this"绑定到全局对象,而不是外部函数的" this"变量。
这是语言的设计方式。
有关详细说明,请参见道格拉斯·克罗克福德(Douglas Crockford)撰写的" JavaScript:好的部分"。
在我的另一个职位上被吞噬了,这比我们想了解的更多。
在开始之前,请记住关于Javascript的最重要的内容,并在没有意义的情况下对自己重复。 Javascript没有类(ES6"类"是语法糖)。如果某个东西看起来像一堂课,那是个聪明的把戏。 Javascript具有对象和功能。 (这不是100%准确的,函数只是对象,但有时将它们视为独立的东西会有所帮助)
此变量添加到函数。每当我们调用一个函数时,都会根据我们调用该函数的方式为其赋予一定的值。这通常称为调用模式。
有四种方法可以调用javascript中的函数。我们可以将函数作为方法,函数,构造函数以及apply调用。
作为一种方法
方法是添加到对象的函数
var foo = {}; foo.someMethod = function(){ alert(this); }
当作为方法调用时,它将绑定到功能/方法所属的对象。在此示例中,这将绑定到foo。
作为功能
如果我们具有独立功能,则此变量将绑定到"全局"对象,几乎总是在浏览器上下文中的window对象。
var foo = function(){ alert(this); } foo();
这可能是使我们绊倒的原因,但并不难过。许多人认为这是一个错误的设计决定。由于回调是作为函数而不是方法调用的,因此这就是为什么我们看到的行为似乎不一致。
很多人通过做类似的事情来解决这个问题
var foo = {}; foo.someMethod = function (){ var that=this; function bar(){ alert(that); } }
我们定义一个指向该变量。闭包(它本身就是一个话题)可以解决这个问题,因此,如果我们将bar作为回调调用,它仍然具有引用。
注意:在"使用严格"模式下(如果用作功能),"此"未绑定到全局。 (它是未定义的)。
作为建设者
我们也可以将函数作为构造函数调用。根据我们正在使用的命名约定(TestObject),这也可能是我们正在做的事情,并且是绊脚石。
我们可以使用new关键字将函数作为构造函数调用。
function Foo(){ this.confusing = 'hell yeah'; } var myObject = new Foo();
当作为构造函数调用时,将创建一个新的Object,并将其绑定到该对象。同样,如果我们具有内部函数并将它们用作回调,则将它们作为函数调用,并且这将绑定到全局对象。使用那个=这个技巧/模式的变量。
有人认为,构造器/新关键字是Java /传统OOP程序员的骨干,可以用来创建类似于类的东西。
使用Apply方法
最后,每个函数都有一个名为" apply"的方法(是的,函数是Javascript中的对象)。应用可以确定其值,也可以传递参数数组。这是一个无用的示例。
function foo(a,b){ alert(a); alert(b); alert(this); } var args = ['ah','be']; foo.apply('omg',args);
函数调用
函数只是对象的一种。
所有Function对象都有调用和Apply方法,这些方法执行被调用的Function对象。
当调用这些方法时,这些方法的第一个参数指定对象,如果该函数为null或者undefined,则在执行函数时将由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相同(窗口)
。
>>> foo() "this is window with 0 arguments" >>> foo.call() "this is window with 0 arguments"
call的其他参数作为函数调用的参数传递,而apply的单个添加参数可以将函数调用的参数指定为类似Array的对象。
因此," 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"的定义中所见,由于函数只是对象的一种,因此可以对其进行引用(因此可以通过引用传递给Function调用,也可以通过引用从Function调用返回)。传递对函数的引用时,函数不会附带有关传递它的位置的其他信息,这就是为什么发生以下情况的原因:
>>> 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); }; }
执行Function时,它将创建一个新的作用域,并引用任何封闭的作用域。在上面的示例中创建匿名函数时,它会引用创建它的作用域,即" bind"的作用域。这称为"关闭"。
[global scope (window)] - whereAmI, foo, obj, baz | [bind scope] - func, context | [anonymous scope]
当我们尝试访问变量时,如果当前作用域中不包含该变量,则会遍历该"作用域链"以查找具有给定名称的变量,然后查看链中的下一个作用域,依此类推,直到到达全球范围。当返回匿名函数并且bind
完成执行时,匿名函数仍然引用bind
的作用域,因此bind
的作用域不会"消失"。
鉴于以上所有内容,我们现在应该能够理解以下示例中的作用域是如何工作的,以及为什么在带有" this"的特定值的" pre-bound"周围传递函数的技术有效的原因:
>>> baz = bind(obj.foo, obj); >>> baz(1, 2); "this is obj with 2 arguments"