1

With ES6 taking ahold, I'm eager to drop jQuery and use native JS for my website builds keeping them quick and lightweight. Also to improve my JS skills as I'm one of those whose jumped straight in with jQuery.

I'm building a tiny tiny library to make the more common used javascript in a function to keep the files small.

function $(elm) {
    var elm = document.querySelectorAll(elm);

    this.forEach = function(f) {
        [].forEach.call(elm, f);
    }

    return elm;
}

function slider() {
    $(".slider").forEach(function() {
        alert("Hello");
    });
}
slider();

This works great in Chrome etc.. but in IE10/11 I'm getting the error

Object doesn't support this property or method "forEach"

referring to the $(".slider").forEach

Any ideas?

3

1 Answer 1

5

You're adding forEach to the window object, not to the object you return; you're calling $ as a function, not a constructor. Since you're using loose mode (apparently), this within the function call is a reference to the global object (also accessible as window on browsers). You're returning the collection from querySelectorAll unchanged.

The reason it works on Chrome is that the collection returned by querySelectorAll has its own forEach (this is a fairly recent addition).

For this to work, four options:

  1. Return an object and add forEach to it, copying the elements from the QSA collection to that object. E.g.:

    function $(selector) {
        const result = Array.from(document.querySelectorAll(selector));
        result.forEach = Array.prototype.forEach;
        // Perhaps map, filter, etc.; add in a loop?
        return result;
    }
    

    Or in ES5:

    var $ = (function() {
        var methods = Array.prototype;
        function $(selector) {
            var result = methods.slice.call(document.querySelectorAll(selector));
            result.forEach = methods.forEach;
            // Perhaps map, filter, etc.; add in a loop?
            return result;
        }
        return $;
    })();
    
  2. Add forEach to the NodeList prototype if it's not already there and use forEach directly on the collection from querySelectorAll. For instance:

    if (typeof NodeList !== "undefined" &&
        NodeList.prototype &&
        !NodeList.prototype.forEach) {
        // Yes, direct assignment is fine here, no need for `Object.defineProperty`
        NodeList.prototype.forEach = Array.prototype.forEach;
    }
    

    (No need for Object.defineProperty above, enumerable [surprisingly], configurable, and writable are all true for it on Chrome and Firefox, so we can just do direct assignment as above.)

    ...and then of course your $ becomes nothing more than

    function $(selector) {
        return document.querySelectorAll(selector);
    }
    

    (To start with. If you wanted to add more features, you'd probably want to go the way of #1.)

  3. Return an array:

    function $(selector) {
        return Array.from(document.querySelectorAll(selector));
    }
    

    Or in ES5:

    function $(selector) {
        return Array.prototype.slice.call(document.querySelectorAll(selector));
    }
    
  4. Subclass Array (which cannot be perfectly polyfilled on pre-ES2015 JavaScript engines) so you can add your own features on top of Array's features:

    class MyThingy extends Array {
        // Perhaps further methods here
    }
    function $(selector) {
        return MyThingy.from(document.querySelectorAll(selector));
    }
    

    No ES5 option here, you'd need to at least transpile and polyfill.

If you're going to add features beyond those provided by Array, I quite like #4 other than the polyfilling available only being "so" good. It may well be sufficient for your purposes.

4
  • 1. Looks like quite a good way, however it's not working in IE10, using const gives a syntax error, using var gives object doesn't support method "from"
    – Tom
    Commented Sep 5, 2017 at 14:56
  • @Tom: If you're writing code for IE10, you need to transpile or limit yourself to ES5 syntax. I've assumed from your opening sentence that you're using ES2015+ (transpiling and polyfilling as necessary). Commented Sep 5, 2017 at 14:57
  • I guess I would like to support IE11 as a minimum. Which polyfill should I be looking at?
    – Tom
    Commented Sep 5, 2017 at 15:00
  • 1
    @Tom: I've added ES5 options for 1-3 above. Re transpiling and polyfilling, I've been very happy with Babel. You can just transpile (which means not using things like Array.from), or both transpile and polyfill; details on the site. Commented Sep 5, 2017 at 15:01

Not the answer you're looking for? Browse other questions tagged or ask your own question.