Bind function without thisArg
Bind is just a wrapper function provided by the engine. You can always create your own:
function myBind(fn, ...args) {
let retval = Object.defineProperties(function(...a) {
console.assert(typeof(fn) == "function");
return fn(...args, ...a)
}, {
name: {
configurable: true,
value: fn.name
},
length: {
configurable: true,
value: fn.length
},
prototype: {
configurable: true,
writable: true,
value: fn.prototype
}
});
}
This should apply your bound arguments before any arguments supplied by the caller without affecting the context object.
oops.... forgot to return retval!
var foo = function(a) { console.assert(this === obj) };
var obj = { foo() { return foo.apply(this, arguments); } };
?
The use case was to use bind to avoid closures such that "foo" is attached to an object on demand.
var obj = {} // some place else obj.foo = fn.bind(, 1, 2, 3)
Where the function "fn" relies on this being the "obj" it is attached to, this considering that calling a bound functions with:
fn.call(thisArg)
Doesn't change the thisArg.
Although this is easy enough to implement in userland, frankly so was
bind
. I'd say it comes up often enough to consider. PrototypeJS (remember
PrototypeJS?) called it curry
.
I'd think the specification changes for it would be minimal as well,
basically A) Allowing Empty as boundThis in BoundFunctionCreate and
[[BoundThis]] on functions; B) Updating [[Call]] on Bound Functions to use
thisArgument when boundThis is Empty; creating a new Function.prototype
method (curry
would be the obvious -- and almost certainly not
websafe -- name) to call BoundFunctionCreate with Empty for boundThis.
But I'd be interested to know how difficult it is for JavaScript engines to
implement, since Empty is largely a spec-level concept and it's valid (in
strict mode) to use null
and undefined
as boundThis...
-- T.J. Crowder
It's not so easy to implement in user land, not because it's difficult to do so:
Function.prototype.bindArgs = (...args) => {
const fn = this;
return function (...moar) {
return fn.apply(this, args.concat(moar));
};
};
rather because of the bloat it produces once transpiled: bit.ly/2SULtFZ
To avoid bloat and arguments
leaking here and there, one must write
something like:
function slice() {
const arr = [];
for (let {length} = arguments, i = +this; i < length; i++)
arr.push(arguments[i]);
return arr;
}
Function.prototype.bindArgs = function () {
const args = slice.apply(0, arguments);
return function () {
const moar = slice.apply(0, arguments);
return fn.apply(this, args.concat(moar));
};
};
which is still a hell of a boilerplate compared to a native const ba = fn.bindArgs.apply(fn, arguments);
or fn.bindArgs(...arguments)
for short.
+1 for having this in core
On 1/14/19 9:47 AM, Andrea Giammarchi wrote:
rather because of the bloat it produces once transpiled:
Hold on.
The transpiling is necessary because of lack of what features in implementations?
And what is the set of implementations that will lack those features but support bindArgs?
(None of that is an argument against having bindArgs in core per se; I'm just trying to understand the transpiling issue here.)
The transpilation is what everyone publishing Web Apps/pages do these days, it's not needed but most developers don't care, they mostly use whatever bundler default is there.
The main lack of ECMAScript feature is a method that is not based on new syntax, doesn't leak arguments, and transform these into an Array.
My proposed slice.apply(index, arguments)
is still not used by
transopilers to solve forever in one place the issue so that production
code is full of unnecessary boilerplate every single time some dev uses
rest parameters for any callback.
If there was a method, not based on new syntax, able to be called through
fn.apply(fn, arguments)
so that arguments wouldn't leak, and transpilers
can use a single polyfill as entry point to provide that ability, no extra
boilerplate would be ever need in production code.
I hope what I've said makes sense 👋
Consider the following example:
var foo = (function(a) { console.assert(this === obj) }).bind(undefined, 1) var obj = {foo: foo}
Calling foo from obj:
obj.foo(1)
Would result in an assertion. How does one go about preserving the this reference of the caller. That is i want to use .bind to only bind "arguments" and not "thisArg".