Becoming Better at

JavaScript

.NET Zuid

new Date(2017, 10, 21);

JavaScript is coming
for you

Gaya Kessler

Freelance Full Stack Web Developer

JavaScript, React, Node.js


Creator of Drammit and Formable

@GayaKessler on Twitter

Frontend is
moving fast

"That is so last week!"

It's hard to start learning or improving skills

When all that's written is already outdated

So what should I do?

The 'why' is more important than the 'how'

Talk outline

1. Understanding the fundamentals

2. Moving forward

3. Beyond the browser

Why is JavaScript perceived so negatively?

Oh, and jQuery.

1. The fundamentals

Scopes and Closures

Lexical scope


var name = 'Gaya';

if (1 === 1) {
	name = 'Kessler';
}

name; // Kessler

// in browsers:
window.name; // Kessler

Lexical scope


var name = 'Gaya';

function changeName(newName) {
	name = newName;
}

name; // Gaya
changeName('Kessler');
name; // Kessler

Function scope


var name = 'Gaya';

function makeName(anArgument) {
  var iamSecret = 'Hi';

  return name;
}

makeName('Hey'); // Gaya

console.log(iamSecret);
	// ReferenceError: iamSecret is not defined
console.log(anArgument);
	// ReferenceError: anArgument is not defined

What the IIFE?


var name = 'Gaya';

(function () {
	var name = 'Kessler';
	console.log(name); // Kessler
})();

console.log(name); // Gaya

Closures


function nameGenerator() {
	var name = 'Gaya';

	function giveName() {
		return name;
	}

	return giveName;
}

var generate = nameGenerator(); // puts giveName() in generate

generate(); // Gaya

Closures


for (var i = 0; i < 5; i++) {
	setTimeout(function timer() {
		console.log(i);
	}, i * 1000);
}

// 5
// 5
// 5
// 5
// 5

Closures


for (var i = 0; i < 5; i++) { // <-- create scope for 'i'
	setTimeout(function timer() {
		console.log(i);
	}, i * 1000);
}

Closures


for (var i = 0; i < 5; i++) {
	setTimeout(function timer() {
		console.log(i);
	}, i * 1000); // <-- 0 * 1000
}

Closures


for (var i = 0; i < 5; i++) {
	setTimeout(function timer() {
		console.log(i);
	}, i * 1000); // <-- 1 * 1000
}

Closures


for (var i = 0; i < 5; i++) {
	setTimeout(function timer() {
		console.log(i);
	}, i * 1000); // <-- 2 * 1000
}

Closures


for (var i = 0; i < 5; i++) {
	setTimeout(function timer() {
		console.log(i);
	}, i * 1000); // <-- 3 * 1000
}

Closures


for (var i = 0; i < 5; i++) {
	setTimeout(function timer() {
		console.log(i);
	}, i * 1000); // <-- 4 * 1000
}

Closures


for (var i = 0; i < 5; i++) { // <-- i++ = 5
	setTimeout(function timer() {
		console.log(i);
	}, i * 1000);
}

Closures


for (var i = 0; i < 5; i++) {
	setTimeout(function timer() {
		console.log(i); // 5
	}, i * 1000);
}

Closures to the rescue!


for (var i = 0; i < 5; i++) {
	(function (j) {
		setTimeout(function timer() {
			console.log(j);
		}, j * 1000);
	})(i);
}

Closures to the rescue!


function createTimer(j) {
	return function timer() {
		console.log(j);
	}
}

for (var i = 0; i < 5; i++) {
	setTimeout(createTimer(i), i * 1000);
}

What is this?


function foo(cb) {
	this.a = 'hey';
	cb();
}

function bar() {
	console.log(this.a);
}

var hi = bar;
hi.a = 'hi';

foo(hi); // hey
hi.a; // hi

function foo(cb) { // <-- `bar` is `cb`
	this.a = 'hey';
	cb(); // <-- call site
}

function bar() {
	console.log(this.a); // <-- now `this` is `foo`
}

var hi = bar;
hi.a = 'hi';

foo(hi);
hi.a;

