Block scoped prototype extensions
One way to solve this might currently include extending Array
and
overridingindexOf
in a derived class to reflect the Option
behavior you're
after.
I tried that approach, but it doesn’t work when creating objects via literal notation ([], {}). A bit clumsy to have to write “new Array(1,2,3)", or “Array(1,2,3)” every time.
This seems like a difficult thing to do because the prototype chain is only objects, so mutating them for the context of a single scope isn't easily done. A few cases come to mind as extremely confusing:
- If you override the prototype in a scope then pass the object to another function inside the block, does it stay overridden when passed? What if you want to pass it as a normal array? Does that mean it needs to change the prototype back to default when calling the function, then change it back to your override again after?
- What if something captures a reference to the object and then the block ends? Does every usage of that object in the captured scope then need to re-mutate the object then change it back again? If not, this means using this behavior in callbacks and such isn't possible.
This approach encourages a huge amount of mutation, and it seems like it would be incredibly confusing for average users to have objects changing behavior out from under them.
Overriding literals with a derived class is an entirely different problem. :)
… which I would be very happy to discuss, nonetheless. It's indeed a little painful to work with derived built-ins if you can't use literals.
Could we maybe have a syntax that, for its current block, declares how to interpret literals? Maybe something along the lines of:
class MyArray extends Array {};
Array as MyArray;
const array = [1, 2, 3];
assert(array instanceof MyArray);
Tagged Template literals exist:
< developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals
You can wrap into a util function like that:
function customIndexOf() {
return 'quack'
}
function withCustomIndexOf(fun) {
const oldIndexOf = Array.prototype.indexOf
Array.prototype.indexOf = customIndexOf
fun()
Array.prototype.indexOf = oldIndexOf
}
withCustomIndexOf(() => {
console.log([123].indexOf()) // quack
})
console.log([123].indexOf()) // -1
Note, it will only work with sync code, async code (callbacks, promises) will ~probably~ be called after the reset of the prototype function
Edit: The problem with this solution is because it will actually change behavior down to the call stack, third party functions will have the behavior compromised
withCustomIndexOf(() => {
$('*').find('div') // may not work because will execute with the `customIndexOf` in Array.prototype instead of the default method
})
On Wed, Jul 5, 2017 at 7:10 PM, Boris Cherny <boris at performancejs.com> wrote:
The use case is similar to Scala’s block scoped implicits. In my application code I want Array.prototype.indexOf to return an Option<number>, rather than number | -1.
Why does it have to be called indexOf
? Why not fooIndexOf
, where foo
is a short app-specific prefix? Changing the meaning of indexOf
seems
like changing black to white and running the risk of getting killed at the
next zebra crossing.
On Wed, Jul 5, 2017 at 7:42 PM, Boris Cherny <boris at performancejs.com> wrote:
I tried that approach, but it doesn’t work when creating objects via literal notation ([], {}). A bit clumsy to have to write “new Array(1,2,3)", or “Array(1,2,3)” every time.
Surely not too bad if you provide yourself a short array-creation wrapper,
like a
?
-- T.J. Crowder
We tried to do this in the early Harmony era. We never managed to get this to work without unacceptable performance and semantic issues. If you dig around the archives looking for scoped object extensions or method extensions you can see the discussion that was had.
It seems like wiki.ecmascript.org is no longer available. The proposal was up there. Maybe someone has a mirror of it somewhere?
Erik
Hey guys,
What would it take to get block scoped prototype extensions into JavaScript? I’m curious to get some thoughts before I write a proposal.
The use case is similar to Scala’s block scoped implicits. In my application code I want Array.prototype.indexOf to return an Option<number>, rather than number | -1. If I patch Array’s prototype directly, it will break other libraries in my project. It would be nice to have a way to extend the prototype for just a block, or just a file.
Would a combination of block-scoped imports (maybe dynamic imports, to patch the prototype) and some sort of onExitBlock hook (to unpatch the prototype) be enough to implement this? Has anyone else thought about this sort of feature?
Boris