Goals:
Why named JavaScript? (Boring and doesn't matter.)
ECMAScript vs JavaScript
ES3, ES5, ES6
Babel (transpilation)
Primitives
Boolean
String
Number
"nonvalues": null & undefined
Symbol (in ES6)
Object (includes plain objects, arrays, functions)
You do not create new Types, even if you are implementing a "class"-like, "inheritance"-like pattern.
var thing; // undefined
thing = null; // null
thing = { foo: 1 }; // Object
thing.bar = 3; // 3
thing = 7; // 7
thing = 'seven'; // 'seven'
thing = function() { console.log('zowee'); }; // Function
thing(); // zowee
thing = true; // true
thing = [thing, thing, false]; // Array [true, true, false]
But object keys are always strings (or coerced to strings).
var anArray = [1, 'one', true, { valid: 'yes' }, [0, 0, 0]];
var anObject = {
bool: true,
num: 2,
obj: { valid: 'yes' },
arr: [1, 2, 3],
func: function() {},
// ES6 Shorthand Methods
func() {}
};
// Key coercion example
anObject[true] = 'yes';
anObject['true']; // 'yes'
Primitives are immutable & assigned by value
var strA = 'horse';
var strB = 'horse';
strA === strB; // true
var objA = { things: 7 };
var objB = { things: 7 };
objA === objB; // false
var strACopy = strA;
strACopy= 'cow';
strACopy; // 'cow'
strA; // 'horse'
var objACopy = objA;
objA.horses = 8;
objACopy.horses; // 8
// Add stuff on initialization
var anObject = {
first: 1,
second: 2
};
// Add stuff later
anObject.third = 3;
var anArray = [1, 2, 3, 4];
anArray[2] = 6; // Insert at index
anArray; [1, 2, 6, 4];
anArray.push('blue'); // Add to end
anArray; [1, 2, 6, 4, 'blue']
The obvious ways:
var a = 8; // 8
typeof a; // 'number'
var b = String(a); // '8'
typeof b; // 'string'
var c = Boolean(c); // true
typeof c; // 'boolean'
var d = '8';
var e = Number(d); // 8
typeof e; // 'number'
var f = {};
Boolean(f); // true
Number(f); // NaN
String(f); // '[object Object]' -- not very useful
Less obvious ways:
var a = 8;
var b = a + 'px'; // '8px'
'' + a; // '8' (string)
var c = '12';
+c; // 12 (number)
// More common is to use parseInt() or parseFloat()
parseInt(c); // 12 (number)
// Parse functions get a number and exclude non-numbers
parseInt(b); // 8 (number)
!a; // false
!!a; // true
Explicit and implicit coercion to Boolean has these rules:
=== (and !==) does not perform type coercion: strict equality
== (and !=) does (implicitly) coerce: loose equality
Loose equality's (implicit) coercion algorithm is kind of obscure and complicated --- usually best to stay away from == and use strict equality, explicitly coercing as needed.
// More "legit" use-cases for ==
// Comparing numbers and string-numbers
let a = 42;
let b = "42";
a === b; // false
a == b; // true
// Checking if value is null or undefined at once
var c = null;
var d = undefined;
c == null; // true
d == null; // true
d === null; // false
// Tricky behaviors, illustrating why you
// should avoid ==
1 == true;
2 == false;
"" == 0;
"" != null;
false != null;
42 == [42];
{} == true;
[] == ![];
// [].toString() === ""; ![] === false
// Instead of ...
const a = '4';
a == 4;
// Do ...
a === '' + 4;
'' + a === '' + 4;
String(a) === String(4);
// Or write a function ...
equalNumberOrString(a, 4);
// Instead of ...
a == null;
// Do ...
a === undefined || a === null;
// Or write a function ...
nullOrUndefined(a);
WARNING: "empty" values are not always falsy.
if (/* test exp */) {}
for (..; /* test exp */; ..) {}
while (/* test exp */) {}
/* test exp */ ? a : b
/* test exp */ || a
/* test exp */ && a
var foo = { bar: 7 };
if (foo) { console.log('foo is truthy'); } // foo is truthy
if (foo.bar) { console.log('bar is truthy'); } // foo.bar is truthy
if (foo.baz) { console.log('baz is truthy'); } // (nothing)
if ([]) { console.log('[] is truthy'); } // [] is truthy
if ({}) { console.log('{} is truthy'); } // {} is truthy
if (function() { return false; }) {
console.log('function returning false is truthy'); // function returning false is truthy
}
if (0) { console.log('0 is truthy'); } // (nothing)
if (10) { console.log('10 is truthy'); } // 10 is truthy
Do not return Booleans: return evaluated expressions.
a || b (not quite "or")
a && b (not quite "and")
var a = 0;
a || 1; // 1
a && 1; // 0
function eff() { return false; }
function yarg() { return 'yarg'; }
eff() || yarg(); // 'yarg'
eff || yarg; // function eff() { return false; }
Default parameters are typical use case for ||
function say(thing) {
thing = thing || 'blergh';
console.log(thing);
}
say(); // blergh
say('nothing'); // nothing
Second term in is lazily evaluated, and code compressor (Uglify) can understand that:
// ES6 Default Parameters
// handle this nicely
function say(thing='blergh') {
console.log(thing);
}
say(); // blergh
say('nothing'); // nothing
// from constantEnum.js
process.env.DEV && u.errorUnless(hasActions,
`The enum '${name}' does not have any 'actionTypes',` +
`so invocation of the action '${key}' failed`
);
// Uglify can remove this altogether if run in
// non-DEV environment, where process.env.DEV is
// never truthy
Instead of Java's method "overloading".
// All functions have an "arguments" list that you can use
// to access unnamed arguments.
function dotJoin(/* arguments */) {
var firstArg = arguments[0];
if (_.isString(firstArg)) { /* handle string list */}
if (_.isArray(firstArg)) { /* handle array */ }
if (_.isPlainObject(firstArg)) { /* handle plain object */ }
if (_.isFunction(firstArg)) { /* handle function */ }
}
// e.g. cssClassAccessor.js
// With ES6 Rest Parameters, "arguments" is deprecated
function dotJoin(first, ...rest) {}
// Signatures like (val[, options], callback) are common
fs.readFile('foo.txt', { encoding: 'utf8' }, myCallback);
fs.readFile('foo.txt', null, myCallback);
fs.readFile('foo.txt', myCallback);
`var`: function scoped
if (true) {
var foo = 4;
}
foo; // 4
var bar = 7;
function oooooo() {
var baz = 8;
console.log(bar); // 7
bar = 12;
console.log(bar); // 12
}
oooooo(); // 12
console.log(bar); // 12
console.log(baz); // Error: baz is not defined
ES6 `const` & `let`: block scoped
`const` also means "final" -- cannot be reassigned
if (true) {
let foo = 4;
}
foo; // Error
let bar = 8;
if (true) {
let bar = 14;
bar; // 14
}
bar; // 8
const fozzy = 'yep';
fozzy = 'nope'; // Error
const fancy = { pants: true };
fancy.hat = true; // (no error)
Declarations are "hoisted" to the top of their scope (during compilation phase).
Assignment expression are not evaluated until execution phase.
console.log(bar); // undefined
var bar = 'abc';
console.log(bar); // abc
// Because of engine behavior,
// above is equivalent to this
var foo;
console.log(foo); // undefined
var foo = 'abc';
console.log(foo); // abc
hoisted(); // pow
// Function declaration
function hoisted() {
console.log('pow');
}
nothoisted(); // TypeError
// Function expression
var nothoisted = function() {
console.log('bam');
}
Functions are "first-class" objects: they can be
Functions receiving other functions
Just call the passed function:
// from Eloquent JavaScript
function unless(test, then) {
if (!test) then();
}
function repeat(times, body) {
for (var i = 0; i < times; i++) body(i);
}
repeat(3, function(n) {
unless(n % 2, function() {
console.log(n, "is even");
});
});
// → 0 is even
// → 2 is even
Functions receiving other functions
Callbacks for asynchronous operations:
function fetchPage(url, callback) {
var result;
// do whatever's necessary to make
// result = the page that's fetched
callback(result);
}
// Anonymous function argument (lambda)
fetchPage('http://google.com', function(result) {
console.log(result);
});
function processPage(result) {
// do things to process the result page
}
// Named function argument
fetchPage('http://mozilla.org', processPage);
Functions receiving other functions
"Iteratees" (?) that process data
var arr = [1, 2, 3, 4];
var logEm = arr.forEach(function(val) {
console.log(val);
});
var squared = arr.map(function(val) {
return val * val;
});
console.log(squared); // [1, 4, 9, 16]
var dataObj = arr.reduce(function(result, val) {
result[val] = {
squared: val * val,
isLessThan3: val < 3
};
return result;
}, {});
console.log(dataObj); // {"1":{"squared":1,"isLessThan3":true}, ...
Functions receiving other functions
Predicates
(truth-values)
const people = [{ name: 'steve'}, { name: 'don' }, { name: 'ed' }];
function find(arr, fn) {
for (let i = 0, l = arr.length; i < l; i++) {
let item = arr[i];
if (fn(item)) {
return item;
}
}
}
const steve = find(people, function(obj) {
return obj.name === 'steve';
});
console.log(steve); // Object { name: 'steve' }
const firstNonSteve = find(people, function(obj) {
return obj.name !== 'steve';
});
console.log(firstNonSteve); // Object { name: 'don' }
const first36YearOld = find(people, function(obj) {
return obj.age === 36;
});
console.log(first36YearOld); // undefined
Functions returning other functions
Partial application
// from Eloquent JavaScript
function greaterThan(n) {
return function(m) { return m > n; };
}
var greaterThan10 = greaterThan(10);
console.log(greaterThan10(11));
// → true
Functions returning other functions
Partial application
const people = [{ name: 'steve'}, { name: 'don' },
{ name: 'ed' }];
function find(arr, fn) {
for (let i = 0, l = arr.length; i < l; i++) {
if (fn(arr[i])) { return arr[i]; }
}
}
function findByName(arr, name) {
return find(arr, function(item) {
return item.name === name;
});
}
function findNameInPeople(name) {
return findByName(people, name);
}
function findDonInPeople() {
return findNameInPeople('don');
}
console.log(findByName(people, 'steve'));
// Object { name: 'steve' }
console.log(findNameInPeople('ed'));
// Object { name: 'ed' }
console.log(findDonInPeople());
// Object { name: 'don' }
Functions returning other functions
Function factory
function cssClassChecker(allowedClasses) {
return function(classname) {
if (allowedClasses.indexOf(classname) !== -1) {
return classname;
}
throw new Error(`The class "${classname}" is not available`);
}
}
var checkNumberClass = cssClassChecker(['one', 'two', 'three']);
console.log(checkNumberClass('one')); // "one"
console.log(checkNumberClass('horse')); // Error: The class "seven" is not available
var checkAnimalClass = cssClassChecker(['horse', 'cow', 'mule']);
console.log(checkAnimalClass('horse')); // "horse"
console.log(checkAnimalClass('one')); // Error: The class "one" is not available
Closure is when a function is able to remember and access its lexical scope even when that function is executing outside its lexical scope.
(the Scopes & Closures book)
var a = 2; // outer scope
function foo() {
console.log(a);
}
foo(); // 2
function bar() {
var b = 222; // inner scope
return b;;
}
console.log(bar()); // 222 -- closure!
b; // ReferenceError: b is not defined
Closures can create real data privacy & encapsulation.
As opposed to common _ convention ...
// _ prefix = conventional privacy
var foo = {
_secret: null,
_private: 'dont look at me',
setSecret(val) {
this._secret = val;
},
getSecret() {
return this._secret;
}
}
foo.setSecret(7);
foo.getSecret(); // 7
foo._secret; // 7 -- revealed!
foo._private; // 'dont look at me' -- but we looked
Closures can create real data privacy & encapsulation.
e.g. store.js
// "module pattern"
function bar(id) {
id = id || 'nobody';
var secret = null;
var priv = 'you cant see me';
function setSecret(val) {
secret = val;
}
function getSecret() {
return secret;
}
function revealId() {
return `My ID is "${id}"`;
}
return {
setSecret: setSecret,
getSecret: getSecret,
revealId: revealId
};
// ES6 shorthand
// return { setSecret, getSecret };
}
var barInstance = bar('horse');
barInstance.setSecret(7);
barInstance.getSecret(); // 7
barInstance.revealId(); // 'My ID is "horse"'
Closures can create real data privacy & encapsulation.
e.g. store.js
function secretHolder(name, secret) {
return {
getName() {
return name;
},
guessSecret(str) {
return str === secret;
}
};
}
var foo = secretHolder('ed', 'eats horse');
foo.name; // undefined
foo.secret; // undefined
foo.getName(); // 'ed'
foo.guessSecret('wears shoes on hands'); // false
foo.guessSecret('eats horse'); // true
To group related data & functionality (and prevent scope pollution). e.g. Math native object (Math.PI, Math.SQRT2, Math.floor(x), Math.abs(x))
var someSet = {};
someSet.members = [1, 2, 3, 4];
someSet.sumOfSet = function() {
var result = 0;
this.members.forEach(function(num) {
result += num;
});
return result;
};
someSet.addMember = function(mem) {
this.members.push(mem);
}
someSet.sumOfSet(); // 10
someSet.addMember(5);
someSet.sumOfSet(); // 15
Our JS "packages" in NUI:
// form.js
exports.formConstants = require('./formConstants');
exports.flux = {};
exports.flux.formStore = require('./flux/formStore');
exports.components = {};
exports.components.FormElement = require('./components/FormElement');
exports.components.FormFull = require('./components/FormFull');
exports.components.FormSubmit = require('./components/FormSubmit');
exports.components.Checkbox = require('./components/Checkbox');
exports.components.Select = require('./components/Select');
exports.components.Label = require('./components/Label');
exports.mixins = {};
exports.mixins.FormControllerMixin = require('./mixins/FormControllerMixin');
const form = require('form');
form.component.FormElement;
form.mixins.FormControllerMixin;
form.formConstants;
this - the magic keyword
var servant = {
master: 'Baal',
praiseMaster() {
return 'Praise ' + this.master + '!';
}
};
servant.praiseMaster(); // 'Prase Baal!'
var peon = _.clone(servant);
peon.master = 'King Whoever';
peon.praiseMaster(); // 'Praise King Whoever!'
servant.praiseMaster(); // 'Prase Baal!'
this is runtime scoped (rather than in "lexical scope"), referring to different objects at different times ---- a big subject ... for another time (?)
It's not real "classical inheritance"
function Person(title) {
this.title = title;
};
Person.prototype.tellTitle = function() {
return 'My job title is ' + this.title;
};
var stacy = new Person('Boss');
stacy.tellTitle(); // 'My job title is Boss'
stacy.pantSize = 10;
stacy.buyPants = function() {
console.log('I will buy pants of size ' + this.pantSize);
}
stacy.buyPants() // I will buy pants of size 10
var julia = new Person('Underling');
julia.tellTitle(); // 'My job title is Underling'
stacy.title; // 'Boss'
julia.buyPants(); // TypeError: julia.buyPants is not a function (undefined)
Object.getPrototypeOf(stacy) === Object.getPrototypeOf(julia) // true
person.isPrototypeOf(stacy); // true
typeof stacy; // object (not Person)
stacy; // Object { title: "Boss", pantSize: 10, buyPants: stacy.buyPants() }
Why are they not "real"? Because they only partially agree with prior conceptions (from e.g. Java) of how "classes" work:
Syntactic sugar over the `Person.prototype` stuff, with new keywords like super, extends, static:
class Person {
constructor(title) {
this.title = title;
}
tellTitle() {
return 'My job title is ' + this.title;
}
static stateMission(x) {
console.log('The purpose of people is ' + x);
}
}
const a = new Person('Master');
Person.stateMission('uncertain'); // The purpose of people is uncertain
class Servant extends Person {
constructor() {
super('Servant');
}
serve() {..}
}
const b = new Servant();
b.tellTitle(); // My job title is Servant
What's really happening:
Behavioral delegation up the prototype chain
a.prop: try a > try a's prototype, b > try b's prototype, c ...
OLOO: Objects Linked to Other Objects (prototypal instead of classical OO)
// What happened in prior example?
b.tellTitle(); // My job title is Servant
// tries on b --- undefined
// tries on b's prototype Servant --- undefined
// tries on b's prototype's prototype (Servant's prototype), Person -- got it
b.toString();
// goes all the way up the chain to Object.prototype
Object.create() creates a new object and sets its prototype.
var person = {
tellTitle() { return 'My job title is ' + this.title; }
};
var stacy = Object.create(person); // set prototype of new object
stacy.title = 'Boss';
stacy.tellTitle(); // 'My job title is Boss'
stacy.pantSize = 10;
stacy.buyPants = function() {
console.log('I will buy pants of size ' + this.pantSize);
}
stacy.buyPants() // I will buy pants of size 10
var julia = Object.create(person);
julia.title = 'Underling';
julia.tellTitle(); // 'My job title is Underling'
stacy.title; // 'Boss'
julia.buyPants(); // TypeError: julia.buyPants is not a function (undefined)
Object.getPrototypeOf(stacy) === Object.getPrototypeOf(julia) // true
person.isPrototypeOf(stacy); // true
typeof stacy; // object (not person)
stacy; // Object { title: "Boss", pantSize: 10, buyPants: stacy.buyPants() }
// (no tellTitle() on stacy, only on person!)
Currently we're using CommonJS syntax,
the (current) node way:
const React = require('react'); // node modules
const moment = require('moment');
const Pikaday = require('pikaday');
const _ = require('customLodash');
const u = require('util'); // alias
const common = require('common');
const formConstants = require('../formConstants'); // relative paths
const StandardizedInput = require('./StandardizedInput');
const Icon = common.components.Icon;
/**
* A standardized input for dates.
*/
const DateInput = React.createClass({
// ...
});
module.exports DateInput;
Advantages:
How it works: Browserify