function foo(cb) {
	this.a = 'hey';
	cb(); // <-- still the same call site
}

function bar() {
	console.log(this.a); // <-- but `this` is bound to `hi`
}

var hi = bar;
hi.a = 'hi';

foo(hi.bind(hi)); // hi

this or that


var Gaya = {
	hobby: 'Whisky',
	sayHobby: function () {
		console.log(this.hobby);
	},
	sayHobbyAfterAFewDrinks: function () {
		setTimeout(function () {
			console.log(this.hobby);
		}, 1000);
	},
};

Gaya.sayHobby(); // Whisky
Gaya.sayHobbyAfterAFewDrinks(); // undefined

this or that


var Gaya = {
	hobby: 'Whisky',
	sayHobby: function () {
		console.log(this.hobby);
	},
	sayHobbyAfterAFewDrinks: function () {
		var that = this;
		setTimeout(function () {
			console.log(that.hobby);
		}, 1000);
	},
};

Gaya.sayHobby(); // Whisky
Gaya.sayHobbyAfterAFewDrinks(); // Whisky

this or that


var Gaya = {
	hobby: 'Whisky',
	sayHobby: function () {
		console.log(this.hobby);
	},
	sayHobbyAfterAFewDrinks: function () {
		setTimeout(function () {
			console.log(this.hobby);
		}.bind(this), 1000);
	},
};

Gaya.sayHobby(); // Whisky
Gaya.sayHobbyAfterAFewDrinks(); // Whisky

this or that


var Gaya = {
	hobby: 'Whisky',
	sayHobby: function () {
		console.log(this.hobby);
	},
	sayHobbyAfterAFewDrinks: function () {
		setTimeout(() => {
			console.log(this.hobby);
		}, 1000);
	},
};

Gaya.sayHobby(); // Whisky
Gaya.sayHobbyAfterAFewDrinks(); // Whisky

Binding events


var Thing = {
	foo: function() {
		this.bar(); //Uncaught TypeError: this.bar is
					//not a function
	},

	bar: function() {
		console.log('Hi');
	},

	bind: function() {
		document.getElementById('something')
  			.addEventListener('click', this.foo);
	}
};

Binding events


var Thing = {
	foo: function() {
		this.bar();
	},

	bar: function() {
		console.log('Hi'); // Hi
	},

	bind: function() {
		document.getElementById('something')
  			.addEventListener('click', this.foo.bind(this));
	}
};

Object context


var Car = {
	sound: 'Vroom vroom',
	drive: function () {
		console.log(this.sound);
	}
};

var Bus = {
	sound: 'Rumble rumble',
	drive: Car.drive,
};

Bus.drive(); // Rumble rumble

Async

non-blocking execution


function superHardMathProblem() { return 1; }

setTimeout(superHardMathProblem, 1000);
// do other stuff

Callbacks


function superHardMathProblem(callback) {
	console.log('Thinking...');

	setTimeout(callback, 1000);
}

superHardMathProblem(function () {
	console.log('Done!');
});

Callback hell


setTimeout(function() {
  setTimeout(function() {
    setTimeout(function() {}, 100);
  }, 100);
}, 100);

Still callback hell


function wait(cb) {
	setTimeout(cb, 100);
}

wait(wait(wait(function () {
	console.log('Waited for 300ms!');
}))); // <- oh my....

Still callback hell


function step1() {
	setTimeout(step2, 100);
}

function step2() {
	setTimeout(step3, 100);
}

function step3() {
	console.log('I am done!');
}

step1();

Callbacks are used heavily in Node.js

Promises


var step1 = () =>
	new Promise(resolve => setTimeout(resolve, 100));

var step2 = new Promise(function (resolve) {
	setTimeout(resolve, 100);
});

var step3 = new Promise(function () {
	return 'I am done!';
});

step1().then(step2).then(step3)
	.then(console.log); // I am done!

Promises are future values

Promises are always async

Promise.all()

Promise.race()

Promise.resolve()

async await

Object Oriented programming in JavaScript

"Composition over inheritance."

