Taking Javascript Seriously

You know, more like your 'real' development

Scott Nonnenberg

github.com/scottnonnenberg/taking-javascript-seriously

'space'/arrow keys/swipe to navigate, `esc` to zoom out

why take it seriously?

  • More code
  • More complex web experiences
  • More hard-to-test interactivity

Raise your hand if you...

  • unit test
  • integration/acceptance test
  • measure code coverage
  • run static analysis
  • run continuous integration
  • use dependency injection
  • use the service locator pattern

your server code?

Do that with your javascript?

Keep those hands up

Let's dig in

Basic tools

nobody writes straight javascript

jquery

use it? raise your hand

$('#id').click(function() {
  alert('something!');
});

$.getJSON('http://somewhere', function(data) {
  alert(data);
});

lodash/underscore

use it? raise your hand

var a = [1, 2, 3, 4, 5];
var result = _.reject(a, function(item) { return item > 3; });
console.log(result);

var b = [3, 4, 5, 6, 7];
var result = _.intersection(a, b);
console.log(result);
run

mvc/mvvm

use one? raise your hand

  • backbone
  • ember
  • angular
  • lots more!

use todomvc to shop around

why wouldn't you? organize your code intelligently!

open source: a different world

XCode/VS/Eclipseopen source
big IDEtext editors
large built-in set of libariesmany options with separate communities
official channelsusually github
intellisense, docs, blogsannotated source, blogs, sometimes docs

choosing an open source project

activity level

  • date of last release, frequency of releases
  • look for open bugs - orphaned? time to resolve?
  • date of the last commit

size of community

  • stars and forks
  • questions/answers on stack overflow

fork it, fix it, and pull request it!

other libraries I use

  • timezone-js
  • easyxdm - cross-domain communication
  • spin.js - non-image progress spinners
  • d3.js - svg-based interactive data visualizations
  • socket.io - websockets, graceful degradation

which brings us to...

dependency management

how do you manage lots of libraries?

bower

npm

  • easily download and install
  • manage those libraries' dependencies
  • check for updates

managing versions over time

semantic versioning (semver)

  • X.0.0 for breaking changes
  • 1.X.0 for addition of features
  • 1.0.X for bugfixes
  • warning: 0.x.x is flexible

jquery "1.x" or jquery "~1.10.2"

npm allows you to be hardcore: shrinkwrap

back to our project

but we're full TDD, right?

let's set up a test harness

mocha: test runner

mocha.setup('bdd');
describe('MyClass', function() {
  before(function(done) {
    // initial setup, async because arity > 0
    done();
  });
  
  beforeEach(function() {
    // before every test
  });
  
  describe('#method', function() { // nested describe
    it('should handle a null parameter', function() {
      // test logic here, exceptions denote failure
    });
  });
});
run

runs in the browser and on the command line

chai: fluid assertions

var expect = chai.expect;

var x;
expect(x).not.to.exist;

var data = { x: 6 };
expect(data).to.have.property('x', 6);
expect(data).to.deep.equal({x: 6});
run

sinon: stubs and spies

var spy = sinon.spy(String.prototype, 'toUpperCase');
expect(spy).to.have.property('callCount', 0);
expect('blah'.toUpperCase()).to.equal('BLAH');
expect(spy).to.have.property('callCount', 1);
spy.restore();

var stub = sinon.stub().returns(6);
expect(stub()).to.equal(6);
expect(stub).to.have.property('callCount', 1);   
run

it can also redirect $.ajax and sandbox entire browser

blanket: code coverage

no instrumentation step!

easy client-side setup

<script src="blanket.js"></script>
<script src="my-library.js" data-cover></script>

easy node.js setup

mocha --require blanket --reporter html-cov testfile1.js testfile2.js

// in package.json:
"scripts": {
  "blanket": {
    "data-cover-only": "src"
  }
}

phantomjs: headless browser

  1. run tests
  2. interact with web pages
  3. capture screenshots
  4. based on webkit

Rounding it out

documentation

groc

comments interspersed with source

jsdoc

meaningful tags

running in a real browser

muscula: error capture and reporting

use only for production; breaks mocha testing

script tags at the bottom

script block while they load by default

source source

only a few js files

browsers will only open so many connections to the server

source source

but how?

want small, single-reponsibility files

don't want a new script tag per file

Modules

CommonJS

var _ = require('lodash');

module.exports = function(options) {};

AMD

define(['lodash'], function(_) {
  return function(options) {};
});

module managers

AMD: requirejs and almond.js

pro: no build step required; detailed control

con: boilerplate required for every file

CommonJS: browserify

pro: stubs out standard node.js components for you

con: requires build step to get into browser

Other: google closure compiler

pro: can prune unused code

con: requires build step to get into browser

dependency injection

function Obj(options) {
  options = options || {};
  
  this.Database = options.Database || Database;
  this.database = new this.Database();
  this.service = options.service || service;
}

Service Locator Pattern

