JS 139 Review Sheet

JavaScript Topics

Hoisting

console.log(foo) // logs undefined
var foo = 2;
console.log(foo) // cannot access ‘foo’ before initialization
let foo = 1;
// function declaration
doSomething(); // logs ’function declarations are hoisted with definition!’
function doSomething() {
console.log(’function declarations are hoisted with definition!);
}
// function expression with var keyword
doAnotherThing(); // type error, doAnotherThing is not a function
var doAnotherThing = function() {
console.log(‘this won’t work’);
}
// function expression with let keyword
doAThirdThing(); // reference error, cannot access doAThirdThing before initialization
let doAThirdThing = function() {
console.log(‘this won’t work’);
}
MyClass.doSomething(); // reference error, cannot access class ‘MyClass’ before initializationclass MyClass {
doSomething() {
console.log(‘class definitions are not hoisted, but class names are’);
}
}
bar(); // logs world
var bar = ‘hello’;
function bar() {
console.log(‘world’);
}
// hoisted is
function bar() {
console.log(‘world’);
}
bar();
bar = ‘hello’;
var bar = ‘hello’;
bar(); // Uncaught TypeError: bar is not a function
function bar() {
console.log(‘world’);
}
// hoisted is
function bar() {
console.log(‘world’);
}
bar = ‘hello’;
bar();

The var statement

// example A
> var qux = 5
> qux = 6 // no TypeError
> const qux2 = 5
> qux2 = 6 // TypeError
// example B
function doSomething() {
if (true) {
let foo1 = 'block scope';
var foo2 = 'function scope';
}
console.log(foo2); // logs 'function scope'
console.log(foo1); // reference error, foo1 out of scope
}
// example C
> var foo = 2;
> global.foo
= 2
> let bar = 2;
> global.bar
= undefined

Strict mode

Scope

let global = 'global'if (true) {
let local = 'local block'
let global1 = 'global'
}
function doSomething() { // doSomething in global scope
let local1 = 'local to function block'
if (true) {
let local2 = 'local block'
var local3 = 'local function'
}
}
let block = 'block'
let function = 'function'
if (true) {
let block1 = 'block'
let function1 = 'function'
}
function doSomething() { // doSomething is function slope
let block2 = 'block'
if (true) {
let block3 = 'still block'
var function3 = 'still function'
}
}
let outer = 'outer scope'if (true) {
let inner = 'inner scope of block'
let outer2 = 'outer scope of block'
}
function doSomething() {
let inner1 = 'inner scope of function'
if (true) {
let inner2 = 'inner scope of block'
var mix3 = 'inner scope of function, outer scope of block'
}
}

Closures

function createCounter() {
let count = 0;
return () => {
count ++;
console.log(count);
}
}
let incrementCount = createCounter();
// count not in scope, but reference to count identifier is in
// closure created by createCounter
incrementCount(); // 1
incrementCount(); // 2
incrementCount(); // 3
let incrementCount2 = createCounter();
incrementCount2(); // 1
incrementCount2(); // 2
incrementCount2(); // 3

Private data

// using closures
function createHiddenRecord() {
let hiddenRecords = [];
return {
add(secret) {
hiddenRecords.push(secret);
},
show() {
return [...hiddenRecords]
}
}
}
let mySecrets = createHiddenRecord();
mySecrets.add(‘surprise party thursday’);
mySecrets.add(‘i hate you’);
mySecrets.show(); // logs copy of hiddenRecords
console.log(mySecrets.hiddenRecords); // NOPE! undefined.
// I have to use provided interface.
// using IIFEs (see below)
let hiddenRecord = (function() {
let hiddenRecords = [];
return {
add(secret) {
hiddenRecords.push(secret);
},
show() {
return [...hiddenRecords];
}
}
})();
hiddenRecord.add('surprise party thursday');
hiddenRecord.add('i hate you');
hiddenRecord.show();
console.log(hiddenRecord.hiddenRecords); // NOPE! undefined.
// I have to use provided interface.

Partial function application

function createAdder(numToAdd) {
return number => {
return number + numToAdd;
}
}
let addFive = createAdder(5); // five is applied now// then the other number is applied later!
addFive(5) // returns 10
addFive(10) // returns 15

IIFEs

// Private SCOPE with IIFEs and blocks
// messy code
console.log((function() {
let array = [1,2,3];
calculateSomethingIndependent(array);
})())
// more messy code{
let array = [1,2,3];
let solution = calculateSomethingIndependent(array);
console.log(solution);
}
// more messy code
let hiddenRecord = (function() {
let hiddenRecords = [];
return {
add(secret) {
hiddenRecords.push(secret);
},
show() {
return [...hiddenRecords];
}
}
})();
hiddenRecord.add('surprise party thursday');
hiddenRecord.add('i hate you');
hiddenRecord.show();
console.log(hiddenRecord.hiddenRecords); // NOPE! undefined.

Shorthand notation (understanding, not using)

