JavaScript中call、apply与bind方法详解
本文详细解析JavaScript中Function对象的call、apply和bind三个核心方法。通过语法说明与代码示例,阐述三者如何改变函数执行时的this指向,对比它们在参数传递方式上的差异(arg列表vs数组),并介绍bind创建绑定函数的特性及兼容性处理,帮助开发者深入理解函数上下文切换与继承技巧。
引言
在JavaScript语言中,call、apply和bind是Function原型上提供的三个重要方法。本文将结合具体应用场景,深入解析这三个方法的用法与区别。
call()方法详解
call()方法
call()方法允许在指定this值和若干指定参数的情况下调用某个函数或方法。
调用函数时,可以通过call方法指定不同的this对象。这里的this引用当前对象,即作为call方法的第一个参数传入的值。
利用call方法,一个对象可以借用另一个对象的方法。例如,Object.prototype.toString.call([])展示了Array对象如何借用Object对象上的toString方法。
语法格式为:fun.call(thisArg[, arg1[, arg2[, ...]]])。其中thisArg表示在fun函数运行时指定的this值,需注意以下几种特殊情况。
(1)若不传参,或传入null、undefined,函数内部的this将指向window对象; (2)若传入另一个函数的函数名,函数中的this指向该函数的引用,但这不一定是该函数执行时真正的this值; (3)若值为原始值(数字、字符串、布尔值),this会指向该原始值的自动包装对象,如String、Number或Boolean; (4)若传入一个对象,函数中的this则指向该对象。
arg1, arg2, ...表示指定的参数列表。
示例:初级应用
function a(){
//输出函数a中的this对象
console.log(this);
}
//定义函数b
function b(){}
var obj = {name:'这是一个屌丝'}; //定义对象obj
a.call(); //window
a.call(null); //window
a.call(undefined);//window
a.call(1); //Number
a.call(''); //String
a.call(true); //Boolean
a.call(b);// function b(){}
a.call(obj); //Object
使用call方法调用匿名函数并指定上下文this。
在以下示例中,调用greet方法时,该方法的this值被绑定到i对象。
function greet() {
var reply = [this.person, '是一个轻量的', this.role].join(' ');
console.log(reply);
}
var i = {function greet() {
var reply = [this.person, '是一个轻量的', this.role].join(' ');
console.log(reply);
}
var i = {
person: 'JSLite.io', role: 'Javascript 库。'
};
greet.call(i);
// JSLite.io 是一个轻量的 Javascript 库。
person: 'JSLite.io', role: 'Javascript 库。'
};
greet.call(i);
// JSLite.io 是一个轻量的 Javascript 库。
使用call方法调用匿名函数。
在下例的for循环体内,创建了一个匿名函数,并通过调用该函数的call方法,将每个数组元素作为指定的this值执行了那个匿名函数。这个匿名函数的主要目的是给每个数组元素对象添加一个print方法,这个print方法可以打印出各元素在数组中的正确索引号。当然,这里不是必须得让数组元素作为this值传入那个匿名函数(普通参数就可以),目的是为了演示call的用法。
var animals = [
{species: 'Lion', name: 'King'},
{species: 'Whale', name: 'Fail'}
];
for (var i = 0; i < animals.length; i++) {
(function (i) {
this.print = function () {
console.log('#' + i + ' ' + this.species + ': ' + this.name);
}
this.print();
}).call(animals[i], i);
}
//#0 Lion: King
//#1 Whale: Fail
使用call方法调用函数传参数。
var a = {
name:'JSLite.io', //定义a的属性
say:function(){ //定义a的方法
console.log("Hi,I'm function a!");
}
};
function b(name){
console.log("Post params: "+ name);
console.log("I'm "+ this.name);
this.say();
}
b.call(a,'test');
//Post params: test
//I'm onepixel
//I'm function a!
apply()方法详解
apply()方法
语法与call()方法的语法几乎完全相同,唯一的区别在于,apply的第二个参数必须是一个包含多个参数的数组(或类数组对象)。apply的这个特性很重要。
在调用一个存在的函数时,你可以为其指定一个this对象。this指当前对象,也就是正在调用这个函数的对象。使用apply,你可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。
语法:fun.apply(thisArg[, argsArray])。注意:Chrome 14 以及 Internet Explorer 9 仍然不接受类数组对象。如果传入类数组对象,它们会抛出异常。
参数说明:thisArg
与call的thisArg参数含义相同。
参数说明:argsArray
一个数组或者类数组对象,其中的数组元素将作为单独的参数传给fun函数。如果该参数的值为null或undefined,则表示不需要传入任何参数。从ECMAScript 5开始可以使用类数组对象。
示例
function jsy(x,y,z){
console.log(x,y,z);
}
jsy.apply(null,[1,2,3]);
// 1 2 3
使用apply来链接构造器的例子
你可以使用apply来给一个对象链接构造器,类似于Java。在接下来的例子中我们会创建一个叫做construct的全局的Function函数,来使你能够在构造器中使用一个类数组对象而非参数列表。
Function.prototype.construct = function(aArgs) {
var fConstructor = this,
fNewConstr = function() {
fConstructor.apply(this, aArgs);
};
fNewConstr.prototype = fConstructor.prototype;
return new fNewConstr();
};
function MyConstructor () {
for (var nProp = 0; nProp < arguments.length; nProp++) {
console.log(arguments,this)
this["property" + nProp] = arguments[nProp];
}
}
var myArray = [4, "Hello world!", false];
var myInstance = MyConstructor.construct(myArray);
console.log(myInstance.property1); // logs "Hello world!"
console.log(myInstance instanceof MyConstructor); // logs "true"
console.log(myInstance.constructor); // logs "MyConstructor"
使用apply和内置函数
聪明的apply用法允许你在某些本来需要写成遍历数组变量的任务中使用内建的函数。在接下里的例子中我们会使用Math.max/Math.min来找出一个数组中的最大/最小值。
//里面有最大最小数字值的一个数组对象
var numbers = [5, 6, 2, 3, 7];
/* 使用 Math.min/Math.max 在 apply 中应用 */
var max = Math.max.apply(null, numbers);
// 一般情况是用 Math.max(5, 6, ..) 或者 Math.max(numbers[0], ...) 来找最大值
var min = Math.min.apply(null, numbers);
//通常情况我们会这样来找到数字的最大或者最小值
//比对上面的栗子,是不是下面的看起来没有上面的舒服呢?
max = -Infinity, min = +Infinity;
for (var i = 0; i < numbers.length; i++) {
if (numbers[i] > max)
max = numbers[i];
if (numbers[i] < min)
min = numbers[i];
}
参数数组切块后循环传入
function minOfArray(arr) {
var min = Infinity;
var QUANTUM = 32768;
for (var i = 0, len = arr.length; i < len; i += QUANTUM) {
var submin = Math.min.apply(null, arr.slice(i, Math.min(i + QUANTUM, len)));
console.log(submin, min)
min = Math.min(submin, min);
}
return min;
}
var min = minOfArray([5, 6, 2, 3, 7]);
bind()方法详解
bind()方法
bind()函数会创建一个新函数(称为绑定函数)。
bind是ES5新增的一个方法。传参和call或apply类似。不会执行对应的函数,call或apply会自动执行对应的函数。返回对函数的引用。语法:fun.bind(thisArg[, arg1[, arg2[, ...]]])。
下面例子:当点击网页时,EventClick被触发执行,输出JSLite.io p1 p2, 说明EventClick中的this被bind改变成了obj对象。如果你将EventClick.bind(obj,'p1','p2') 变成 EventClick.call(obj,'p1','p2') 的话,页面会直接输出 JSLite.io p1 p2。
var obj = {name:'JSLite.io'};
/**
* 给document添加click事件监听,并绑定EventClick函数
* 通过bind方法设置EventClick的this为obj,并传递参数p1,p2
*/
document.addEventListener('click',EventClick.bind(obj,'p1','p2'),false);
//当点击网页时触发并执行
function EventClick(a,b){
console.log(
this.name, //JSLite.io
a, //p1
b //p2
)
}
// JSLite.io p1 p2
兼容性与应用场景
兼容性处理
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this, // this在这里指向的是目标函数
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP
? this //此时的this就是new出的obj
: oThis || this,//如果传递的oThis无效,就将fBound的调用者作为this
//将通过bind传递的参数和调用时传递的参数进行合并,并作为最终的参数传递
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
//将目标函数的原型对象拷贝到新函数中,因为目标函数有可能被当作构造函数使用
fBound.prototype = new fNOP();
//返回fBond的引用,由外部按需调用
return fBound;
};
}
兼容例子来源于:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility。
应用场景:继承
function Animal(name,weight){
this.name = name;
this.weight = weight;
}
function Cat(){
// 在call中将this作为thisArgs参数传递
// Animal方法中的this就指向了Cat中的this
// 所以Animal中的this指向的就是cat对象
// 在Animal中定义了name和weight属性,就相当于在cat中定义了这些属性
// cat对象便拥有了Animal中定义的属性,从而达到了继承的目的
Animal.call(this,'cat','50');
//Animal.apply(this,['cat','50']);
this.say = function(){
console.log("I am " + this.name+",my weight is " + this.weight);
}
}
//当通过new运算符产生了cat时,Cat中的this就指向了cat对象
var cat = new Cat();
cat.say();
//输出=> I am cat,my weight is 50
原型扩展
在原型函数上扩展和自定义方法,从而不污染原生函数。例如:我们在Array上扩展一个forEach。
function test(){
// 检测arguments是否为Array的实例
console.log(
arguments instanceof Array, //false
Array.isArray(arguments) //false
);
// 判断arguments是否有forEach方法
console.log(arguments.forEach);
// undefined
// 将数组中的forEach应用到arguments上
Array.prototype.forEach.call(arguments,function(item){
console.log(item); // 1 2 3 4
});
}
test(1,2,3,4);
JavaScript闭包与类(Class)的区别及适用场景对比
2026-11-05 15:48:57
JavaScript入门要学ES6吗?let与箭头函数等新特性解析
2026-11-05 14:49:16
JavaScript 8种数据类型详解:基本类型与引用类型的区别
2026-11-05 14:25:15
怎么在JavaScript中使用async/await优化Ajax异步流程
2026-11-05 13:56:15
JavaScript replace回调函数实现动态正则替换逻辑
2026-11-05 13:52:13
相关标签:
