在 JavaScript 中,我们可以使用四种方式调用函数。
- 作为普通函数调用
- 作为对象的方法调用
- 作为构造器调用
- 使用 apply() 和 call() 方法调用
在研究四种调用方式之前,我们先来看看函数参数的特殊之处。
函数参数
首先,我们来熟悉下实参与形参的概念。
|
上面的代码片段中,function fn(a, b) {}
中的 a
与 b
是函数声明时定义的参数名,称之为形参。
而 fn(1, array);
中的 1
和 array
是在函数调用时实际传递的参数,称之为实参。
大多数语言,一般会要求实参与形参必须在数量上匹配,静态类型语言还要求类型匹配。
但在 JavaScript 中,实参跟形参的数量可以不匹配,这是一个很有意思的特性,之后我们再讨论该特性的应用。
JavaScript 分两种策略来处理实参与形参不一致的问题。
- 实参数量大于形参:不报错,并提供访问多余实参的方法。
- 实参数量小于形参:不报错,未提供参数的形参值为
undefinded
。
实际上,当调用函数时,会隐式传递两个参数:arguments
和 this
。
隐式表明这两个参数不需要出现在函数签名中,但可以在函数体内显式访问。
arguments 参数
arguments
是一个类数组结构的对象,储存了所有传递给该函数的显式实参。
|
this 参数
this
参数比 arguments
参数更有趣也更重要。this
引用了一个与当前函数调用有关的隐式对象,这个对象称为函数上下文 (function context)。
函数上下文的概念出自于面对对象编程范式,在面对对象中,函数声明在一个类中,而声明在类中的函数又被称作为方法,要调用方法,就可能通过一个类的实例来调用。而
this
就是指这个调用方法的实例。
但在 JavaScript
中,情况发生了转变,因为 JavaScript
是一个多编程范式的编程语言,它不仅包含了面对对象编程范式,还包含了函数式编程范式。所以,在 JavaScript
中,函数不仅仅是作为方法来调用,还存在其他的调用方式。此时,我们将 this
称为调用上下文 (invocation context) 也许更合适。
作为普通函数调用
所谓的普通函数,只是除了通过之后要介绍的三种特殊调用方式之外的所有函数的统称。
|
需要注意的一点是 (function fn() {})()
其实是一个组合表达式。
我们可以通过逐步分解来理解该表达式:
function fn() {}
是一个函数字面量。(function fn() {})
是一个表达式,返回一个函数对象。(function fn() {})()
是个函数调用表达式,通过()
操作符调用该函数。
我们再来看看,普通函数调用时 this
的值。
|
在普通函数中,this
指向全局对象 window
。
|
严格模式下,普通函数调用的隐式参数 this
为 undefined
。
注意最后一条语句,通过
window
对象调用fn()
,此时fn
作为window
对象的方法被调用,this
为window
。
作为对象的方法调用
当函数赋值给对象的属性时,我们只能通过 .
操作符调用该函数。
|
此时,我们将这种与对象绑定在一起的函数称之为方法。
|
方法中的 this
为调用该方法的对象。
作为构造器来调用
当我们在普通函数调用前加上 new
关键字来创建一个对象时,我们一般称该函数为构造器。
|
使用构造器语法创建一个对象,会执行一些隐式语句,下面给出一个大概的示例:
|
__proto__
是对象的隐式属性,指向原型链的上级。prototype
是函数的隐式属性,指向函数原型,相当于面对对象中的类。this
参数为隐式创建的对象。
另一个注意点,new
操作符会默认返回一个对象,但如果用户指定返回对象的话,将会覆盖默认返回的对象。然而,如果用户返回的是原始值的话,最终返回的仍是默认对象。
|
使用构造器时,使用新对象覆盖返回对象是没有实际意义,应避免这种用法。
使用 apply()
和 call()
方法调用
有时,我们会想在调用函数时修改默认的 this
参数,JavaScript
提供两个方法可以实现对 this
参数的修改。
apply
和 call
都是函数对象特有的方法。
两者唯一的不同在于接收的参数列表:
|
这两个方法,接收的第一个参数都将传递给 this
参数。
而 apply
的第二个参数必须是类数组结构的对象,该对象中的每一个数组元素都将传递给原函数的参数列表;call
则会将第二个参数及其之后的所有参数都传递给原函数的参数列表。
|
在回调函数中,我们常常会修改回调函数的 this
参数。
比如,我们可以构建一个 forEach
函数,用来遍历数组,并对每个元素执行一些操作。
|
this 深入
关于 this
参数,还有几点需要深入讨论一下。
this
不会被继承
|
obj.fn()
通过对象obj
调用fn
,所以fn
的this
参数为obj
。setTimeout
的回调函数是一个普通函数调用,所以this
参数为window
。- 内嵌函数也是作为普通函数调用,所以
this
参数为window
。
内部函数的
this
参数与外部函数的this
没有关联,即JavaScript
默认不会将 外部函数的this
参数传递给内部函数的this
参数。
要想内部函数访问外部函数的this
参数,就必须先在外部函数保存this
参数到变量中(假设是self
),再在内部函数中访问self
即可。
在 DOM 事件的事件处理器中 this
为事件触发对象
|