- Design Patterns by Gang of Four (1994)

How we explain inheritance

But what if we want a mammal which lays eggs?

Classes in JavaScript

A complete and utter mess

The pre ES6 way


function StrangeThing() {
	// ... some constructor
}

StrangeThing.prototype.upsideDown = function () {
	// ... some action
}

console.log(StrangeThing);
	// function StrangeThing()

var instance = new StrangeThing();
console.log(instance);
	// Object { }

What is a class?


class StrangeThing {
	upsideDown() {
		// some action
	}
}

console.log(StrangeThing);
	// function StrangeThing()

var instance = new StrangeThing();
console.log(instance);
	// Object {  }

🤔

What is happening?


function StrangeThing() {} //<-- already an instance
StrangeThing.prototype.upsideDown = function() {};

var instance = new StrangeThing();
	//1. new object
	//2. delegate to StrangeThing.prototype
	//3. call constructor of StrangeThing on new object

Delegation vs object creation

Explicit behaviour delegation


var Foo = {
	a: 'World',
	bar: function() {
		console.log(this.a);
	},
};

var Whoop = {
	a: 'Hello',
	bar: Foo.bar,
};

Whoop.bar(); // Hello

Behaviour delegation


var Foo = {
	a: 'World',
	bar: function() {
		console.log(this.a);
	},
};

var Whoop = Object.create(Foo);
Whoop.a = 'Hello',

Whoop.bar(); // Hello

Foo.bar = function () { console.log('Fooled you!'); };
Whoop.bar(); // Fooled you!

Extending


var Foo = {
	a: 'World',
	bar: function() {
		console.log(this.a);
	},
};

var Whoop = Object.assign({}, Foo, { a: 'Hello' });

Whoop.bar(); // Hello

Foo.bar = function () {};
Whoop.bar(); // Hello

My advise:

Use composition

break;

2. Moving forward

Modules and structure

"Do one thing, and do it well."

require vs import

Require


// someModule.js

module.exports = function someModule(input) {
	return input + 1;
}

// other file

var someModule = require('./someModule');

someModule(41); // 42

imports


// someModule.js

export default function someModule(input) {
	return input + 1;
}

// other file

import someModule from './someModule';

someModule(41); // 42

Named exports


// someModule.js

export function addTwo(input) {
	return input + 2;
}

export function addOne(input) {
	return input + 1;
}

// other file

import { addOne, addTwo } from './someModule';

addOne(addTwo(39)); // 42

Dynamic imports


import('./someModule')
	.then(({ addOne, addTwo}) => {
		addOne(addTwo(39)); // 42
	});

You'll need transpilers

Next generation JavaScript

or ES6 ES7 ES2015 ESNEXT...

Arrow functions


var returnFirstArg = param => param;
returnFirstArg(42, 'Hi', 13); // 42

var noArgs = () => 'Test';
noArgs(); // Test

var withArgs = (a, b, c, d) => a + b + c + d;
withArgs(12, 20, 8, 2); // 42

var withBody = () => {
	var something = 'Something';

	return something;
};

const and let


const hello = 'Hi';
hello = 'Overwrite';
	// TypeError: invalid assignment to const `hello'

let hi = 'Hello';

if (hi === 'Hello') {
	let hi = 'World';
	hi; // World
}

hi; // Hello

Bonus...


for (let i = 0; i < 10; i++) {
	setTimeout(() => console.log(i), 100);
}

// will log:
//	0, 1, 2, 3, ...

Bonus...


var _loop = function _loop(i) {
	setTimeout(function () {
		return console.log(i);
	}, 100);
};

for (var i = 0; i < 10; i++) {
	_loop(i); // Hey, a closure
}

Template strings


const firstName = 'Gaya';
const surName = 'Kessler';

const fullName = `${firstName} ${surName}`;
	// Gaya Kessler

const multiline = `Well,
this is new!
Awesome.`;

const showName = true;
const expressions =
	`Hello ${showName ? firstName : 'you'}!`;
	// Hello Gaya

Destructuring


const Gaya = { name: 'Gaya', height: 184, age: 30 };

