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 are undefined
at runtime, not a runtime error:
"example".foo
(3).foo
(Requires extra parentheses if you're using a numerical literal, for syntax purposes)true.foo
false.foo
However, null
and undefined
are unique in that we can't even try to access properties on them. null.foo
and undefined.foo
are runtime errors, not just undefined
. So null
and undefined
cannot 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 types
string
number
bigint
boolean
undefined
symbol
null
And Typescript defines object
to be: every value which is not one of these seven things.
Typescript also defines Object
with a capital "O". This is the type of all Javascript values that satisfy the interface defined by the Object
type.
Generally, that's going to be very similar to {}
, as any object that is not null
or undefined
in Typescript is going to (directly or indirectly) have Object
as a prototype and hence will inherit all of the built-in Object
properties. But we can break that. For instance, Object
defines toString(): string
, so { toString: function() { return 7; } }
is a value which is not of type Object
, since its toString
is incompatible with that of Object
. Though that value is of type {}
(since it can have its properties accessed) and of type object
(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 null
and undefined
"? I recommend the following guidelines.
any
. This will basically disable typechecking on that particular variable.typeof
, for instance) before doing anything, use unknown
. unknown
is the top of the Typescript type hierarchy (excepting any
), so anything is assignable to it, but it's not assignable to any other type (again, excepting any
)class
, new
, or an object literal), use Record<string, unknown>
(or Record<string, any>
for weaker typing). This type is like Object
except that it won't accept other primitives like strings and can't be broken with a faulty toString
or 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 with Map
in mind, as it's usually a mistake to pass a primitive string or number to such functions.