call() 方法调用一个函数, 其具有一个指定的this值和分别地提供的参数(参数列表)。
apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或类数组对象)提供的参数。
bind() 方法创建一个新的函数,在调用时设置this关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。
以上内容源自MDN
下面我用自己的理解说一下call,apply及bind都做了啥
foo.call(obj,argument1,augument2,argument3) 复制代码
以上代码的作用:
执行函数foo,并将其this指定为obj,后面的参数为调用foo时传入的参数
foo.apply(obj,[argument1,augument2,argument3]) 复制代码
以上代码的作用:
执行函数foo,并将其this指定为obj并将数组
[
argument1,augument2,argument3]中的每一项作为调用foo时传入的参数
let bar = foo.bind(obj,argument1,augument2,argument3); bar(); 复制代码
以上代码的作用:
返回一个绑定了this为obj,参数为argument1,augument2,argument3的函数,所以如果想要执行foo,需要把返回结果再执行一下
了解了方法的功能和原理,接下来让我们分别实现它们
call的实现:
首先实现一个简单的call方法
Function.prototype.call2 = function(obj){ obj.fn = this; if(arguments.length>1){ obj.fn(...([...arguments].slice(1))); }else{ obj.fn(); } delete obj.fn; } 复制代码
以上代码仅仅实现了this的绑定以及函数调用传参,但是忽略了函数的返回值,而且默认把当前函数赋给了obj.fn,而没有考虑obj.fn存在的情况,改进后的代码如下:
Function.prototype.call2 = function(obj){ let _fn = "fn",result; while (obj.hasOwnProperty(_fn)) { _fn = "fn" + Math.random(); // 循环判断并重新赋值 } obj[_fn] = this; if(arguments.length>1){ result = obj[_fn](...([...arguments].slice(1))); }else{ result = obj[_fn](); } delete obj[_fn]; return result; } 复制代码
这样似乎完善了一些,但是还有一种情况需要考虑,即调用call方法没有传参的情况,或者第一个参数为null或者undefined
var name = 'window.name'; function foo(){ console.log(this); console.log(this.name) }; foo.call(); // window window.name foo.call(1); // Number {1} undefined 复制代码
通过上面的代码我们可以得出结论,如果调用call方法没有传入参数或者第一个参数为null或者undefined,会将函数的this绑定到window对象(注意如上测试代码,不可以使用let声明name哦),而如果传入的参数为基本数据类型,会将其转换为对象
继续完善后的call方法如下:
Function.prototype.call2 = function(obj){ obj = obj?Object(obj):window; let _fn = "fn",result; while (obj.hasOwnProperty(_fn)) { _fn = "fn" + Math.random(); // 循环判断并重新赋值 } obj[_fn] = this; if(arguments.length>1){ result = obj[_fn](...([...arguments].slice(1))); }else{ result = obj[_fn](); } delete obj[_fn]; return result; } 复制代码
而有了如上思考过程,实现apply就很简单了,代码如下:
Function.prototype.apply2 = function(obj,arr){ obj = obj?Object(obj):window; let _fn = "fn",result; while (obj.hasOwnProperty(_fn)) { _fn = "fn" + Math.random(); // 循环判断并重新赋值 } obj[_fn] = this; if(arr){ result = obj[_fn](...arr); }else{ result = obj[_fn](); } delete obj[_fn]; return result; } 复制代码
bind实现:
Function.prototype.bind2 = function (obj) { obj = obj ? Object(obj) : window let myArguments = arguments let self = this if (arguments.length > 1) { return function () { self.apply(obj, [...[...myArguments].slice(1), ...arguments]) } } return function () { self.apply(obj, [...arguments]) } } 复制代码
bind的实现借用了apply方法,有兴趣的朋友可以尝试不借用apply实现bind
如果有错误或者不严谨的地方,请给予指正,十分感谢!