原文博客地址 欢迎讨论、star
这三个函数存在于函数的原型链上,call和apply用来改变函数的this指向,也就是执行上下文对象。bind函数也是用来修改执行上下文对象,只是它返回一个修改后的一个新函数,不会自动执行。
call
首先看一下这样的代码:
let obj = {
a: 1
}
function fn() {
console.log("@@ this.a", this.a)
}
obj.fn = fn
obj.fn() // 1
如上可以看到,如果单独去执行fn函数的时候,this是指向默认执行window对象的。当把fn挂载到obj上,然后通过obj去访问的时候,fn这时候的this就会指向obj了。这种情况是基本知识,应该都是知道的。那么重点就是利用这种方式来模拟call的实现。
function call(fn, obj, ...args) {
if (typeof fn !== 'function') {
return
}
let ctx = obj || {}
let key = Symbol()
ctx[key] = fn
let rst = ctx[key](...args)
delete ctx[key]
return rst
}
- 首先判断传入的
fn是否为函数 - 如果传入的
obj为空,这里其实可以默认为window,默认为一个对象是为了好让fn绑定到这个对象的某一个属性上。 - 使用
Symbol创建一个不会重复的key。 - 将
fn绑定到对象上,然后执行得到结果,删除刚添加的属性,将值返回。
以上就是怎么实现call的思路了,至于apply的实现,几乎就是一样的,只是因为传入的参数有所不同,所以在执行传参的时候差别,如下:
function apply(fn, obj, args){
...
if (!Array.isArray(args)) {
args = [args]
}
...
let rst = ctx[key](...args)
...
}
bind
主要重点就是bind是返回一个新的函数,并且可以绑定传入参数。
function bind(fn, obj, ...args1) {
if (typeof fn !== 'function') {
return fn
}
let self = obj || {}
return function (...args2) {
return fn.apply(self, [].concat(args1).concat(args2))
}
}
- 返回一个函数形成一个闭包,将传入的
obj进行一下判断复制。 - 调用
fn的apply方法修改this指向,然后拼接两次传入的参数为一个数组。注意这里的拼接顺序。
对于bind函数,网上还有对fn函数为构造函数时候的情况进行判断。主要是使用判断当使用new关键字的时候再去new fn(...args)。
function bind(fn, obj, ...args1) {
...
return function bindFn(...args2) {
// 判断是否用于构造函数
if (this instanceof bindFn) {
return new self(...args1, ...args2)
}
...
}
}
小结
理解了这三个函数的作用,就可以利用this指向的原理去模拟这些方法,以上都用使用es6的一些方法,代码看起来简洁不少。
参考文章 awesome-coding-js