今年到处看到关于手写bind函数的话题,似乎面试题也趋于跟风形式。这让我有种感觉,那就是不面试的问题都不会,只要扯上面试的话,那么问题就不是问题了。不禁感叹,前端要学的越来越多了~
不扯太多,我也来试着实现一下bind,不过我觉得call和apply也可以实现下。
实现 call
关于call函数的作用,我觉得不需要太多赘述,只要关注两点:
- 改变this指向
- 直接调用函数
代码实现:1
2
3
4
5
6
7
8Function.prototype.myCall = function(context) {
context = context || window // call函数第一个参数为this指向目标,若不传,指向window
context.fn = this // 将要执行的函数赋值于context对象的fn属性,这样调用函数时,内部this就指向了context
const args = [...arguments].slice(1) // 获取参数
const result = context.fn(...args) // call函数会调用原函数,所以我们也调用此函数,注意此时函数this指向了context
delete context.fn // 删除context对象的fn属性
return result // 调用结果返回
}
实现 apply
apply和call的区别只在于参数的传递方式,apply是以数组形式传入参数,代码实现:1
2
3
4
5
6
7
8
9
10
11
12
13Function.prototype.myApply = function(context) {
context = context || window
context.fn = this
let result
// 与call 的区别只在于参数传递方式
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
实现 bind
bind的实现稍微复杂点,它与前两个的主要区别在于:它不会调用函数,只改变了this指向,并且返回函数。因为返回了函数,那么就可以使用new方式来调用,我们先来看看原生bind的使用结果:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16var obj = {
a: 2
}
function test() {
console.log(this)
}
test.prototype.hello = function() {
console.log('hello')
}
t = test.bind(obj)
t() // {a:2}
f = new t()
f.hello // 使用new调用,关键在于实例可以访问原函数原型上的hello方法
那么,我们要做的有两步,第一是改变this指向,第二是new出来的实例继承原型,代码实现:1
2
3
4
5
6
7
8
9
10
11
12Function.prototype.myBind = function(context, ...args1) {
context = context || window
const _this = this
// 要返回的函数,内部直接借用apply改变this指向
function F(...args2) {
_this.apply(context, [...args1, ...args2])
}
F.prototype = Object.create(this.prototype) // 返回的函数的原型继承原函数的原型
// F.prototype.contructor = this // 此处可以不写,思考为什么?
return F
}
注意上述代码中的args1和args2,因为bind函数调用情况可能是这样test.bind(obj,1)(2),所以为了好理解,我将两个参数列表都表示出来,最后参数是要合并的。
结束
以上就是三种函数的实现方式,只是基于原理的简单实现。面试只是一个小目的,最关键的还是学好JavaScript这门语言啊。