The solutions presented fail if one of the values in obj is a function. E.g., JSON.parse(JSON.stringify({foo:x=>x})) returns {}.
It's not intuitively obvious what the "correct" solution ought to be. If cloner is a deep-cloning function, and f is a function, which of the following should be true?
1. cloner(f) === f?
2. For all ...args, cloner(f)(...args) === f(...args)?
3. For all ...args, cloner(f)(...args) === cloner(f(...args))?
4. For all ...args, cloner(f)(...args) === f(...args.map(cloner))?
5. For all ...args, cloner(f)(...args) === cloner(f(...args.map(cloner)))?
Ohai. Author here. That’s indeed one of the trip-wires. So is that any kind of prototype is lost.
The point of the structured clone algorithm is to make values transferable to other realms, so functions are actively ignored. Most of the time, I think that is “good enough”.
You might have missed the main thread pointing out that your claim that "JavaScript passes everything by reference" or is call by reference is wrong in every way. Javascript is call by value, those values can be strings, numbers and references to objects and arrays and such. "Call by reference" is very specific terminology that means that when you run somefunction(n), the caller's variable, n, might get entirely changed, even if it's just a number.
It's not intuitively obvious what the "correct" solution ought to be. If cloner is a deep-cloning function, and f is a function, which of the following should be true?
1. cloner(f) === f?
2. For all ...args, cloner(f)(...args) === f(...args)?
3. For all ...args, cloner(f)(...args) === cloner(f(...args))?
4. For all ...args, cloner(f)(...args) === f(...args.map(cloner))?
5. For all ...args, cloner(f)(...args) === cloner(f(...args.map(cloner)))?
6. Something else entirely?