by Bartek Swierczynski / @bswierczynski
this contextfunction init() { var foo = 100; bar = 42; } init();console.log(bar);//Logs: 42
Forgetting var declares a global variable.
function init() { "use strict"; var foo = 100; bar = 42; } init(); console.log(bar);//ReferenceError: bar is not defined
Forgetting var throws in Strict Mode.
We only have the good ol' for loop:
var arr = ["foo", "bar", "baz"];
for (var i = 0; i < arr.length; i++) {
    var value = arr[i];
    console.log("#" + i + ": " + value);
}
                    
var arr = ["foo", "bar", "baz"];
for (var i in arr) {
    var value = arr[i];
    console.log("#" + i + ": " + value);
}
                        Don't use for..in on arrays!
And I mean it! Don't!
.forEach()
var arr = ["foo", "bar", "baz"];
arr.forEach(function(value, i) {
    console.log("#" + i + ": " + value);
});
                    Still much slower than a for loop.
for-of
var arr = ["foo", "bar", "baz"];
for (var [i, value] of arr) {
    console.log("#" + i + ": " + value);
}
                    Or, when you just need the value:
var arr = ["foo", "bar", "baz"];
for (var value of arr) {
    console.log(value);
}
                        isNaN()
isNaN(NaN);   // true
isNaN(123);   // false
isNaN("123"); // false
                    isNaN("abc");// true
The global isNaN(v) function returns
                        true not only if v is NaN,
                        but also if it cannot be parsed as a Number.
To make things even more funny…
var n = NaN;
n == NaN;  // false
n === NaN; // false
                        NaN is not equal to anything, including NaN.
isNaN()Number.isNaN()
Number.isNaN(NaN);   // true
Number.isNaN(123);   // false
Number.isNaN("123"); // false
Number.isNaN("abc"); // false
                    Returns true only for NaN.
var decentRates = employees
        .map(function(emp) {
            return emp.getDailyRate();
        })
        .filter(function(rate) {
            return rate >= 1500;
        });
                    
describe("Stack", function() {
    given(function() { this.stack = new Stack() })
    when (function() { this.stack.push("foo")   })
    then (function() { return this.stack.length === 1  })
})
                    
var decentRates = employees
        .map( (emp) => emp.getDailyRate() )
        .filter( (rate) => rate >= 1500 )
;
                    
describe("Stack", () => {
    given(() => this.stack = new Stack() )
    when (() => this.stack.push("foo")   )
    then (() => this.stack.length === 1  )
})
                    new)this context
function AutoSlidingGallery($slides) {
    var slideIndex = 0;
    
    setInterval(function() {
        this.switchToSlideAt(slideIndex);
        slideIndex = (slideIndex + 1) % $slides.length;
    }, 2000);
    
    this.switchToSlideAt = function() { /* ... */ };
}
                    Boom! Cannot call this.switchToSlideAt() in the anonymous function
                        since this no longer points to the gallery.
It points to the global object in sloppy mode and to undefined
                        in strict mode.
this contextFortunately, there are some fixes (still, mildly annoying):
function AutoSlidingGallery($slides) {
    var that = this;
    var slideIndex = 0;
    
    setInterval(function() {
        that.switchToSlideAt(slideIndex);
        slideIndex = (slideIndex + 1) % $slides.length;
    }, 2000);
    
    this.switchToSlideAt = function() { /* ... */ };
}
                    
function AutoSlidingGallery($slides) {
    var slideIndex = 0;
    
    setInterval(function() {
        this.switchToSlideAt(slideIndex);
        slideIndex = (slideIndex + 1) % $slides.length;
    }.bind(this), 2000);
    
    this.switchToSlideAt = function() { /* ... */ };
}
                    this context
function AutoSlidingGallery($slides) {
    var slideIndex = 0;
    
    setInterval( () => {
        this.switchToSlideAt(slideIndex);
        slideIndex = (slideIndex + 1) % $slides.length;
    }, 2000);
    
    this.switchToSlideAt = function() { /* ... */ };
}
                    In arrow functions, this always points to the this
                    of the outer scope.
function init(slideElems) {
    var numberOfSlides = 3;
    for (var i = 0; i < numberOfSlides; i++) {
        var index = i;
        slideElems[index].addEventListener("click", function() {
            switchToSlideAt(index);
        });
    }
    
    function switchToSlideAt(slideIndex) {
        // ...
    }
}
                    Bad! Clicking on any slide will call switchToSlideAt(3)
                        because 3 is the last value of the index variable.
There's only 1 copy of index per call to init().
let has block scope
function init(slideElems) {
    var numberOfSlides = 3;
    for (var i = 0; i < numberOfSlides; i++) {
        let index = i;
        slideElems[index].addEventListener("click", function() {
            switchToSlideAt(index);
        });
    }
    // index is not visible here
    
    function switchToSlideAt(slideIndex) {
        // ...
    }
}
                    Good! There's one copy of the index variable per block (per each turn of the for loop).
