Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do lodash's .isObject, .isPlainObject behave differently than "typeof x === 'object'"?

Consider the following:

var o1 = {}
var O = function () {
  return this
}
var o2 = new O()
var o3 = function() {}
var o4 = [o1, o1]

var output = [
    [_.isObject(o1), _.isObject(o2), _.isObject(o3), _.isObject(o4)], 
    [_.isPlainObject(o1), _.isPlainObject(o2), _.isPlainObject(o3), _.isPlainObject(o4)],
    [typeof o1 === 'object', typeof o2 === 'object', typeof o3 === 'object', typeof o4 === 'object'],
    [o1 instanceof Array, o2 instanceof Array, o3 instanceof Array, o4 instanceof Array]
]

/* outputs:

[
    [true,true,true,true],
    [true,false,false,false],
    [true,true,false,true],
    [false,false,false,true]
]

*/

Clearly we can see that there is a disconnect between .isObject():

Which checks if value is the language type of Object. (e.g. arrays, functions, objects, regexes, new Number(0), and new String(' ')

.isPlainObject():

Which if value is a plain object, that is, an object created by the Object constructor or one with a Prototype of null

And our good ol' trusty friend typeof x === 'object'.

I have three questions:

  1. Was there a conscious design decision to make .isObject and .isPlainObject behave differently than the native .js type checking?
  2. If my first question is true, what was the design decision and what are the benefits of doing it this way?
  3. Is there any native lodash (or underscore.js) is* function that behaves exactly the same as typeof x === 'object'?

Obviously I can just continue to use typeof, but syntactically it's a bit weird to use one or the other in some places, for example the usage of .isObject will return false positives when checking for typeof x === 'object' && typeof x !== 'function'. I don't really see any benefit of .isObject returning true for functions when .isFunction already exists.

like image 826
brandonscript Avatar asked Dec 05 '15 22:12

brandonscript


3 Answers

typeof has nothing to do with whether something is an object. Functions, strings, and {} have different typeof and they are all objects. Functions are of course first-class objects, just like strings are first-class objects, therefore isObject must return true for strings and objects.

For the record the documentation covers this:

Checks if value is the language type of Object. (e.g. arrays, functions, objects, regexes, new Number(0), and new String(''))

Wow! that really is a lot to test for without having a handy isObject method. To be fair most return typeof as object, but the point of higher level methods, especially in libraries like lodash, is so the programmer can forget about that nonsense.

If you care about the typeof an argument, then use typeof. If you care about objects that are not functions, you have a couple options: you can use typeof and check strings specially, or you can use isObject && !isFunction. I would prefer the latter. The latter does happen to say exactly what you are trying to convey so it really is the correct thing to code. If you think when you say "object" that you implicitly don't mean functions, then you do not think functions are first-class objects, or rather you would like your code to more closely resemble a language in which they're not. But then you can't blame lodash for being a library that extensively uses the fact that functions are first-class objects to make a language in which functions are first-class objects more expressive.

I believe that was the bulk of your question. I believe the use case for isPlainObject is to answer the question "is this just data?" or "is this code?" so objects created as pseudo-classes (new of something) don't count.

like image 115
djechlin Avatar answered Oct 26 '22 23:10

djechlin


Sometimes code speaks louder than words. Here's the source from lodash:

function isObject(value) {
  var type = typeof value;
  return !!value && (type == 'object' || type == 'function');
}
function isPlainObject(value) {
  var Ctor;
  if (!(isObjectLike(value) && objToString.call(value) == objectTag && !isHostObject(value) && !isArguments(value)) ||
      (!hasOwnProperty.call(value, 'constructor') && (Ctor = value.constructor, typeof Ctor == 'function' && !(Ctor instanceof Ctor)))) {
    return false;
  }
  var result;
  if (lodash.support.ownLast) {
    baseForIn(value, function(subValue, key, object) {
      result = hasOwnProperty.call(object, key);
      return false;
    });
    return result !== false;
  }
  baseForIn(value, function(subValue, key) {
    result = key;
  });
  return result === undefined || hasOwnProperty.call(value, result);
}

According to the lodash docs:

isObject

Checks if value is the language type of Object. (e.g. arrays, functions, objects, regexes, new Number(0), and new String(''))

isPlainObject

Checks if value is a plain object, that is, an object created by the Object constructor or one with a [[Prototype]] of null.

What is an object?

Perhaps the existence of an isObject function is inherently a bit confusing when in JavaScript so many things act like objects. The key idea is that some values in JavaScript are considered primitives and others considered full-blown objects. Strings, numbers, and booleans are treated differently at an internal level than objects created with the object literal or constructor functions (this doesn't end up have much practical significance because primitives will automatically cast themselves to objects when necessary).

The fact that typeof null === 'object' probably originates from the fact that objects are reference types and null is often returned from functions in lieu of an object. (A quirk which likely hearkens back to the concept of pointers and the NULLPTR in C and C++. The type void * has many values but the NULLPTR is still considered a valid value for the pointer type.)

like image 45
Ethan Lynn Avatar answered Oct 27 '22 00:10

Ethan Lynn


  1. The lodash docs describes Lodash as "a JavaScript utility library delivering consistency, modularity, performance, & extras." isObject and isPlainObject would be an extra. They are utility functions. I'm sure they were aware that they are different then typeof which is why they may be useful to some people. They probably thought the performance, consistency, and syntax of the native typeof didn't warrant the making of .typeOf that does the same thing.
  2. As mentioned above, the benefits are that it functions a little differently than typeof which may be handy to some people. Also lodash focuses on better performance as well, although I'm not sure if there performance of such a simple function could be significantly improved. It is said to improve the performance of loops though. You can test the performance difference using JSPerf and let us know! As far as why they decided to have .isObject return true for functions is because functions in JS are objects. It is kind of deceiving when native typeof returns function and not object. It is further convoluted by the fact that typeof [] doesnt return array and instead returns object so why should it return function and not object on a function.
  3. Neither lodash or underscore appear to have a function that is the same as typeof. Underscore has _.isObject which is the same as the lodash .isObject. You could use lodash's .isFunction and .isObject to create your own typeof function that returns the same thing native typeof would.
like image 24
Daniel Kobe Avatar answered Oct 27 '22 00:10

Daniel Kobe