lodash's isElement equivalent in pure JS

341 Views Asked by At

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

3

There are 3 best solutions below

4
On BEST ANSWER

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

2
On

One could give a try to ...

function exposeImplementation(value) {
  return Object
    .prototype
    .toString
    .call(value);
}
function getInternalClassName(value) {
  return (/^\[object\s+(?<className>[^\]]+)\]$/)
    .exec(
      exposeImplementation(value)
    )
    ?.groups
    ?.className;
}
function isHTMLElement(value) {
  return !!value && (/^HTML(?:[A-Z][A-Za-z]+)?Element$/)
    .test(
      String(
        getInternalClassName(value)
      )
    );
}

console.log(
  getInternalClassName(document.createElement('h1')),
  isHTMLElement(document.createElement('h1'))
);
console.log(
  getInternalClassName(document.body),
  isHTMLElement(document.body)
);
console.log(
  getInternalClassName(document),
  isHTMLElement(document)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

0
On

Look at the implementation from library source code:

https://github.com/lodash/lodash/blob/master/isElement.js