myCall
题目描述
实现一个 myCall 方法来模拟 Function.prototype.call 的功能:
- 可以修改函数的
this指向 - 支持传递任意数量的参数
- 需要遵循 ECMAScript 规范的非严格模式逻辑
thisArg为null或undefined时指向window- 原始值会被包装成对象
本题考查 this 绑定机制、函数调用原理 和 ECMAScript 规范理解。
核心知识点
1. Function.prototype.call 原理
- 作用: 调用一个函数,同时指定函数运行时的
this值 - 语法:
func.call(thisArg, arg1, arg2, ...) - 特点: 立即执行函数,不同于
bind的延迟执行
2. this 绑定规则(非严格模式)
- null/undefined: 绑定到全局对象 (
window) - 原始值: 自动装箱为包装对象
- 对象: 直接使用该对象作为
this - 函数: 作为对象使用
3. Symbol 的巧妙运用
- 避免属性冲突: 使用 Symbol 作为临时属性名
- 保证唯一性: 每次调用生成新的 Symbol
- 安全清理: 调用完成后删除临时属性
代码实现
javascript
Function.prototype.mycall = function (thisArg, ...args) {
const key = Symbol(1)
if (thisArg === void 0 || thisArg === null)
thisArg = window
else if (typeof thisArg !== 'object')
thisArg = new Object(thisArg)
thisArg[key] = this
const result = thisArg[key](...args)
delete thisArg[key]
return result
}关键技术点
1. thisArg 处理的细节
javascript
// 严格区分各种情况
if (thisArg === void 0 || thisArg === null) {
// undefined 和 null 的处理
thisArg = window
}
else if (typeof thisArg !== 'object') {
// 原始值的自动装箱
thisArg = new Object(thisArg)
}
// 具体的装箱示例
new Object(123) // Number {123}
new Object('abc') // String {'abc'}
new Object(true) // Boolean {true}
new Object(Symbol('s')) // Symbol {Symbol(s)}2. Symbol 防冲突机制
javascript
// 为什么使用 Symbol?
const key = Symbol() // 保证属性名唯一
thisArg[key] = this // 不会覆盖已有属性
// 对比字符串属性名的风险
thisArg.tempMethod = this // 可能覆盖已有属性3. 参数传递技巧
javascript
// 使用展开运算符处理任意数量参数
function myCall(thisArg, ...args) {
// args 是一个数组,包含所有剩余参数
const result = thisArg[key](...args) // 展开传递
return result
}4. 常见陷阱和坑点
- 忘记处理 null/undefined: 导致类型错误
- 不使用 Symbol: 可能覆盖对象原有属性
- 忘记清理临时属性: 造成内存泄漏和意外行为
- 严格模式处理: 题目要求非严格模式的实现
- 返回值遗漏: 必须返回函数执行结果
使用示例
javascript
// 基本用法
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`
}
const person = { name: 'Alice' }
// 原生 call
console.log(greet.call(person, 'Hello', '!')) // "Hello, Alice!"
// 自实现 myCall
console.log(greet.mycall(person, 'Hello', '!')) // "Hello, Alice!"
// 测试 thisArg 为 null
function showThis() {
console.log(this === window) // 非严格模式下为 true
}
showThis.mycall(null)
// 测试原始值自动装箱
function checkType() {
console.log(typeof this) // object
console.log(this.valueOf()) // 123
}
checkType.mycall(123)
// 测试复杂场景
const calculator = {
base: 10,
add(a, b) {
return this.base + a + b
},
}
const newBase = { base: 20 }
console.log(calculator.add.mycall(newBase, 5, 3)) // 28
// 测试箭头函数(注意:箭头函数不能改变 this)
function arrowFunc() {
console.log(this === window) // 始终为 true(在全局作用域)
}
arrowFunc.mycall(person) // this 不会改变扩展思考
1. apply 方法实现
javascript
Function.prototype.myApply = function (thisArg, argsArray) {
const key = Symbol()
if (thisArg === void 0 || thisArg === null) {
thisArg = window
}
else if (typeof thisArg !== 'object') {
thisArg = new Object(thisArg)
}
thisArg[key] = this
// 处理参数数组
let result
if (argsArray === void 0 || argsArray === null) {
result = thisArg[key]()
}
else {
// 确保 argsArray 是类数组对象
result = thisArg[key](...Array.from(argsArray))
}
delete thisArg[key]
return result
}2. bind 方法实现
javascript
Function.prototype.myBind = function (thisArg, ...bindArgs) {
const fn = this
return function boundFunction(...callArgs) {
// 判断是否是 new 调用
if (this instanceof boundFunction) {
// new 调用时,this 指向新创建的对象
return new fn(...bindArgs, ...callArgs)
}
else {
// 普通调用,使用 myCall
return fn.mycall(thisArg, ...bindArgs, ...callArgs)
}
}
}3. 严格模式版本
javascript
Function.prototype.myCallStrict = function(thisArg, ...args) {
'use strict'
const key = Symbol()
// 严格模式下不进行 thisArg 转换
if (thisArg === void 0 || thisArg === null) {
// 严格模式下保持 null/undefined
thisArg = thisArg
}
// 不进行原始值装箱
thisArg[key] = this
const result = thisArg[key](...args)
delete thisArg[key]
return result
}4. 性能优化版本
javascript
// 避免重复创建 Symbol
const tempSymbol = Symbol('myCall')
Function.prototype.myCallOptimized = function (thisArg, ...args) {
if (thisArg === void 0 || thisArg === null) {
thisArg = window
}
else if (typeof thisArg !== 'object') {
thisArg = new Object(thisArg)
}
// 检查是否已有该属性
const hasOwnProperty = thisArg.hasOwnProperty(tempSymbol)
const oldValue = thisArg[tempSymbol]
thisArg[tempSymbol] = this
const result = thisArg[tempSymbol](...args)
// 恢复原状态
if (hasOwnProperty) {
thisArg[tempSymbol] = oldValue
}
else {
delete thisArg[tempSymbol]
}
return result
}