Week 1
Lecture slides and code¶
Recommended reading¶
If you have not worked with JavaScript before, take some time to familiarize yourself with the basics of the language, as most of the labs in this class will be in JavaScript.
We recommend the Mozilla Developer Network's JavaScript Guide, but there are many other tutorials on the Internet.
Dave Herman's Effective JavaScript is very good reference that covers a lot of the JavaScript intricacies. You will not need this for the class, but if you end up writing JavaScript code in the outside world, this book is must-read.
Additional resources/reading for the curious¶
- Block
bindings
and the difference between
var
,let
, andconst
. - Why a language like JavaScript took over the world? Not that uncommon, see The Rise of "Worse is Better".
- Closures explained; see, especially the creating closures in a loop section.
- Private properties in (modern) JavaScript; we will revisit the idea of private properties (encapsulation) later in the course.
- Short intro to memory management for JavaScript.
- ES6 In Depth contains more information on the more recent features introduced to JavaScript.
- Arrow functions.
- JavaScript No-No's.
Source code used in class¶
Below you'll find the source files we used during lecture. You can run these with Node.js.
Scoping¶
Block scoping in modern JS:
function hello(x) { console.log(`A: x = ${x}`); // 42 { let x = 45; console.log(`B: x = ${x}`); // 45 } { console.log(`C: x = ${x}`); // 42 } } hello(42);
Function (but not block) scoping for var
s:
function hello(x) { console.log(`A: x = ${x}`); // ?? { var x = 45; console.log(`B: x = ${x}`); // ?? } { console.log(`C: x = ${x}`); // ?? } } hello(42);
Mimicking block scoping with functions:
function hello(x) { console.log(`A: x = ${x}`); // ?? (function () { var x = 45; console.log(`B: x = ${x}`); // ?? })(); (function () { console.log(`C: x = ${x}`); // ?? })(); } hello(42);
Now, with arrow functions:
function hello(x) { console.log(`A: x = ${x}`); // ?? (function () { var x = 45; console.log(`B: x = ${x}`); // ?? })(); (() => { console.log(`C: x = ${x}`); // ?? })(); } hello(42);
Performance¶
Without high-order functions, we'd perform reads and write synchronously:
const fs = require('fs'); const r1 = fs.readFileSync('./perf-sync.js', 'utf8'); // blocks until read is done processFile('perf-sync.js', r1); // blocks until processing (write) is done const r2 = fs.readFileSync('./perf-async.js', 'utf8'); // etc. processFile('perf-async.js', r2); // note that you can declare a function after the point it's used. Hoisting // essentially moves it to the top. function processFile(fname, str) { fs.writeFileSync(`/tmp/${fname}`, str); console.log(`DONE writing /tmp/${fname}`); }
Passing (callback) functions as arguments allows the runtime system to call our function whenever it's ready. This allows it to perform IO concurrently and more efficiently:
const fs = require('fs'); fs.readFile('./perf-sync.js', 'utf8', cb1); // returns immediately, cb1 is queued on the event loop and called later when actual file read is done fs.readFile('./perf-async.js', 'utf8', cb2); // returns immediately, " " function processFile(fname, str) { fs.writeFileSync(`/tmp/${fname}`, str); console.log(`DONE writing /tmp/${fname}`); } function cb1(err, str) { // line cb1.1 processFile('perf-sync.js', str); } function cb2(err, str) { //line cb2.1 processFile('perf-async.js', str); } Can cb2 execute before cb1? A: yes, B: no
Once we can return functions we can also express our code more compactly too:
const fs = require('fs'); fs.readFile('./perf-sync.js', 'utf8', processFile('perf-sync.js')); fs.readFile('./perf-async.js', 'utf8', processFile('perf-async.js')); function processFile(fname) { return (err, str) => { fs.writeFileSync(`/tmp/${fname}`, str); console.log(`DONE writing /tmp/${fname}`); }; }
And, slightly cleaner:
const fs = require('fs'); readAndProcessFile('perf-sync.js'); readAndProcessFile('perf-async.js'); function readAndProcessFile(name) { return fs.readFile(`./${name}`, 'utf8', processFile(name)); } function processFile(fname) { return (err, str) => { fs.writeFileSync(`/tmp/${fname}`, str); console.log(`DONE writing /tmp/${fname}`); }; }
Expressiveness¶
High-order functions enables expressiveness:
const list = [1, 2, 3, 4]; console.log(filter(list, function (el) { return el > 2; })); // ?? console.log(map(list, el => { return el + 42; })); // ?? function filter(list, pred) { const dup = []; for (let i = 0; i < list.length; i++) { if (pred(list[i])) { dup.push(list[i]); } } return dup; } function map(list, f) { const dup = []; for (let i = list.length-1; i >= 0; i--) { dup.unshift(f(list[i])); } return dup; }
It also can enable more efficient code:
const list = [1, 2, 3, 4]; const add42 = (el) => { return el + 42; }; function mul1337 (el) { return el * 1337; } console.log(map(map(list, add42), mul1337)); console.log(map(list, compose(mul1337, add42))); function compose (f, g) { return (x) => { return f(g(x)); } } function map(list, f) { const dup = []; for (let i = list.length-1; i >= 0; i--) { dup.unshift(f(list[i])); } return dup; }
Abstraction¶
We can also use functions to implement module systems.
Consider a simple module in Node.js:
const secret = "cse130 is fun!"; // scoped to this function, hidden to outside world exports.myVar = 42; exports.myFunc = function (x) { if (x === secret) { console.log('yes!'); } else { console.log('guess again!'); } };
This module can be loaded with require
, which is (very) roughly implemented
as follows:
// using node's requie: { const mod = require('./module-node.js'); console.log(mod.myVar); // ?? mod.myFunc("what?"); // ?? mod.myFunc("cse130 is fun!"); // ?? } // using our fake require: { const mod = requireMyModule(); console.log(mod.myVar); // ?? mod.myFunc("what?"); // ?? mod.myFunc("cse130 is fun!"); // ?? } function myModule(exports) { // same code as module-node.js: const secret = "cse130 is fun!"; // scoped to this function, hidden to outside world exports.myVar = 42; exports.myFunc = function (x) { if (x === secret) { console.log('yes!'); } else { console.log('guess again!'); } }; } function requireMyModule() { // create new object that will be populated by the module const exports = {}; myModule(exports); return exports; }
Objects¶
We'll be looking at objects later in the class. Objects can be expressed ad-hoc, using object literal notation:
const obj = { "x-w00t": 10, x: 1337, f: function (y) { this.x++; return this.x + y; } }; console.log(obj.x); // ?? console.log(obj.f(3)); // ?? console.log(obj["x"]); // ?? console.log(obj["x-w00t"]) // ??
But we can (again) use functions to construct objects:
function Car(make, model) { this.make = make; this.model = model; this.toString = function () { return `${this.make}@${this.model}`; }; } Car.mySweetProp = 42; const f = new Car("Ford", "Focus"); console.log(f.toString()); const t = new Car("Toyota", "Corola"); console.log(t.toString()); // Car.prototype is shared by all objects created by calling new Car(...) // That's right you can treat functions like objects! console.log(f.__proto__ === Car.prototype); // ?? // Let's define property common to all cars: Car.prototype.color = "black"; console.log(f.color); // ?? // getProperty "color" of f // if it has it, return it // else getProperty "color" of f.__proto__ console.log(t.color); // ?? // Can override the default color that is defined on the prototype: t.color = "red"; console.log(t.color); // ?? console.log(f.color); // ?? // We can define a method on the prototype: Car.prototype.toColorString = function () { return `${this.make}, ${this.model}, ${this.color}`; }; console.log(f.toColorString()); // ?? console.log(t.toColorString()); // ??
More recently, however, JavaScript adopted classes. You can think of them as being syntactic sugar for the above:
class Car { constructor(make, model) { this.make = make; this.model = model; } toString() { return `${this.make}@${this.model}`; } static get mySweetProp() { return 42; } } const f = new Car("Ford", "Focus"); console.log(f.toString()); const t = new Car("Toyota", "Corola"); console.log(t.toString()); // Car.prototype is shared by all objects created by calling new Car(...) // That's right you can treat functions like objects! console.log(f.__proto__ === Car.prototype); // ?? // We can define property common to all cars as before: Car.prototype.color = "black"; console.log(f.color); // ?? // getProperty "color" of f // if it has it, return it // else getProperty "color" of f.__proto__ console.log(t.color); // ?? // Can override the default color that is defined on the prototype: t.color = "red"; console.log(t.color); // ?? console.log(f.color); // ?? // We can define a method on the prototype as before: Car.prototype.toColorString = function () { return `${this.make}, ${this.model}, ${this.color}`; }; console.log(f.toColorString()); // ?? console.log(t.toColorString()); // ??