A simple function that removes empty or tags containing just '&nbsp;' I've written a function that should get rid of empty p, span, etc tags and those with just '&nbsp;' and am looking for ways to improve it. My original solution was very 'wet', but I've managed to come up with a drier solution.

The Original HTML:

&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"&gt;&lt;/script&gt;
&lt;div id='test'&gt;
    &lt;p&gt;text&lt;/p&gt;
    &lt;p&gt;&amp;nbsp;&lt;/p&gt;
    &lt;p&gt;text&lt;/p&gt;
    &lt;p&gt;&lt;span&gt;text&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;text&lt;/p&gt;
    &lt;p&gt;&lt;strong&gt;text&lt;/strong&gt;&lt;/p&gt;
    &lt;p&gt;&lt;/p&gt;
    &lt;p&gt;&amp;nbsp;&lt;/p&gt;
    &lt;p&gt;text&lt;/p&gt;
    &lt;p&gt;&lt;span&gt;&lt;strong&gt;&amp;nbsp;&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;&lt;span&gt;&lt;strong&gt;text&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;&amp;nbsp;&lt;/p&gt;
    &lt;p&gt;&lt;span&gt;text&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;&lt;/p&gt;
    &lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;&lt;span&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;text&lt;/p&gt;
&lt;/div&gt;


My Original Solution:

/*
    How to make this drier?
    ORIGINAL UNCLEAN SOLUTION
*/
    var ps = document.getElementsByTagName('p'),
    spans = document.getElementsByTagName('span'),
    strongs = document.getElementsByTagName('strong');

    for (let el of ps) {
        if (el.innerHTML == '&amp;nbsp;') { // can't also include if '' at this stage
            el.parentNode.removeChild(el);
        }
    }
    for (let el of spans) {
        if (el.innerHTML == '&amp;nbsp;' || el.innerHTML == '') {
            el.parentNode.removeChild(el);
        }
    }
    for (let el of strongs) {
        if (el.innerHTML == '&amp;nbsp;' || el.innerHTML == '') {
            el.parentNode.removeChild(el);
        }
    }
    for (let el of ps) {
        if (el.innerHTML == '') {
            el.parentNode.removeChild(el);
        }
    }


My 'drier' solution:

/*
    MY CLEANER SOLUTION
*/
    var ps = document.getElementsByTagName('p'),
    spans = document.getElementsByTagName('span'),
    strongs = document.getElementsByTagName('strong');

    for (let el of ps) {
        cleaner(el);
    }
    for (let el of spans) {
        cleaner(el);
    }
    for (let el of strongs) {
        cleaner(el);
    }

    function cleaner(el) {
        if (el.innerHTML == '&amp;nbsp;' || el.innerHTML == '') {
            el.parentNode.removeChild(el);
        }
    }


Would someone mind quickly running over both solutions and verifying that my 2nd solution is best? Also, I wonder whether that could be improved, or whether anyone has any better ideas for a solution? Thanks for the help here - for brevity, I'm looking at writing concise but also clear code.
There is a bug

You need to run the script several times to remove all empty elements.

Two points


You say remove empty tags that contain "" or a single space "&amp;nbsp;". Does that include " " or "  " two or more spaces. What about other white space characters?
Your element removal is order dependent because you use getElementsByTagName which returns a live list.

Consider the html &lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;  You first check all the p tags which fail the empty test, then you test the span tags which passes and you get &lt;p&gt;&lt;/p&gt; which is, by your definition, empty and should have been removed.

On the other hand the html &lt;span&gt;&lt;p&gt;&lt;/p&gt;&lt;/span&gt; will first remove the p then remove the span. 

The removal process is order dependent. Not what your question indicates.


Changes

For the first point you could use element.textContent to check for empty elements. It will ignore the HTML and convert the &amp;nbsp; to a space for you. You could even use element.textContent.trim() and thus get all blank elements (like the pseudo-class :blank (Which has very limited support FF only))

This also covers the second point.

Example Mark and remove

To reduce the DOM calls you can mark and remove deleting the marked elements only.

