ES6
Created: 2015-08-25 18:06:19 -0700 Modified: 2021-05-15 11:22:10 -0700
Coming back to JS after a while?
Section titled Coming back to JS after a while?Read this: https://turriate.com/articles/modern-javascript-everything-you-missed-over-10-years
Intro
Section titled IntroThese sites are both great resources:
I’m only going to be making notes here that I think are useful for the future.
================================================================================
Function with default object parameters
Section titled Function with default object parametersThis syntax gives defaults to not only the operands, but it also gives a default to the object passed in altogether, so you can call it with zero arguments if you want.
Yes, “let” is block-scoped, and will probably become the default scoping for almost everything, because function-scoping (and hoisting) with “var” isn’t usually what you want. However, there’s a nice little gem thanks to having block-scoping:
ES5 (this sucked):
ES6:
================================================================================
Arrow functions
Section titled Arrow functionsShorthand for anonymous functions in JavaScript. Useful for callbacks to turn this:
Into this:
You can define a whole function with this too:
To define a function with no arguments, do:
Context:
- It still captures any other variables in closures.
- ‘this’ is always the ‘this’ from the scope that defines the function, meaning call, apply, and bind have NO effect.
Conclusions:
- Because ‘this’ can’t be modified, I would only ever use this as a lambda or as a quick callback function. You would never use this to write class functions.
================================================================================
Variable destructuring
Section titled Variable destructuringA way to destructure objects and arrays:
let test = {name: ‘Adam’, x: 2};
let {name: myName, x: numHands} = test;
console.log(‘My name is: ’ + myName + ’ and I have ’ + numHands + ’ hands.’); // My name is Adam and I have 2 hands.
This is handy for something like this:
function getResult() {
return {
result: 1234,
error: null
};
}
var {result, error} = getResult();
console.log(result); // 1234
I’ve also seen it used in code like this:
ES5: var black = this.props.black;
ES6: const { black } = this.props; // it’s fewer characters and it’s const
You can also do this for arrays:
var [first, last] = [‘Adam’, ‘Learns’];
console.log(first, last); // “Adam” “Learns”
That may be useful if you got data in an array that you know is something it’s in a certain structure, but to me that means you should’ve used an object where you could explicitly name your properties.
However, there is a “car”/“cdr” style too:
let [first, … remaining] = [1,2,3,4,5]; // the ”…” is the “spread” operator
console.log(first); // 1
console.log(remaining); // [2,3,4,5]
You can also implicitly reject elements in an array without having to use something like an “_ignore” variable:
let [,,third] = [1,2,3]; // 3
Similarly, the “spread” operator can be used for calling a function:
var extraParams = [3,4,5];
function foo(a, b, c, d, e) {
console.log(a+b+c+d+e);
}
foo(1,2, … extraParams); // 15 (and note: the spread operator may be copy/pasted out of OneNote as a single-character ellipsis, but it is supposed to be three periods)
The spread operator can also do something like this:
var a = {
foo: 5,
bar: 6
};
var b = {
…a, // shallow-copies all properties of ‘a’ into this, meaning b.foo.nested will now modify a.foo.nested too.
baz: 7
};
console.log(‘b: ’ + JSON.stringify(b)); // b: {“foo”:5,“bar”:6,“baz”:7}
================================================================================
Classes
Section titled ClassesTry to use underscore variables at LEAST in your getters/setters.
Invalid code (well, not invalid, but you’ll infinitely recurse, meaning it won’t work):
class Person {
constructor(first, last) {
this.first = first;
this.last = last;
}
get first() { return this.first; }
set first(f) { this.first = f; } // NOT GOOD: this function calls itself infinitely
toString() {
return ${this.first} ${this.last}
;
}
}
Change it to:
class Person {
constructor(first, last) {
this.first = first;
this.last = last;
}
get first() { return this._first; } // I only had to change the getter/setter to use “_first” instead of “first”.
set first(f) { this._first = f; } // the rest of the code works fine because these functions don’t need parens to be invoked.
toString() {
return ${this.first} ${this.last}
;
}
}
Remember: don’t use getters/setters instead of explicit function calls when you want side effects. For example, having a getter for something like “get lengthInInches() { return this.lengthInFeet * 12; }” is fine because you’re not modifying the state of the class. But saying “get nextId() { return this.id++; }” is bad because it’s not obvious that there are even side effects, e.g.:
if (this.nextId > 0 && this.nextId < 10) {
console.log(‘Congrats! You are one of our first users.’);
}
if (this.nextId === 0) {
console.log(‘This will never be hit now!’);
}
The problem is that you’re incrementing nextId just by accessing it, but it’s not obvious to a user of the API that there would even be side effects.
================================================================================
Modules / Imports
Section titled Modules / ImportsIn ES5, you basically had to pollute the global namespace for client-side imports. For example, in the Google Closure Library, you had something like this:
// constants.js
goog.provide(‘my.app.constants’);
my.app.constants.PI = PI = 3.141592653589793;
// main.js
goog.require(‘my.app.constants’);
console.log(my.app.constants.PI); // 3.141592653589793
The problem with this is that you really had a global namespace in the form of “window.my” (well, it wasn’t necessarily rooted at ‘window’, but you get the idea). This meant that “my” was now a global variable.
With ES6, you don’t need to come up with a namespace at all since you import based on relative paths.
import * as constants from “src/constants.js”
constants.PI; // 3.141592653589793
What’s nice is that “constants.js” isn’t the one coming up with the name for the namespace; the user of the file is. This means constants.js can now look like this:
// constants.js
export var PI = 3.141592653589793; // now we don’t care if some other module defined their own PI
================================================================================
This was actually in ES5, but I didn’t know about it:
x = 5;
y = ‘hi’;
console.log(x); // 5
console.log(y); // hi
console.log({x}); // Object {x: 5}
console.log({y}); // Object {y: ‘hi’}
console.log({x, y}); // Object {x: 5, y: ‘hi’}
So console.log({someObj}) is essentially shorthand for console.log(‘someObj: ’ + someObj);
It doesn’t beat the “stringify” snippet I wrote in Sublime to do console.log(‘objectToLogHere: ’ + JSON.stringify(objectToLogHere));
Thanks to default parameters, you shouldn’t ever see something like this anymore:
function Person(name) {
this.name = name || ‘Adam’; // BAD: see below for why
}
Instead, change the signature of the function:
function Person(name=‘Adam’) {
this.name = name; // much nicer!
}
This is great because sometimes defaults didn’t work how you might expect. E.g. if you had a default number that was non-zero:
function Person(numArms) {
this.numArms = numArms || 2; // BAD: if you lost both of your arms and passed in 0, “0 || 2” evaluates to 2, so you’re still given two arms.
}
// Fix it like this:
function Person(numArms = 2) {
this.numArms = numArms;
}
And yes, I actually did run into this in practice. I had something along the lines of “this.resourceMultiplier = multiplier || 1”, so I couldn’t ever set it to 0 thanks to that line.
================================================================================
Setting up a project
Section titled Setting up a projectNote: I am doing this on 8/26/2015, so ES5 is still common enough for me to have to use a transpiler.
mkdir learning_es6
cd learning_es6
express
npm install
npm install —save-dev gulp
npm install —save-dev gulp-babel
npm install —save-dev babel
Make a basic gulpfile.js in the root directory:
var gulp = require(‘gulp’);
var babel = require(‘gulp-babel’);
var paths = {
jsCode: [‘./src/**/*.js’],
dist: ‘dist’
};
gulp.task(‘default’, function () {
return gulp.src(paths.jsCode, {base: ’./’})
.pipe(babel())
.pipe(gulp.dest(paths.dist));
});
Expose “dist” to your views in Express:
app.use(‘/dist’, express.static(path.join(__dirname, ‘dist’)));
Note that I explicitly expose it here as “dist”. If you leave off the first argument, then you can also leave off “dist” from the path below.
Set up your index.jade:
doctype html
html
head
title Learning ES6
script(type=‘text/javascript’, src=‘dist/src/main.js’)
body(onload=‘main.start()’ style=“overflow: hidden;”)
Set up a main.js (as shown above, I put this in a folder called “src”, but you may want it in publicjavascripts):
class Person {
constructor(name) {
this.name = name;
console.log(‘this.name: ’ + JSON.stringify(this.name));
}
}
window.main = {};
main.start = function() {
let person = new Person(‘Adam’);
};
Run “gulp” so that your file is transpiled and placed into the “dist” folder:
gulp
Finally, launch your HTTP server with this on Windows:
set DEBUG=learning_es6:* & node .binwww
Go to http://localhost:3000
Now you’re up and running with ES6/Babel.