JavaScript中的函数执行上下文
- 函数上下文就是函数中的this
- 函数中的this指向和函数的定义位置、执行位置无关
- 函数中this的指向只取决于函数的调用方式
所有的JavaScript函数执行时都是有上下文的。那么,什么是函数的执行上下文呢?简单说来就是函数中this所指向的对象。我们来看下面这段代码:
function f1(){
console.log(this.name);
}
这段代码最终会打印出什么来呢?也就是这里的this指向谁呢?在定义的时候我们并不知道,函数最终的调用方式决定this的指向。一般说来JavaScript里有4中调用函数的方式:
- 直接调用
- 作为对象的方法调用
- 通过函数对象的apply或者call方法调用
- 通过关键词new调用
以直接调用方式执行的函数上下文指向全局对象。一般情况下全局对象中并不存在一个name的属性,所以this.name的值是undefined。
function f1(){
console.log(this.name);
}
f1();
//console should print "undefined"
作为对象方法调用的函数上下文指向对象本身。例如,我们在这里我们定义了一个对象o,o有一个属性name,还有一个方法f1,这个方法f1指向前面定义的全局函数f1。那么这里的this将指向对象o,this.name的值就是o.name的值,所以打印出来的是o.name的值。
function f1(){
console.log(this.name);
}
var o ={
name:"qwan",
f1:f1
};
o.f1();
//console will print "qwan"
通过函数对象的apply或者call方法调用函数可以用apply的第一个参数显式指定上下文。在这里我们定义了两个对象o和p,都只包含一个name属性。我们可以看到,f1.apply(o)调用时this指向o,this.name的值就是o.name,参数换成p最后打印的就是p.name的值。
function f1(){
console.log(this.name);
}
var o ={
name:"qwan"
};
var p ={
name:"bob"
};
f1.apply(o);
//console will print "qwan"
f1.apply(p);
//console will print "bob"
函数的最后一种调用方式是通过关键词new调用,通过new调用的函数就是我们通常说的构造函数,在调用之前会自动创建一个空对象,这个空对象就是函数的上下文,调用完后返回这个对象。
function f1(name){
this.name = name;
console.log(this.name);
}
var o = new f1("qwan");//will print "qwan" here
console.log(o.name);// also print "qwan" here
var p = new f2("bob");//will print "bob" here
console.log(p.name);//also print "bob" here
在这我们能清楚的看到函数不同的调用方式对函数上下文的影响。那么函数定义的位置会不会影响函数执行时的上下文呢?答案是否定的,JavaScript中函数的上下文只取决于函数的调用方式,与函数定义的位置无关。上面例子中使用的是全局域中定义的函数,我们还可以将函数定义在对象中。请看下面的例子:
var o ={
name:"qwan",
f:function(){console.log(this.name);}//函数定义成对象的方法
};
var p ={
name:"bob"
};
o.f.apply(o);
//console will print "qwan"
o.f.apply(p);
//console will print "bob"
至此我们基本搞清楚了JavaScript中函数上下文的指向问题。总结一下,直接调用函数时其上下文是全局对象;作为对象方法调用时其上下文是对象本身;使用函数对象的apply或者call方法调用函数其上下文是apply或者call方法的第一个参数;通过new关键词调用的函数上下文是自动创建的一个空对象,调用完之后返回上下文对象。
接下来,我们来研究一个实例,代码如下:
function invoke(f){
f();
}
var o ={
name:"qwan",
f:function(){console.log(this.name);}//函数定义成对象的方法
};
invoke(o.f);
在这个例子中最终打印出来的this.name的值会是什么呢? 应该是”undefined”,你答对了吗?因为在这里f是直接调用的,所以this指向全局对象。但是实际使用过程中,程序员的意图往往是希望f调用的时候使用的上下文是o,这又如何实现呢?请看下面的代码:
function invoke(f){
f();
}
function hitch(o,f){
return function(){
return f.apply(o);
};
}
var o ={
name:"qwan",
f:function(){console.log(this.name);}//函数定义成对象的方法
};
invoke(hitch(o,o.f));
hitch函数返回了一个函数,这个函数体里显式设置了函数o.f的执行上下文为o,所以最后打印的值是o.name。