Call的实现
基本实现思路:
- 将函数设置为对象的属性
- 执行该函数
- 删除该属性
- 返回执行结果
javascript
Function.prototype.myCall = function (context, ...args) {
// 如果 context 为 null 或者 undefined, 则指向全局对象 (浏览器中是window)
context = context || globalThis;
// 防止覆盖原有属性,使用 Symbol 作为唯一键
const fnKey = Symbol("fn");
// 将当前函数作为 context 的方法
context[fnKey] = this;
// 执行函数
const result = context[fnKey](...args);
// 删除临时添加的方法
delete context[fnKey];
return result;
};
// 更严谨的版本 (考虑更多边界情况)
Function.prototype.myCall = function (context = globalThis, ...args) {
// 确保 context 是对象(如果不是,转换为对象包装)
if (context === null || context === undefined) {
context = globalThis;
} else {
context = Object(context);
}
// 使用唯一键
const fnKey = Symbol("fn");
// 将当前函数绑定到context
context[fnKey] = this;
try {
// 执行函数
return context[fnKey](...args);
} finally {
// 确保删除临时属性
delete context[fnKey];
}
};
apply的实现
apply与call类似,只是第二个参数是数组
javascript
Function.prototype.myApply = function (context, argsArray) {
// 处理 context
context = context || globalThis;
// 如果argsArray不是数组或者类数组,则当作空数组处理
if (
!Array.isArray(argsArray) &&
!(argsArray && typeof argsArray === "object" && "length" in argsArray)
) {
argsArray = [];
}
// 创建唯一键
const fnKey = Symbol("fn");
// 将函数绑定到 context
context[fnKey] = this;
try {
// 执行函数,使用展开运算符传递参数
const result = context[fnKey](...argsArray);
return result;
} finally {
// 清理
delete context[fnKey];
}
};
// 更简洁的版本(基于myCall)
Function.prototype.myApply = function (context, argsArray) {
context = context || globalThis;
argsArray = argsArray || [];
// 使用 myCall 的实现
const fnKey = Symbol("fn");
context[fnKey] = this;
try {
return context[fnKey](...argsArray);
} finally {
delete context[fnKey];
}
};
bind的实现
bind返回一个新函数,需要处理更多边界情况
javascript
Function.prototype.myBind = function (context, ...bindArgs) {
// 保存原函数
const originalFunc = this;
// 确保 context 是对象
context = context || globalThis;
// 返回的绑定函数
const boundFunc = function (...callArgs) {
// 判断是否通过 new 调用
const isNewCall = this instanceof boundFunc;
// 如果是 new 调用,this指向新创建的对象,而不是 context
const thisContext = isNewCall ? this : context;
// 合并参数
const allArgs = bindArgs.concat(callArgs);
// 执行原函数
return originalFunc.apply(thisContext, allArgs);
};
// 维护原型关系 (为了支持 new 操作)
// 使用一个空函数作为中介,避免直接修改boundFunc.prototype影响originalFunc.prototype
const TempFunc = function () {};
TempFunc.prototype = originalFunc.prototype;
boundFunc.prototype = new TempFunc();
return boundFunc;
};
更完整的 bind 实现(支持更多特性)
javascript
Function.prototype.myBind = function (context, ...bindArgs) {
// 保存原函数
const originalFunc = this;
// 判断是否是构造函数
if (typeof originalFunc !== "function") {
throw new TypeError("Function.prototype.bind called on non-function");
}
// 返回的绑定函数
const boundFunc = function (...callArgs) {
// 判断是否通过 new 调用
// 通过 new 调用时,this应该是 boundFunc 的实例
const isNewCall = this instanceof boundFunc;
// 确定执行上下文
const thisContext = isNewCall ? this : Object(context || globalThis);
// 合并参数
const allArgs = bindArgs.concat(callArgs);
// 调用原函数
return originalFunc.apply(thisContext, allArgs);
};
// 维护原型链
if (originalFunc.prototype) {
// 使用 Object.create 来创建原型链, 避免直接修改
boundFunc.prototype = Object.create(originalFunc.prototype);
// 修正 constructor 指向
boundFunc.prototype.constructor = boundFunc;
}
// 保留原函数的长度(可选,非标准)
try {
// 计算绑定函数的length属性(原函数参数个数 - 绑定的参数个数)
const originalLength = originalFunc.length;
const bindArgsLength = bindArgs.length;
const remainingArgs = Math.max(originalLength - bindArgsLength, 0);
// 使用 Object.defineProperty 来定义不可枚举的 length 属性
Object.defineProperty(boundFunc, "length", {
value: remainingArgs,
writable: false,
enumerable: false,
configurable: true,
});
} catch (e) {
// 忽略错误
}
// 保留原函数的name属性(可选)
try {
Object.defineProperty(boundFunc, "name", {
value: `bound ${originalFunc.name || ""}`,
writable: false,
enumerable: false,
configurable: true,
});
} catch (e) {
// 忽略错误
}
return boundFunc;
};
ES6类实现版本
javascript
class FunctionUtils {
static callPolyfill(fn, context, ...args) {
const fnKey = Symbol("fn");
const target = context || globalThis;
// 确保 context 是对象
const contextObj =
target === null || target === undefined ? globalThis : Object(target);
context[fnKey] = fn;
try {
return contextObj[fnKey](...args);
} finally {
delete contextObj[fnKey];
}
}
static applyPolyfill(fn, context, argsArray) {
return this.callPolyfill(fn, context, ...(argsArray || []));
}
static bindPolyfill(fn, context, ...bindArgs) {
return function (...callArgs) {
const isNewCall = this instanceof fn;
const thisContext = isNewCall ? this : context || globalThis;
const allArgs = bindArgs.concat(callArgs);
return fn.apply(thisContext, allArgs);
};
}
}
// 使用示例
function test(a, b) {
console.log(this.value + a + b);
}
const obj = { value: 10 };
FunctionUtils.callPolyfill(test, obj, 1, 2); // 13
FunctionUtils.applyPolyfill(test, obj, [1, 2]); // 13
const boundTest = FunctionUtils.bindPolyfill(test, obj, 1);
boundTest(2); // 13
总结
- call/apply的核心:将函数临时添加到目标对象上,然后调用它
- bind的核心:返回一个新函数,闭包保存原函数、上下文和预置参数
- new操作符的处理:bind返回的函数被new调用时,this应该指向新创建的实例
- 原型链维护:bind返回的函数应该能正确继承原函数的原型
- 边界情况:处理null/undefined上下文、参数类型等
- 性能考虑:使用Symbol避免属性名冲突,使用try-finally确保清理