let { qux, foo, bar } = obj; // or
let { foo, bar, qux } = obj; // or
let { qux: myQux, foo, bar } = obj;
// is the same as
let obj = {
foo: “foo”,
bar: “bar”,
qux: 42,
};
let foo = obj.foo;
let bar = obj.bar;
let qux = obj.qux;
// MORE OBJECT DESTRUCTURINGfunction xyzzy({ foo, bar, qux }) {
console.log(qux); // 3
console.log(bar); // 2
console.log(foo); // 1
}
let obj = {
foo: 1,
bar: 2,
qux: 3,
};
xyzzy(obj);// MORE OBJECT DESTRUCTURING({ foo, bar, qux } = obj);
let foo = [1, 2, 3, 4];
let [ bar, ...qux ] = foo; // rest syntax groups 2, 3, & 4
console.log(bar); // 1
console.log(qux); // [2, 3, 4]
// MORE ARRAY DESTRUCTURINGlet [ first, , , fourth, fifth, , seventh ] = bar;
let foo = { qux: 1, baz: 2 };
let bar = { ...foo };
console.log(bar); // { qux: 1, baz: 2 }
console.log(foo === bar); // false — bar is a new object
// MORE SPREAD SYNTAX
let foo = { qux: 1, baz: 2 };
let xyz = { baz: 3, sup: 4 };
let obj = { ...foo, ...xyz };
obj; // => { qux: 1, baz: 3, sup: 4 }
// Insert an array into another array
let foo = [1, 2, 3]
let bar = […foo, 4, 5, 6, …foo];
bar; // => [1, 2, 3, 4, 5, 6, 1, 2, 3]
let foo = {bar: 1, qux: 2, baz: 3, xyz: 4};
let { bar, baz, …otherStuff } = foo;
console.log(bar); // 1
console.log(baz); // 3
console.log(otherStuff); // {qux: 2, xyz: 4}
// MORE REST SYNTAXlet foo = [1, 2, 3, 4];
let [ bar, …otherStuff ] = foo;
console.log(bar); // 1
console.log(otherStuff); // [2, 3, 4]
// MORE REST SYNTAXfunction maxItem(first, ...moreArgs) {
let maximum = first;
moreArgs.forEach(value => {
if (value > maximum) {
maximum = value;
}
});
return maximum;
}
console.log(maxItem(2, 6, 10, 4, -3));

Modules (CommonJS)

function print(string) {
console.log(string);
}

module.exports = print;

// new file
const print = require("./print.js");
print("You rock!");

// node
$ node main.js
You rock!
// example with multiple exports!

let punctuation = "!!!";

function print(string) {
console.log(`${string}${punctuation}`);
}

function setPunct(newPunctuation) {
punctuation = newPunctuation;
}

module.exports = {
print,
setPunct,
};

// new file
const { logIt, setPrefix } = require("./logit");
print("You rock"); // You rock!!!
setPunct("???");
logIt("You rock"); // You rock???
// foo.js
import { bar } from "./bar";

let xyz = 1; // not exported

export function foo() {
console.log(xyz);
xyz += 1;
bar();
}
// bar.js
export let items = [];
export let counter = 0;

export function bar() {
counter += 1;
items.push(`item ${counter}`);
}

export function getCounter() {
return counter;
}
// main.js
import { foo } from "./foo";
import { bar, getCounter, items, counter } from "./bar";

foo();
console.log(items); // ["item 1"]
console.log(getCounter()); // 1
console.log(counter); // 1

bar();
console.log(items); // ["item 1", "item 2"]
console.log(getCounter()); // 2
console.log(counter); // 2

Exceptions

ex 1//
function divide(dividend, divisor) {
if (divisor === 0 || divisor === -0) {
throw new Error("Can't divide by zero");
}
return dividend/divisor;
}
divide(10, 0) // Error: Can't divide by zero
// ex 2
class DivideByZeroError extends Error {}

function divide(dividend, divisor) {
if (divisor === 0) {
throw new DivideByZeroError("Divide by zero!");
}

return dividend / divisor;
}

let result = divide(1, 0); // DivideByZeroError: Divide by zero!
console.log(result); // not run
class DivideByZeroError extends Error {}