function logPerson({ name, height, age }) {
	console.log(`
${name} is ${age} years old
and ${height / 100}m tall.
	`);
}

logPerson(Gaya);
	// Gaya is 30 years old
	// and 1.84m tall.

Destructuring


const Gaya = ['Gaya', 184, 30];

function logPerson([name, height, age]) {
	console.log(`
${name} is ${age} years old
and ${height / 100}m tall.
	`);
}

logPerson(Gaya);
	// Gaya is 30 years old
	// and 1.84m tall.

Destructuring


const { name, age, height } = getPersonInformation('Gaya');

console.log(`
${name} is ${age} years old
and ${height / 100}m tall.
`);

// Gaya is 30 years old
// and 1.84m tall.

Spread operator


const Gaya = { name: 'Gaya', height: 184, age: 30 };
const Harry = { ...Gaya, name: 'Harry' };
	// { name: 'Harry', height: 184, age: 30 };

// same as

const Harry = Object.assign({}, Gaya, { name: 'Harry' });

Spread operator


const numbers = [1, 2, 3, 4, 5];
const floats = [0.2, 0.5, 3.72];

const combined = [...numbers, ...floats];
	// [1, 2, 3, 4, 5, 0.2, 0.5, 3.72]

const countTo10 = [...numbers, 6, 7, 8, 9, 10];
	// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

async await


async function fetchContent() {
	try {
		const result = await fetch('https://www.google.com');
		const text = await result.text();

		return text;
	} catch (e) {
		throw new Error(e.message);
	}
}

fetchContent()
	.then(console.log)
	.catch(console.error);

Pure vs. Impure

Pure functions

  • no side-effects
  • given the same input, will always
    return the same output
  • are predictable
  • are very testable

Impure functions

  • Makes sense to call without using the return value
  • Can adjust external values

Some tips

  • Never touch arguments, copy them
  • Avoid using state in objects
    or this altogether
  • Most methods are better off as functions

Get the hang of (pure) Array functions

map reduce filter find

The map() method creates a new array with the results of calling a provided function on every element in the calling array.


[1, 2, 3, 4, 5].map(function (item) {
	return item * 2;
});

// [2, 4, 6, 8, 10]

The reduce() method applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value.


[1, 2, 3, 4, 5].reduce(function (prev, current) {
	return prev + current;
});

// (((1 + 2) + 3) + 4) + 5
// 15

The filter() method creates a new array with all elements that pass the test implemented by the provided function.


[1, 2, 3, 4, 5].filter(function (item) {
	return item > 3;
});

// [4, 5]
The find() method returns the value of the first element in the array that satisfies the provided testing function.

[1, 2, 3, 4, 5].find(function (item) {
	return item > 3;
});

// 4

The power here is chaining


[1, 2, 3, 4, 5]
	.filter(item => item > 3) // [4, 5]
	.map(item => item * 3) // [12, 15]
	.reduce((prev, next) => prev + next); // 27

Composition

"a product of mixing or combining
various elements or ingredients"

Composing in JavaScript


function compose(...fns) {
	const reversedFns = [...fns].reverse();
	return (input) =>
		reversedFns.reduce((value, fn) => fn(value), input);
}

const roundString = compose(Math.round, parseFloat);
roundString('80.5'); // 81

Higher Order Components

Object factories


const canLoose = (obj) => ({
	...obj,
	loose() { console.log(`${this.name} lost!`); }
});

const canWin = (obj) => ({
	...obj,
	win() { console.log(`${this.name} won!`); }
});

function createGame(input) {
	return canLoose(canWin(input));
	// or
	return compose(canLoose, canWin)(input);
}

3. Beyond the browser

Using babel & webpack

Problem! It's confusing and hard.

create-react-app

angular-starter

preact-cli

use linters and a styleguide

What I use

ESLint

airbnb-styleguide

Type strict JavaScript

Flow

Typescript

Try some Node.js

Thanks!

Gaya Kessler

Freelance Web Developer


https://theclevernode.com
@GayaKessler on Twitter