Not a common thing in javascript world yet.

Angular - the first mainstream project to take it seriously.

other folks are experimenting with it

finally some code!

but how well do you know javascript?

falsey vs. truthy

console.log(!!null);
console.log(!!undefined);
console.log(!!false);
console.log(!!0);
console.log(!!'');
console.log(!!NaN);

var f = function() {};
console.log(!!f);
console.log(!!1);
console.log(!!'something');
console.log(!!true);
run

=== vs ==

var o = {a: 0};
console.log(o.a == '0');
console.log(o.a == 0);
console.log(o.a === '0');
console.log(o.a === 0);

o.a = null;
console.log(o.a == null);
delete o.a; // now it's undefined
console.log(o.a == null);

run

functions are objects

var f = function(a) {
  console.log(arguments);
}

console.log(typeof f);
console.log(f);
console.log(f.length);

f.a = 4;
console.log(f.a);

f(6, 7);
f.call(null, 6, 7);
f.apply(null, [6, 7]);
run

context and closures

var a = 1;
var f = function(b) {
  return function(c) {
    console.log('a: ' + a + '; b: ' + b + '; c: ' + c);
  }
}

var g = f('2');
g(3);
g(4);

a = 2;
g(5);
run

single-threaded, always

x = 0;

var first = setInterval(function() {
  x += 1;
  console.log(x);
}, 1);
var second = setInterval(function() {
  x -= 1;
  console.log(x);
}, 1);

setTimeout(function() {
  clearInterval(first);
  clearInterval(second);
}, 250); 
run

(web workers do now allow concurrency, but you don't have access to the same context)

prototype inheritance

function Super(options) { this.a = options.a };
Super.prototype.f = function() { console.log(this.a); };

function Sub(options) {
  Super.call(this, options);
};
Sub.prototype = Object.create(Super.prototype, {
  constructor: { value: Sub }
});

var p = new Super({a: 'super'});
p.f();
var c = new Sub({a: 'sub'});
c.f();

Sub.prototype.g = function() { console.log('post create add!'); };
c.g();
run

getters/setters

function Obj() { this.value = 1; };

Object.defineProperty(Obj.prototype, 'property', {
  get: function() { 
    return 'computed: ' + this.value;
  },
  set: function(value) { 
    this.value = value;
  }
});

var o = new Obj();

console.log(o.property);
o.property = 2;
console.log(o.value);
run

Object.create and Object.defineProperty are ES5 features.

which brings us to...

uneven browser support!

nooo!

how bad is it?

caniuse

support matrix for javascript features, css, svg, html5

kangax

detailed es5 feature matrix

the horrible truth

overall javascript feature support

browser usage

all is not lost!

polyfills to the rescue!

polyfill: downloadable code providing functionality not built into a web browser

html5please.com

es5-shim

html5-shiv

testing in actual browsers

test tools

karma

Test'em

services

crossbrowsertesting.com

browserstack.com

many more...

back to the code

this

function Obj() { this.x = 7; };
Obj.prototype.f = function() { console.log(this); };

var o = new Obj();
o.f();

var g = o.f; // pull the method out
g(); // raw call, gets global context

g.call(o);

g = _.bind(g, o);
g();

var nope = Obj(); // forgot the new
console.log(nope); // undefined!
console.log(x); // where did this come from?
run

which brings us to...

strict mode

fixing some of the key problems with javascript

'use strict';

// these no longer parse
var broken = function(dupe, dupe) {}; // no duplicate parameters
var broken = { dupe: 1, dupe: 2 }; // no duplicate keys

with ({}) { x = 4; y = 3; } // no more with
var x = 015; // you probably don't mean octal syntax


// these fail at runtime
function Obj() { this.x = 7; };
var nope = Obj(); // this not set to global anymore

y = 5; // using undefined var
delete Object.prototype; // this would normally fail silently
run

full docs at MDN

strict mode support

you'll need to be a careful, since it changes behavior

okay, it's a bit tricky

but there are tools to help!

jshint

  • forgot 'new' keyword?
  • did you really mean == instead of ===?
  • warn on use of undeclared variable
  • never-used variables, unreachable code
  • cyclomatic complexity, with error threshold
  • force strict mode
  • style: indentation and quote character

jshint

jslinterrors.com: a useful guide

complexityReport.js

perhaps you'd like to check the maintainability of your code?

(best implemented using a grunt task)

which brings us to...

grunt: project automation

tasks available for jshint, mocha, watch, concat/minify, etc.

powerful api for developing your own tasks

plugs into continuous integration: jenkins, travis-ci

whew!

we covered an extreme amount

fortunately, there are tools to help you jump in really fast

[sudo] npm install -g yo generator-webapp
mkdir yeoman-app
cd yeoman-app
yo webapp

study it for best practices, improve it, fork it, pull request it!

many, many other generators

Javascript is pretty great

why not try node.js? :0)

Thanks

Scott Nonnenberg / scottnonnenberg.com