function divide(dividend, divisor) {
if (divisor === 0) {
throw new DivideByZeroError("Divide by zero!");
}

return dividend / divisor;
}
function divideTenBy(divisor) {
try {
return divide(10, divisor);
} catch (error) {
if (error instanceOf DivideByZeroError) {
console.log('Can't divide by zero'); // doesn't terminate prog
} else {
throw error;
}
}
}
divideTenBy(10); // 1
divideTenBy(0); // Can't divide by zero!
divideTenBy(5); // 2

Pure functions and side effects

Asynchronous programming (setTimeout, setInterval)

// hello world
setTimeout(function() {
console.log('!');
}, 3000);

setTimeout(function() {
console.log('World');
}, 1000);

console.log('Hello');
// countdown
let countdown = function() {
let timer = 1000;
for (let count = 10; count >= 0; count -= 1) {
setTimeout(() => console.log(count), timer);
timer += 1000; // otherwise it will log them all after 1 sec
}
}
let takeAChance = () => console.log('take a chance take a chance take a take a chance chance');let abba = setInterval(takeAChance, 2000);// wait some time
clearInterval(abba); // stops printing

// counting up to 1000
function startCounting() {
let count = 0;
let counterId = setInterval(function() {
count += 1;
console.log(count);
}, 1000);
return counterId;
}
function stopCounting(counterId) {
clearInterval(counterId);
}
let counterId = startCounting();
// some time later
stopCounting(counterId);

Testing With Jest

Testing terminology

Jest

> npm install jest -g
> touch jest.config.js
> jest temp.test.js
const Car = require("./car");

describe("The Car class", () => {
test("has four wheels", () => {
let car = new Car();
expect(car.wheels).toBe(4);
});
});

Expect and matchers

test("has four wheels", () => {
let car = new Car();
expect(car.wheels).toBe(4);
});
test('two new cars are equal objects', () => {
let car1 = new Car();
let car2 = new Car();

expect(car1).toEqual(car2);
});
test('does not have doors', () => {
let car = new Car();
expect(car.doors).toBeUndefined();
});
test('raises a TypeError when called drive on it', () => {
let car = new Car();
expect(() => car.drive()).toThrow(TypeError);
});
test('a new car has no mileage info', () => {
let car = new Car();
expect(car.mileageInfo).toBeNull();
});
test('wheels is truthy', () => {
let car = new Car();
expect(car.wheels).toBeTruthy();
});
test('array contains car', () => {
let car = new Car();
let arr = [1, 2, 3];
arr.push(car);

expect(arr).toContain(car);
});
// ortest('string contains "car"', () => {
let man = "His scars have healed";

expect(man).toContain("car");
});
test('car has wheels', () => {
let car = new Car();
expect(car.wheels).not.toBeUndefined();
});

SEAT approach

describe('TestObject works', () => {
let testObj;
beforeEach(() => {
testObj = new TestObject();
}
}
describe('TestObject class', () => {
test('myFunction shows all objects in testObj', () => {
testObj.push(importantValue);
// ...
}
}
describe('TestObject class', () => {
// ...
test('doSomething method does something', () => {
expect(testObj.doSomething()).toBe('done');
}
}
afterEach(() => {
cleanUpFiles();
}

Understanding code coverage

Packaging Code

Project directory layout

npm and npx

> npm install packageName --global
// or
> npm install packageName --save
const _ = require('lodash');
console.log(_.chunk([1, 2, 3, 4, 5, 6, 7, 8], 2));
// or
const chunk = require('lodash/chunk');
console.log(chunk([1, 2, 3, 4, 5, 6, 7, 8], 2));
// or
const chunk = require('lodash').chunk;
console.log(chunk([1, 2, 3, 4, 5, 6, 7, 8], 2));

package.json and package-lock.json

> npm init
{
"name": "todolist",
"version": "1.0.0",
"description": "",
"main": "index.js",
"directories": {
"lib": "lib",
"test": "test"
},
"dependencies": { // add this section to file
"express": "4",
"http-errors": "1",
"morgan": "1.9"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
{
"name": "todolist",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"accepts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
"requires": {
"mime-types": "~2.1.24",
"negotiator": "0.6.2"
}
},
// a whole bunch of omitted dependencies
"express": {
"version": "4.17.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
"requires": {
"accepts": "~1.3.7",
"array-flatten": "1.1.1",
// A long list of requires
"vary": "~1.1.2"
}
},
// a whole bunch of omitted dependencies
"http-errors": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
"integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==",
"requires": {
"depd": "~1.1.2",
"inherits": "2.0.4",
"setprototypeof": "1.1.1",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.0"
},
"dependencies": {
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
}
}
},
// a whole bunch of omitted dependencies
"morgan": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz",
"integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==",
"requires": {
"basic-auth": "~2.0.0",
"debug": "2.6.9",
"depd": "~1.1.2",
"on-finished": "~2.3.0",
"on-headers": "~1.0.1"
}
},
// a whole bunch of omitted dependencies
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
}
}
}
> npm uninstall lodash --save // to also remove from package.json
// or
> npm uninstall lodash --save-dev // to remove from package.json dev
// or
> npm prune // removes the actual node_modules files for
// dependencies that you manually removed from package.json
> eslint fileName.js // will use the globally installed version
> npx eslint fileName.js // will look first for local version

Transpilation

npm scripts

"babel": "npx babel lib --out-dir dist --presets=@babel/preset-env"// or"babel": "babel lib --out-dir dist --presets=@babel/preset-env"

packaging projects

module.exports = {
TodoList: require('./dist/todolist'),
Todo: require('./dist/todo'),
};

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store