The traditional way to give a parameter a default is x = x || fallback, which is wrong whenever a legitimate value is falsy. Passing 0 for a timeout, or an empty string for a prefix, silently gets the default instead.
function retry(fn, attempts) {
attempts = attempts || 3; // retry(fn, 0) still gets 3
}
function retry(fn, attempts = 3) {
// retry(fn, 0) gets 0
}
A default parameter fires only when the argument is undefined, which is the rule you meant. Defaults are evaluated at call time and can refer to earlier parameters, so function slice(arr, start = 0, end = arr.length) works. Explicitly passing null does not trigger the default — only undefined does.