Looking at the utility type 'NonNullable' of the type script, it is implemented as this.
/**
* Exclude null and undefined from T
*/
type NonNullable = T & {};
type Object1 = {};
type Object2 = object;
const testObj1: Object1 = {
test: 'test',
}; // ok
const testObj2: Object2 = {
test: 'test',
}; // ok
const testStr1: Object1 = ''; // ok
const testStr2: Object2 = ''; // Type 'string' is not assignable to type 'object'.ts(2322)
{} Instead, what happens inside the type script when extends the 'object'?
Why does the type checker detect differently as in the example above?
I'd appreciate it if you could answer my questions, thank you!
An object literal type, written as curly braces containing zero or more property declarations, is an assertion that the value of that type
Now, the empty object literal type
{}in particular has no specified properties, so only (1) is in play. That is,{}is the type of anything that can have properties accessed at runtime. This includes object literals (naturally), but it also includes strings, numbers, and Booleans. That is, all of the following areundefinedat runtime, not a runtime error:"example".foo(3).foo(Requires extra parentheses if you're using a numerical literal, for syntax purposes)true.foofalse.fooHowever,
nullandundefinedare unique in that we can't even try to access properties on them.null.fooandundefined.fooare runtime errors, not justundefined. Sonullandundefinedcannot be assigned to any object literal, not even{}, since they don't satisfy condition (1) above.object, on the other hand, is defined to be the type of all non-primitive things in Javascript. Javascript defines seven primitive typesstringnumberbigintbooleanundefinedsymbolnullAnd Typescript defines
objectto be: every value which is not one of these seven things.Typescript also defines
Objectwith a capital "O". This is the type of all Javascript values that satisfy the interface defined by theObjecttype.Generally, that's going to be very similar to
{}, as any object that is notnullorundefinedin Typescript is going to (directly or indirectly) haveObjectas a prototype and hence will inherit all of the built-inObjectproperties. But we can break that. For instance,ObjectdefinestoString(): string, so{ toString: function() { return 7; } }is a value which is not of typeObject, since itstoStringis incompatible with that ofObject. Though that value is of type{}(since it can have its properties accessed) and of typeobject(since it is not a primitive value).Most Typescript style guides recommend avoiding these types, as they cause confusion and are honestly seldom what you want. I mean, when do you actually mean "I want this function to accept any value, anything at all, but I specifically forbid
nullandundefined"? I recommend the following guidelines.any. This will basically disable typechecking on that particular variable.typeof, for instance) before doing anything, useunknown.unknownis the top of the Typescript type hierarchy (exceptingany), so anything is assignable to it, but it's not assignable to any other type (again, exceptingany)class,new, or an object literal), useRecord<string, unknown>(orRecord<string, any>for weaker typing). This type is likeObjectexcept that it won't accept other primitives like strings and can't be broken with a faultytoStringor other method.Record<string, unknown>is useful for the many Javascript functions that accept an object behaving sort of like a key-value dictionary which were not designed withMapin mind, as it's usually a mistake to pass a primitive string or number to such functions.