js call简单实现,fn.call.call调用原理

1,255 阅读2分钟

call简单实现及原理解析

call强制转换this指向,以便访问其他作用域

let obj = {
    name: 'obj'
}

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

foo.call(obj) // obj

call的几种操作

let obj = {
    name: 'obj'
}

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

function foo2(){
    console.log('foo2')
}

function bar(){
    console.log('bar)
}

foo.call(obj) // obj
foo2.call() // foo2
foo2.call(bar) // foo2
foo2.call(1) // foo2
foo2.call(false) // foo2
foo2.call('string') // foo2
foo2.call.call(bar) // bar
foo2.call.call(1) // TypeError: foo2.call.call is not a function
foo2.call.call() // TypeError: foo2.call.call is not a function
foo2.call.call(obj) // TypeError: foo2.call.call is not a function

从上面的测试可以发现

  • 直接调用call接收引用类型数据,当传入原始类型时无效但不会报错
  • 链式调用call如果传入原始值或者不传或者传的非函数会报错
    • 说明链式调用需要接收一个函数

手动实现一个call

思路:

  • 首先判断第一个参数(context)是否存在
    • 否,使用globalThis(es11新增,浏览器可用window)
    • 是,如果是基本类型,转成对象
  • 把this赋值给context.fn
  • 执行context.fn把剩余参数传入,此过程是把this指向目标对象
  • 删除fn
  • 返回执行结果

实现

Function.prototype.call = function (context, ...rest) {
    context = context ? Object(context) : globalThis || window; 
    context.fn = this
    
    if(typeof context.fn !== 'function'){
        throw new TypeError('fn is not a function')
    }

    let ret = context.fn(...rest)
    delete context.fn
    return ret
}

call执行原理解析

  • call第一个参数接收this指向的目标对象(context),剩下的是一堆参数;
  • 第一个参数必须是引用类型比如functon,array,object
  • 这样方便给context添加属性fn函数,把调用函数foo赋到context.fn上执行,使其能访问context作用的属性,也就是绑定了this指向;
  • 如果参数是原始值,那么把其转为对象Object(context)
  • 非第一个参数作为fn的参数
  • 如果没传参数,分两种情况
    • A 函数直接调用call -此时context默认为window -call内部实际调用window.foo
    • B foo.call.call(bar)
      • 由于Function原型上有call方法,所以函数都可以调用call
      • 无论调用几次call实际只最后的执行call.call()
      • 内部分两种情况
        • 如果call存在context 且必须为函数
          • 此时context = bar; ocntext.fn = call; 执行bar.call()内部走A
        • 若果context不存在或非函数,需要执行两次
          • 第一次
            • 此时context = window; context.fn = call; 执行了window.call();执行第二次call
          • 第二次
            • 此时context = window; context.fn = window(fn此时为一个对象而不是函数)
            • 执行context.fn()是会报fn不是函数
  • 删除fn
  • 返回执行结果