call、apply和bind是JavaScript中function对象的三个方法,他们的共同点有如下几点:
- 改变函数运行时的this指向
- 第一个参数都是this要指向的对象
- 都可以利用后续参数传参
不同点在于:
- bind是返回对应函数便于稍后调用;apply、call则是立即调用
- apply和call可以实现继承;而bind不可以
- bind会产生新的函数;而apply和call不会产生新函数,只是在调用时绑定一下
- apply的第二个参数是一个数组(包含函数的所有参数),call的第二个参数是参数列表
一、具体使用
var person = {
name: "CD",
age:21,
sex:"boy",
can:function (score,price){
alert(this.sport + score + "分," + this.food + price + "/kg");
},
write:function(){
alert(this.age);
}
};
var p = {
sport:"football",
food:"bread",
age:22
};
person.can.apply(p,[90,20]);
person.can.call(p,90,20);
person.can.bind(p)(90,20);
二、应用场景
- 将类数组转换为数组
var tArr = Array.prototype.slice.call(lArr); //举个栗子 function fn(){ var arr = Array.prototype.slice.call(arguments); console.log(arr.shift()); } fn(0,1,2,3,4,5);//0
- 数组追加:以下这两种写法都可行。apply也可以被替换成call和bind。当然,使用不同的方法也存在着不同之处:apply的结果是,arr1变成一个arr2的各元素依次追加到arr1的末尾的新数组,arr2自身保持不变;call的结果是,arr2整体作为一个元素追加到arr1的末尾,arr2自身保持不变;bind与call的效果相同。
Array.prototype.push.apply(arr1,arr2); [].push.apply(arr1,arr2); var arr1 = [1,2,3]; var arr2 = [4,5,6]; //apply [].push.apply(arr1, arr2); console.log(arr1);// [1, 2, 3, 4, 5, 6] console.log(arr2);// [4,5,6] //call [].push.call(arr1, arr2); console.log(arr1);// [1, 2, 3,[ 4, 5, 6]] console.log(arr2);// [4,5,6] //bind [].push.bind(arr1, arr2)(); console.log(arr1);// [1, 2, 3,[ 4, 5, 6]] console.log(arr2);// [4,5,6]
- 判断变量类型
function isArray(obj){ return Object.prototype.toString.call() == "[object Array]"; } console.log(isArray([]));//true console.log(isArray('string'));//false
三、用js实现
- 原生js实现bind方法:
Function.prototype.myBind = function(){ var self = this; //保存原函数 var context = Array.prototype.shift.call(arguments); //保存第一个参数:this上下文 var args = Array.prototype.slice.call(arguments); //剩余的参数(除了this上下文)转为数组 return function(){ self.apply(context,Array.prototype.concat.call(args,Array.prototype.slice.call(arguments))); } } //应用 var arr1 = [1,2,3]; var arr2 = [4,5,6]; [].push.myBind(arr1,arr2)(); console.log(arr1,arr2);//[1,2,3,[4,5,6]] [4,5,6]
myBind()与bind()的应用结果相同,由此可见,两者等价。
-
原生js实现apply方法:
//实现一:ES5 Function.prototype.myApply = function (context, arr) { if (typeof context === 'object') { context = context || window } else { context = Object.create(null); } context.fn = this; var result; if (!arr) { // 判断函数参数是否为空 result = context.fn(); } else { var args = []; for (var i = 0; i < arr.length; i++) { args.push('arr[' + i + ']'); } result = eval('context.fn(' + args + ')'); } delete context.fn; return result; }
//实现二:ES6 Function.prototype.myApply = function(context, parameter) { if (typeof context === 'object') { context = context || window; } else { context = Object.create(null); } let fn = Symbol() context[fn] = this; var result = context[fn](...parameter); delete context[fn]; return result; }
-
原生js实现call方法(与apply的实现类似):
//实现一:ES5 Function.prototype.myCall = function (context) { if (typeof context === 'object') { context = context || window } else { context = Object.create(null); } context.fn = this; let args = []; for(var i=1; i< arguments.length; i++) { args.push('arguments[' + i + ']'); } // args => [arguments[1], arguments[2], arguments[3], ...] var result = eval('context.fn(' + args + ')'); delete context.fn; return result; }
//实现二:ES6 Function.prototype.myCall = function (context,...params) { if (typeof context === 'object') { context = context || window } else { context = Object.create(null); } let fn = Symbol(); context[fn] = this var result = context[fn](...params); delete context.fn; return result; }
原生js实现这三种方法的写法多种多样,欢迎交流分享与指正。侵删。