Open fatcerberus opened 7 years ago
The former sounds easier just quickly thinking.
To avoid a lot if IF and branch instructions a LDIFUNDEF load instruction would be nice.
Side effects apparently need to happen in order, and only if the corresponding argument is omitted:
> function foo(a=123, b=(console.log('A:', a), Math.sqrt(9)), c=console.log('A+B:', a +b)) {}
undefined
> foo()
A: 123
A+B: 126
undefined
> foo(100)
A: 100
A+B: 103
undefined
> foo(100, 200)
A+B: 300
undefined
> foo(100, 200, 400)
undefined
The arguments may also reference both previous and following arguments. So, a LDIFUNDEF doesn't really work except for side effect free cases. Those cases just might be worth special casing for (say simple strings and other primitive values) to avoid emitting actual IF branches around each argument. But a general solution requires an "if undefined ..." check so that side effects come out right.
There are also some corner cases involved in scope handling and IIRC all engines didn't handle these correctly yet. For example, you can create a closure in the default argument value and reuse it after the function has exited:
> myFn = null;
null
> function foo(a = 123, b = (myFn = function (str) { return eval(str); }), c = a * 5) { var d = 'foobar'; console.log(a, b, c) }
undefined
> foo()
123 [Function: myFn] 615
undefined
> myFn('a')
123
> myFn('b')
[Function: myFn]
> myFn('c')
615
> myFn('d')
ReferenceError: d is not defined
at eval (eval at myFn (repl:1:74), <anonymous>:1:1)
at myFn (repl:1:69)
at repl:1:1
at sigintHandlersWrap (vm.js:22:35)
at sigintHandlersWrap (vm.js:96:12)
at ContextifyScript.Script.runInThisContext (vm.js:21:12)
at REPLServer.defaultEval (repl.js:313:29)
at bound (domain.js:280:14)
at REPLServer.runBound [as eval] (domain.js:293:12)
at REPLServer.<anonymous> (repl.js:513:10)
The interesting thing here is that:
I'm not sure if these are the correct semantics but at least V8 behaves this way. I'd need to read ES2015 with a large cup of coffee.
Huh, I assumed it would only allow constant expressions to be used as default values. I knew that arguments could reference previous arguments, but allowing arbitrary expressions in that context seems like overkill from a language design POV.
Requiring a constant expression would on the other hand be very limiting. For example, you couldn't use undefined
because it's not a literal (not that you'd want to), and couldn't use Math.PI
, etc.
So considering the rules for side effects, etc. here, it seems that an optional parameter is essentially the same thing as putting:
if (arg === undefined) arg = defValueExpr;
at the top of a function. That's nice, as it closely mimics idioms already used in JS before optional arguments were added to the language. In particular, each argument is tested independently (rather than basing the decision on the number of arguments at the call site) which is similar to what happens for arg || defValue
(another common idiom).
Overall, I have to commend the ES committee for being able to introduce all-new language features like this while largely maintaining the semantics of existing idioms.
Both for parsing as well as compilation. Parsing should in theory be straightforward (just check for an equals sign followed by an expression), but I'm not sure what kind of compiler/executor changes would be necessary. One way would be to emit extra instructions in the function's prologue to check for
undefined
arguments and then replace them with their default values if so. But it could also maybe be done in call handling (this would require the compiler to know the function's complete signature though).