Skip to content

JavaScript call 的原理

什么是 call 方法?

Function.prototype.call 允许调用一个函数,显式的指定执行上下文

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

说明

  • thisArg: 执行时传入的 this 的值。如果传入 nullundefined, 在非严格模式下 this 会指向全局对象(浏览器中是window,Node.js 中 是 global);严格模式下,this 就是传入的值。
  • arg1arg2 是传递给函数的参数。

判断数据类型示例

js
Object.prototype.toString.call(null).slice(8, -1) // 'Null'
Object.prototype.toString.call([]).slice(8, -1) // 'Array'

call 的实现原理

js
Function.prototype.myCall = function(thisArg, ...args) {
  // 非函数调用处理
  if (typeof this !== 'function') {
    throw new TypeError("myCall must be called on a function");
  }
  // 判断是否 null 或者 undefined
  if (thisArg == null) {
    thisArg = typeof window !== 'undefined' ? window : global;
  }

  // 确保 thisArg 是对象
  thisArg = Object(thisArg)
  // 生成临时函数的key, 使用 Symbol 确保不和其他属性撞车
  const fnKey = Symbol('tempFn')
  // 将函数调用时的 this 绑定到 thisArg 上,这时 this 指向的是要执行函数
  thisArg[fnKey] = this
  // 执行函数并拿到返回结果
  const result = thisArg[fnKey](...args)
  // 删除临时属性
  delete thisArg[fnKey]
  // 返回函数执行结果
  return result
}

实现过程说明

  1. 处理 thisArg
    • 如果 thisArgnull 或者 undefined 时,将其设置为全局对象
    • 使用 Object 将基本数据类型(数字、字符串)其转成对象,确保可以添加属性
  2. 绑定函数
    • this 是调用 myCall 的函数(即要执行的函数)
    • 将临时函数绑定到 thisArg 上(使用 Symbol 确保不会和其他键名冲突)
  3. 执行函数
    • 通过 thisArg[fnKey](...args) 执行函数,执行时的 this 指向了 thisArg,达到了切换函数执行时上下文;参数通过拓展运算符 ...args 传递
  4. 清理和返回
    • 删除临时属性
    • 返回函数执行结果

使用示例

js
const person = { name: 'Alice' }
function greet(greeting, end) {
  console.log(`${greeting}, ${this.name}${end}`)
}

greet.myCall(person, 'Hi', '!')

测试 null 上下文

js
const person = { name: 'Alice' }
function greet(greeting, end) {
  console.log(`${greeting}, ${this.name}${end}`)
}

greet.myCall(person, 'Hello', '!')

// null 作为上下文
greet.myCall(null, 'Hi', '?') // 输出 Hi, ?

严格模式下的实现

js
Function.prototype.myCall = function(thisArg, ...args) {
  'use strict';
  // 创建key, 严格模式下,thisArg 无需做强制转换成对象的处理
  const fnKey = Symbol('tempFn')

  // 临时存储上下文, null 或者 undefined 使用全局对象作为上下文
  const context = thisArg ?? globalThis

  // 绑定函数
  context[fnKey] = this

  // 执行函数
  const result = context[fnKey](...args)
  // 删除临时属性
  delete context[fnKey]
  // 返回结果
  return result
}