Search code examples
javascriptobject-detectioninstanceoftypecheckinghtmlelements

lodash's isElement equivalent in pure JS


Right now I am using isElement by lodash to check if an element is DOM likely. I want to get rid of this library so I am looking for a pure JS implementation of this function.

I'm using this, but I don't know if it's the right implementation and if I'm ignoring any edge cases:

const isDOMElement = el => el instanceof HTMLElement

Update

I have tested (codepen link) the four proposed implementation, in all cases is working as expected, maybe the one proposed by @cesare covers most of the cases, but also the one proposed by @peter-seliger is clever and work with an "outside of the BOX" thinking. Finally, both @David Thomas and the one I firstly use, works for most of the cases.

So in conclusion I think, as David Thomas points It's just a matter of preference.

P.S: The tags used for the test are extracted from MDN's HTML elements reference

P.S2: Not sure on how to proceed and what answer accept. Both cesare and peter have a good point


Solution

  • This is the lodash util:

    function isElement(value) {
      return isObjectLike(value) && value.nodeType === 1 && !isPlainObject(value)
    }
    

    Basically with isObjectLike it checks that the value passed is a non null object ( since null is an object in js ) and with !isPlainObject that it is not a plain object ( since it could be an object carrying a "nodeType": 1 entry and because an HTMLElement instance has nested protos.

    The isPlainObject util is this:

    function isPlainObject(value) {
      if (!isObjectLike(value) || getTag(value) != '[object Object]') {
        return false
      }
      if (Object.getPrototypeOf(value) === null) {
        return true
      }
      let proto = value
      while (Object.getPrototypeOf(proto) !== null) {
        proto = Object.getPrototypeOf(proto)
      }
      return Object.getPrototypeOf(value) === proto
    }
    

    As you can see the first check is redundant since it checks isObjectLike again, but the main caveat in my opinion is that it doesn't cover other objects case, since for example "Arrays" are objects too hence they pass the check:

    const arr = [1, 2]; // array
    arr.nodeType = 1; // add nodeType property to the array object
    console.log(isElement(arr)); // true Lodash
    

    Try it yourself.

    I think it's safer to check if the object has a nodeType property that is inherited:

    const _isElement = (node) =>
        typeof node === 'object' &&
        node !== null &&
        !node.hasOwnProperty('nodeType') &&
        node.nodeType &&
        node.nodeType === 1
      
    
    const el = document.createElement('h1'); // node
    const arr = [1, 2]; // array
    arr.nodeType = 1; // add nodeType property to the array object
    
    console.log(isElement(arr)); // true Lodash
    console.log(isElement(el)); // true Lodash
    
    console.log(_isElement(arr)) // false
    console.log(_isElement(el)) // true
    

    In any case I prefer using your check which already covers most of those checks since any primitive non object is not instance of HTMLElement, null is not instance of HTMLElement, and an HTMLElement instance has a "nodeType" poperty but it is inherited from proto and not an own property, :

    const isDOMElement = el => el instanceof HTMLElement
    //or
    const isDOMElement = el => el instanceof Node