本文详解JavaScript中call和apply方法的参数传递差异及核心用途,重点阐述如何通过二者改变函数内部的this指向。结合代码示例分析事件处理中的this修正场景,并深入解析Function.prototype.bind的底层实现原理,包括参数预填充的高级用法,帮助开发者掌握函数调用的核心机制。
call与apply的参数传递机制
apply方法接收两个参数:第一个参数用于指定函数体内this对象的指向,第二个参数是一个带有下标的集合(可以是数组或类数组对象)。该方法会将该集合中的元素作为独立参数传递给被调用的函数。
var func = function( a, b, c ){
alert ( [ a, b, c ] ); // 输出 [ 1, 2, 3 ]
};
func.apply( null, [ 1, 2, 3 ] );
在上述代码示例中,数值1、2、3被封装在数组中传入func函数,它们分别对应func函数定义中的参数a、b、c。
call方法接受的参数数量不固定。与apply相同,其第一个参数同样代表函数体内的this指向,但从第二个参数开始,后续每个参数会被依次传入被调用的函数中。
var func = function( a, b, c ){
alert ( [ a, b, c ] ); // 输出 [ 1, 2, 3 ]
};
func.call( null, 1, 2, 3 );
在调用函数时,JavaScript解释器并不严格检查形参与实参在数量、类型及顺序上的差异,因为函数参数在内部实际上是由一个数组来表示的。基于这一机制,apply的使用频率往往高于call,因为开发者无需关心具体传入参数的数量,只需将参数集合一次性传入即可。call可视为建立在apply之上的语法糖;若明确知道函数接受的参数数量,且希望直观表达形参与实参的对应关系,则可以使用call来传递参数。
改变函数内部的this指向
call和apply的主要用途
1. 改变this指向
call和apply最常见的应用场景是修改函数内部的this指向。以下是一个具体示例:
var obj1 = {
name: 'sven'
};
var obj2 = {
name: 'anne'
};
window.name = 'window';
var getName = function(){
alert ( this.name );
};
getName(); // 输出: window
getName.call( obj1 ); // 输出: sven
getName.call( obj2 ); // 输出: anne
当执行getName.call( obj1 )这行代码时,getName函数体内的this便指向obj1对象,因此此处的
var getName = function(){
alert ( this.name );
};
实际上等价于:
var getName = function(){
alert ( obj1.name ); // 输出: sven
};
在实际开发中,经常遇到this指向被意外改变的情况。例如,对于一个div节点,其onclick事件处理函数中的this原本指向该div元素:
document.getElementById( 'div1' ).onclick = function(){
alert( this.id ); // 输出:div1
};
如果该事件处理函数内部定义了一个子函数func,在事件内部调用func时,func体内的this将指向window对象,而非预期的div元素,具体代码如下:
document.getElementById( 'div1' ).onclick = function(){
alert( this.id ); // 输出:div1
var func = function(){
alert ( this.id ); // 输出:undefined
}
func();
};
此时,我们可以使用call来修正func函数内的this指向,使其仍然指向div元素:
document.getElementById( 'div1' ).onclick = function(){
var func = function(){
alert ( this.id ); // 输出:div1
}
func.call( this );
};
Function.prototype.bind的实现原理
2. Function.prototype.bind
大多数现代浏览器已内置Function.prototype.bind方法,用于指定函数内部的this指向。即使没有原生的bind实现,手动模拟也并非难事,代码如下:
Function.prototype.bind = function( context ){
var self = this; // 保存原函数
return function(){ // 返回一个新的函数
return self.apply( context, arguments ); // 执行新的函数的时候,会 把之前传入的context
// 当作新函数体内的this
}
};
var obj = {
name: 'sven'
};
var func = function(){
alert ( this.name ); // 输出:sven
}.bind( obj);
func();
我们通过Function.prototype.bind来“包装”func函数,并传入一个对象context作为参数,该context对象即为我们希望修正的this目标。
在Function.prototype.bind的内部实现中,首先保存func函数的引用,然后返回一个新函数。当未来执行func函数时,实际上先执行的是这个返回的新函数。在新函数内部,self.apply( context, arguments )这行代码负责执行原始的func函数,并将context对象指定为func函数体内的this。
这是一个简化版的Function.prototype.bind实现。通常,我们会将其实现得更为复杂,
以便向func函数中预先填入部分参数:
Function.prototype.bind = function(){
var self = this, // 保存原函数
context = [].shift.call( arguments ), // 需要绑定的this 上下文
args = [].slice.call( arguments ); // 剩余的参数转成数组
return function(){ // 返回一个新的函数
return self.apply( context, [].concat.call( args, [].slice.call( arguments ) ) );
// 执行新的函数的时候,会把之前传入的context 当作新函数体内的this
// 并且组合两次分别传入的参数,作为新函数的参数
}
};
var obj = {
name: 'sven'
};
var func = function( a, b, c, d ){
alert ( this.name ); // 输出:sven
alert ( [ a, b, c, d ] ) // 输出:[ 1, 2, 3, 4 ]
}.bind( obj, 1, 2 );
func( 3, 4 );
以上内容为本文的全部讲解,希望这些内容能对您的学习或工作有所帮助,同时也欢迎继续支持本站!