【JavaScript】手写call/apply/bind

178 阅读2分钟

一、手写call

原理:由于函数的this指向它的直接调用者,我们变更调用者即完成this指向的变更

举个例子:

function foo(str){
	console.log(str + this.name)
}

const obj = {
	name: "liang"
}

obj.foo = foo
obj.foo("")  //这步操作变让obj成为了调用foo方法的调用者了,最后打印出来的就是liang

因此,我们可以开始写call()方法了,

Function.prototype.myCall = function(thisArg, ...args){
	thisArg.fn = this              // this指向调用call的对象,即我们要改变this指向的函数
    return thisArg.fn(...args)     // 因为call方法是会立即执行的,因此执行函数并return其执行结果
}

上面已经简单的实现了一个call方法了,接下来我们需要把他变得更加准确:

Function.prototype.myCall = function(thisArg, ...args) {
    const fn = Symbol('fn')        // 声明一个独有的Symbol属性, 防止fn覆盖已有属性
    thisArg = thisArg || window    // 若没有传入this, 默认绑定window对象
    thisArg[fn] = this              // this指向调用call的对象,即我们要改变this指向的函数
    const result = thisArg[fn](...args)  // 执行当前函数
    delete thisArg[fn]              // 删除我们声明的fn属性
    return result                  // 返回函数执行结果
}

//测试
foo.myCall(obj,"name")     // 输出'nameliang'

二、手写apply

apply()和call()类似,区别在于call()接收参数列表,而apply()接收一个参数数组,所以我们在call()的实现上简单改一下入参形式即可

Function.prototype.myCall = function(thisArg, argsArr) {
    const fn = Symbol('fn')        // 声明一个独有的Symbol属性, 防止fn覆盖已有属性
    thisArg = thisArg || window    // 若没有传入this, 默认绑定window对象
    thisArg[fn] = this              // this指向调用call的对象,即我们要改变this指向的函数
    const result = thisArg[fn](...argsArr)  // 执行当前函数
    delete thisArg[fn]              // 删除我们声明的fn属性
    return result                  // 返回函数执行结果
}

//测试
foo.myCall(obj,["name"])     // 输出'nameliang'

三、手写bind

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用

从用法上看,似乎给call/apply包一层function就实现了bind():

Function.prototype.myBind = function(thisArg, ...args) {
    return () => {
        this.apply(thisArg, args)
    }
}

但是:

  • bind()除了this还接收其他参数,bind()返回的函数也接收参数,这两部分的参数都要传给返回的函数
  • new会改变this指向:如果bind绑定后的函数被new了,那么this指向会发生改变,指向当前函数的实例
  • 没有保留原函数在原型链上的属性和方法

因此,我们需要像这样写:

Function.prototype.myBind = function (thisArg, ...args) {
    var self = this
    // new优先级
    var fbound = function () {
        self.apply(this instanceof self ? this : thisArg, args.concat(Array.prototype.slice.call(arguments)))
    }
    // 继承原型上的属性和方法
    fbound.prototype = Object.create(self.prototype);

    return fbound;
}
//测试
const obj = { name: 'liang' }
function foo() {
    console.log(this.name)
    console.log(arguments)
}

foo.myBind(obj, 'a', 'b', 'c')()  //liang   ['a', 'b', 'c']

以上就是我们手写的三种改变this指向的方法了