Javascript进阶之函数的四种调用模式

前言

    在JavaScript中,函数是一等公民,相信这句话大家多少都有所耳闻吧,其实主要是因为函数在JavaScript中是一个数据类型,而非像C#或其他描述性语言那样仅仅作为一个模块来使用。函数有四种调用模式,分别是:作为函数调用形式、作为对象的方法调用、作为构造函数调用、以及使用call或者apply调用。这里所有的调用模式中,其实最大的区别就是this指向的不同,有一个方便理解this指向的方法就是:“牢记谁调用的函数,函数内部的this就指向谁,this是动态确定指向谁的” ;另外要注意this是个关键字,是有特殊含义的,下面分别介绍这几种调用形式。

1.作为函数调用

例:

1
2
3
4
5
6
function func() {
var age = 18;
console.log(this.age);// undefined
console.log(this);//window
}
func();
1
2
3
4
5
6
function func() {
var age = 18;
console.log(this.age);// undefined
console.log(this);//window
}
window.func();

对于初学者来说是不是觉得跟自己想象的有出入,但其实一分析就知道这刚好印证了我们上面的this就是指向调用它的对象,而这里上述两个直接func()和window.func()结果是一样的,你可以暂时这样记住在函数调用模式中,函数里的 this 关键字指全局对象,如果在浏览器中就是 window 对象。这里需要说明的是在js的严格版中this指向的不是window,但是我们这里不探讨严格版的问题,想了解的可以自行上网查找。

2.作为对象方法调用,this就是当前调用方法的对象

    函数调用模式很简单,是最基本的调用方式。可以这么理解:同样的是函数,将其赋值给一个对象的成员以后,就不一样了。将函数赋值给对象的成员后,那么这个就不在称为函数,而应该叫做方法,这是业内的一贯称谓;
例:

1
2
3
4
5
6
7
8
9
10
var obj = {
name: "functionName",
age: 19,
getName: function() {
console.log(this.name);// this指向obj(当前调用方法的对象)


}
};
obj.getName();

对比下函数调用与方法调用的区别,函数调用中,this专指全局对象window,而在方法中this专指当前对象即o.fn中的 this指的就是对象o。

3.作为构造函数调用

    在单纯的函数模式下,this 表示 window;在对象方法模式下,this 指的是当前对象。除了这两种情况,JavaScript 中函数还可以作为构造函数来调用,这时候是在函数调用前面加上一个 new 关键字,另外要注意构造函数最大作用是为了初始化一个实例对象,真正创建对象的是new,很多人不了解的以为就是构造函数创建了对象,这一点以后要注意。
例:

1
2
3
4
5
function Person(name) {
this.name = name;// this 指向新创建出来的对象p,这种方法可以很方便的让任何对象来使用构造函数里的属性
}
var p = new Person("小明");
console.log(p.name);//小明

其实在构造函数内部会进行下面这段代码

1
2
3
4
5
6
7
function Person(name) {
var this;
this.name = name;
return this;
}
var p = new Person("小明");
console.log(p.name);//小明

其实上面的可以描述为:创建Person的新实例,必须通过new操作符,以这种方式调用构造函数会经历以下四个步骤:

  • ①、创建一个新对象;
  • ②、将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
  • ③、执行构造函数中的代码(初始化对象);
  • ④、返回新对象

需要注意的点

  • 构造函数当中没有return,则默认就是返回当前对象,即this;
  • 如果有return,后面是基本数据类型,忽略,和没写一样
  • 但是如果后面是一个有效的引用类型的话,无论构造函数中写什么内容,都会返回这个引用类型,也就是不会返回this,而是返回这个引用类型(对象)了
    看如下代码:
    1
    2
    3
    4
    5
    6
    7
    8
    function Person(name) {
    this.name = name;
    return {
    name : "小王"
    }
    }
    var p = new Person("小明");
    console.log(p.name);//小王

其实这是一种寄生模式的形式创建的对象,在这里先不详细介绍,大家暂时只需先知道不要在构造函数内部再重新return,除非真的有特殊情况再行分析;

4.使用call或者apply调用(上下文调用),this由上下文决定

使用apply模式和call模式,可以任意的操作控制 this 的意义,所以在函数 js 的设 计模式中使用还是很广泛的,现在你也可以在Javascript中很多优秀的类库当中都能看到它的身影,因为它具有一个强大的作用,就是“调用上下文”; 首先看它的语法:
// call和apply的区别:就是参数不同

1
2
3
// call(上下文, arg1, arg2, ...) call后面的参数不是数组形式;
// apply(上下文,[arg1, arg2, ...]) apply后面的参数必须是数组形式;
例:func.call(obj); // this 指向传入的参数:obj(第一个参数)

拿数组的push方法来举例

1
2
3
4
5
6
7
8
9
var arr = [1,2,3];
<!-- 使用apply -->
[].push.apply(arr,[4,5,6]);
cosole.log(arr);//[1,2,3,4,5,6];
这时我们就将push作为一个方法,通过arr调用,也就是说apply会自动将它后面传入的参数数组按每一个传入push这个函数当中,其实等同于arr.push(4,5,6);

<!-- 使用call -->
[].push.call(arr,4,5,6);
cosole.log(arr);//[1,2,3,4,5,6];

总的来说

apply的参数,第一个参数表示指定的this,第二个参数要求是数组,表示函数的参数
call的参数,第一个参数也是表示指定的this,第二个参数不是数组,表示函数的参数;

结语

    其实在Javascript中的很多重要的概念和应用场景都是围绕着这四种调用模式来进行的,只不过很多时候大家都只是了解的不系统,在一些容易混淆的点上出了问题,所以也希望大家能够认真分析,更上一层楼吧!