众所周知,javascript的一大弊端便是全局作用域的使用。在想要计数的时候,定义一个全局变量计数显然是不可取的。利用面向对象编程的思想,我们想要实现一个计数器可能需要定义一个计数器类。但是想要利用javascript实现一个计数器,我们可以利用哪些方法呢?
首先,新手最先想到的可能是这样的:
function counter(){
var cnt=0;
return ++cnt;
}
但是,执行后是
counter();//结果是1
counter();//结果是1
原因在于每次函数执行时候都把cnt重新定义并且初始化成0了。那么会想,怎么只让它初始化一次呢?自然我们想到的就是让cnt的定义和初始化只执行一次。于是有:
function counter(){
var cnt=0;
this.increase = function(){
cnt++;
return cnt;
}
return this;
}
var myCounter= counter();
myCounter.increase();//结果是1
myCounter.increase();//结果是2
到这里,计数器就可以使用了。但是为什么counter函数要返回this呢?
原因在于函数未显式指定返回值时候它默认会返回undefined。
更重要的问题是:此时this指向的是谁呢?是counter对象吗?我们接下去进行测试:
console.log(global.increase);//结果是[Function]
以上测试说明this指向的是global。为什么this不指向自身而指向counter呢?
这就涉及this作用域的问题,this的指向取决于函数调用的模式。接下来介绍常用的函数调用模式
(1)函数调用模式
当一个函数并非一个对象的属性的时候,他直接以functionName()的方式被调用时候,它的this指向的是global全局对象。上述counter函数的调用就是这种模式。
var counter = {
cnt:0,
increase:function(){
this.cnt ++;
return this.cnt;
},
increaseTo:function(i){
var helper = function(i){
this.cnt=i;
}
helper(i);//函数调用模式
return this.cnt;
}
}
counter.increase();//结果是1
counter.increase();//结果是2
counter.increaseTo(9);//结果是2
console.log(global.cnt);//结果是9
上述helper的调用就是函数调用模式,由于helper内this指向的是全局对象,所以导致global.cnt值为9。此bug的改进可以将increaseTo函数改为如下形式
var counter = {
cnt:0,
increase:function(){
this.cnt++;
return this.cnt;
},
increaseTo:function(i){
var that = this;
var helper = function(i){
that.cnt=i;
}
helper(i);//函数调用模式
return this.cnt;
}
}
counter.increase();//结果是1
counter.increase();//结果是2
counter.increaseTo(9);//结果是9
console.log(global.cnt);//结果是undefined
(2)方法调用模式
在面向对象编程中,我们知道对象具有各种属性。当一个函数被保存为对象的一个属性的时候,我们称它为一个方法。当一个方法被对象调用的时候,this指向了这个对象。
var counter = {
cnt:0,
increase:function(){
this.cnt++;
return this.cnt;
}
}
counter.increase();//结果为1(方法调用模式)
counter.increase();//结果为2(方法调用模式)
counter是一个计时器对象,increase是计时器的一个方法。increase方法可以通过this去访问对象,因为此时this指向的就是调用它的counter对象。
(3)构造器调用模式
如果在一个函数前面带上new来调用,那么将创建一个包含该函数上下文的新对象。并且this将指向这个对象。
function Counter(){
this.cnt=0;
this.increase = function(){
this.cnt++;
return this.cnt;
}
}
var myCounter= new Counter();//构造器调用模式
myCounter.increase();//结果是1
myCounter.increase();//结果是2
console.log(global.cnt);//结果是undefined
结合new前缀调用的函数被称为构造器函数。按照约定,他们以首字母大写命名。
(4)apply调用模式
格式Function.apply(obj,args);
通俗的讲,函数的功能就是把Function的代码放到obj内去执行,同时将args参数传递给Function。apply调用模式可以直接将Function函数的this指向obj。
function increase(cnt){
this.increase=function(){
cnt++;
return cnt;
}
}
function Counter(){
var cnt=0;
increase.apply(this,[cnt]);
}
var myCounter= new Counter();
myCounter.increase();//结果是1
myCounter.increase();//结果是2