Lodash also has "isPlainObject". Not sure if that's as performant as using Array.isArray or even simpler trickery (see below), but it seems that this fixes it.
"isObject" is just not well-defined for other cases that might be interesting, in my opinion. Not just "null" or arrays.
Functions can have properties (although they don't have the object prototype or primitive type).
Class instances behave differently compared to plain objects when used with libraries or other code that inspects the shape or prototype of objects, or wants to serialize them to JSON.
If you deal with unknown foreign values that are expected to be a JSON-serializable value, you could go with something like
isObject = (o) => !!o && typeof o === "object" && !Array.isArray(o)
But it does
not deal with all the non-JSON objects, e.g. Map or other class instances that are not even JSON-serializable by default.
When data comes from parsing unknown JSON, the check above should be enough.
In other cases, the program should already know what is being passed into a function. No matter if through discipline or with the aid of an additional type syntax.
For library code that needs to reflect on unknown data at runtime, I think it's worth looking at Vue2's hilarious solution, probably a very good and performant choice, no matter how icky it might look:
https://github.com/vuejs/vue/blob/547a64e9b93d24ca5927f653710b5734fa909673/src/util/lang.js#L293
Since the string representations of built-ins have long ago become part of the spec, it seems that there is no real issue with this!