let: Temporal dead zonevar declarations are hoisted.
function init() {
    console.log(foo); //Logs: undefined
    var foo = 42;
    console.log(foo); //Logs: 42
}
                        let-declared variables cannot be used in their block before
                        the declaration .
function init() {
    //console.log(foo); //ReferenceError: foo is uninitialized
    let foo = 42;
    console.log(foo); //Logs: 42
}
                        let: Temporal dead zoneIf there's a let variable in a block, you can only use
                        it in the block after its declaration, even if there's another
                        such variable in the outer scope.
function init() {
    let foo = 123;
    if (true) {
        // consle.log(foo); //ReferenceError: foo is uninitialized
        let foo = 456;
        console.log(foo); //Logs: 456
    }
    console.log(foo); //Logs: 123
}
                    
// 1
"shop.example.com".indexOf("shop") === 0;
var haystack = "foo.bar.example.com";
var needle = "example.com"
haystack.slice(-needle.length) === needle;
// 2
" item message error ".indexOf(" message ") >= 0
~" item message error ".indexOf(" message ") // returns a truthy/falsy value
// 3
(new Array(4)).join("la") // lalala
                    
"shop.example.com".startsWith("shop.")        // true
"foo.bar.example.com".endsWith("example.com") // true
" item message error ".contains(" message ")  // true
"la".repeat(3)                                // "lalala"
// Some unicode / UTF-16 methods, mostly unimplemented yet:
"abc".normalize();
"∑".codePointAt(0);
String.fromCodePoint(0x2F804);                // A japanese character
                    
var slideIndex = 0;
var slideCount = 5;
$slide.append(
    "<div>" +
        "<p>Slide " + (slideIndex + 1) + " of " + slideCount + "</p>" +
    "</div>"
);
                    
var slideIndex = 0;
var slideCount = 5;
$slide.append(`
    <div>
        <p>Slide ${slideIndex + 1} of ${slideCount}</p>
    </div>
`);
                    Yes, they are multiline.
function createClient(name, isOrganization) {
    var nameKey = isOrganization ? "orgName" : "fullName";
    return {
        nameKey: name,
        isOrganization: isOrganization
    };
}
var astronaut = createClient("Neil Armstrong", false); 
var agency = createClient("NASA", true);
                    Property keys are just static!
console.log(astronaut);
// { nameKey: "Neil Armstrong", isOrganization: false }
console.log(agency);
// { nameKey: "NASA", isOrganization: true }
                        
function createClient(name, isOrganization) {
    var nameKey = isOrganization ? "orgName" : "fullName";
    var client = {
            isOrganization: isOrganization
        };
    client[nameKey] = name;
    return client;
}
var astronaut = createClient("Neil Armstrong", false); 
var agency = createClient("NASA", true);
                    Works, but is a bit annoying.
console.log(astronaut);
// { fullName: "Neil Armstrong", isOrganization: false }
console.log(agency);
// { orgName: "NASA", isOrganization: true }
                        
function createClient(name, isOrganization) {
    var nameKey = isOrganization ? "orgName" : "fullName";
    return {
        [nameKey]: name,
        isOrganization: isOrganization
    };
}
var astronaut = createClient("Neil Armstrong", false); 
var agency = createClient("NASA", true);
                    
console.log(astronaut);
// { fullName: "Neil Armstrong", isOrganization: false }
console.log(agency);
// { orgName: "NASA", isOrganization: true }
                    function Person(name) { this.name = name; } Person.prototype.introduce = function() { console.log("Hi, I'm " + this.name + "!"); }; function Superhero(name, tagline, powers) { Person.call(this, name); this.tagline = tagline; this.powers = powers; } Superhero.prototype = Object.create(Person.prototype); Superhero.prototype.constructor = Superhero; Superhero.prototype.hasManyPowers = function() { return this.powers.length >= 2; }; Superhero.prototype.introduce = function() { Person.prototype.introduce.call(this); console.log(this.tagline); };var superman = new Superhero( "Clark Kent", "Up, up and away!", ["flight", "heat vision"] ); superman.introduce(); /* Logs: Hi, I'm Clark Kent! Up, up and away! */
