Search code examples
javascriptfirefox-addonfirefox-addon-sdk

JavaScript `of` Keyword (for...of loops)


I just spotted, in Firefox SDK JavaScript (on MDN), the use of a keyword I have never seen before:

var tabs = require('sdk/tabs');
for (let tab of tabs)
  console.log(tab.title);

Is the of keyword made up by Mozilla or is it standardized?


Solution

  • The for...of loop, which iterates over property values, is a feature added to the JavaScript specification in ECMAScript 2015.

    Given that the context of this question is a Firefox add-on, the issue is not when, or if, it is available in other browsers. The issue is when this ECMAScript 2015 feature was added to Firefox and any limitation on backward compatibility which using it causes.

    It was added to Firefox in Firefox 13. Thus, using it will result in restricting your add-on to be Firefox 13+. Given that the current release, as of October 2014, is Firefox 33.0 and there have been multiple ESR releases between Firefox 13 and now, using a for...of loop probably will not significantly reduce the number of people who are able to use your add-on. It is likely that some other feature which you are using will restrict your add-on to a more recent version.

    Using for...of

    Unlike Array.prototype.forEach(), for...of loops are not restricted to only Arrays and will iterate over other types of iterable objects, which includes a considerable number of different types of Objects.

    One of the things that sometimes confuses people is that for...of iterates over property values, not the property key. Depending on what you are doing, this can either be quite convenient, of make it such that a for...of loop is inappropriate.

    Example: for..of iterates over NodeList

    const listItems = document.querySelectorAll('li');
    
    for (let item of listItems) {
        console.log('item text:', item.textContent); // "first", "second", "third", "fourth"
    }
    <ol>
        <li>first</li>
        <li>second</li>
        <li>third</li>
        <li>fourth</li>
    </ol>

    Plain Objects are not normally iterable (can't use for...of)

    Trying to use for...of on a plain Object will throw an error.

    const obj = { first: 3, second: 5, third: 7, fourth: "hello" };
    
    // with Object.keys()
    
    for (let value of obj) { //This is an error. obj is not iterable
        console.log('value:', value);
    }

    Other ways to iterate over Object property values

    Array.prototype.forEach()

    If you are looking for other ways to perform a similar task, MDN shows examples of using Array.prototype.forEach() to iterate over property values for Arrays and Objects:

    forEach directly over an Object's values obtained form Object.values():

    const obj = { first: 3, second: 5, third: 7, fourth: "hello" };
    
    // with Object.keys()
    
    Object.values(obj).forEach(function (value) {
        console.log('value:', value); // logs "3", "5", "7", "hello"
    });

    forEach over an Object's keys obtained form Object.keys():

    const obj = { first: 3, second: 5, third: 7, fourth: "hello" };
    
    // with Object.keys()
    
    Object.keys(obj).forEach(function (key) {
        //obj[key] is the property value
        console.log('key:', key);        // logs "first", "second", "third", "fourth"
        console.log('value:', obj[key]); // logs "3", "5", "7", "hello"
    });

    forEach over an Array's values:

    const arr = [ 3, 5, 7 ];
    
    arr.forEach(function (value, index) {
        console.log('value:', value);     // logs "3", "5", "7"
        console.log('index:', index);     // logs "0", "1", "2"
    });

    for..in

    The primary drawback to using a for..in loop is that it iterates over the Object's enumerable properties, which will include properties on the Object's prototype. This can cause unexpected errors. Thus, it's always a good idea to test that the loop's key value is one of the Object's own using Object.prototype.hasOwnProperty(), or other method, unless you know you want to iterate over the enumerable properties which are not the Object's own properties (you rarely want to).

    While it's not strictly necessary, it's a good idea to use a known-good copy of Object.prototype.hasOwnProperty(), as any Object could define their own hasOwnProperty, either intentionally, or by mistake.

    const obj = { first: 3, second: 5, third: 7, fourth: "hello" };
    
    // with for..in
    
    for (let key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            //obj[key] is the property value
            console.log('key:', key); // logs "first", "second", "third", "fourth"
            console.log('value:', obj[key]); // logs "3", "5", "7", "hello"
        }
    }

    Browser compatibility:

    If you are intending to port your add-on to other browsers, or use for...of in a webpage, then you should be aware of when the feature was added to various browsers. As can be seen in the Browser Compatibility table on MDN, the primary issue is that it is not supported by Internet Explorer.

    This is the compatibility table from MDN as of 2018-03-11:

    browser compatibility

    1. Chrome 29–37: The for...of loop feature is available behind a preference. In chrome://flags, activate the entry “Enable Experimental JavaScript”.
    2. Prior to Firefox 51, using the for...of loop construct with the const keyword threw a SyntaxError ("missing = in const declaration").