Alternative way to get "C" letter in jsfuck

The fact that escape is semi-deprecated kept bothering me, so I took another stab at it. Let's rebuild JSFuck from scratch.

Level 0

You can get the following values as primitives:

false           ![]
true            !![]
undefined       [][[]]
NaN             +[![]]
""              []+[]
0               +[]
1               +!+[]
2               +!+[]+!+[]
3               +!+[]+!+[]+!+[]
4               +!+[]+!+[]+!+[]+!+[]
5               +!+[]+!+[]+!+[]+!+[]+!+[]
6               +!+[]+!+[]+!+[]+!+[]+!+[]+!+[]
7               +!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]
8               +!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]
9               +!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]

Level 1

With the above values and the fact that value+[] converts to string, we can set up the following character substitution rules:

"0"             0+[]
"1"             1+[]
"2"             2+[]
"3"             3+[]
"4"             4+[]
"5"             5+[]
"6"             6+[]
"7"             7+[]
"8"             8+[]
"9"             9+[]
"a"             (false+[])[1]
"d"             (undefined+[])[2]
"e"             (true+[])[3]
"f"             (false+[])[0]
"i"             ([false]+undefined)[1+[0]]
"l"             (false+[])[2]
"n"             (undefined+[])[1]
"r"             (true+[])[1]
"s"             (false+[])[3]
"t"             (true+[])[0]
"u"             (undefined+[])[0]
"N"             (NaN+[])[0]

Level 2

With the above characters, we can construct these four strings:

"11e100"        +!+[]+[+!+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]]
"1e1000"        +!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]
"flat"          (![]+[])[+[]]+(![]+[])[+!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]
"entries"       (true+[])[3]+(undefined+[])[1]+(true+[])[0]+(true+[])[1]+([false]+undefined)[1+[0]]+(true+[])[3]+(false+[])[3]

With which we can get three more values:

1.1e+101                +("11e100")
Infinity                +("1e1000")
Array Iterator          []["entries"]()
Array.prototype.flat    []["flat"]

The last one is particularly useful, because when converted to a string, it yields this:

"function flat() {\n    [native code]\n}"

Or this:

"function flat() { [native code] }"