class Person { constructor(name) { this.name = name; } introduce() { console.log("Hi, I'm " + this.name + "!"); } } class Superhero extends Person { constructor(name, tagline, powers) { super( name); this.tagline = tagline; this.powers = powers; } hasManyPowers() { this.powers.length >= 2; } introduce() { super.introduce(); console.log(this.tagline); } }var superman = new Superhero( "Clark Kent", "Up, up and away!", ["flight", "heat vision"] ); superman.introduce(); /* Logs: Hi, I'm Clark Kent! Up, up and away! */
Sample: storing data related to a DOM Node, out of the DOM.
function createSafeScopeManager() { // for something like Angular's $el.scope() var scopes = {}; return { setScope: function(elem, scope) { scopes[elem] = scope; }, getScope: function(elem) { return scopes[elem]; }, scopes: scopes // published for debugging purposes } } var sm = createSafeScopeManager(); var elemOne = document.getElementById("one"); var elemTwo = document.getElementById("two"); sm.setScope(elemOne, { name: "scopeOne" }); sm.setScope(elemTwo, { name: "scopeTwo" }); console.log( sm.getScope(elemOne) ); console.log( sm.getScope(elemTwo) );// { name: "scopeTwo" } // { name: "scopeTwo" }console.log( Object.keys(sm.scopes) );// ["[object HTMLSpanElement]"]
Object keys are always converted to strings.
Map, Set, WeakMap…function createSafeScopeManager() { var scopes = new Map(); return { setScope: function(elem, scope) { scopes.set(elem, scope); }, getScope: function(elem) { return scopes.get(elem); } } } var sm = createSafeScopeManager(); var elemOne = document.getElementById("one"); var elemTwo = document.getElementById("two"); sm.setScope(elemOne, { name: "scopeOne" }); sm.setScope(elemTwo, { name: "scopeTwo" }); console.log( sm.getScope(elemOne) ); console.log( sm.getScope(elemTwo) );// { name: "scopeOne" } // { name: "scopeTwo" }
So what?
(function(global) {
    var ADULT_AGE = 18; // private, but common to all instances
    function Person(name) {
        this._name = name;
    }
    Person.prototype.introduce = function() {
        console.log("Hi, I'm " + this._name + "!");
    };
    
    global.Person = Person;
})(window);
                    
                    _name is not really private
(function(global) {
    var nameKey = Symbol();
    
    function Person(name) {
        this[nameKey] = name;
    }
    Person.prototype.introduce = function() {
        console.log("Hi, I'm " + this[nameKey] + "!");
    };
    
    global.Person = Person;
})(window);
                    
                    Object.keys()Reflect.ownKeys() and Object.getOwnPropertySymbols()
                            …so not fully private – accessible through reflection (like Java)
(function(global) {
    var NAMES = new WeakMap();
    
    function Person(name) {
        NAMES.set(this, name);
    }
    Person.prototype.introduce = function() {
        console.log("Hi, I'm " + NAMES.get(this) + "!");
    };
    
    global.Person = Person;
})(window);
                    
                    The NAMES map stores all names, each stored for a particular
                    Person.
This time, really private, as no one else has access to NAMES.
arguments' sux
function fixture(collectionName/*, items*/) {
    var items = [].slice.call(arguments, 1);
    items.forEach(function() {
        db.insert(collectionName, items);
    });
}
                    Array.isArray(arguments) === falsearguments[0] would mutate collectionNamearguments' sux
function fixture(collectionName, ...items) {
    items.forEach(function() {
        db.insert(collectionName, items);
    });
}
                    Array.isArray(items) === trueimport/requireOnly universal way: global variables, possibly namespaced.
// myHelper.js
var MYAPP = MYAPP || {};
MYAPP.myHelper = {
    help: function() {
        // ...
    },
    meaningOfLife: 42
}
// myComponent.js
var MYAPP = MYAPP || {};
MYAPP.MyComponent = function() {
    MYAPP.myHelper.help();
    // ...
};
                    import/require
// myHelper.js
define("myHelper", function() {
    return {
        help: function() {
            // ...
        },
        meaningOfLife: 42
    };
});
// myComponent.js
define("myComponent", ["meHelper.js"], function(myHelper) {
    function MyComponent() {
        myHelper.help();
        // ...
    }
    
    return MyComponent;
});                    
                    import/require
// myHelper.js
exports.help = function() {
    // ...
};
exports.meaningOfLife = 42;
// myComponent.js
var myHelper = require("myHelper.js");
exports = MyComponent;
function MyComponent() {
    myHelper.help();
    // ...
}
                    import/require
// myHelper.js
export function help() {
    // ...
}
export const meaningOfLife = 42;
// myComponent.js
import * as myHelper from "myHelper.js";
export default function MyComponent() {
    myHelper.help();
    // ...
}
                    Or:
// myComponent.js
import { help, meaningOfLife } from "myHelper.js";
export default function MyComponent() {
    help();
    // ...
}
                    'use strict' all over again
// Some non-strict lib, like jQuery
(function() {
    // ...
})();
// Our strict component 1
(function galleryModule() {
    "use strict";
    // ...
})();
// Our strict component 2
(function carouselModule() {
    "use strict";
    // ...
})();
                    'use strict' all over againModule and class bodies are automatically in strict mode.
\o/
Keep Icebergin'! Bartek Swierczynski / @bswierczynski