在JavaScript中call、apply、bind是Function 对象自带的三个方法,这三个方法的主要作用是改变函数中的 this 指向,从而可以达到`接花移木`的效果。本文将对这三个方法进行详细的讲解,并列出几个经典应用场景。
区分:
1、call(object,arg1,arg2) ,call方法的第一个参数是函数中this重新指向的对象,剩下的参数是传入该函数的形参
不传,或者传null,undefined, 函数中的 this 指向 window 对象,传递另一个函数的函数名,函数中的 this 指向这个函数的引用,传递字符串、数值或布尔类型等基础类型,函数中的 this 指向其对应的包装对象,如 String、Number、Boolean,传递一个对象,函数中的 this 指向这个对象。
function say(){
console.log(this);
}
function eat(){
}
var obj={
name:'Bob',
};
say.call(); //this指向window
say.call(null); //this指向window
say.call(undefined); //this指向window
say.call(eat); //this指向函数得引用 function eat(){}
say.call(1); //this指向数值基本类型得包装对象Number
say.call("str"); //this指向数值基本类型得包装对象String
say.call(true); //this指向数值基本类型得包装对象Boolean
say.call(obj); //this指向这个对象obj
call的作用是允许在一个对象上调用该对象没有定义的方法,并且这个方法可以访问该对象中的属性,如下
var a={
name:'Bob',
food:'fish',
say:function(){
console.log('HI,this is a.say!!');
}
}
function b(name){ //b.call(a,'Tom');使得a对象能调用其他函数方法
console.log("post Params:"+name); //a对象使用了b('Tom')方法, 输出post Params: Tom
console.log('I am '+this.name); //a对象获取了自己的属性 ,输出 I am Bob
this.say(); //a对象使用自己的方法, 输出 HI,this is a.say!!
}
b.call(a,'Tom');
2、apply(object,[arg1,arg2]),apply方法的第一个参数是函数中this重新指向的对象,第二个参数数组是传入该函数的形参;和call方法唯一区别是第二参数的传递形式是数组。使用如下
function b(x,y,z){
console.log(x,y,z); //会将多个参数拼接输出
}
//apply() 方法接收的参数是数组形式,但是传递给调用的函数时是用参数列别的形式传递的
b.apply(null,[1,2,3]); //输出 1 2 3 ,这里等同于window对象调用b方法并使用参数[1,2,3]
3、bind(object,arg1,arg2) ,bind方法是ES5 新增的一个方法,传参和call方法一致。与call、apply方法的区别是,call和apply方法会对目标函数进行自动执行,会返回一个新的函数。call和apply无法在事件绑定函数中使用。而bind弥补了这个缺陷,在实现改变函数 this 的同时又不会自动执行目标函数,因此可以完美的解决上述问题,
var obj = {
name: 'onepixel'
};
//给document添加click事件监听函数,并绑定onclick函数
//通过bind方法设置onclick的this指向是obj,并传递参数p1,p2
document.addEventListener('click',onClick.bind(obj,22,66),false);
//可以理解为当网页触发click事件时,obj对象执行onclick函数并传递给该函数参数p1,p2
function onClick(p1,p2){
console.log(this.name,p1,p2); //输出 onepixel 22 66
}
var button=document.getElementById("button"),
text=document.getElementById("text");
button.onclick=function(){ //声明按钮的点击事件触发的函数
console.log(this.id);
}.bind(text); //改变this的指向
注意:一旦函数通过bind绑定了有效的this对象,那么在函数执行过程中this会指向该对象,即使使用call、apply也不能改变this的指向
bind 进行深入的理解,我们来看一下 bind 的 polyfill 实现(ie6~ie8不支持该方法):
if (!Function.prototype.bind) { //浏览器js中不支持bind方法情况下
Function.prototype.bind = function(context) { //在函数原型对象自定义bind方法,context是this重指向的对象,也就是上面function(){}.bind(text)中的text对象
var self = this //因为这个自定义的bind方法返回的是一个匿名函数,匿名函数具有全局性,其this会指向window。所以这里需要将bind方法触发时的调用对象this进行保存
, args = Array.prototype.slice.call(arguments); //这里借用了数组原型对象的slice方法将形参 类数组转化为真正的数组
return function() { //bind方法返回一个匿名函数
return self.apply(context, args.slice(1)); //self就是函数触发时的调用对象,this重指向bind方法的第一个参数对象,
}
};
}
代码解析:在浏览器不支持bind方法时,自定义bind方法中会返回一个匿名函数,该匿名函数保存bind方法调用时的this,这里是self=this; 并通过self.apply(newObj, args); 的原理实现改变this指向不执行函数的效果
使用场景:
1、继承
JavaScript中没有诸如Java、C# 等高级语言中的extend 关键字,因此JS 中没有继承的概念,如果一定要继承的话,call 和 apply 可以实现这个功能:
function Animal(name,weight){
this.name=name;
this.weight=weight;
}
function Cat(){
Animal.apply(this,['cat','10kg']); //理解为在Cat函数对象创建时会使用Animal()方法
//Animal.call(this,'cat','10kg');
this.say=function(){
console.log('I am '+this.name+' , my weight is '+this.weight);
}
}
var cat = new Cat();
cat.say(); //输出 I am cat , my weight is 10kg
2、移花接木
在讲下面的内容之前,我们首先来认识一下JavaScript 中的一个非标准专业术语:ArrayLike (类数组/伪数组)
ArrayLike 对象即拥有数组的一部分行为,在DOM 中早已表现出来,而jQuery 的崛起让ArrayLike 在JavaScript 中大放异彩。ArrayLike 对象的精妙在于它和JS 原生的 Array 类似,但是它是自由构建的,它来自开发者对JavaScript 对象的扩展,也就是说:对于它的原型(prototype)我们可以自由定义,而不会污染到JS原生的Array。
ArrayLike 对象在JS中被广泛使用,比如DOM 中的NodeList, 函数中的arguments 都是类数组对象,这些对象像数组一样存储着每一个元素,但它没有操作数组的方法,而我们可以通过call 将数组的某些方法`移接`到ArrayLike 对象,从而达到操作其元素的目的。比如我们可以这样遍历函数中的arguments:
function test(){
console.log(typeof(arguments)); //输出Object ,ArrayLike是类数组对象
//检测arguments是否是Array的实例
console.log(arguments instanceof Array); //输出 false
console.log(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 5
});
}
test(1,2,3,4,5);
除此之外,对于apply 而言,我们上面提到了它独有的一个特性,即apply 接收的是数组,在传递给调用函数的时候是以参数列表传递的。 这个特性让apply 看起来比call 略胜一筹,比如有这样一个场景:给定一个数组[1,3,4,7],然后求数组中的最大元素,而我们知道,数组中并没有获取最大值的方法,一般情况下,你需要通过编写代码来实现。而我们知道,Math 对象中有一个获取最大值的方法,即Math.max(), max方法需要传递一个参数列表,然后返回这些参数中的最大值。而apply 不仅可以将Math 对象的max 方法应用到其他对象上,还可以将一个数组转化为参数列表传递给max,看代码就能一目了然:
var arr = [-1,2, 3, 1, 5, 4, 223, 11];
var a=Math.max.apply(null, arr); // 223
console.log(a);
学习网址:https://blog.csdn.net/u014267183/article/details/52610600 、软谋前端 (深入理解 call,apply 和 bind)