Serious JavaScript
Underpinnings of the great web experience
About me
Raymond Julin
Lead Product Developer at Keyteq Labs
& eZ Exceed lead ++
<3 JS
@nervetattoo (Twitter,Github)
JavaScript 1-2-3
- Prototypial language
- First class functions
- Context
- Closures
- Where are my privates!?
- Functional programming
Prototypial inheritance
-
Every value has a prototype
- Prototype properties are accessible on the concrete value
- "foo" => String.prototype
- OOP can be implemented using prototypes
- "foo".match(/f/); "Translates" to:
- String.prototype.match.call('foo', /f/)
var myProto = {foo:function(){return'bar';}};
var Class = function() {};
Class.prototype = myProto; var obj = new Class(); obj.foo();
Metaprogramming!
Easily misused; Don't modify or overwrite prototypes of builtins
First class functions
- A function is just a value, no different from a string
- var foo = function() {};
- bar = foo; // Passes a reference to same function
- foo = null;
- bar() still works
Function context
- this is not idempotent as in PHP
- Context (this) is defined at call-time:
var obj = {
greeting: 'Hello world',
hello: function() {
console.log(this.greeting);
}
};
// Lets break things
obj.hello(); // So far so good
var em = new require('events').EventEmitter();
em.on('hello', obj.hello);
em.emit('hello'); // Oh hello error
obj.hello.call({greeting:'Oh no'})
var boundHello = obj.hello.bind(obj); // Always Hello world
Functions are closures
var foo = 'bar';
var f = function() { return foo; }
foo = 'foo';
f(); // bar
Where are my privates?
obj.greeting = 'mwhahaha';
obj.hello();
No builtin way of defining greeting as private or protected.
Use the power of closures!
var obj = (function() {
var greeting = 'Hello world';
return {
hello: function() {
console.log(greeting);
}
};
}());
Functional programming
- Functions are first class citizens
-
== pleasant functional programming in JavaScript
forEach over for
Processing an array, multiplication
var arr = [0,1,2];
var result = [];
for (var i = 0, l = arr.length; i < l; i++) {
result.push(arr[i] * 2);
}
arr.forEach(function(item) {
result.push(item * 2);
});
Creating arrays from arrays
- Using forEach was undoubtedly cleaner
- But this is so important it has its own method
var result = arr.map(function(num) { return num * 2 });
var arr = [1,2,3,'a'];
var justNumbers = arr.filter(function(item) {
return typeof item === 'number';
});
var result = arr.filter(function(item) {
return typeof item === 'number';
}).map(function(item) {
return item * 2;
});
Promotes reuse
var isNumber = function(num) {
return typeof num === 'number';
};
var multiplyByTwo = function(num) {
return num * 2;
};
var result = arr.filter(isNumber).map(multiplyByTwo);
Higher order functions
Functions that create functions
var createMultiplicator = function(factor) {
return function(num) {
return num * factor;
}
};
var result = arr.filter(isNumber).map(createMultiplicator(2));
Reduce
Reducing arrays to simple values
var add = function(a,b) { return a + b; };
[1,2].reduce(add);
arr.filter(isNumber).reduce(add);
Concats strings as well
["Hello"," world"].reduce(add);
arr.reduce(add);
Live coding
Calculation example
Javascript
- The experience happens in the browser
- JS, CSS and the browser itself shapes this
Then
JavaScript used to be horrendous:
<input type="button" onclick="javascript:doFoo();">
And completely ruled by chaos and hidden dependencies:
<script src="jquery.js"></script>
<script src="jquery.plugin.js"></script>
<script src="scripts.js"></script>
Future
You will soon (ES6) be writing this:
HTML5 brings great APIs and further browser standardisations for JS/CSS.import $ from "jquery";
import {View, Model, Controller} from "framework-x";
class Widget extends View {
render() {
// rendering
}
}
Media, offline, web workers, RTC, pushstate, animations, transformations.
Today
We have the tools to write proper JavaScript applications without tearing our hair out.
-
DOM: jQuery, zepto
- MV*: Backbone,Angular,Ember++
- Modules: AMD, CommonJS
- Templating: Mustache,Handlebars
- Utilities: Underscore (Lodash)
- Packages: Bower, Jam, Component
- CSS libs: Bootstrap, Foundation, Pure
- Testing: Mocha, Jasmine, PhantomJS, Casper
Hello world
document.getElementById('#message').innerHTML = '<strong>Hello world</strong>';
Nope, not that easy.
You wouldn't do this:
Would you?
<?php
echo '<strong>Hello world</strong>';
AMD
Asynchronous Module Definition
define(['dependency1', 'dependency2'], function(Dep1, Dep2) {
return myModule;
});
require(['mymodule'], function(App) {
App.run();
});
Verbs: define and require
A module / Why modules
- Unnamed or named
- URI path, or path mapping, used to load unnamed modules
- Callback triggered once dependencies are loaded
- Loader takes care of resolving dependencies
- Optimizable (combine and uglify)
- Doesn't break the save/reload cycle
- Easily achieve lazy loading
Code
- Fork/clone eZJSFunBundle and install into src/nervetattoo/
- git@github.com:nervetattoo/eZJSFunBundle.git
- Enable it in EzPublishKernel.php
- Symlink assets ezpublish/console assets:install web --symlink
- Create a new object and take note of its ID
- Edit Resources/config/overrides.yml and replace the Location Id
Hello world module
Resources/public/js/hello_world.js
define(['jquery'], function($) {
return function(selector) {
$(selector).text('Hello world');
};
});
Run it
In dev tools:
What have we achieved?require(['hello_world'], function(hello) {
hello('body');
});
- No global scope pollution
- While using hello_world we do not have to provide its jquery dependency — we don't know about it and it can be refactored.
- Loose coupling, can rewire hello_world to something API compliant:
requirejs.config({
paths: {
'hello_world': 'hello_moon'
}
});
Initial Require.js configuration
requirejs.config({
baseUrl: '/ezjsfunbundle/js',
paths: {
jquery: "libs/jquery/jquery.js", underscore: "libs/lodash/dist/lodash.compat.js", backbone: "libs/backbone/backbone" }
});
Bower
-
JavaScript package manager for the frontend
- Think npm for the client
- bower install jquery
- bower search backbone
- Easier third-party dependency management
-
bower update
- Updates according to .bowerrc
- Require.js path mappings?
- bower list --paths --json
- bower.io
Task
Creating our first module.
- Install required packages from bower
- Create public/js/main.js
- Configure require.js path mappings from bower
- Create a hello world module that depends on jquery
Automation with ...
A JavaScript task runner
grunt.initConfig({
//task: {configobj}
uglify: { build: { src: 'Resources/public/js/main.js', dest: 'Resources/public/js/main.min.js' } }
});
> grunt uglify
Lots of existing tasks:
- Sass, Less, Stylus, Uglify, JSHint, Handlebars, HTMLMin, Imagemin, LiveReload, Watch, Requirejs +++
- 1314 existing tasks
- Write your own if somethings missing
- Validating and building frontend code is best done using JS
Installing
Depends on Node.js + NPM
- sudo npm install -g grunt-cli
- npm install --save-dev grunt
Installing plugins
- npm install --save-dev grunt-contrib-jshint grunt-contrib-uglify
Node.js
- Chromes V8 ripped out into a non-blocking I/O-platform
- Fast and popular — JS on the server — JS everywhere!
- Node Packaged Modules: 40k modules raring to go
- Whatever your needs, NPM got you covered.
- (From AST based code transformations to flying drones)
- package.json in your repo describes your project + deps
- npm init . to generate it
Task
Building our module
- Using r.js to trace dependencies and combine
- Via grunt-contrib-requirejs
Live coding
Serious JavaScript
By nervetattoo
Serious JavaScript
- 2,970