I do think it's a little weird, but it's useful to be able to say that "this key exists and its value is null" as opposed to "there is no such key".
That is the difference between not changing a value and changing it to null.
I guess in TS2 there would now also be a difference between a field defined as key: string | null | undefined and a field defined as key?: string | null | undefined, because the first one requires setting the key to one of the values on initialization and the other one not.
I've also found it useful when a function needs to indicate it failed but is in a situation where exceptions are not useful. This is code where the consumer of the function can throw its own much more useful exception or can try to recover from the function failing (eg: it can try and fix the fault). This helps people avoid using exceptions for flow control of the application and lets exceptions be truly exceptional.
Examples:
//3rd party code could dictate 'bar' be in the spot that it is
function needsUndef(foo, bar, other){
if(bar === undefined) bar = expensive_query();
//rest of code
}
needsUndef(42, cachedResultExpensiveQuery, something);
needsUndef(42, undefined, something);
and: foo = getFromDisk(foo_path);
if(foo === undefined)
foo = getFromNetwork(last_resort);
is much nicer than:
try{
foo = getFromDisk(foo_path);
}catch(ex){
//Yes, I know you can filter exceptions, but what if someone forgets or sets too wide
//of a net?
foo = getFromNetwork(last_resort);
}
or:
if(path_isValid(foo_path) && canReadFromPath(foo_path))
foo = getFromDisk(foo_path);
else foo = getFromNetwork(last_resort);When I see null it means a value could not be set or that it was intentionally unset. undefined means it was never set in the first place.