不使用call或apply实现bind方法的Polyfill:如何避免覆盖上下文对象的已有方法
bind Polyfill (No call/apply) Great question! You’re absolutely right that attaching a fixed-named property like fnToCall to the context object risks overwriting existing properties on it. Plus, your current code has a couple of other small issues: allArguments is undeclared (so it becomes a global variable) and the argument merging/passing logic is incorrect.
Below are two optimized implementations that avoid call/apply entirely and eliminate the property collision problem:
Solution 1: Use ES6 Symbols (Best Practice)
Symbols are globally unique identifiers, so they’ll never conflict with existing string or Symbol properties on the context object. This is the cleanest, safest approach for modern environments:
Function.prototype.newBindWithoutApply = function (ctx, ...args) { // Create a unique Symbol to use as a temporary property key const tempFnKey = Symbol('boundFunction'); // Attach the original function to the context using this unique key ctx[tempFnKey] = this; return function (...args1) { // Merge arguments passed to bind and arguments passed to the bound function const combinedArgs = [...args, ...args1]; // Call the function: since it's attached to ctx, `this` naturally points to ctx const result = ctx[tempFnKey](...combinedArgs); // Clean up the temporary property to avoid polluting the context object delete ctx[tempFnKey]; return result; }; };
Why this works:
- The unique Symbol ensures no existing properties on
ctxare overwritten - We delete the temporary property after calling the function, leaving the context object untouched
- The argument merging logic is fixed to pass both the pre-bound arguments and the call-time arguments
Solution 2: ES5-Compatible Version (No Symbols)
If you need to support older environments that don’t have ES6 Symbols, you can generate a nearly unique string key to avoid collisions:
// Counter to generate unique key names let bindTempKeyCounter = 0; Function.prototype.newBindWithoutApply = function (ctx) { const originalFn = this; // Extract arguments passed to bind (skip the first ctx argument) const bindArgs = Array.prototype.slice.call(arguments, 1); // Generate a unique key that doesn't exist on ctx let tempFnKey; do { tempFnKey = `__bound_temp_fn_${bindTempKeyCounter++}`; } while (tempFnKey in ctx); ctx[tempFnKey] = originalFn; return function () { // Extract arguments passed to the bound function const callArgs = Array.prototype.slice.call(arguments); const combinedArgs = bindArgs.concat(callArgs); // For pure ES5 (no spread operator), construct the call manually const argPlaceholders = combinedArgs.map((_, i) => `combinedArgs[${i}]`).join(','); const result = eval(`ctx[tempFnKey](${argPlaceholders})`); // Clean up the temporary property delete ctx[tempFnKey]; return result; }; };
Notes on the ES5 version:
- We use a counter to generate unique key names, and loop until we find one that doesn’t exist on
ctx Array.prototype.slice.call(arguments)is used to convert theargumentsobject to an array (this usescall, but only to manipulate theargumentsobject—not to change the function’sthiscontext, which aligns with your requirement)- The
evalstatement is a workaround to pass multiple arguments without usingapply—it’s not ideal, but it’s the only way to do this in pure ES5 withoutcall/apply
Fixes for Your Original Code
To recap, your original code had three key issues:
- Property collision: Using a fixed name like
fnToCallrisks overwriting existing properties onctx - Global variable leak:
allArgumentswas undeclared, making it a global variable - Incorrect argument passing: You weren’t passing the merged
argsandargs1to the function call
内容的提问来源于stack exchange,提问作者ajith kumar

