我们先来看一下原本的call,bind,apply函数是怎样的。
function foo() {
console.log('foo函数被执行')
}
foo.call()
function bar() {
console.log('bar函数被执行')
}
bar.apply()
function leon() {
console.log('leon函数被执行')
}
var result = leon.bind()
console.log(result)
1.call()方法使用一个指定的this值和单独给出的一个或多个参数来调用一个函数。
2.apply()方法的作用和call()方法类似,区别就是call()方法接受的是参数列表,而apply()方法
接受的是一个参数数组。
3.bind()方法创建一个新的函数,在bind被调用时,这个新函数的this被指定为bind()的第一个参数,
而其余参数将作为新函数的参数,供调用时使用。
解析:就是说三个函数都会绑定this,不同之处的是apply接收的参数是一个数组。bind会返回一个新的
函数。具体在下面实现时更能体现。
具体绑定this以及this的指向问题,可以以后继续讨论。
参考:MDN
call
我们要自己实现call函数,也就是说我们可以这样写。
function foo() {
console.log('自己实现的call函数')
}
foo.cycall() //cycall就是我们自己实现的call函数 命名比较自由
这样可以调用foo函数,那么就实现成功了。
还需要知道一个东西:
Function.prototype.cycall = function() {
}
这样写就会让foo函数或者定义的其他函数本身都会有一个cycall属性。
可以理解为构造函数的原型。(如果这里不理解可以查阅资料理解面向对象,原型链相关)
(我应该还会有相关文章的后续更新)
现在就可以这样写
Function.prototype.cycall = function() {
console.log('cycall函数被调用了')
}
function foo() {
console.log('foo函数被执行')
}
foo.cycall()
// cycall函数被调用了
执行后的结果就说明了foo函数确实是有cycall这样一个属性的。
但是我们需要执行的是foo函数,也就是说我们需要在cycall函数内部实现foo函数被执行。
(这就是自己实现call函数,我们把call函数改个名字)
我们先想一下,我们可以把foo函数直接写到cycall函数里面执行: foo()
但是这样写是不具备通用性的。如果有一个新的函数想要调用cycall函数执行自身,并且绑定this。
那么就又需要把这个函数执行写道cycall函数里面。所以肯定不能这样写。
关键:所以我们要获取到的是哪个函数执行了cycall
需要注意的是我们是这样调用cycall函数的: foo.cycall()
这样调用是会给cycall函数隐式绑定this的。而这个this刚好就是调用cycall的函数。
看下一个代码块
Function.prototype.cycall = function() {
//获取到是哪个函数执行了cycall
//这里的this,也就是fn 其实就是调用了cycall的函数
//这里可以自己调试一下,已经可以执行调用cycall的函数了。
var fn = this
fn()
}
往下就是要实现绑定this
Function.prototype.cycall = function(thisArg) {
var fn = this
//我们可以这样写
//这里看了可能有些懵逼 我尽量解释清楚
//thisArg.fn = fn 这样做thisArg对象(下一个代码块有边界处理)上面就有fn这个属性
//这里的thisArg就是我们要传入的需要被绑定的绑this参数
//那当thisArg.fn()这样直接执行 不就是相当于给fn隐式绑定了参数thisArg嘛。
thisArg.fn = fn
thisArg.fn()
//我们这样写会给thisArg上面添加了一个属性fn 所以要删掉
delete thisArg.fn
//这里其实也可以直接执行下面这行代码
//fn.call(thisArg)
//这样也可以绑定this,但是这样又好像使用了本身的call函数,所以没必要这样写
}
但是又有一个问题,当thisArg这个参数不是一个对象,比如说thisArg传入一个数字,这样是会报错的。
因为数字上面是不能添加属性的(1.fn)
所以这里需要把数字,布尔类型,字符串转换为可以添加属性的相关类型:Object(number/string/boolean)
还有一个边界情况,当传入的是null或者undefined,源call函数的this会指向window。 所以还需要做一些处理。
thisArg = (thisArg !== undefined && thisArg !== null) ? Object(thisArg) : window
接下来需要用到一个ES6的语法,剩余参数。 function sum(...nums) {} 这样写参数是可以传入多个参数的,它是一个数组类型。
Function.prototype.cycall = function(thisArg, ...args) {
//获取需要被执行的函数
var fn = this;
//对thisArg转换成对象类型(防止传入的是非对象类型)
thisArg = Object(thisArg)
//调用需要被执行的函数
thisArg.fn = fn
var result = thisArg.fn(...args) //展开运算符 这里和剩余参数是不一样的
delete thisArg
//将最终结果返回
return result
}
最后测试一下
function sum(num1, num2) {
console.log(this, num1, num2);
return num1 + num2
}
var result = sum.cycall('abc', 20, 30)
console.log(result);
到这里call函数的实现就完成啦。如果有什么错误,欢迎指正讨论。