首先我们要知道call、apply、bind的作用都是用来改变this的指向的,可以从以下几点来考虑如何实现这几个函数:
- 不传入第一个参数,那么上下文默认是window
- 改变了this指向,让新的对象可以执行该函数
实现call
// 通过配置默认参数的方式,第一个参数如果不传,默认为window Function.prototype.myCall = function (context = window) { // 上下文校验 if (typeof this !== 'function') { throw new TypeError('Errors') } // 改变this指向 context.fn = this // 截取参数 const args = [...arguments].slice(1) // 通过新的上下文调用函数并得到结果 const result = context.fn(...args) // 删除临时绑定在context上的方法 delete context.fn return result } function foo(x, y) { console.log(this, this.a * x * y) } obj = { a: 1, b: 2 } foo.myCall(obj, 2, 3)
以下是对实现的分析:
- 首先context为可选参数,如果不传,默认上下文为window
- 接下来给context创建一个fn属性,并将值设置为需要调用的函数
- 因为call可以传入多个参数作为调用函数的参数,所以需要将参数剥离出来
- 然后调用函数并将对象上的函数删除
以上就是call的实现思路了
apply
apply的实现和call类似,区别在于对参数的处理,call是可以接受多个参数,而apply是接受一个数组作为第二个参数,将数组中的项作为函数的参数。
Function.prototype.myApply = function(context) { if (typeof this !== 'function') { throw new TypeError('Error') } 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
Function.prototype.myBind = function (context = window) { if (typeof this !== 'function') { throw new TypeError('Error') } const _this = this /* if (arguments[1]) { args = arguments[1] } else { args = [] } */ const args = [...arguments].slice(1) return function F () { // 因为返回了一个函数,所以可能存在用new调用的情况,所以需要判断 if (this instanceof F) { return new _this(...args, ...arguments) } return _this.apply(context, arsg.concat(...arguments)) } }
以下是对实现的分析:
- bind返回了一个函数,对于函数来说又两种方式调用,一种是直接调用,一种是通过new的方式
- 对于直接调用来说,这里选择了apply的方式实现,但是对于参数需要注意以下情况:因为bind可以实现类似这样的代码f.bind(obj, 1)(2), 所以我们需要将两边的参数拼接起来,于是就有了这样的实现args.concat(...arguments)
- 对于通过new的方式,因为new调用函数是不会被任何方式改变this的,所以要忽略传入的this, 用最外层保存的_this