【JavaScript】call、apply、bind
1、概述
在JavaScript 中,call、apply 和 bind 是 Function 对象自带的三个方法,这三个方法的主要作用是改变函数中的 this 指向。
2、call
call([thisObj[,arg1[, arg2[, [,.argN]]]]])
调用一个对象的一个方法,以另一个对象替换当前对象。
call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。
参数说明:
- thisObj:改变调用call方法的函数内的this,此到达函数内部后会自动转成对象形式变成this
- arg1…:对应调用call方法的函数的第一个参数及之后。
thisObj 的取值:
- 不传,或者传null,undefined, 函数中的 this 指向 window 对象
- 传递另一个函数的函数名,函数中的 this 指向这个函数的引用
- 传递字符串、数值或布尔类型等基础类型,函数中的 this 指向其对应的包装对象,如 String、Number、Boolean
- 传递一个对象,函数中的 this 指向这个对象
应用场景:
-
js的函数上可以使用call方法来改变函数中的this指向
function a(){ console.log(this); // 输出函数a中的 this 对象 } function b(){} // 定义函数 b var c = {name:"call"}; // 定义对象 c 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(c); // Object
-
通过call实现两个函数之间的继承
function Animal(name,weight){ this.name = name; this.weight = weight; } function Cat(){ Animal.call(this,'cat','50'); //Animal.apply(this,['cat','50']); 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 50
function class1(){ this.name = function(){ console.log("method - class1"); } } function class2(){ class1.call(this); //此行代码执行后,当前的this指向了class1(也可以说class2继承了class1) } var f = new class2(); f.name(); //调用的是class1内的方法,将class1的name方法交给class2使用
-
通过call实现两个函数之间的替换
function eat(x,y){ console.log(x+y); } function drink(x,y){ console.log(x-y); } eat.call(drink,3,2); // 输出:5 // 这个例子中的意思就是用 eat 来替换 drink,eat.call(drink,3,2) == eat(3,2) ,所以运行结果为:console.log(5); // 注意:js 中的函数其实是对象,函数名是对 Function 对象的引用。
-
通过call实现两个函数之间的方法和属性的继承
function Animal(){ this.name="animal"; this.showName=function(){ console.log(this.name); } } function Dog(){ this.name="dog"; } var animal=new Animal(); var dog=new Dog(); animal.showName.call(dog); //输出:dog //在上面的代码中,我们可以看到Dog里并没有showName方法,那为什么(this.name)的值是dog呢? //关键就在于最后一段代码(animal.showName.call(dog)),意思是把animal的方法放到dog上执行,也可以说,把animal 的showName()方法放到 dog上来执行,所以this.name 应该是 dog。
//继承 function Animal(name){ this.name=name; this.showName=function(){ console.log(this.name); } } function Dog(name){ Animal.call(this,name); } var dog=new Dog("Crazy dog"); dog.showName(); //输出:Crazy dog //Animal.call(this) 的意思就是使用 Animal对象代替this对象,那么Dog就能直接调用Animal的所有属性和方法。
3、apply
apply(thisArgs [,args[]])
apply 和 call 的唯一区别是第二个参数的传递方式不同,apply 的第二个参数必须是一个数组(或者类数组),而 call 允许传递一个参数列表。
虽然 apply 接收的是一个参数数组,但在传递给调用函数时,却是以参数列表的形式传递。
function b(x,y,z){
console.log(x,y,z);
}
b.apply(null,[1,2,3]); // 1 2 3
4、bind
bind(thisArgs [,args...])
bind是ES5 新增的一个方法,它的传参和call类似,但又和 call/apply 有着显著的不同,即调用 call 或 apply 都会自动执行对应的函数,而 bind 不会执行对应的函数,只是返回了对函数的引用。
ES5引入 bind 的真正目的是为了弥补 call/apply 的不足,由于 call/apply 会对目标函数自动执行,从而导致它无法在事件绑定函数中使用,因为事件绑定函数不需要我们手动执行,它是在事件被触发时由JS 内部自动执行的。而 bind 在实现改变函数 this 的同时又不会自动执行目标函数,因此可以完美的解决上述问题。
var obj = {name:'onepixel'};
/**
* 给document添加click事件监听,并绑定onClick函数
* 通过bind方法设置onClick的this为obj,并传递参数p1,p2
*/
document.addEventListener('click',onClick.bind(obj,'p1','p2'),false);
//当点击网页时触发并执行
function onClick(a,b){
console.log(
this.name, //onepixel
a, //p1
b //p2
)
}
当点击网页时,onClick 被触发执行,输出onepixel p1 p2, 说明 onClick 中的 this 被 bind 改变成了obj 对象,为了对 bind 进行深入的理解,我们来看一下 bind 的 polyfill 实现:
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
var args = Array.prototype.slice.call(arguments, 1);
var fToBind = this; // this在这里指向的是目标函数
var fBind = function () {
return fToBind.apply(
// 如果外部执行var obj = new fBind(), 则将obj作为最终的this,放弃使用oThis
this instanceof fBind
? this // 此时的 this 就是 new 出的 obj
: oThis || this, // 如果传递的 oThis 无效,就将 fBind 的调用者作为this
// 将通过bind传递的参数和调用时传递的参数进行合并,并作为最终的参数传递
args.concat(Array.prototype.slice.call(arguments))
);
};
// 将目标函数的原型对象拷贝到新函数中,因为目标函数有可能被当作构造函数使用
fBind.prototype = this.prototype;
// 返回fBind的引用,由外部按需调用
return fBind;
};
}
一旦函数通过bind传递了有效的this对象,则该函数在运行期的this将指向这个对象,即使通过call或apply来试图改变this的指向也是徒劳的。
var obj1 = {
name: 'Tom'
}
var obj2 = {
name: 'Joy'
}
setTimeout(function() {
console.log(this.name);
}.bind(obj1).bind(obj2), 0);
// Tom
当对一个函数调用多次bind的时候,最终起作用的是第一个bind,这是因为每个bind 都会返回一个闭包 fBind,fBind 里保存了执行目标函数的 scope 和 arguments。执行多次 bind ,目标函数会被多个闭包包装,然后从外到里去执行,当执行真正目标函数的时候,apply 函数中的 scope 和 arguments 读取当前闭包里的变量。
// 实现数组的去重功能
Array.prototype.unique = function(fn) {
var rst = [];
var tmp = {};
this.forEach(function(val) {
// 使用call来改变fn的this指向,这里传window
var key = 'uniq' + (typeof fn === 'function' ? fn.call(window, val) : val);
if (!tmp.hasOwnProperty(key)) {
rst.push(val);
tmp[key] = null;
}
}, this);
return rst;
}
// 对象数组去重
var arr = [
{ id: 2 }, { id: 4 }, { id: 3 }, { id: 3 }, { id: 4 }, { id: 6 }
]
arr.unique(function(v) {
console.log(this) // 使用bind传递了Array,则this一定是Array,而不会是window
return v.id
}.bind(Array));
10、资料
-
JavaScript进阶 —— 深入理解 call,apply 和 bind
-
JS call() 的使用