const isNotMarked = el =&gt; {
    while (el &amp;&amp; el.parentNode &amp;&amp; !el.parentNode.marked) {
       el = el.parentNode;
       if (el.marked) { return false }
    }
    return true;
}
[...document.querySelectorAll("span, p, strong")]
    .filter(el =&gt; el.textContent.trim() === "" &amp;&amp; isNotMarked(el) ? el.marked = true : false)
    .forEach(el =&gt; el.parentNode.removeChild(el));


Example simple brute force

Mark and remove saves you attempting to delete already deleted elements but you may not care, as the shorter form, is a two liner, and thus could be argued to be the better solution.

document.querySelectorAll("span, p, strong")
    .forEach(el =&gt; el.textContent.trim() === "" &amp;&amp; el.parentNode.removeChild(el))


The following snippet shows the HTML after using your function and then the two example functions 



   /*================================================================================= 
OP ver modified for example
 =================================================================================*/

var ps = cleaned.getElementsByTagName('p'),
    spans = cleaned.getElementsByTagName('span'),
    strongs = cleaned.getElementsByTagName('strong');

    for (let el of ps) { cleaner(el); }
    for (let el of spans) {  cleaner(el); }
    for (let el of strongs) { cleaner(el);  }

    function cleaner(el) {
        if (el.innerHTML == '&amp;nbsp;' || el.innerHTML == '') {
            el.parentNode.removeChild(el);
        }
    }
    content.textContent = cleaned.innerHTML;




/*================================================================================= 
 Mark and remove
 =================================================================================*/
const isNotMarked = el =&gt; {
    while (el &amp;&amp; el.parentNode &amp;&amp; !el.parentNode.marked) {
       el = el.parentNode;
       if (el.marked) { return false }
    }
    return true;
}
[...cleanerClean.querySelectorAll("span, p, strong")]
    .filter(el =&gt;  el.textContent.trim() === "" &amp;&amp; isNotMarked(el) ? el.marked = true : false)
    .forEach(el =&gt; el.parentNode.removeChild(el));

contentA.textContent = cleanerClean.innerHTML;



/*================================================================================= 
 Brute force remove
 =================================================================================*/
simplerClean.querySelectorAll("span, p, strong")
    .forEach(el =&gt; el.textContent.trim() === "" &amp;&amp; el.parentNode.removeChild(el))

contentB.textContent = simplerClean.innerHTML;
#content {
   display: block;
}
&lt;div id="cleaned" style="display:none;"&gt;
  &lt;p&gt;text&lt;/p&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;p&gt;text&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;text&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;text&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;text&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;p&gt;text&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;&lt;strong&gt;&amp;nbsp;&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;&lt;strong&gt;text&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;text&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;text&lt;/p&gt;
&lt;/div&gt;  
&lt;fieldset&gt;
&lt;legend&gt;Original OPs script &amp; Resulting HTML&lt;/legend&gt;
&lt;code id = "content"&gt;&lt;/code&gt;
&lt;/fieldset&gt;


&lt;div id="cleanerClean"  style="display:none;"&gt;
  &lt;p&gt;text&lt;/p&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;p&gt;text&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;text&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;text&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;text&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;p&gt;text&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;&lt;strong&gt;&amp;nbsp;&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;&lt;strong&gt;text&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;text&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;text&lt;/p&gt;
&lt;/div&gt;  

&lt;fieldset&gt;
&lt;legend&gt;Mark and remove&lt;/legend&gt;
&lt;code id = "contentA"&gt;&lt;/code&gt;
&lt;/fieldset&gt;

&lt;div id="simplerClean"  style="display:none;"&gt;
  &lt;p&gt;text&lt;/p&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;p&gt;text&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;text&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;text&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;text&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;p&gt;text&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;&lt;strong&gt;&amp;nbsp;&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;&lt;strong&gt;text&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;text&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;&lt;span&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;text&lt;/p&gt;
&lt;/div&gt;  

&lt;fieldset&gt;
&lt;legend&gt;Brute force remove&lt;/legend&gt;
&lt;code id = "contentB"&gt;&lt;/code&gt;
&lt;/fieldset&gt;