This is a bit wonky to work with, but the characters up to and including the { are always the same, as is the last character.

Array Iterator will convert to something more stable:

"[object Array Iterator]"

This gives us more characters to work with:

" "             ([false]+[]["flat"])[2+[0]]
"("             ([]+[]["flat"])[1+[3]]
")"             ([]+[]["flat"])[1+[4]]
"+"             (+("11e100")+[])[4]
"."             (+("11e100")+[])[1]
"["             ([]+[]["entries"]())[0]
"]"             ([]+[]["entries"]())[2+[2]]
"{"             ([true]+[]["flat"])[2+[0]]
"c"             ([]["flat"]+[])[3]
"j"             ([]+[]["entries"]())[3]
"o"             ([true]+[]["flat"])[1+[0]]
"y"             (true+[Infinity])[1+[1]]
"A"             ([NaN]+([]+[]["entries"]()))[1+[1]]
"I"             (Infinity+[])[0]

Level 3

Combining level 1 and 2 characters and values, we can now build three new strings:

".0000001"      (+("11e100")+[])[1]+[0]+[0]+[0]+[0]+[0]+[0]+[1]
"constructor"   ([]["flat"]+[])[3]+([true]+[]["flat"])[1+[0]]+(undefined+[])[1]+(false+[])[3]+(true+[])[0]+(true+[])[1]+(undefined+[])[0]+([]["flat"]+[])[3]+(true+[])[0]+([true]+[]["flat"])[1+[0]]+(true+[])[1]

And this gives us access to a bunch of further values:

1e-7            +(".0000001")
Boolean         (![])["constructor"]
Number          (+[])["constructor"]
String          ([]+[])["constructor"]
Function        []["flat"]["constructor"]

By converting to strings, we get yet more characters:

"-"             (+(".0000001")+[])[2]
"b"             ([]+(+[])["constructor"])[1+[2]]
"g"             (false+[0]+([]+[])["constructor"])[2+[0]]
"m"             ([]+(+[])["constructor"])[1+[1]]
"B"             ([NaN]+(![])["constructor"])[1+[2]]
"F"             ([NaN]+[]["flat"]["constructor"])[1+[2]]
"S"             ([NaN]+([]+[])["constructor"])[1+[2]]

Level 4

Given the uppercase S, we could now build the string "toString manually. However, if we first build the string "name", we can achieve an overall shorter code:

"name"         (undefined+[])[1]+(false+[])[1]+([]+(+[])["constructor"])[1+[1]]+(true+[])[3]
"toString"     (true+[])[0]+([true]+[]["flat"])[1+[0]]+([]+[])["constructor"]["name"]

And with that we can call Number.toString(), giving us all remaining lowercase letters:

"h"             (+(1+[0]+[1]))["toString"](2+[1])[1]
"k"             (+(2+[0]))["toString"](2+[1])
"p"             (+(2+[1]+[1]))["toString"](3+[1])[1]
"q"             (+(2+[1]+[2]))["toString"](3+[1])[1]
"v"             (+(3+[1]))["toString"](3+[2])
"w"             (+(3+[2]))["toString"](3+[3])
"x"             (+(1+[0]+[1]))["toString"](3+[4])[1]
"z"             (+(3+[5]))["toString"](3+[6])

At the same time, we can construct two more strings:

"slice"         (false+[])[3]+(false+[])[2]+([false]+undefined)[1+[0]]+([]["flat"]+[])[3]+(true+[])[3]
"-1"            (+(".0000001")+[])[2]+[+!+[]]

And that gives us one last character that we need for the next level:

"}"             ([true]+[]["flat"])["slice"]("-1")

Level 5

At this point, there's one primitive we obtained that we haven't used yet: using Function as an eval primitive:

[]["flat"]["constructor"](...)()

Since we have all lowercase letter now as well as space, +, ., [, ], { and }, we can build:

"try{String().normalize(false)}catch(f){return f}"

By means of:

(true+[])[0]+(true+[])[1]+(true+[Infinity])[1+[1]]+([true]+[]["flat"])[2+[0]]+([]+[])["constructor"]["name"]+([]+[]["flat"])[1+[3]]+([]+[]["flat"])[1+[4]]+(+("11e100")+[])[1]+(undefined+[])[1]+([true]+[]["flat"])[1+[0]]+(true+[])[1]+([]+(+[])["constructor"])[1+[1]]+(false+[])[1]+(false+[])[2]+([false]+undefined)[1+[0]]+(+(3+[5]))["toString"](3+[6])+(true+[])[3]+([]+[]["flat"])[1+[3]]+![]+([]+[]["flat"])[1+[4]]+([true]+[]["flat"])["slice"]("-1")+([]["flat"]+[])[3]+(false+[])[1]+(true+[])[0]+([]["flat"]+[])[3]+(+(1+[0]+[1]))["toString"](2+[1])[1]+([]+[]["flat"])[1+[3]]+(false+[])[0]+([]+[]["flat"])[1+[4]]+([true]+[]["flat"])[2+[0]]+(true+[])[1]+(true+[])[3]+(true+[])[0]+(undefined+[])[0]+(true+[])[1]+(undefined+[])[1]+([false]+[]["flat"])[2+[0]]+(false+[])[0]+([true]+[]["flat"])["slice"]("-1")

Calling String.prototype.normalize() with a value that isn't a valid Unicode Normalization Form will throw a RangeError, which we catch and return to the caller. We thus have:

RangeError      []["flat"]["constructor"]("try{String().normalize(false)}catch(f){return f}")()

Note that the above is an instance - we'd have to use ["constructor"] to get the function/constructor, but we can just convert it to string as-is, giving us two more uppercase letters:

"E"             ([false]+[]["flat"]["constructor"]("try{String().normalize(false)}catch(f){return f}")())[1+[0]]
"R"             ([]+[]["flat"]["constructor"]("try{String().normalize(false)}catch(f){return f}")())[0]

Level 6

With two more characters unlocked, we can now construct this string:

"return RegExp" (true+[])[1]+(true+[])[3]+(true+[])[0]+(undefined+[])[0]+(true+[])[1]+(undefined+[])[1]+([false]+[]["flat"])[2+[0]]+([]+[]["flat"]["constructor"]("try{String().normalize(false)}catch(f){return f}")())[0]+(true+[])[3]+(false+[0]+([]+[])["constructor"])[2+[0]]+([false]+[]["flat"]["constructor"]("try{String().normalize(false)}catch(f){return f}")())[1+[0]]+(+(1+[0]+[1]))["toString"](3+[4])[1]+(+(2+[1]+[1]))["toString"](3+[1])[1]

And that gives us a new value/function:

RegExp          []["flat"]["constructor"]("return RegExp")()

When invoked with no arguments and converting the resulting RegExp to string, we get:

"/(?:)/"        []+[]["flat"]["constructor"]("return RegExp")()()

So we have a bunch of new special characters:

"/"             ([]+[]["flat"]["constructor"]("return RegExp")()())[0]
":"             ([]+[]["flat"]["constructor"]("return RegExp")()())[3]
"?"             ([]+[]["flat"]["constructor"]("return RegExp")()())[2]

Level 7

Now we feed one of those characters back into the regex to get a new string:

"/\\//"         []+RegExp("/")

This gives us access to a single new character:

"\\"            ([]+RegExp("/"))[1]

Level 8

Let's build a new string:

"try{Function([]+[[]].concat([[]]))()}catch(f){return f}"

By:

(true+[])[0]+(true+[])[1]+(true+[Infinity])[1+[1]]+([true]+[]["flat"])[2+[0]]+[]["flat"]["constructor"]["name"]+([]+[]["flat"])[1+[3]]+([]+[]["entries"]())[0]+([]+[]["entries"]())[2+[2]]+(+("11e100")+[])[4]+([]+[]["entries"]())[0]+([]+[]["entries"]())[0]+([]+[]["entries"]())[2+[2]]+([]+[]["entries"]())[2+[2]]+(+("11e100")+[])[1]+([]["flat"]+[])[3]+([true]+[]["flat"])[1+[0]]+(undefined+[])[1]+([]["flat"]+[])[3]+(false+[])[1]+(true+[])[0]+([]+[]["flat"])[1+[3]]+([]+[]["entries"]())[0]+([]+[]["entries"]())[0]+([]+[]["entries"]())[2+[2]]+([]+[]["entries"]())[2+[2]]+([]+[]["flat"])[1+[4]]+([]+[]["flat"])[1+[4]]+([]+[]["flat"])[1+[3]]+([]+[]["flat"])[1+[4]]+([true]+[]["flat"])["slice"]("-1")+([]["flat"]+[])[3]+(false+[])[1]+(true+[])[0]+([]["flat"]+[])[3]+(+(1+[0]+[1]))["toString"](2+[1])[1]+([]+[]["flat"])[1+[3]]+(false+[])[0]+([]+[]["flat"])[1+[4]]+([true]+[]["flat"])[2+[0]]+(true+[])[1]+(true+[])[3]+(true+[])[0]+(undefined+[])[0]+(true+[])[1]+(undefined+[])[1]+([false]+[]["flat"])[2+[0]]+(false+[])[0]+([true]+[]["flat"])["slice"]("-1")

This is equivalent to:

"try{Function(',')()}catch(f){return f}"

Except for the fact that we can't write ',' (yet). Evaluating that will return a SyntaxError object, which, when converted to string, will yield:

"SyntaxError: Unexpected token ','"

We can then feed that string into RegExp("[\u0027]").exec(...)[0] to extract the single quote.
So we wanna run:

RegExp("[\u0027]").exec(Function("try{Function([]+[[]].concat([[]]))()}catch(f){return f}")())[0]

Applying a whole bunch of substitutions from above, we get one final character:

"'"             RegExp(([]+[]["entries"]())[0]+([]+RegExp("/"))[1]+(undefined+[])[0]+[+[]]+[+[]]+[+!+[]+!+[]]+[+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+([]+[]["entries"]())[2+[2]])[(true+[])[3]+(+(1+[0]+[1]))["toString"](3+[4])[1]+(true+[])[3]+([]["flat"]+[])[3]]([]["flat"]["constructor"]((true+[])[0]+(true+[])[1]+(true+[Infinity])[1+[1]]+([true]+[]["flat"])[2+[0]]+[]["flat"]["constructor"]["name"]+([]+[]["flat"])[1+[3]]+([]+[]["entries"]())[0]+([]+[]["entries"]())[2+[2]]+(+("11e100")+[])[4]+([]+[]["entries"]())[0]+([]+[]["entries"]())[0]+([]+[]["entries"]())[2+[2]]+([]+[]["entries"]())[2+[2]]+(+("11e100")+[])[1]+([]["flat"]+[])[3]+([true]+[]["flat"])[1+[0]]+(undefined+[])[1]+([]["flat"]+[])[3]+(false+[])[1]+(true+[])[0]+([]+[]["flat"])[1+[3]]+([]+[]["entries"]())[0]+([]+[]["entries"]())[0]+([]+[]["entries"]())[2+[2]]+([]+[]["entries"]())[2+[2]]+([]+[]["flat"])[1+[4]]+([]+[]["flat"])[1+[4]]+([]+[]["flat"])[1+[3]]+([]+[]["flat"])[1+[4]]+([true]+[]["flat"])["slice"]("-1")+([]["flat"]+[])[3]+(false+[])[1]+(true+[])[0]+([]["flat"]+[])[3]+(+(1+[0]+[1]))["toString"](2+[1])[1]+([]+[]["flat"])[1+[3]]+(false+[])[0]+([]+[]["flat"])[1+[4]]+([true]+[]["flat"])[2+[0]]+(true+[])[1]+(true+[])[3]+(true+[])[0]+(undefined+[])[0]+(true+[])[1]+(undefined+[])[1]+([false]+[]["flat"])[2+[0]]+(false+[])[0]+([true]+[]["flat"])["slice"]("-1"))())[0]

Level 9

At this point, we can return every character we want simply by doing:

Function("return '\uXXXX'")()

Demo

Let's take the character "C" from your question:

Function("return '\u0043'")()

Running this through all the substitutions above produces an absolute nightmare of 167'060 bytes. This exceeds the maximum post length on SO, but I pasted it into a gist, so feel free to try it. Though you might want to run it by means other than manually pasting it into your console...


Here I develop Siguza answer if we use non deprecated de/encodeURI (level 5) we have

console.log(
  decodeURI(encodeURI(" ")[0]+"43"),
  (NaN+[]["entries"]()["to"+String["name"]]["call"]())[11] // here is for U came from
)