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) === false
arguments[0]
would mutate collectionName
arguments
' sux
function fixture(collectionName, ...items) {
items.forEach(function() {
db.insert(collectionName, items);
});
}
Array.isArray(items) === true
import
/require
Only 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