javascript bind函数的实现
既然讲到bind,我们就不得不说call 和 apply 。在Javascript中,涉及到函数式语言风格的代码,都离不开call 和apply。那么我们在讲bind之前,就先好好分析一下call 和 apply。
call
和 apply
方法是ECMAScript 3 给Function 的原型定义的2个方法。分别是Function.prototype.call 和 Function.prototype.apply。
1: call 和apply 的作用
call 的mdn定义: call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
apply的mdn定义:apply()方法调用一个具有给定
this值的函数,以及作为一个数组
(或类似数组对象)提供的参数
。
通过以上的定义,我们可以看出,call 和 apply 的相同点都是执行一个函数,并指定this。
唯一的区别就是:
- apply 函数第二个参数为一个带下标的集合,这个集合可以是数组,也可以是类数组。apply 把这个集合中的元素作为参数传递给被调用的函数。
- call 函数的参数数量不固定,从第一个参数以后, 每一个参数被依次传入函数。
请看下面2个🌰
var func = function (a,b,c) {
console.log([a,b,c]) // 输出 [1,2,3]
}
func.apply(null, [1,2,3])
var func = function (a,b,c) {
console.log([a,b,c]) // 输出[1,2,3]
}
func.call(null, 1,2,3)
其实Javascript参数在内部就是用一个类数组来表示的。我们可以通过arguments类数组来接受。call其实是包装了apply上的一个语法糖。如果我们明确的知道函数接受多少个参数,而且想一目了然的表达形参和实参的对应关系,可以使用call 来传递参数。
有一点需要注意的是:当使用call 或apply时,第一个参数为null, 函数体内的this会指向默认的宿主对象。
// 在浏览器环境下
var func = function (a,b,c) {
console.log(this === window) // true
}
func.apply(null, [1,2,3])
Math.min.apply(null, [1,2,3,34,4]) // 1
2: 说清楚apply和call ,我们开始讲讲bind。
bind 用来改变this的指向,并返回一个改变了this指向的新函数。
我们首先实现一个比较简单的bind。
Function.prototype.bind = function (context) {
// this 指的是需要绑定的函数
let self = this
return function () {
// context 指需要绑定的this
return self.apply(context, arguments)
}
}
来一个🌰演示一下
let obj = {name: 'jack'}
var func = function () {
console.log(this.name) // jack
}.bind(obj)
func()
以上代码的实现原理是,我们先把func函数的引用保存了起来。然后返回一个新函数。当我们将来执行func函数时。实际上是执行这个返回的新函数。在新函数内部,我们执行了self.apply(context, arguments)这一句才是执行了原来func函数。并指定了context为func函数体内的this。
说清楚了原理,我们实现一个更完善的bind实现:
Function.prototype.bind = function () {
let self = this
// 获取bind第一个参数,即需要绑定的对象。
context = [].shift.call(arguments)
// 获取bind剩余的参数,并转换为数组
args = [].slice.call(arguments)
// 返回一个函数,当执行的时候,执行self.apply
return function () {
// arguments 是执行返回的函数时,传入的参数。
return self.apply(context, [].concat.call(args, [].slice.call(arguments)))
}
}
我们再给一个🌰演示下:
var obj = {
name: 'jack',
}
var func = function (a,b,c,d) {
console.log(this.name) // jack
console.log([a,b,c,d]) // 输出 [1,2,3,4]
}.bind(obj, 1,2)
func(3,4)
我们用es6 来改造下这个写法:
Function.prototype.bind = function (context, ...res) {
let self = this
// 获取bind第一个参数,即需要绑定的对象。
context = context
// 获取bind剩余的参数,并转换为数组
args = res
// 返回一个函数,当执行的时候,执行self.apply
return function () {
// arguments 是执行返回的函数时,传入的参数。
return self.apply(context, [...args, ...arguments])
}
}
几行代码就可以解决,是不是简洁了许多😄