A more efficient way to invoke this function?

Hey folks,

I've finally gotten around to learning pure JavaScript a bit better, and I've run into a situation where I think I can optimize my code but I don't know how exactly as I can't find the terms I need to Google this question. I'm currently creating a CSS table header using a function that takes the div as input. This is the code I currently have:

function addTableContent(targetDiv, element, text) {
    var cellHeader = document.createElement(element);
    var textToAdd = document.createTextNode(text);
    targetDiv.appendChild(cellHeader).appendChild(textToAdd);
}

tableCell = createTableSubDiv(calendarTable);
addTableContent(tableCell, "h2", "Event");

I'd like to be able to 'append' the method behind the div as you see with some of the built-ins (e.g. .appendChild). I honestly can't find the term for this, if you do know it then please tell me. To put that into code, I'd like to be able to have it in the following way:

    tableCell.addTableContent("h2", "Event");

What exactly would I have to change to get it to work like that?

EDIT: elaboration on question

EDIT2: Thanks for the comments guys, I'll have to sit down and read up on the linked material next!

4 comments · 67% upvoted · other discussions

comments

i_am_smurfing

The way to achieve what you want would be by assigning your function to a property of a prototype of all HTML elements. I'm assuming you've never worked with prototypes in JS before based on your question, so you might find Object prototypes article on MDN interesting.

Your code would look something like this:

/**
 * Exploration phase
 *
 * You could also find needed interface via browsing/searching MDN or DOM spec
 */

var div = document.createElement("div"); // a sample `<div>` for us to find
                                         // out what prototype it's based on

Object.getPrototypeOf(div); //=> HTMLDivElement
// sounds like this is a prototype of `<div>`s only, not all HTML elements

// let's go one level deeper:
Object.getPrototypeOf(Object.getPrototypeOf(div)); //=> HTMLElement
// this sounds about right



/**
 * Actual implementation
 */

HTMLElement.prototype.addTableContent = function(element, text) {
  var cellHeader = document.createElement(element);
  var textToAdd = document.createTextNode(text);

  // `this` inside this function is equivalent to `targetDiv` in old implementation
  this.appendChild(cellHeader).appendChild(textToAdd);
};

div.addTableContent("h1", "Hello, world"); //=> <div><h1>Hello, world</h1></div>

 

So this would certainly work, but what exactly did it do to our app?

  • We'll be writing the same amount of code, albeit it might look a bit more JavaScript-y

    addTableContent(tableCell, "h2", "Event");  // old way
    tableCell.addTableContent("h2", "Event");   // new way
    
    // notice that we didn't get rid of passing `tableCell` to `addTableContent`,
    // we just put it outside of parens.
    
  • We've potentially broke some code in unrelated places

  • If someone ever wants to change this code, they need to have an understanding of how prototypes and this work in JS (which is probably OK, since that's their job as JS developers)

Given all that, I would advise you to not do what you set out to do and stick to your current implementation, using a regular function — your code will be just simpler that way.

gremy0

To add the function to the tableCell object use*

tableCell.addTableContent = addTableContent

You'll probably want to do this increateTableSubDiv, where presumably you are just creating and returning tableCell so something like

function createTableSubDiv() {
    ...
    tableCell.addTableContent = addTableContent
    return tableCell
}

Then in the actual addTableContent function, remove targetDiv from the arguments, and cange any references to it tothis, i.e.

this.appendChild(cellHeader).appendChild(textToAdd);

Note, none of this will actually improve the efficiency of the code. It may make it more readable, but that depends on the situation, and how the rest of the code is laid out.

If you are concerned about actual efficiency, it's good practice to (as far as possible), finish creating child elements before you add them to parent elements. Just incase something is actually attached to the DOM. E.g. if targetDiv was added to the DOM already, this:

targetDiv.appendChild(cellHeader).appendChild(textToAdd);

will trigger 2 rerenders of the DOM. Once to append cellHeader, and then once more to append textToAdd. Whereas this:

cellHeader.appendChild(textToAdd);
targetDiv.appendChild(cellHeader);

will only trigger 1 render, since cellHeader already has it's text before it's added to the DOM.

*There are in fact loads of ways and patterns for doing this in JS: Function.bind(), Object.prototype, Object.assign(), Extends. Also worth reading about inheritance in and the prototype chain

Bixbeat [OP]

Interesting comment about having a fully constructed child before appending. I'm rather used to keeping functions as short as possible, but I suppose I have to think differently if I want to construct complete children before appending them. Thanks!

jml26

One thing you could consider is that you have a single function, addTableContent, doing two things: creating an element and appending it. If you created a function that was only responsible fore creating the new element:

function createElement(tagName, textContent) {
    var element = document.createElement(tagName);
    if (textContent) {
        element.appendChild(document.createTextNode(textContent));
    }
    return element;
}

You could then invoke it as follows:

var tableCell = createTableSubDiv(calendarTable);
tableCell.appendChild(createElement(“h2”, “Event”));

Advantages to this are:

  • You have a reusable function in the form of createElement, which can be used in a wider variety of situations, even if you don’t need the element to contain text content
  • It means the function takes the form tableCell.doSomething(...), which is kinda what you were after
  • You don’t have to add new methods to the tableCell object. Extending native objects, including DOM elements, is a frowned-upon idea.

It is possible to do exactly what you wanted, by also including the line

HTMLTableRowElement.prototype.addTableContent = function (tagName, textContent) {
    this.appendChild(createElement(tagName, textContent));
}

But I include that for example only, and wouldn’t recommend it at all. It adds the addTableContent method to all table cell elements.