JSF**k with only 5 symbols?

After brainstorming, the result seems to be that, on modern browsers at least, there’s no way to do this.

I’ll try to summarize the entire process, adding some reasoning about why we’ve exhausted our options in any given domain before moving on. Then, barring some kind of amazing new insight (like, a corner case of JavaScript syntax everyone is forgetting about) it’ll be pretty clear that there’s no way to get the remaining letters.

Literals

The only immediate literals you can make with +()[] are the nested empty arrays [], [[]], [[[]]], etc. From there, we can start casting values using +:

  • +[] gets zero, which Jens’s trick expands to arbitrary positive integers using ++.

  • []+[] is "". In fact, []+x gets us a string representation of x in general.

[]’s next use is indexing. Indexing an object out-of-bounds ([][[]]) gets you undefined. Casting that to a string and indexing the result gets you the letters d e f i n u; casting it to an integer first using + gets you NaN, from which the letters a N follow.

Using the ++ trick on any non-integer value reached so far either gives NaN or an error. Also, none of the objects we can create are callable (yet), so () doesn’t help (except for grouping).

The remaining tricks up our sleeve are casting and indexing. So the question is: which strings can we create using the characters 0123456789adefinuN that either

  • are number literals we can round-trip cast to integer to get new strings, or
  • are property names of objects we can already reach?

Number literals

As an example of the second option, we can make the string "1e1000", and then get Infinity from +"1e1000", and casting that back to string gets us the letters y and I.

Also, we can make "11e100", cast to number and back to string, to get "1.1e+101", from which we extract . and +.

Using that ., in turn, we can make the string ".0000001", cast it to number and back, to get "1e-7", winning us -.

That’s basically all floats will get you: there aren’t any more interesting values other than Infinity and NaN, and there aren’t any more characters used in their usual string representations other than -+.0123456789e.

Properties

So we have the letters -+.0123456789adefinuyIN. Which properties can we reach? Let’s ask JavaScript.

>>> R = /^[-+.0123456789adefinuyIN]+$/
>>> [Array, Object, String, Number].reduce((h, f) => {
        h[f.name] = Object.getOwnPropertyNames(f.prototype).filter(x => x.match(R));
        return h }, {})

{ Array: [ 'find' ], Object: [], String: [], Number: [] }

Only [].find, which Jens already found. Let’s cast that to a string, reap all of its letters, and try again. The string representation is a bit different across browsers. On Chrome and Edge, "function find() { [native code] }" contains acdefinotuv()[]{} and a space; our full alphabet is now +-.()[]{}0123456789INacdefinotuvy. On Firefox, there are more spaces and newlines, but the letters are the same.

We repeat our search:

>>> R = /^[+-.()\[\]{}0123456789INacdefinotuvy]+$/
>>> [Array, Object, String, Number, Function].reduce((h, f) => {
        h[f.name] = Object.getOwnPropertyNames(f.prototype).filter(x => x.match(R));
        return h }, {})

{ Array: [ 'concat', 'find' ],
  Object: [],
  String: [ 'concat' ],
  Number: [],
  Function: [] }

String.prototype.concat is deprecated: it does exactly what + does, which we can already do. So we got Array.prototype.concat and Array.prototype.find. What can we do with them?

Functions

concat() lets us create, for the first time, longer arrays. [[]].concat([[]]) is [[], []], and casting that to a string gets us ",". (This doesn’t help us find new properties.) But .concat doesn’t modify our values, and it can never return null or anything like that.

Calling find() doesn’t help us either: the MDN documentation says

The find() method returns a value in the array, if an element in the array satisfies the provided testing function. Otherwise undefined is returned.

Both of those we can already do using indexing.


And from here, there’s nowhere else to go. If you doubt anything I’ve written, let me know in the comments.


The 3 functions in Lynn's answer weren't that useless. But the strict mode in ECMAScript 5 foiled my plan.

There is a quirk in the older versions of JavaScript / ECMAScript. If a method is called without an object, the global object window is assumed. So we can do this:

a = {f:function(){return this}};
a.f();                            // Returns a.
g = a.f;
g();                              // Returns window.
window.g();                       // Also returns window.

This is still true for modern browsers, but only if the function isn't defined in strict mode. And all built-in functions (with native code) seemed to be in strict mode. In older browsers when there isn't the strict mode yet, this also works for built-in functions.

Assume we are using older browsers. Then if we want window, we have to find a built-in function that returns something containing this. Within the only choices we had, there is the function Array.prototype.concat doing exactly that. We can test it like this:

Number.prototype.concat = Array.prototype.concat;
1..concat(2);                     // Returns [1, 2]
concat = Array.prototype.concat;
window.concat(2);                 // Returns [window, 2]
concat(2)                         // TypeError in modern browsers while
                                  //   returning the same thing in older ones.
concat.bind(window)(2)            // A workaround in modern browsers.

So basically it doesn't care whether the object it is called upon is an array (but it must be an object for the least). It just wraps it in an array if not.

If we had window, firstly we can get the string [object Window] by casting it to a string. With the new character b, we can get r and s using the following two lines respectively, and every character we didn't have in constructor:

window["atob"]("cuaa")[0]
window["atob"]("cyaa")[0]

But the other problem is to remove the object reference from [].concat. Wrapping it in an array and extracting doesn't work, because [].concat already means []["concat"]. The only way I know which could possibly be constructed using +[]() is to return it from a function. Array.prototype.find seemed to be able to do that:

[[]["concat"]]["find"](x=>1)      // Returns Array.prototype.concat, where x=>1 can
                                  //   be replaced with any always truthy function.

We had always truthy functions. Array.prototype.concat and String.prototype.concat both return truthy if the object is window. If we use the later one, we made use of all of the three available functions.

But, unfortunately, Array.prototype.find doesn't exist in the old browser we are using. At least I didn't find one that works. And I didn't find another way removing the object reference.

The complete code that is testable in modern browsers that returns r and s, with the .bind(window) workaround:

[[]["concat"]]["find"](""["concat"].bind(window)).bind(window)()[0]["ato"+([]+[[]["concat"]]["find"](""["concat"].bind(window)).bind(window)()[0])[2]]("cuaa")[0];
[[]["concat"]]["find"](""["concat"].bind(window)).bind(window)()[0]["ato"+([]+[[]["concat"]]["find"](""["concat"].bind(window)).bind(window)()[0])[2]]("cyaa")[0]