一、call apply bind的区别
call apply bind均为函数原型上的方法
相同点:
- 都可以改变函数中this的指向
- 第一个参数都是this要指向的对象
- 都可以利用后续参数给函数传参
区别:
-
fn.call(targetObj, arg1, arg2, ..., argN);
// 立即执行了fn函数,并且fn中的this指向targetObj目标对象,后续参数逗号分隔作为fn的参数
-
fn.apply(targetObj, [arg1, arg2, ..., argN]);
// 立即执行了fn函数,并且fn中的this指向了targetObj目标对象,后续参数用数组包起来作为fn的参数
-
fn.bind(targetObj, arg1, arg2, ..., argN)(argN+1, ..., argM);
// 并不立即执行fn,返回的是一个函数,并且这个函数也可以接收参数,只有当bind返回的函数执行的时候 才执行fn,并且fn中的this指向targetObj,bind的后续参数和返回的函数参数连起来作为fn的参数
二、call apply bind的手动实现
call的实现
// es6 版本
Function.prototype.myCall = function(targetObj, ...args) {
const target = targetObj || window; // 如果target为null this指向window
target.fn = this; // this代表Function的实例 也就是当前调用call的函数。 这一步函数作为目标对象的属性
const result = target.fn(...args); // 执行函数,如果一个函数是一个对象的属性,那么通过这个对象调用函数时,函数中的this指向这个对象
delete target.fn; // 删除fn属性
return result;
}
// es5版本
Function.prototype.myCall = function(context) {
// 首先要获得这个调用myCall的函数f,也就是Function的实例,f调用它的原型的方法myCall,
// 因此myCall的this就指向f。可以通过this获取f。
var context = context || window;//兼容参数为null的情况
context.fn = this;//把函数作为context对象的一个属性。
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
// eval()函数是一种接受字符串作为参数,并且可以将接受的字符串转换成js表达式并且立即执行该表达式
// 数组转换为字符串默认是用逗号分隔,所以最终是执行context.fn(arguments[0],arguments[1],..., arguments[length-1])
var result = eval('context.fn(' + args +')');
delete context.fn
return result;
}
// 用正则替换法
Function.prototype.myCall = function(context){
var str;
var reg = new RegExp('this','g');
str = this.toString().replace(reg,'context');
//A、replace('this','context')只会替换第一处,正则表达式会替换所有
var args = [];//B、实现参数传入,将当前函数中的参数除去第一个将剩余的传入目标函数
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
//var args=[].slice.call(arguments,1);
//不能用这种方法,否则eval执行时会提示参数变量没有定义,需要跟B处一样,将每个参数名转换为字符串形式,eval执行时动态计算每一个参数值
var result = eval('('+str+')('+args+')'); //C、以js表达式的形式执行字符串
return result;
}
apply的实现
// es5 版本
Function.prototype.myApply = function (context, arr) {
var context = context || window;
context.fn = this;
var result;
if (!arr) {
result = context.fn();
}
else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('context.fn(' + args + ')')
}
delete context.fn
return result;
}
// es6 版本
Function.prototype.myApply = function (targetObj, arr) {
const target = targetObj || window;
target.fn = this;
const result = arr ? target.fn(...arr) : target.fn();
delete target.fn
return result;
}
bind的实现
Function.prototype.myBind = function(target, ...rest) {
const self = this;
const fn = function() {};
const resultFn = function(...args) {
self.apply(this instanceof self ? this : target, [...rest, ...args]);
}
fn.prototype = this.prototype;
resultFn.prototype = new fn();
return resultFn;
};