JavaScript call 的原理
什么是 call 方法?
Function.prototype.call 允许调用一个函数,显式的指定执行上下文
js
function.call(thisArg, arg1, arg2, ...)说明:
thisArg: 执行时传入的this的值。如果传入null或undefined, 在非严格模式下this会指向全局对象(浏览器中是window,Node.js 中 是global);严格模式下,this就是传入的值。arg1,arg2是传递给函数的参数。
判断数据类型示例
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
}实现过程说明
- 处理
thisArg- 如果
thisArg是null或者undefined时,将其设置为全局对象 - 使用
Object将基本数据类型(数字、字符串)其转成对象,确保可以添加属性
- 如果
- 绑定函数
this是调用myCall的函数(即要执行的函数)- 将临时函数绑定到
thisArg上(使用Symbol确保不会和其他键名冲突)
- 执行函数
- 通过
thisArg[fnKey](...args)执行函数,执行时的this指向了thisArg,达到了切换函数执行时上下文;参数通过拓展运算符...args传递
- 通过
- 清理和返回
- 删除临时属性
- 返回函数执行结果
使用示例
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
}