call apply bind 模拟实现

142 阅读3分钟

一、call apply bind的区别

call apply bind均为函数原型上的方法

相同点:

  1. 都可以改变函数中this的指向
  2. 第一个参数都是this要指向的对象
  3. 都可以利用后续参数给函数传参

区别:

  1. fn.call(targetObj, arg1, arg2, ..., argN);

    // 立即执行了fn函数,并且fn中的this指向targetObj目标对象,后续参数逗号分隔作为fn的参数

  2. fn.apply(targetObj, [arg1, arg2, ..., argN]);

    // 立即执行了fn函数,并且fn中的this指向了targetObj目标对象,后续参数用数组包起来作为fn的参数

  3. 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;
};