手写apply call bind

108 阅读3分钟

1 动机

最近在面试,面试官让我写写apply call bind,没写出来就很尴尬。网上写法比较多,自己整理然后写了一些思路比较清晰的写法。

2 call

用法:

function.call(thisArg, arg1, arg2, ...)

其中thisArg是要绑定的this的值,在非严格模式下如果传入的是undefined或者null就会指向全局对象。arg1就是传入的参数。

返回值:

使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined

代码: 基于此,设计了如下的call函数的简单版本,尽量不使用es6里面的语法,分别就是解析传入的thiscontext,然后将函数挂载在这个context对象上,这样函数里面遇到this相关的取值就会从context上面去找了

Function.prototype.call2 = function (context) {
    // context就是传入的thisArg
    context = context || window
    // this就是调用的function
    context.fn = this 
    // 收集参数
    const arr = []
    for (let i=1; i<arguments.length; i++) {
        arr.push(arguments[i])
    }
    // 执行函数
    const result = eval('context.fn('+arr+')')
    delete context.fn
    return result
}

但是这样还是不够,另外考虑context可能不是对象,还有就是contextfn被占用的情况。

Function.prototype.call2 = function (context) {
    context = context || window
    if(typeof(context) !== 'object') {
        context = Object(context)
    }
    let rand = 'fn'+Math.ceil(Math.random()*10000)
    while(context[rand] === undefined){
        // this就是调用的function
        context[rand] = this 
    }
    // 收集参数
    const arr = []
    for (let i=1; i<arguments.length; i++) {
        arr.push(arguments[i])
    }
    // 执行函数
    const result = eval('context[rand]('+arr+')')
    delete context.fn
    return result
}

3 apply

apply是类似的,直接上代码

Function.prototype.apply2 = function(context,args){
    context = context || window
    if(typeof(context) !== Object) {
        context = Object(context)
    }
    // 新加fn
    let randomNumber = Math.ceil(Math.random)
    while(context['fn'+randomNumber] === undefined){
        context['fn'+randomNumber] =  this
    }
    const result = eval("context['fn'+randomNumber]("+args+")")
    delete context['fn'+randomNumber]
    return result
}

4 bind

根据文档,我们需要实现这几个功能

  • 接收若干个参数,其中第一个是要绑定的this对象,后面的部分接收的参数
  • 返回一个函数,接收的参数作为剩余部分的传入参数
  • bind返回的函数可以作为一个构造函数使用

前2个目标比较简单,先进行实现。整体的流程就是,读取传入的要绑定的this,在这里命名为context对象,读取args参数,然后返回一个函数bindFunction

在函数bindFunction,读取剩余参数为args2,然后直接执行函数,获取result,返回。

Function.prototype.bind2 = function(context) {
    context = context || window
    if(typeof(context) !== 'object') {
        context = Object(context)
    }
    let rand = 'fn'+Math.ceil(Math.random()*10000)
    while(context[rand] === undefined){
        context[rand] = this 
    }
    const args = []
    for(let i=1; i<arguments.length; i++){
        args.push(arguments[i])
    }

    const bindFunction = function() {
        const context2 = context
        const rand2 = rand
        const args2 = []
        for(let j=0; j<arguments.length; j++) {
            args2.push(arguments[j])
        }
        result = eval('context2[rand2]('+args.concat(args2)+')')
        return result
    }
    
    return bindFunction
} 

第三个目标要考虑两个部分,一个是作为构造函数的话,需要继承原型链,这里采取一个中间函数newFun来继承

const newFun = function(){}
newFun.prototype = context[rand].prototype
bindFunction.prototype = new newFun()

新加一个判断,当作为构造函数的时候,在运行语句里面加一个new

// 作为构造函数调用
if(new.target !== undefined){
    result = eval('new context2[rand2]('+args.concat(args2)+')')
// 作为普通函数调用
} else{
    result = eval('context2[rand2]('+args.concat(args2)+')')
}

那么完整的代码就是

Function.prototype.bind2 = function(context) {
    context = context || window
    if(typeof(context) !== 'object') {
        context = Object(context)
    }
    let rand = 'fn'+Math.ceil(Math.random()*10000)
    while(context[rand] === undefined){
        // this就是调用的function
        context[rand] = this 
    }
    const args = []
    for(let i=1; i<arguments.length; i++){
        args.push(arguments[i])
    }


    const bindFunction = function() {
        const context2 = context
        const rand2 = rand
        const args2 = []


        for(let j=0; j<arguments.length; j++) {
            args2.push(arguments[j])
        }
        // 作为构造函数调用
        if(new.target !== undefined){
            result = eval('new context2[rand2]('+args.concat(args2)+')')
        // 作为普通函数调用
        } else{
            result = eval('context2[rand2]('+args.concat(args2)+')')
        }

        return result
    }

    const newFun = function(){}
    newFun.prototype = context[rand].prototype
    bindFunction.prototype = new newFun()

    return bindFunction
} 

最后补几个测试用例

function add(a,b,c,d){
    console.log(this.age)
    return {sum : a+b+c+d}
}

const bd = {
    age:100861
}

const func1 = add.bind2(bd)
console.log(func1(3,4,88,8))

const b = new add(1,2,3,4)
console.log(b)

const func2 = add.bind(bd)
const c = new add(1,2,3,4)
console.log(c)


function sum1(a,b){
    this.sum = a+b
}
sum1.prototype.ssum = function(){
    console.log(this.sum,"jj")
}



// const math1 = new sum1(3,5)
const math2 = {name:10086}
const mathSum = sum1.bind2(math2)
const obj = new mathSum(2,3)

obj.ssum()
console.log(obj)