函数是JavaScript
世界里的第一公民,换句话来说,就是我们如果可以精通
JavaScript
函数的使用,那么对
JavaScript
的运用可以更游刃有余了。熟悉
JavaScript
的人应该都知道,同样的函数,以不同的方式调用的话,受影响最大的应该是
this
。下面我们来说说
JavaScript
函数的各种调用模式,希望对大家
学习javascript有所帮助。
一、普通函数的调用模式
所谓普通函数的调用模式,也是JavaScript
函数的最简单的一种调用模式,直接就是函数名后接一个
()
实现调用,看下面代码:
function
func(){
console.log(
this === window); //true
}
func();
上面代码,我们用function
关键字声明了一个
func
函数,并且在函数体内打印
this===window
,然后我们直接调用函数
func
,我们可以看到控制台是直接打印出
true
,也就是说, 函数的这种普通调用模式,函数体内的
this
是指向全局环境
window
的 。不清楚这点的同学,可以能会遇到这样的一个
bug
:
var color = 'gg';
var obj = {
color : 'red',
show :
function(){
function
func1(){
console.log(
this.color); //gg
}
func1();
}
}
obj.show();
我们在全局环境下声明了一个变量 color
和一个对象
obj
,在对象
obj
里面我们还声明了一个
color
属性 为
'red'
,一个
show
方法。而且在
show
方法里面呢,我们还声明了一个函数
func1
并且调用了
func1
,
func1
的作用是打印
this.color
。最后我们运行代码
obj.show();
调用
obj
里面的
show
方法。不清楚函数的普通调用模式的特点的同学可能会认为此时在控制台答应出来的会是
'red'
。实际上此时在控制台答应出来的应该是
gg
。因为函数
func1
的调用模式是 普通函数调用模式(即使它是在
obj
的
show
方法里面调用的),所以此时函数体内的
this
是指向 全局环境
window
的,所以就打印了全局环境下的变量
color
。
可能有些同学会问:如果我们希望 func1
函数打印出来的是
'red'
呢,应该怎么改?其实很简单,因为
obj.color
才是
'red'
,所以我们只需要把
指向
obj
的
this
引入到函数
func1
里面就行了:
var color = 'gg';
var obj = {
color : 'red',
show :
function(){
var that =
this;
function
func1(){
console.log(that.color); //red
}
func1();
}
}
obj.show();
在上面的代码中,因为 show
里面的
this
指向
obj
的,所以我们在
show
里面声明一个变量
that = this;
用来把指向
obj
的
this
引入到
func1
中,然后再把
func1
函数体内的
this.color
改为
that.color ,
此时在控制台打印出来的就是我们想要的
'red'
了。
可能现在又有同学会问:为什么 show
里面的
this
是指向
obj
的呢?这就是我们要说的
JavaScript
函数的第二种调用模式:方法调用模式
二、方法调用模式
方法调用模式,简单来说就是把一个 JavaScript
函数作为一个对象的方法来调用,当一个函数被保存为一个对象的属性是,我们就把它称为方法,例如上文的
obj
对象里的
show
, 当一个方法被调用时,函数体里面的
this
就会绑定到这个对象 ,例如上文的
show
里面的
this
。方法调用模式也很容易辨别:
obj.show()
,对象名
.
属性名
()
;代码的话可以参考上文的
obj
代码 ,博主就不多写了。记住: 方法的调用是可以在函数体内通过
this
访问自己所属的那个对象的。
三、构造器调用模式
博主认为构造器调用模式是相对于其他模式来说较为复杂点的调用模式了。通过关键字 new
可以把一个函数作为构造器来调用。关键字
new
可以改变函数的返回值:
function
func2(
name){
this.name = name;
}
name; //undefined
//
普通函数调用模式
var foo = func2('afei');
foo; //undefined
name; //afei
//
构造器调用模式
var bar = new func2('lizefei');
bar.__proto__ === func2.prototype; //true
bar; //{name:'lizefei'}
bar.
name; //'lizefei'
在上示代码中我们声明了一个函数 func2
,分别用两种不同的调用模式去调用它。因为函数
func2
并没有显式返回值,所以作为普通函数去调用时,它什么也没有返回,所以
foo
的值是
undefined
。因为普通调用模式的
this
是指向 全局环境
window
的,所以
func2('afei');
后,全局环境下就多了一个
name
变量且等于
'afei'
。
func2
作为构造器调用时,我们可以看到,它返回的是一个对象,因为关键字
new
使得函数在调用是发生了如下的特殊变化:
1.
创建了一个新对象,而且这个新对象是链接到
func2
的
prototype
属性的
2.
把函数里的
this
指向了这个新对象
3.
如果没有显式的返回值,新对象作为构造器
func2
的返回值进行返回(所以
bar
是
{name:'lizefei'}
)
这样子我们就可以看出构造器的作用:通过函数的调用来初始化新创建出来的对象。在JavaScript
的面向对象编程里面,这个可是相当重要的。
因为在函数的声明上,在未来作为构造器调用的函数和普通函数的声明没什么区别,所以导致后来的开发者很容易因为调用模式的错误导致程序出问题。所以开发者们都默契地约定,
用来做构造器调用的函数的函数名的第一个字符应该大写
,例如:Person
,
People
。这样子后来的开发者一看到函数名就知道要用构造器调用模式调用此函数了。
四、使用apply()和call()方法调用
这种调用的模式是为了更灵活控制函数运行的上下文环境而诞生的。简单的说就是为了灵活控制函数体内 this
的值。
apply
和
call
这两个方法的第一个参数都是要传递被函数上下文的对象(简单点说就是要绑定给函数
this
的对象)。其他参数就有所不同了:
apply
方法的第二个参数是一个数组,数组里面的值将作为函数调用的参数;
call
方法,从第二个参数起(包括第二个参数),剩下的参数都是作为函数调用的参数;
让我们看看栗子:
var obj = {
name :'afei'
}
function
say(ag1,ag2){
console.log(ag1+':'+ag2+" "+
this.name);
}
say.apply(obj,['apply
方法
','hello']); //apply
方法
:hello afei
say.call(obj,'call
方法
','hi'); //call
方法
:hi afei
正如栗子所示,我们把对象 obj
作为函数
say
的上下文来调用函数
say
,所以函数里的
this
是指向 对象
obj
的。在
apply
方法里,我们通过数组
['apply
方法
','hello']
给
say
方法传递了两个参数(
'apply
方法
'
和
'hello'
),所以打印出来是:
apply
方法
:hello afei
。
同理 call
也是一样,而且函数传递的方式通过上面的代码也一目了然我,博主就不多做解释了。
另外,博主还听说apply
和
call
这两个方法除了传递参数的方式不一样,执行的速度还是
apply
比
call
要快呢。不过博主就没有实验过。
五、总结
在JavaScript
里面,函数只要的调用模式就是这几种了(在
ES6
里面还有一种很奇怪很特殊的函数调用模式,叫做
’
标签模板
‘
,在这里博主也不多说了,有空另更),只要掌握了这几种主要的调用模式,那么日后再也不用担心
this
的值变来变去了。
来源:
博客园