这意味着,根据函数如何被调用,同一个函数可以拥有不同的函数上下文。刚开始可能觉得奇怪,但这相当有用。
在默认情况下,函数调用的上下文(this)是对象,其属性包含可以调用该函数的引用。参看如下代码:
- var ride = {
- make: "Yamaha",
- model: "V-Star Silverado 1100",
- year: 2005,
- purchased: new Date(2005,3,12),
- owner: {name: "Spike Spiegel", occupation: "bounty hunter"},
- whatAmI: function(){
- return this.year+" "+this.make+" "+this.model;
- }
- };
那么上面代码的层次应该如下图所示:
上图的模型清晰地显示函数不是Object的一部分,只是被名为whatAmI的Object属性所引用
当函数通过属性引用而被调用时,如下所示
view source
print?1var bike = ride.whatAmI();
函数上下文(this引用)被设置为ride所指向的对象实例。作为结果,变量bike被设置为字符串2005 Yamaha V-Star Silverado 1100,因为该函数经由this进行调用并获得对象的属性。
对于顶层函数来说也是如此。请记住:顶层函数是window的属性,因此在作为顶层函数而被调用时,其函数上下文是window对象。
虽然以上是常见的隐式行为,但JavaScript提供办法来显式地控制什么被用作函数上下文。通过Function方法call()或apply()来调用函数,可以把函数上下文设置为所想的任何东西。
对,作为一等对象,函数甚至拥有定义为Function构造器的几个方法。
用call()方法来调用函数(这个函数作为第一个参数),指定对象作为函数上下文,而其余参数成为被调用函数的参数,也就是说,call()的第二个参数成为被调用函数的第一个实参,以此类推。apply()方法的工作方式与call()相似,除了第二个参数要求是对象数组以外(这些对象将成为被调用函数的实参)。
弄糊涂了?该是介绍更为综合的示例的时间了。考虑下面的代码:
在这个示例中,我们定义3个简单的对象,每个对象都带有handle属性,使得易于标识对象(给定一个引用)①。我们也经window实例添加handle属性,因此window实例也易于标识。
然后定义顶层函数,无论什么对象作为其函数上下文,都返回handle属性值②。把同一函数实例指派给o1对象的名为identifyMe的属性③。我们可以说,这会在o1对象上创建名为identifyMe的方法,而注意到这一点很重要:这个函数独立于o1对象而被声明。
最后,我们弹出4个警告消息框,每个警告消息框都使用不同的机制来调用相同的函数实例。在用浏览器加载这个页面时,4个警告框的序列如下图所示:
这个警告消息框说明了以下几点:
在函数作为顶层函数而被直接调用时,函数上下文是window实例④。
在函数作为对象(在这种情况下是o1)的属性被调用时,那个对象成为函数调用的函数上下文⑤。我们可以说,函数就像那个对象的方法而(在面向对象语言里面类似)进行操作。请不要对这个类比感到厌烦。如果你不小心谨慎,就会误入歧途,就像这个实例其作部分所显示的那样。
采用Function的call()方法导致函数上下文被设置为作为第一个参数而传递给call()的任何对象。在这种情况下是o2⑥。在这个示例中,函数表现得像是o2的方法,即使这个函数与o2没有任何关联(连o2的属性都不是)。
就像对待call()那样,利用Function的apply()方法来把函数上下文设置为作为第一个参数而传入的任何对象⑦。在传递更多的参数给函数时(为了简洁,在这个示例中没有那样做),这两个方法之间的差别才变得显著起来。
这个示例页面清楚地演示:函数上下文以每个调用为基础而被决定,并且一个函数能以任何对象作为其上下文而被调用。因此说“函数是对象的方法”肯定是不正确的。下面的陈述则准确得多:
在对象o充当函数F的调用的函数上下文时,函数F充当对象o的方法。
为了更进一步说明这个概念,考虑给示例添加下面的语句所带来的效果:
alert(o1.identifyMe.call(o3));
即使我们作为o1属性而引用函数,这个调用的函数上下文还是o3。这就进一步强调:不是函数被如何声明,而是函数被如何调用决定了函数的上下文。
在使用采取了回调函数的jQuery命令和函数时,将证明这是最重要的概念。