- Source: MDN
- JavaScript only knows one construct:
Object
- Each object has a link to a prototype object
Properties
// parent object
var parent = {b: 3, c: 4};
// child object with inheritance
var child = Object.create(parent);
child.a = 1;
child.b = 2;
// prototype chain:
// child.[[Prototype]] = {b: 3, c: 4}
// child.__proto__ is deprecated
// since ES6 [[Prototype]] is accessed using Object.getPrototypeOf() and Object.setPrototypeOf()
// {a: 1, b: 2} » {b: 3, c: 4} » null
console.log(child.a); // 1
// Is there an 'a' own property on child? Yes, and its value is 1.
console.log(child.b); // 2
// Is there a 'b' own property on child? Yes, and its value is 2.
// The prototype also has a 'b' property, but it’s not visited.
// This is called "property shadowing"
console.log(child.c); // 4
// Is there a 'c' own property on child? No, check its prototype.
// Is there a 'c' own property on child.[[Prototype]]? Yes, its value is 4.
console.log(child.d); // undefined
// Is there a 'd' own property on child? No, check its prototype.
// Is there a 'd' own property on child.[[Prototype]]? No, check its prototype.
// child.[[Prototype]].[[Prototype]] is null, stop searching.
// no property found, return undefined
- Using getter and setter
// define a getter and setter for the year property
var d = Date.prototype;
Object.defineProperty(d, 'year', {
get: function() { return this.getFullYear(); },
set: function(y) { this.setFullYear(y); }
});
// use the getter and setter in a "Date" object
var now = new Date();
console.log(now.year); // 2016
new.year = 2015; // 2015
console.log(now); // Tue Aug 11 2015 11:23:16 GMT+0200 (CEST)
Methods
- Any function can be added to an object in the form of a property
- An inherited function acts just as any other property, including property shadowing (method overriding)
- When an inherited function is executed, the value of
this
points to the inheriting object, not to the prototype object where the function is an own property
// define object with property a and method m
var o = {
a: 2,
m: function(b) {
return this.a + 1;
}
};
console.log(o.m()); // 3
// When calling o.m in this case, "this" refers to o
var p = Object.create(o);
// p is an object that inherits from o
p.a = 4; // creates an own property "a" on p
console.log(p.m()); // 5
// When p.m is called, "this" refers to p
// So when p inherits the function m of o, "this.a" means p.a, the own property "a" of p
Creating objects
Created with syntax constructs
var o = {a: 1};
// The newly created object o has Object.prototype as its [[Prototype]]
// o has no own property named "hasOwnProperty"
// hasOwnProperty is an own property of Object.prototype
// So o inherits hasOwnProperty from Object.prototype
// Object.prototype has null as its prototype
// o » Object.prototype » null
var a = ['yo', 'whadup', '?'];
// Arrays inherit from Array.prototype (which has methods like indexOf, forEach, etc.)
// The prototype chain looks like
// a » Array.prototype » Object.prototype » null
function f() {
return 2;
}
// Functions inherit from Function.prototype (which has methods like call, bind, etc.)
// f » Function.prototype » Object.prototype » null
Created with a constructor
// A "constructor" in JavaScript is "just" a function that happens to be called with the new operator
function Graph() {
this.vertices = [];
this.edges = [];
}
Graph.prototype = {
addVertex: function(v) {
this.vertices.push(v);
}
};
var g = new Graph();
// g is an object with own properties "vertices" and "edges"
// g.[[Prototype]] is the value of Graph.prototype when new Graph() is executed
Created with Object.create
// ES5 introduced a new method: Object.create()
// Calling this method creates a new object; prototype of this object is the first argument of the function
var a = {a: 1};
// a » Object.prototype » null
var b = Object.create(a);
// b » a » Object.prototype » null
console.log(b.a); // 1 (inherited)
var c = Object.create(b);
// c » b » a » Object.prototype » null
var d = Object.create(null);
// d » null
console.log(d.hasOwnProperty); // undefined, because d doesn't inherit from Object.prototype
Created with class
keyword
// ES6 introduced a new set of keywords implementing classes (remaining prototype-based): class, constructor, static, extends, super
'use strict';
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
class Square extends Polygon {
constructor(sideLength) {
super(sideLength, sideLength);
}
get area() {
return this.height * this.width;
}
set sideLength(newLength) {
this.height = newLength;
this.width = newLength;
}
}
var square = new Square(2);
console.log(square.area); // 4
square.sideLength = 3;
console.log(square.area); // 9
Performance
- Lookup for properties that are high up on the chain can have negative impact on performance
- Trying to access nonexisting properties will always traverse the full prototype chain
- When iterating over the properties of an object, every enumerable property that is on the prototype chain will be enumerated
- To check existence of property on own object use
hasOwnProperty
; inherited fromObject.prototype
(only thing in JS which deals with properties and does not traverse the prototype chain)
Bad Practice
- Don't extend
Object.prototype
or one of the other built-in prototypes (monkey patching) as it breaks encapsulation - Only good reason is backporting newer JavaScript engine features; for example
Array.forEach
, etc.
Prototype Chain
function A(a) {
this.varA = a;
}
A.prototype = {
// Optimize speed by initializing instance variables
varA: null,
doSomething: function() {
// ...
}
}
function B(a, b) {
A.call(this, a);
this.varB = b;
}
B.prototype = Object.create(A.prototype, {
varB: {
value: null,
enumerable: true,
configurable: true,
writable: true
},
doSomething: {
// override
value: function() {
// call super
A.prototype.doSomething.apply(this, arguments);
},
enumerable: true,
configurable: true,
writable: true
}
});
B.prototype.constructor = B;
var b = new B();
b.doSomething();
- Important parts: Types are defined in
.prototype
, you useObject.create()
to inherit - Reference to the prototype object is copied to the internal
[[Prototype]]
property of the new instance - When you access properties of the instance, JavaScript first checks object, and if not, it looks in
[[Prototype]]
- This means that all the stuff you define in
prototype
is effectively shared by all instances - You can even later change parts of
prototype
and have the changes appear in all existing instances
var a1 = new A();
var a2 = new A();
// Object.getPrototypeOf(a1).doSomething =
// Object.getPrototypeOf(a2).doSomething =
// A.prototype.doSomething
prototype
is for types, whileObject.getPrototypeOf()
is the same for instances[[Prototype]]
is looked at recursively
var o = new Foo();
// JavaScript actually just does
var o = new Object();
o.[[Prototype]] = Foo.prototype;
Foo.call(o);
- Source: MDN
- Scope of a variable declared with
var
is its current execution context (enclosing function or global) - Assigning a value to an undeclared variable implicitly creates it as a global variable
- Variable declarations are processed before any code is executed
- Variable can appear to be used before it’s declared
- Hoisting: Variable declaration is moved to the top of the function or global code
- Source: MDN
- A way to opt in to a restricted variant of JavaScript
- Eliminates some silent errors by changing them to throw errors
- Impossible to accidentally create global variables
- Makes assigments which would otherwise silently fail throw an exception
- Throws an error if you attempt to delete undeletable properties
- Requires that all properties named in an object literal be unique
- Requires that function parameter names be unique
- Forbids octal syntax
- Forbids setting properties on primitive values
- Improves possibilities to perform optimizations by Engines (faster)
- Prohibits
with
eval
of strict mode code does not introduce new variables into the surrounding scope- Forbids deleting plain names:
var a; delete a;
- Names
eval
andarguments
can’t be bound or assigned - Doesn’t alias properties of
arguments
object created within it arguments.callee
,arguments.caller
andcaller
are no longer supported- value passed as
this
to a function is not forced into being an object (a.k.a boxing): primitive values are returned with their value, not as objects
- Prohibits
- Prohibits some syntax likely to be defined in future versions of ES
- List of identifiers become reserved keywords:
implements
,interface
,let
,package
,private
,protected
,public
,static
, andyield
- Prohibits function statements not at the top level of a script or function
- List of identifiers become reserved keywords:
- Sources: MDN, Kirupa
- Every event starts at the root of the document, makes its way through the DOM and stops at the element that triggered the event (Event Capturing Phase)
- Once the event reaches its target, the event returns back to the root (Event Bubbling Phase)
// listen for click event during capturing phase
item.addEventListener('click', doSomething, true);
// listen for click event during bubbling phase
item.addEventListener('click', doSomething, false);
// listen for click, defaults to bubbling phase
item.addEventListener('click', doSomething);
- Call
stopPropagation()
onEvent
object to prevent it to be propagated further down or up - Call
preventDefault()
to turn off default behavior of an element getting an event
- Source: Ben Alman
- Every function, when invoked, creates a new execution context
- Invoking a function provides a very easy way to create privacy
(function() {
// ...
})();
- Any function defined inside another function can access the outer function’s passed-in arguments and variables (this relationship is known as a closure)
- IIFE can be used to „lock in“ values and save state
var elems = document.getElementsByTagName('a');
// this doesn't work, because the value of "i" never gets locked in
// instead every link click alerts the total number of elements
for (var i=0; i<elems.length; i++) {
elems[i].addEventListener('click', function(e) {
e.preventDefault();
alert('I am link #' + i);
});
}
// this works, because inside the IIFE, the value of "i" is locked in as "lockedinIndex"
for (var i=0; i<elems.length; i++) {
(function(lockedInIndex) {
elems[i].addEventListener('click', function(e) {
e.preventDefault();
alert('I am link #' + lockedInIndex);
});
})(i);
}
// alternative
for (var i=0; i<elems.length; i++) {
elems[i].addEventListener('click', (function(lockedInIndex) {
return function(e) {
e.preventDefault();
alert('I am link #' + lockedInIndex);
};
})(i));
}
- Sources: MDN Web Components, MDN Custom Elements, MDN HTML Templates, MDN Shadow DOM, MDN HTML Imports
- Web Components are reusable user interface widgets that are created using open Web technology
- Consists of four technologies: Custom Elements, HTML Templates, Shadow DOM, and HTML Imports
Custom Elements
- Capability for creating custom HTML tags and elements with own scripted behavior and CSS styling
- Attach behaviors to different parts of element’s lifecycle
- Lifecycle callbacks
constructor
: The behavior occurs when the element is created or upgradedconnectedCallback
: Called when the element is inserted into the DOMdisconnectedCallback
: Called when the element is removed from the DOMattributeChangedCallback(attrName, oldVal, newVal)
: The behavior occurs when an attribute of the element is added, changed, or removed, including when these values are initially set
<flag-icon country="nl"></flag-icon>
class FlagIcon extends HTMLElement {
constructor() {
super();
this._countryCode = null;
}
static get observedAttributes() {
return ['country'];
}
attributeChangedCallback(name, oldValue, newValue) {
// name will always be "country" due to observedAttributes
this._countryCode = newValue;
this._updateRendering();
}
connectedCallback() {
this._updateRendering();
}
get country() {
return this._countryCode;
}
set country(v) {
this.setAttribute('country', v);
}
_updateRendering() {
// ...
}
}
// Define element
customElements.define('flag-icon', FlagIcon);
HTML Templates
- HTML template element
<template>
is a mechanism for holding un-rendered client-side content - Content fragment that is being stored for subsequent use
- Parser checks validity of content only
<table id="product-table">
<thead>
<tr>
<th>UPC Code</th>
<th>Product Name</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<template id="product-row">
<tr>
<td class="record"></td>
<td></td>
</tr>
</template>
Shadow DOM
- Provides encapsulation for the JavaScript, CSS, and templating in a Web Component
- Seperation from DOM
- Must always be attached to an existing element (literal element, or an element created by scripting): native or custom element
<html>
<head></head>
<body>
<p id="hostElement"></p>
<script>
// create shadow DOM on the <p> element above
var shadow = document.querySelector('#hostElement').createShadowRoot();
// add some text to shadow DOM
shadow.innerHTML = '<p>Here is some new text</p>';
// add some css to make the text red
shadow.innerHTML += '<style>p { color: red; }</style>';
</script>
</body>
</html>
HTML Imports
- Intended to be the packaging mechanism for Web Components
- Import an HTML file by using a
<link>
tag in an HTML document
<link rel="import" href="myfile.html">
- Source: MDN
Web Workers API
- Worker is an object (
new Worker()
) that runs a named JavaScript file - Code runs in worker thread with another global context (no access to
window
) - Dedicated worker is only accessible from the script that first spawned it, whereas shared workers can be accessed from multiple scripts
- Can’t directly manipulate the DOM
- Data is sent between workers and the main thread via a system of messages (
postMessage()
method andonmessage
event handler) - Workers may spawn new workers (within same origin)
Dedicated workers
var first = document.querySelector('#number1');
var second = document.querySelector('#number2');
var result = document.querySelector('#result');
// Check if browser supports the Worker API
if (window.Worker) {
var myWorker = new Worker('worker.js');
var changeHandler = function() {
myWorker.postMessage([first.value, second.value]);
};
first.onchange = changeHandler;
second.onchange = changeHandler;
myWorker.onmessage = function(e) {
result.textContent = e.data;
};
}
onmessage = function(e) {
var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
postMessage(workerResult);
}
- Immediately terminate a running worker from the main thread:
myWorker.terminate();
- Workers may close themselves:
close();
- When a runtime error occurs in the worker, its
onerror
event handler is called - Have access to a global function,
importScripts()
Shared workers
// multiply.js
var first = document.querySelector('#number1');
var second = document.querySelector('#number2');
var result = document.querySelector('#result');
if (!!window.SharedWorker) {
var myWorker = new SharedWorker('worker.js');
var changeHandler = function() {
myWorker.postMessage([first.value, second.value]);
};
// ...
}
// square.js
var squareNumber = document.querySelector('#number3');
var result2 = document.querySelector('#result2');
if (!!window.SharedWorker) {
var myWorker = new SharedWorker('worker.js');
var changeHandler = function() {
myWorker.postMessage([squareNumber.value, squareNumber.value]);
};
// ...
}
// worker.js
onconnect = function(e) {
var port = e.ports[0];
port.onmessage = function(e) {
var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
postMessage(workerResult);
}
}
- Sources: Noeticforce, colorlib
- Interesting (beside Angular and React): Polymer, Riot
Ember
- Source: About
- Auto-updating Handlebars Templates: Ember makes Handlebars templates even better, by ensuring HTML stays up-to-date when the underlaying model changes
- Components: Create application-specific HTML tags, using Handlebars to describe their markup and JS to implement custom behavior
- Loading data from a server: Eliminates the boilerplate of displaying JSON retrieved from server
- Routing: Downright simple to create sophisticated, multi-page JS applications with great URL support
Aurelia
- Source: Features
- Forward-Thinking: Written with ES 2016; integrates Web Components
- Two-way databinding: Enables powerful two-way binding to any object by using adaptive techniques (efficient way to observe each property in model and automatically sync UI)
- Routing & UI composition: Pluggable pipeline, dynamic route patterns, child routers and asynchronous screen activation
- Broad language support: ES5, ES 2015 (ES6), ES 2016 (ES.Next) and TypeScript
- Modern architecture: Composed of smaller, focused modules
- Extensible HTML: Custom HTML elements, add custom attributes to existing elements and control template generation
- MV* with Conventions: Levery conventions to make constructing effortless
- Testable: ES 2015 modules combined with DI container make it easy to create highly cohesive, yet minimally coupled code, making unit testing a snap
Meteor
- Source: Introducing
- Full-stack JavaScript platform for developing modern web and mobile applications
- Includes a key set of technologies for building connected-client reactive applications, a build tool, and a curated set of packages
- Allows you to develop in one language (application server, web browser, and mobile device)
- Uses data on the wire, meaning the server sends data, not HTML, and the client renders it
- Embraces the ecosystem, bringing the best parts of the community in a careful and considered way
- Provides full stack reactivity, allowing UI to seamlessly reflect the true state with minimal development effort
Backbone
- Source: Getting Started
- Represent data as models, which can be created, validated, destroyed, and saved to the server
- Whenever a UI action causes an attribute of the model to change, the model triggers a „change“ event
- All views that display the model’s state can be notified of the change (able to respond accordingly, re-rendering themselves with the new information)
- Minimal set of data-structuring (models and collections) and user interface (views and URLs)
- Helps to keep business logic separate from user interface
Polymer
- Source: Feature Overview
- Provides a set of features for creating custom elements
- Designed to make it easier and faster to make custom elements
- Elements can be instantiated (Constructor or
document.createElement
) - Elements can be configured using attributes or properties
- Elements can be populated with internal DOM inside each instance
- Elements are responsive to property and attribute changes
- Elements are styled with internal defaults or externally
- Elements are responsive to methods that manipulate their internal state
- Features are divided into
- Registration and lifecycle: Registering an element associated a class (prototype) with a custom element name. The element provides callbacks to manage its lifecycle. Use behaviors to share code
- Declared properties: Declared properties can be configured from markup using attributes. Declared properties can optionally support change observers, two-way data binding, and reflection to attributes. You can also declare computed properties and read-only properties
- Local DOM: Local DOM is the DOM created and managed by the element
- Events: Attaching event listeners to the host object and local DOM children. Event retargeting.
- Data binding: Property bindings. Binding to attributes.
- Behaviors: Behaviors are reusable modules of code that can be mixed into Polymer elements.
- Utility function: Helper methods for common tasks.
- Experimental features and elements: Experimental template and styling features. Feature layering.
Examples
<dom-module id="hello-world">
<template>
<input type="text" value="{{name::keyup}}">
<h1>Hello, [[name]]</h1>
</template>
<script>
Polymer({
is: 'hello-world'
});
</script>
</dom-module>
<dom-module id="lifecycle-element">
<template>
<button id="btn">Hello World</button>
</template>
<script>
Polymer({
is: 'lifecycle-element',
created: functon() {
this.log('created');
this.addEventListener('click', function() {
this.remove();
});
},
ready: function() {
this.log('ready');
this.tickCount = 1;
this.ticker = setInterval(this.tick.bind(this), 500);
},
attached: function() {
this.log('attached');
},
detached: function() {
this.log('detached');
clearInterval(this.ticker);
},
attributeChanged: function(name, oldValue, newValue) {
console.log('%s was changed to %s from %s', name, newValue, oldValue);
},
tick: function() {
this.setAttribute('data-id', Math.random());
this.tickCount++;
if (this.tickCount > 10) {
clearInterval(this.ticker);
}
},
updateAttribute: function(cycle) {
this.setAttribute('class', cycle);
},
log: function(cycle) {
console.log('» ' + cycle);
this.$ && console.dir(this.$.btn);
this.updateAttribute(cycle);
}
});
</script>
</dom-module>
<dom-module id="business-card">
<template>
<h1>Deadpool</h1>
<h2>Superhero</h2>
<h3>Marvel</h3>
<style>
:host {
--card-color: red;
--text-color: black;
}
:host {
background-color: var(--custom-card-color, --card-color);
}
h1, h2, h3 {
color: var(--custom-text-color, --text-color);
}
</style>
<script>
Polymer({
is: 'business-card'
});
</script>
</dom-module>
<link rel="import" href="business-card.html">
<dom-module id="my-card">
<template>
<business-card></business-card>
<style>
business-card {
--custom-card-color: green;
--custom-text-color: white;
}
</style>
</template>
<script>
Polymer({
is: 'my-card'
});
</script>
</dom-module>
<!--
<prop-element name="Deadpool"></prop-element>
rewritten to
<prop-element name="Deadpool" position="Bad Hero"></prop-element>
-->
<dom-module id="prop-element">
<template>
<h1>[[name]]</h1>
<h2>[[position]]</h2>
</template>
<script>
Polymer({
is: 'prop-element',
properties: {
type: String,
reflectToAttribute: true,
readOnly: true,
computed: 'computedPosition(name)'
},
computedPosition: function(name) {
return name === 'Deadpool' ? 'Bad Hero' : 'Hero';
}
});
</script>
</dom-module>
<dom-module id="child-element">
<template>
<p>Child</p>
<input type="text" value="{{data::input}}">
<p>Output: {{data}}</p>
</template>
<script>
Polymer({
is: 'child-element',
properties: {
data: {
type: String,
notifiy: true
}
},
ready: function() {
this.addEventListener('data-changed', function(e) {
console.log(e.detail.value);
});
}
});
</script>
</dom-module>
<link rel="import" href="child-element.html">
<dom-module id="parent-element">
<template>
<p>Parent</p>
<input type="text" value="{{parentData::input}}">
<child-element data="[[parentData]]"><child-element>
</template>
<script>
Polymer({
is: 'parent-element'
});
</script>
</dom-module>
<dom-module id="observer-element">
<template>
<input type="text" value="{{color::input}}">
<h1 id="hello">Hello</h1>
</template>
<script>
Polymer({
is: 'observer-element',
properties: {
color: {
type: String,
observer: 'colorChanged'
}
},
colorChanged: function(newValue) {
this.$.hello.style.color = newValue;
}
})
</script>
</dom-module>
<dom-module id="listener-element">
<template>
<button on-click="clickHandler">Annotated</button>
<button id="btnId">Listener</button>
</template>
<script>
Polymer({
is: 'listener-element',
clickHandler: function() {
console.log('click');
},
listeners: {
'btnId.click': 'clickHandler'
}
});
</script>
</dom-module>
<!--
<my-range value="10"></my-range>
<script>
document.querySelector('my-range').addEventListener('valueChanged', function(e) {
console.log(e.detail.increased);
});
</script>
-->
<dom-module id="my-range">
<template>
<input type="range" value="{{value::input}}" max="100" min="0">
</template>
<script>
Polymer({
is: 'my-range',
properties: {
value: {
type: Number,
observer: 'handleInput'
}
},
handleInput: function(newValue, oldValue) {
if (oldValue) {
this.fire('valueChanged', {increased: newValue > oldValue});
}
}
});
</script>
</dom-module>
<dom-module id="cart-list">
<template>
<template is="dom-repeat" items="{{foods}}">
<button on-click="add">+</button>
<span>{{item.quantity}} - {{item.name}} (#{{index}})</span>
</template>
</template>
<script>
Polymer({
is: 'cart-list',
ready: function() {
this.foods = [
{name: 'Pizza', quantity: 0},
{name: 'Burger', quantity: 0},
{name: 'Taco', quantity: 0}
];
},
add: function(e) {
e.model.set('item.quantity', e.model.item.quantity + 1);
}
});
</script>
</dom-module>
Knockout
- Source: Key concepts
- Declarative Bindings: Easily associate DOM elements with model data using a concise, readable syntax
- Automatic UI Refresh: When data model’s state changes, UI updates automatically
- Dependency Tracking: Implicitly set up chains of relationships between model data, to transform and combine it
- Templating: Quickly generate sophisticated nested UIs as a function of model data
Vue
- Source: Overview
- Library for building interactive web interfaces
- Provide benefits of reactive data binding and composable view components
- Focused on view layer only
- Very easy to pick up and to integrate with other libraries or existing projects
- Embraces the concept of data-driven view (bind the DOM to the underlaying data)
- Small, self-contained, and often reusable components (very similar to Custom Elements)
Mercury
- Source: Mercury vs React
- Leverages Virtual DOM (immutable vdom structure)
- Comes with
observ-struct
(immutable data for state atom) - Truly modular (swap out subsets)
- Encourages zero DOM manipulation
- Strongly encourages FRP (Functional reactive programming) techniquices and discourages local mutable state
- Highly performant (faster than React, Om, ember)
MobX
- Source: Concepts & Principles
- State: data that drives application (domain specific state like a list of todo items and view state such as the currently selected element)
- Derivations: Computed values (Derived from the current observable state using a pure function) and Reactions (Side effects that need to happen automatically if the state changes)
- Actions: Any piece of code that changes the state
- Supports an uni-directional data flow where Actions changes the state, which in turn updates all affected views
- Derivations are updated automatically, atomically and synchronously, computed values are updated lazily and should be pure (not supposed to change state)
Omniscient
- Source: Rationale
- Functional programming for UIs
- Memoization for stateless React components
- Top-down rendering of components (unidirectional data flow)
- Favors immutable data
- Encourages small, composable components, and shared functionality through mixins
- Natural separation of concern (components only deal with their own piece of data)
- Efficient (centrally defined
shouldComponentUpdate
)
Ractive.js
- Source: Ractive
- Live, reactive templating: Template-driven UI library
- Powerful and extensible: Two-way binding, animations, SVG support
- Optimised for your sanity: Ractive works for you and plays well with other libraries
WebRx
- Source: WebRx
- MVVM: Clean separation of concerns between View-Layer and Application-Layer by combining obversable View-Models with Two-Way declarative Data-Binding
- Components and Modules: Combine View-Models and View-Templates into self-contained, reusable chunks and package them into modules
- Client-Side Routing: Organize the parts into a state machine that maps Components onto pre-defined (optionally nested) regions of the page
Deku
- Source: Deku
- Library for rendering interfaces using pure functions and virtual DOM
- Pushed responsibility of all state management and side-effect onto tools like Redux
- Can be used in place of libraries like React and works well with Redux
Riot
- Source: Riot
- Brings Custom Tags to all browsers
- Human-readable
- Virtual DOM: Smallest possible amount of DOM updates and reflows, one way data flow, pre-compiled and cached expressions, lifecycle events for more control, server-side rendering for universal apps
- Close to standards
- Tooling friendly
- Think React + Polymer but without the bloat
Examples
<!--
<hello-world></hello-world>
<script src="hello.tag" type="riot/tag"></script>
<script>
riot.mount('hello-world', {
greeting: 'Hello'
});
</script>
-->
<hello-world>
<p>{ opts.greeting }, { who }!</p>
<input name="who" type="text" value="{ who }" onkeyup="{ whoChanged }">
<script>
var self = this;
this.who = 'Marco';
this.whoChanged = function() {
this.who = self.whoInput.value;
};
</script>
</hello-world>
<!--
<app></app>
<script src="app.tag" type="riot/tag"></script>
<script>
riot.mount('app');
</script>
-->
<app>
<h2>Rice Krispie Treats Recipe</h2>
<ingredient each="{ ingredients }"></ingredient>
<script>
this.ingredients = [
{name: 'Butter', amount: '3 Tbsp'},
{name: 'Marshmallow Fluff', amount: '10 oz'},
{name: 'Rice Krispies Cereal', amount: '6 cups'}
];
</script>
</app>
<ingredient>
<label class="{ added: added }">
<input type="checkbox" onchange="{ onCheck }">
{ name }
</label>
<span>{ amount }</span>
<style>
label.added {
text-decoration: line-through;
}
</style>
<script>
this.onCheck = function(e) {
this.added = e.target.checked;
};
</script>
</ingredient>
Mithril
- Source: Mithril
- Client-side MVC framework
- Light-weight: small size, small API, small learning curve
- Robust: Safe-by-default templates, hierarchical MVC via components
- Fast: Virtual DOM diffing and compilable templates, intelligent auto-redrawing system
Stapes.js
- Source: Stapes.js
- Agnostic about your setup and style of coding
- Class creation, custom events, and data methods
Om
- Source: Om
- Global state management facilities built in
- Components may have arbitrary data dependencies, not limited to props & state
- Component construction can be intercepted via
:instrument
(simplifies debugging components and generic editors) - Provides stream of all application state change deltas via
:tx-listen
(simplifies synchronization online and offline) - Customizable semantics: Fine grained control over how components store state
Definition
- Goal: Provide a more fluid user experience
- Resources are dynamically loaded and added to the page as necessary, usually in response to user actions
- Page does not reload at any point in the process, nor does control transfer to another page
Pros
- More fluid user experience
- Mobile phone friendly
Cons
- Search engine optimization (lack of JavaScript execution on crawlers)
- Client/Server code partitioning (duplication of business logic)
- Browser history (breaks page history navigation using the Forward/Back buttons)
- Analytics (full page loads are required)
- Speed of initial load (slower first page load)
- JavaScript has to be enabled
- Sources: MDN, oio
- ECMAScript 5.0 released in 2009, ES 5.1 released in 2011 (maintenance)
- Introduced Strict mode
- Native JSON support:
JSON.parse()
,JSON.stringify()
- Property descriptor maps: Specify how the properties of your object can be altered after creation
value
: The intrinsic value of the propertywritable
: Can value be changed after being set?enumerable
: Can property be iterated on for example in for-loopsconfigurable
: Specifys if a property can be deleted and how the values of its property descriptor map can be modified
var obj = {};
Object.defineProperty(obj, 'attr', {
value: 1,
writable: true,
enumerable: true,
configurable: true
});
- Getters and Setters
var obj = {};
(function() {
var _value = 1;
Object.defineProperty(obj, 'value', {
get: function() {
return _value;
},
set: function(newValue) {
_value = newValue;
}
});
})();
console.log(obj.value); // 1
obj.value = 5;
console.log(obj.value); // 5
Object
Object
constructor creates an object wrapper for the given value (empty object if value isnull
orundefined
)Object.assign()
: Creates a new object by copying the values of all enumerable own properties from one or more source objects to a target objectObject.create()
: Creates a new object with the specified prototype object and propertiesObject.defineProperty()
/Object.defineProperties()
: Adds the named property described by a given descriptor to an objectObject.entries()
: Returns an array of a given object’s own enumerable property[key, value]
pairsObject.freeze()
: Freezes an object (can’t delete or change any properties)Object.getOwnPropertyDescriptor()
/Object.getOwnPropertyDescriptors()
: Returns a property descriptor for a named property on an object or an array of all ownObject.getOwnPropertyNames()
: Returns an array containing the names of all of the given object’s own propertiesObject.getOwnPropertySymbols()
: Returns an array of all symbol properties found directly upon a given objectObject.getPrototypeOf()
: Returns the prototype of the specified objectObject.is()
: Compares if two values are the sameObject.isExtensible()
: Determines if extending of an object is allowedObject.isFrozen()
: Determines if an object was frozenObject.isSealed()
: Determines if an object is sealedObject.keys()
: Returns an array containing the names of the given object’s own enumerable propertiesObject.preventExtensions()
: Prevents any extensions of an objectObject.seal()
: Prevents other code from deleting properties of an objectObject.setPrototypeOf()
: Sets the prototype (the internal[[Prototype]]
property)Object.values()
: Returns an array of a given object’s own enumerable values
Array
Array.isArray(someVar)
: Check ifsomeVar
is an arrayforEach()
: iterate on an arraymap()
: iterate on an array and returns a new array with applied callback methodfilter()
: iterate on an array and return a new array with elements which return true in callback methodevery()
: returnstrue
if callback method returnstrue
for all elementssome()
: returnstrue
if callback method returnstrue
for at least one elementreduce()
: invokes callback method on every element and returns a single element
Function
Function.prototype.apply()
: Calls a function and sets itsthis
to the provided value, arguments can be passed as anArray
objectFunction.prototype.bind()
: Creates a new function which, when called, has itsthis
set to the provided value, with a given sequence of arguments preceding any provided when the new function was calledFunction.prototype.call()
: Calls a function and sets itsthis
to the provided value, arguments can be passed as they are
- Source: Luke Hoban
Arrows
- Function shorthand using
=>
syntax - Support both statement block bodies as well as expression bodies which return value of expression
- Unlike functions, arrows share the same lexical
this
as their surrounding code
// Expression bodies
var odds = evens.map(v => v + 1);
var nums = evens.map((v, i) => v + i);
var pairs = evens.map(v => ({even: v, odd: v + 1}));
// Statement bodies
nums.forEach(v => {
if (v % 5 === 0)
fives.push(v);
});
// Lexical this
var bob = {
_name: 'Bob',
_friends: [],
printFriends() {
this._friends.forEach(f =>
console.log(this._name + ' knows ' + f)
);
}
}
Transpiled
// es6
let identity = value => value;
// es5
var identity = function(value) {
return value;
}
// es6
var deliveryBoy = {
name: 'John',
handleMessage: function(message, handler) {
handler(message);
},
receive: function() {
this.handleMessage('Hello, ', message => console.log(message + this.name));
}
};
// es5
var deliveryBoy = {
name: 'John',
handleMessage: function handleMessage(message, handler) {
handler(message);
},
receive: function receive() {
var _this = this;
this.handleMessage('Hello, ', function(message) {
return console.log(message + _this.name);
});
}
};
Classes
- Sugar over the prototype-based OO pattern
class SkinnedMesh extends THREE.Mesh {
constructor(geometry, materials) {
super(geometry, materials);
this.idMatrix = SkinnedMesh.defaultMatrix();
this.bones = [];
this.boneMatrices = [];
}
update(camera) {
// ...
super.update();
}
get boneCount() {
return this.bones.length;
}
set matrixType(matrixType) {
this.idMatrix = SkinnedMesh[matrixType]();
}
static defaultMatrix() {
return new THREE.Matrix4();
}
}
Transpiled
// es6
class Mesh {
constructor() {}
update() {}
}
class SkinnedMesh extends Mesh {
constructor() {
super();
}
update() {
super.update();
}
}
// es5
var _get = function get(object, property, receiver) {
if (object === null) object = Function.prototype;
var desc = Object.getOwnPropertyDescriptor(object, property);
if (desc === undefined) {
var parent = Object.getPrototypeOf(object);
if (parent === null) {
return undefined;
} else {
return get(parent, property, receiver);
}
} else if ('value' in desc) {
return desc.value;
} else {
var getter = desc.get;
if (getter === undefined) {
return undefined;
}
return getter.call(receiver);
}
};
var _createClass = function() {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ('value' in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function(Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
function _possibleConstructorReturn(self, call) {
if (!self) {
throw new ReferenceError('this hasn\'t been initialised - super() hasn\'t been called');
}
return call && (typeof call === 'object' || typeof call === 'function') ? call : self;
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function');
}
}
var Mesh = function() {
function Mesh() {
_classCallCheck(this, Mesh);
}
_createClass(Mesh, [{
key: 'update',
value: function update() {}
}]);
return Mesh;
};
var SkinnedMesh = function(_Mesh) {
_inherits(SkinnedMesh, _Mesh);
function SkinnedMesh() {
_classCallCheck(this, SkinnedMesh);
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(SkinnedMesh).call(this));
return _this;
}
_createClass(SkinnedMesh, [{
key: 'update',
value: function update() {
_get(Object.getPrototypeOf(SkinnedMesh.prototype), 'update', this).call(this);
}
}])
};
Enhanced Object Literals
- Support for setting the prototype at construction, shorthand for
foo: foo
assignments, defining methods, making super calls, and computing property names with expressions
var obj = {
// __proto__
__proto__: theProtoObj,
// shorthand for "handler: handler"
handler,
// methods
toString() {
// super callse
return 'd' + super.toString();
},
// computed (dynamic) property names
[ 'prop_' + (() => 42) ]: 42
}
Template Strings
- Provide syntactic sugar for constructing strings
// Basic literal string creation
`In Javascript '\n' is a line-feed.`
// Multiline strings
`In Javascript this is
not legal.`
// String interpolation
var name = 'Bob', time = 'today';
`Hello ${name}, how are you ${time}?`
// Construct an HTTP request
// prefix is used to interpret the replacements and construction
POST`http://foo.org/bar?a=${a}&b=${b}
Content-Type: application/json
X-Credentials: ${credentials}
{ "foo": ${foo},
"bar": ${bar}}`(myOnReadyStateChangeHandler);
Transpiled
// es6
let saluation = 'Hello';
let greeting = `${salutation}, World`;
let twoLines = `${salutation},
World`;
let x = 1, y = 2;
let equation = `${x} + ${y} = ${x + y}`;
// es5
var salutation = 'Hello';
var greeting = salutation + ', World';
var twoLines = salutation + ',\nWorld';
var x = 1, y = 2;
var equation = x + ' + ' + y + ' = ' + (x + y);
Destructuring
- Makes it possible to extract data from array or objects into distinct variables
// syntax
var a, b, rest;
[a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2
[a, b, ...rest] = [1, 2, 3, 4, 5];
console.log(a); // 1
console.log(b); // 2
console.log(rest); // [3, 4, 5]
({a, b} = {a: 1, b: 2});
console.log(a); // 1
console.log(b); // 2
// Array destructing ==
// default values
[a = 5, b = 7] = [1];
console.log(a); // 1
console.log(b); // 7
// swapping variables
var a = 1;
var b = 3;
[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1
// parsing an array returned from a function
function f() {
return [1, 2];
}
var [a, b] = f();
console.log(a); // 1
console.log(b); // 2
// ignore some returned values
function f() {
return [1, 2, 3];
}
var [a, , b] = f();
console.log(a); // 1
console.log(b); // 3
// Object destructing ==
// basic assignment
var o = {p: 42, q: true};
var {p, q} = o;
console.log(p); // 42
console.log(q); // true
// assigment without declaration
var a, b;
({a, b} = {a: 1, b: 2});
// assigning to new variable names
var {p: foo, q: bar} = o;
console.log(foo); // 42
console.log(bar); // true
// default values
var {a = 10, b = 5} = {a: 3};
console.log(a); // 3
console.log(b); // 5
Transpiled
// es6
let {color, position} = {
color: 'blue',
name: 'John',
state: 'New York',
position: 'Forward'
};
// es5
var _color$name$state$pos = {
color: 'blue',
name: 'John',
state: 'New York',
position: 'Forward'
};
var color = _color$name$state$pos.color;
var position = _color$name$state$pos.position;
// es6
function generateObj() {
return {
color: 'blue',
name: 'John',
state: 'New York',
position: 'Forward'
};
}
let {name, state} = generateObj();
let {name:firstName, state:location} = generateObj();
// es5
function generateObj {
return {
color: 'blue',
name: 'John',
state: 'New York',
position: 'Forward'
};
}
var _generateObj = generateObj();
var name = _generateObj.name;
var state = _generateObj.state;
var _generateObj2 = generateObj();
var firstName = _generateObj2.name;
var location = _generateObj2.state;
// es6
let [first,,,,fifth] = ['red', 'yellow', 'green', 'blue', 'orange'];
// es5
var _ref = ['red', 'yellow', 'green', 'blue', 'orange'];
var first = _ref[0];
var fifth = _ref[4];
Default + Rest + Spread
- Callee-evaluated default parameter values
- Turn an array into consecutive arguments in a function call
- Bind trailing paramters to an array
// default
function f(x, y=12) {
return x + y;
}
console.log(f(3)); // 15
// rest
function f(x, ...y) {
return x * y.length;
}
console.log(f(3, 'hello', true)); // 6
// spread: pass each element of array as argument
function f(x, y, z) {
return x + y + z;
}
console.log(f(...[1, 2, 3])); // 6
Transpiled
// es6
function greet(greeting, name = 'John') {
console.log(greeting + ', ' + name);
}
// es5
function greet(greeting) {
var name = arguments.length <= 1 || arguments[1] === undefined ? 'John' : arguments[1];
console.log(greeting + ', ' + name);
}
// es6
function receive(complete = () => console.log('complete')) {
complete();
}
// es5
function receive() {
var complete = arguments.length <= 0 || arguments[0] === undefined ? function() {
return console.log('complete');
} : arguments[0];
complete();
}
Let + Const
- Block-scoped binding constructs
let
is the new varconst
is a single-assignment- Static restrictions prevent use before assignment
function f() {
{
let x;
{
// okay, block scoped name
const x = 'sneaky';
// error, const
x = 'foo';
}
// error, already declared in block
let x = 'inner';
}
}
Transpiled
// es6
const VALUE = 'hello world';
// es5
var VALUE = 'hello world';
Iterators + For..Of
- Enable custom iteration
- Generalize
for..in
to custom iterator-based iteration withfor..of
let fibonacci = {
[Symbol.iterator]() {
let pre = 0, cur = 1;
return {
next() {
[pre, cur] = [cur, pre + cur];
return {done: false, value: cur};
}
}
}
}
for (var n of fibonacci) {
// truncate the sequence at 1000
if (n > 1000)
break;
console.log(n);
}
Generators
- Simplify iterator-authoring using
function*
andyield
- Function delcared as
function*
returns a Generator instance - Generators are subtypes of iterators which include additional
next
andthrow
var fibonacci = {
[Symbol.iterator]: function*() {
var pre = 0, cur = 1;
for (;;) {
var temp = pre;
pre = cur;
cur += temp;
yield cur;
}
}
}
for (var n of fibonacci) {
// truncate the sequence at 1000
if (n > 1000)
break;
console.log(n);
}
Transpiled
// es6
function* greet() {
console.log(`You called 'next()'`);
}
let greeter = greet();
let next = greeter.next();
// es5
var _marked = [greet].map(regeneratorRuntime.mark);
function greet() {
return regeneratorRuntime.wrap(function greet$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
console.log('You called \'next()\'');
case 1:
case 'end':
return _context.stop();
}
}
}, _marked[0], this);
}
var greeter = greet();
var next = greeter.next();
Unicode
- Non-breaking additions to support full Unicode
Modules
- Language-level support for modules for component definition
- Codifies patterns from popular module loaders (AMD, CommonJS)
// lib/math.js
export function sum(x, y) {
return x + y;
}
export var pi = 3.141593;
// app.js
import * as math from 'lib/math';
alert('2π = ' + math.sum(math.pi, math.pi));
// otherApp.js
import {sum, pi} from 'lib/math';
alert('2π = ' + sum(pi, pi));
// lib/mathplusplus.js
export * from 'lib/math';
export var e = 2.71828182846;
export default function(x) {
return Math.log(x);
}
// app.js
import ln, {pi, e} from 'lib/mathplusplus.js';
alert('2π = ' + ln(e) * pi * 2);
Transpiled
// es6
import {sumTwo} from 'math/addition';
export function sumTwo(a, b) {
return a + b;
}
export function sumThree(a, b, c) {
return a + b + c;
}
// es5
var _addition = require('math/addition');
Object.defineProperty(exports, '__esModule', {
value: true
});
function sumTwo(a, b) {
return a + b;
}
exports.sumTwo = sumTwo;
Module Loaders
- Support dynamic loading, state isolation, global namespace isolation, compilation hooks, nested virtualization
- Default loader can be configured
- New loaders can be constructed to evaluate and load code in isolated or constrained contexts
// dynamic loading - "System" is default loader
System.import('lib/math').then(function(m) {
alert('2π = ' + m.sum(m.pi, m.pi));
});
// create execution sandboxes - new loaders
var loader = new Loader({
global: fixup(window)
});
loader.eval('console.log("Hello, World!");');
// directly manipulate module cache
System.get('jquery');
System.get('jquery', Module({$: $}));
Map + Set + WeakMap + WeakSet
- Efficient data structures for common algorithms
- WeakMaps provides leak-free object-key’d side tables
// sets
var s = new Set();
s.add('hello').add('goodbye').add('hello');
s.size === 2;
s.has('hello') === true;
// maps
var m = new Map();
m.set('hello', 42);
m.set(s, 34);
m.get(s) === 34;
// weak maps
var wm = new WeakMap();
wm.set(s, {extra: 42});
wm.size === undefined;
// weak set
var ws = new WeakSet();
ws.add({data: 42});
Proxies
- Is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc.)
- handler: Placeholder object which contains traps
- traps: The methods that provide property access
- target: Object which the proxy virtualizes
Basic example
var handler = {
get: function(target, name) {
return name in target ? target[name] : 37;
}
};
var p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a., p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37
No-op forwarding proxy
var target = {};
var p = new Proxy(target, {});
p.a = 37; // operation forwarded to the target
console.log(target.a); // 37
Validation
let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
obj[prop] = value;
}
};
let person = new Proxy({}, validator);
person.age = 100;
console.log(person.age); // 100
person.age = 'young'; // Throws an exception
person.age = 300; // Throws an exception
Extending constructor
function extend(sup, base) {
var descriptor = Object.getOwnPropertyDescriptor(base.prototype, 'constructor');
base.prototype = Object.create(sup.prototype);
var handler = {
construct: function(target, args) {
var obj = Object.create(base.prototype);
this.apply(target, obj, args);
return obj;
},
apply: function(target, that, args) {
sup.apply(that, args);
base.apply(that, args);
}
};
var proxy = new Proxy(base, handler);
descriptor.value = proxy;
Object.defineProperty(base.prototype, 'constructor', descriptor);
return proxy;
}
var Person = function(name) {
this.name = name;
};
var Boy = extend(Person, function(name, age) {
this.age = age;
});
Boy.prototype.sex = 'M';
var Peter = new Boy('Peter', 13);
console.log(Peter.sex); // 'M'
console.log(Peter.name); // 'Peter'
console.log(Peter.age); // 13
// proxying a normal object
var target = {};
var handler = {
get: function(receiver, name) {
return `Hello, ${name}!`;
}
};
var p = new Proxy(target, handler);
p.world === 'Hello, world!';
// proxying a function object
var target = function() {
return 'I am the target';
};
var handler = {
apply: function(receiver, ...args) {
return 'I am the proxy';
}
};
var p = new Proxy(target, handler);
p() === 'I am the proxy';
Symbols
- Enable access control for object state
- Allow properties to be keyed by either
string
orsymbol
- Symbols are a new primitive type
var MyClass = (function() {
// module scoped symbol
var key = Symbol('key');
function MyClass(privateData) {
this[key] = privateData;
}
MyClass.prototype = {
doStuff: function() {
... this[key] ...
}
};
return MyClass;
})();
var c = new MyClass('hello');
c['key'] === undefined
Subclassable Built-Ins
- Built-Ins can be subclassed
// Pseudo-code of Array
class Array {
constructor(...args) { /* ... */ }
static [Symbol.create]() {
// ...
}
}
// User code of Array subclass
class MyArray extends Array {
constructor(...args) { super(args); }
}
// Two-phase "new":
// 1) Call @@create to allocate object
// 2) Invoke constructor on new instance
var arr = new MyArray();
arr[1] = 12;
arr.length == 2
Math + Number + String + Array + Object APIs
- Many new library additions, including core Math libraries, Array conversion helpers, String helpers, and Object.assign for copying
Number.EPSILON
Number.isInteger(Infinity) // false
Number.isNaN('NaN') // false
Math.acosh(3) // 1.762747174039086
Math.hypot(3, 4) // 5
Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) // 2
'abcde'.includes('cd') // true
'abc'.repeat(3) // 'abcabcabc'
Array.from(document.querySelectorAll('*')) // returns a real array
Array.of(1, 2, 3) // similar to new Array(...), but without special one-arg behavior
[0, 0, 0].fill(7, 1) // [0, 7, 7]
[1, 2, 3].find(x => x == 3) // 3
[1, 2, 3].findIndex(x => x == 2) // 1
[1, 2, 3, 4, 5].copyWithin(3, 0) // [1, 2, 3, 1, 2]
['a', 'b', 'c'].entries() // iterator [0, 'a'], [1, 'b'], [2, 'c']
['a', 'b', 'c'].keys() // iterator 0, 1, 2
['a', 'b', 'c'].values() // iterator 'a', 'b', 'c'
Object.assign(Point, {origin: new Point(0, 0)});
Binary and Octal Literals
- Two new numeric literal forms are added for binary (
b
) and octal (o
)
0b111110111 === 503 // true
0o767 === 503 // true
Promises
- Library for asynchronous programming
- First class representation of a value that may be made available in the future
function timeout(duration = 0) {
return new Promise((resolve, reject) => {
setTimeout(resolve, duration);
});
}
var p = timeout(1000).then(() => {
return timeout(2000);
}).then(() => {
throw new Error('hmm');
}).catch(err => {
return Promise.all([timeout(100), timeout(200)]);
});
- Source: TypeScript
Basic Types
let isDone: boolean = false;
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let color: string = 'blue';
let fullName: string = 'Bob Bobbington';
let sentence: string = `Hello, my name is ${fullName}.`
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
let x: [string, number];
x = ['hello', 10]; // correct
x = [10, 'hello']; // incorrect
enum Color {Red, Green, Blue};
let c: Color = Color.Green;
// start values at 1 instead of 0
enum Color {Red = 1, Green, Blue};
let c: Color = Color.Green;
// manually set the values
enum Color {Red = 1, Green = 2, Blue = 4};
let c: Color = Color.Green;
let colorName: string = Color[2]; // Green
let notSure: any = 4;
notSure = 'maybe a string instead';
notSure = false;
let list: any[] = [1, true, 'free'];
// no return type
function warnUser(): void {
alert('This is a warning message');
}
// type assertions
let someValue: any = 'this is a string';
let strLength: number = (<string>someValue).length;
let strLength: number = (someValue as string).length;
Variable Declarations
- No use before declaration
- No re-declarations and Shadowing
- New scope per iteration if used in loops
- Support for Destructuring
// let
let hello = 'Hello!';
// block-scoping
function f(input: boolean) {
let a = 100;
if (input) {
// still okay to reference 'a'
let b = a + 1;
return b;
}
// error: "b" doesn't exist here
return b;
}
// const
const numLivesForCat = 9;
const kitty = {
name: 'Aurora',
numLives: numLivesForCat
};
// can’t re-assign to them
numLivesForCat = 8; // error
// but internal values are still modifiable
kitty.name = 'Rory';
kitty.numLives--;
Interfaces
- Duck Typing/Structural Subtyping: Interfaces fill the role of naming these types
// first interface
// parameter has to be an object and has to have a label property
function printLabel(labelledObj: {label: string}) {
console.log(labelledObj.label);
}
let myObj = {size: 10, label: 'Size 10 Object'};
printLabel(myObj);
// with interface keyword
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
// optional properties
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): {color: string; area: number} {
let newSquare = {color: 'white', area: 100};
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
let mySquare = createSquare({color: 'black'});
// additional properties
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}
// function types
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}
// indexable types
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ['Bob', 'Fred'];
let myStr: string = myArray[0];
// class types
interface ClockInterface {
currentTime: Date;
setTime(d: Date);
}
interface ClockConstructor {
new (hour: number, minute: number);
}
class Clock implements ClockConstructor, ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) {}
}
// extending interfaces
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
let square = <Square>{};
square.color = 'blue';
square.sideLength = 10;
square.penWidth: 5.0;
// hybrid types
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
// interfaces extending classes
class Control {
private state: any
}
interface SelectableControl extends Control {
select(): void;
}
class Button extends Control {
select() { }
}
Classes
// classes
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return 'Hello, ' + this.greeting;
}
}
let greeter = new Greeter('world');
// inheritance
class Animal {
name: string;
constructor(theName: string) { this.name = theName; }
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Snake extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 5) {
console.log('Slithering...');
super.move(distanceInMeters);
}
}
class Horse extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 45) {
console.log('Galloping...');
super.move(distanceInMeters);
}
}
let sam = new Snake('Sammy the Python');
let tom: Animal = new Horse('Tommy the Palomino');
sam.move(); // Slithering... Sammy the Python moved 5m.
tom.move(34); // Galloping... Tommy the Palomino moved 34m.
// public, private, and protected modifiers
// public by default
// private
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
new Animal('Cat').name; // error
// protected
class Person {
protected name: string;
constructor(name: string) { this.name = name; }
}
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee('Howard', 'Sales');
console.log(howard.getElevatorPitch()); // Hello, my name is Howard and I work in Sales.
console.log(howard.name); // error
// parameter properties
class Animal {
// shorthand to create and initialize the "name" member
constructor(private name: string) { }
}
// static properties
// visible on the class itself rather than on the instances
class Grid {
static origin = {x: 0, y: 0};
calculateDistanceFromOrigin(point: {x: number, y: number}) {
let xDist = (point.x - Grid.origin.x);
let yDist = (point.y - Grid.origin.y);
return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
}
constructor (public scale: number) { }
}
let grid1 = new Grid(1.0); // 1x scale
let grid2 = new Grid(5.0); // 5x scale
console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));
// abstract classes
abstract class Animal {
// must be implemented in the derived classes
abstract makeSound(): void;
move(): void {
console.log('roaming the earth...');
}
}
Functions
// writing the function type
let myAdd: (x: number, y: number) => number =
function(x: number, y: number): number { return x + y; }
// optional parameters
function buildName(firstName: string, lastName?: string) {
return lastName? firstName + ' ' + lastName : lastName;
}
// default parameters
function buildName(firstName: string, lastName = 'Smith') {
return firstName + ' ' + lastName;
}
// rest parameters
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + ' ' + restOfName.join(' ');
}
// lambdas and using "this"
let deck = {
suits: ['hearts', 'spades', 'clubs', 'diamonds'],
cards: Array(52),
createCardPicker: function() {
return function() {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
// "this" does reference "window" instead of "deck"
return {
suit: this.suits[pickedSuit],
card: pickedCard % 13
};
};
}
};
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
alert('card: ' + pickedCard.card + ' of ' + pickedCard.suit);
let deck = {
suits: ['hearts', 'spades', 'clubs', 'diamonds'],
cards: Array(52),
createCardPicker: function() {
// notice: the line below is now a lambda, allowing us to capture "this" earlier
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return {
suit: this.suits[pickedSuit],
card: pickedCard % 13
};
};
}
};
// overloads
let suits = ['hearts', 'spades', 'clubs', 'diamonds'];
function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
if (typeof x == 'object') {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
} else if (typeof x == 'number') {
let pickedSuit = Math.floor(x / 13);
return {suit: suits[pickedSuit], card: x % 13};
}
}
let myDeck = [
{suit: 'diamonds', card: 2},
{suit: 'spades', card: 10},
{suit: 'hearts', card: 4}
];
let pickedCard1 = myDeck[pickCard(myDeck)];
let pickedCard2 = pickCard(15);
Generics
Identity function example (think of it as echo
)
// we loose type information hier
function identity(arg: any): any {
return arg;
}
// use type variable
function identity<T>(arg: T): T {
return arg;
}
// function declaration
let myIdentity: <T>(arg: T) => T = identity;
let output = identity<string>('myString'); // type of output will be "string"
// with type argument inference compiler will set value of "T"
let output = identity('myString'); // type of output will be "string"
// generic interface
interface GenericIdentityFn {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
// with type information
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
// generic classes
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
// generic constraints
interface Lengthwise {
length: number;
}
// constraint is, that T has "length" member
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
// using type parameters in generic constraints
function copyFields<T extends U, U>(target: T, source: U): T {
for (let id in source) {
target[id] = source[id];
}
return target;
}
let x = {a: 1, b: 2, c: 3, d: 4};
copyFields(x, {b: 10, d: 20}); // ok
copyFields(x, {Q: 90}); // error: property "Q" isn't declared in "x"
Enum
enum Direction {Up = 1, Down, Left, Right}
// reverse mapping
enum Enum {A}
let a = Enum.A; // 0
let nameOfA = Enum[Enum.A]; // "A"
// const enum
const enum Directions {Up, Down, Left, Right}
// generated code (without possibility to lookup names):
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];
Type Inference
let x = 3; // type is inferred to be "number"
let x = [0, 1, null]; // type is inferred with best common type algorithm
Type Compatibility
interface Named {
name: string;
}
class Person {
name: string;
}
let p: Named;
p = new Person(); // ok, because of structural typing
let x: Named;
// y's inferred type is {name: string, location: string}
let y: {name: 'Alice', location: 'Seattle'};
x = y;
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
y = x; // ok
x = y; // error
Symbols
let sym1 = Symbol();
// optional string key
let sym2 = Symbol('key');
// symbols are immutable and unique
let sym3 = Symbol('key');
sym2 === sym3; // false
// can be used as keys for object properties
let obj = {
[sym]: 'value'
};
console.log(obj[sym]); // value
Iterators and Generators
// iterables
let someArray = [1, 'string', false];
for (let i in someArray) {
console.log(i); // 0, 1, 2
}
for (let i of someArray) {
console.log(i); // 1, "string", false
}
Namespaces
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
const lettersRegexp = /^[A-Za-z]+$/;
const numberRegexp = /^[0-9]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return numberRegexp.test(s);
}
}
}
let strings = ['Hello', '98052', '101'];
let validators: {[s: string]: Validation.StringValidator; } = {};
validators['ZIP code'] = new Validation.ZipCodeValidator();
validators['Letters only'] = new Validation.LettersOnlyValidator();
for (let s of strings) {
for (let name in validators) {
console.log(`"${s}" - ${validators[name].isAccetable(s) ? 'matches' : 'does not match'} ${name}`);
}
}
// splitting across files
// validation.ts
namespace Validation {
// ...
}
// letters-only-validator.ts
/// <reference path="validation.ts" />
namespace Validation {
// ...
}
// aliases
namespace Shapes {
export namespace Polygons {
export class Triangle { }
export class Square { }
}
}
import polygons = Shapes.Polygons;
let sq = new polygons.Square();
// ambient namespaces
declare namespace D3 {
export interface Selectors {
select: {
(selector: string): Selection;
(element: EventTarget): Selection;
}
}
export interface Event {
x: number;
y: number;
}
export interface Base extends Selectors {
event: Event;
}
}
declare var d3: D3.Base;
Namespaces and Modules
// myModules.d.ts
declare module "SomeModule" {
export function fn(): string;
}
// myOtherModule.ts
/// <reference path="myModules.d.ts" />
import * as m from "SomeModule";
JSX
- Embeddable XML-like syntax
- Meant to be transformed into valid JavaScript
- In order to use JSX:
- Name files with a
.tsx
extension - Enable the
jsx
option
- Name files with a
- Angle bracket type assertions are disallowed in
.tsx
files:var foo = bar as foo;
instead ofvar foo = <foo>bar;
Mixins
// disposable mixin
class Disposable {
isDisposed: boolean;
dispose() {
this.isDisposed = true;
}
}
// activatable mixin
class Activatable {
isActive: boolean;
activate() {
this.isActive = true;
}
deactivate() {
this.isActive = false;
}
}
class SmartObject implements Disposable, Activatable {
constructor() {
setInterval(() => console.log(this.isActive + ' : ' + this.isDisposed), 500);
}
interact() {
this.activate();
}
isDisposed: boolean = false;
dispose: () => void;
isActive: boolean = false;
activate: () => void;
deactivate: () => void;
}
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
derivedCtor.prototype[name] = baseCtor.prototype[name];
});
});
}
applyMixins(SmartObject, [Disposable, Activatable]);
let smartObj = new SmartObject();
setTimeout(() => smartObj.interact(), 1000);
- Source: Webpack
- Most pressing reason for development was Code Splitting and modularized static assets
- Goals:
- Split dependency tree into chunks loaded on demand
- Keep initial loading time low
- Every static asset should be able to be a module
- Ability to integrate 3rd-party libraries as modules
- Ability to customize nearly every part of the module bundler
- Suited for big projects
How is webpack different?
- Code Splitting
- webpack has two types of dependencies in its tree: sync and async
- async dependencies act as split points and form a new chunk
- after chunk tree optimization a file for each chunk is emitted
- Loaders
- used to transform other resources into JavaScript
- by doing so, every resource forms a module
- Clever parsing
- can nearly process every 3rd party library
- handles most common module styles: CommonJS and AMD
- Plugin system
- features a rich plugin system
- most internal features are based on this
- possibility to customize webpack
- Package manager for the SystemJS universal module loader, built on top of the dynamic ES6 module loader
- Loads any module format (ES6, AMD, CommonJS and globals) directly from any registry such as
npm
andGithub
with flat versioned dependency management - For development: Load modules as separate files with ES6 and plugins compiled in the brwoser
- For production: Optimize into a bundle, layered bundles or a self-executing bundle with a single command
- JavaScript module bundler
- Allows writing application or library as a set of modules (using ES5
import
/export
syntax) - Bundle them up into a single file
- A bundle is more portable and easier to consume than a collection of files
- Compression works better with fewer bigger files
- In the browser, a 100kb bundle loads much faster than 5 20kb files (not valid for HTTP/2)
- By bundling code, we can take advantage of tree-shaking (fewer wasted bytes)
- Source: Jasmine
- Behavior-driven development framework for testing
- Does not depend on any other JavaScript framework
- Does not require a DOM
- Could be run from command line with jasmine-node
- Source: Mocha
- Feature-rich JavaScript test framework running on Node.js and in the browser
- There are different assertion libraries: Node.js’ built-in assert module, should.js (BDD), expect.js, chai, better-assert, unexpected
- Source: Jest
- Uses Jasmine assertions by default
- Virtualizes JavaScript environments, provides browser mocks and runs test in parallel across workers
- Automatically mocks JavaScript modules, making most existing code testable
- Test Coverage: Istanbul
- Static Code Analysis: Sidekick
- Static Code Analysis: Plato
- Linting: eslint
- Web Performance Metrics Collector and Monitoring Tool: phantomas
- Source: What is Angular 1?
- Structural framework for dynamic web apps
- HTML as template language with extended syntax
- Data binding & dependency injection
- Attempts to minimize the impedance mismatch between document centric HTML and what an application needs by creating new HTML constructs (Directives)
- Well-defined structure for all of the DOM and AJAX glue code
- Opinionated about how a CRUD application should be built
- Everything you need: Data-binding, basic templating directives, form validation, routing, deep-linking, reusable components, dependency injection
- Testability story: Unit-testing, end-to-end testing, mocks and test harnesses
- Seed application with directory layout and test scripts as a starting point
- Simplifies application development by presenting a higher level of abstraction (comes at a cost of flexibility)
- CRUD applications are a good fit, Games and GUI editors are not
- Belief that declarative code is better than imperative:
- Decouple DOM manipulation from app logic (improves testability)
- Regard app testing as equal in importance to app writing
- Decouple client side of an app from the server side
- Common tasks should be trivial and difficult tasks should be possible
- Angular frees you from the following pains:
- Registering callbacks
- Manipulating HTML DOM programmatically
- Marshaling data to and from the UI
- Writing tons of initialization code just to get started
Pros
- Quick prototyping
- Development is fast once you’re familiar with it
- Very expressive (less code)
- Easy testability
- Good for apps with highly interactive client side code
- Two-way data binding
- Dependency injection system
- Extends HTML
Cons
- Learning curve becomes very steep
- Complexity of DI and services
- Scopes are easy to use, but hard to debug
- Documentation is definitely not up to par
- Directives are powerful, but difficult to use
- Lack of configuration after Bootstrap
- Router is limited
- Search engine indexability
- Source: Performance
- Accessing the DOM is expensive
- Any time a new scope is created, that adds more values for the garbage collector
- Every scope stores an array of functions:
$$watchers
- Every time
$watch
is called on a scope value, or a value is bound from the DOM a function gets added to the$$watchers
array of the innermost scope - When any value in scope changes, all watchers in the
$$watchers
array will fire, and if any of them modify a watched value, they will all fire again (will continue until a full pass of the$$watchers
array makes no changes) - Use bind-once syntax where possible:
{{::scopeValue}}
$on
,$broadcast
, and$emit
are slow as events have to walk entire scope hierarchy- Always call
$on('$destroy')
The bad parts
- ng-click and other DOM events
- scope.$watch
- scope.$on
- Directive postLink
- ng-repeat
- ng-show and ng-hide
The good (performant) parts
- track by
- oneTime bindings with ::
- compile and preLink
- $evalAsync (queue operations up for execution at the end of the current digest cycle)
- Services, scope inheritance, passing objects by reference
- $destroy
- unbinding watches and event listeners
- ng-if and ng-switch
- Source: DI
- Components such as services, directives, filters, and animations are defined by an injectable factory method or constructor function
- Controllers are defined by a constructor function, which can be injected with components as dependencies, but can also be provided with special dependencies
- The
run
method cannot inject providers - The
config
method cannot inject services or values
- Source: Component Router
- Recommended to develop apps as a hierarchy of isolated components with own UI and well defined programmatic interface to the component that contains it
- Root Router matches it’s Route Config against the URL; if a Route Definition in the Route Config recognizes a part of the URL then the Component associated with the Route Definition is instantiated and rendered in the Outlet
- If the new Component contains routes of its own then a new Router (Child Router) is created for this Routing Component
- Backbone: 3rd party templating (underscore), No two-way binding, Unopinionated
- React: No routing, Uni-directional data flow, Virtual DOM (faster updates), Probably used with flux (architecture template with dispatcher)
- Source: CLI
- CLI for Angular 2 applications based on ember-cli
- Build system now uses Webpack as well
Examples
$ ng new app-name
// src/main.ts
import { bootstrap } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { AppComponent, environment } from './app/';
if (environment.production) {
enableProdMode();
}
bootstrap(AppComponent);
// src/app/index.ts
export * from './environments/environment';
export * from './app.component';
// src/app/environments/environment.ts
export const environment = {
production: false
};
// src/app/app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.css']
})
export class AppComponent {
}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>AppName</title>
<base href="/">
</head>
<body>
<app-root>Loading...</app-root>
</body>
</html>
ng generate route about
// src/app/+about/index.ts
export { AboutComponent } from './about.component';
// src/app/+about/about.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
moduleId: module.id,
selector: 'app-about',
templateUrl: 'about.component.html',
styleUrls: ['about.component.css']
})
export class AboutComponent implements OnInit {
constructur() {}
ngOnInit() {}
}
ng generate directive footer
import { Directive } from '@angular/core';
@Directive({
selector: '[footer]'
})
export class Footer {
constructor() {}
}
ng generate pipe uppercase
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'uppercase'
})
export class Uppercase implements PipeTransform {
transform(value: any, args?: any): any {
return null;
}
}
ng generate service data
import { Injectable } from '@angular/core';
@Injectable()
export class DataService {
constructor() {}
}
- Source: Universal
- Server-side Rendering for Angular 2 apps
- Better perceived performance
- Optimized for Search Engines
- Site Preview
- Source: RxJS
- Event-driven, resilient and responsive Architecture
- Set of libraries for composing asynchronous and event-based programs
- Developers represent asynchronous data streams with Observables, query asynchronous data streams using Operators, and parameterize the concurrency in async data streams using Schedulers
- Observable sequences are data streams
- Source: Introduction
- RxJS powered state management inspired by Redux for Angular 2 apps
- Store builds on the concepts made popular by Redux (state management container for React) supercharged with the backing of RxJS
- Three main pieces: Reducers, Actions, and a single application Store
- Store (Database)
- „Single source of truth“,
- Snapshot of Store at any point will supply a complete representation of relevant application state
- Centralized, immutable state
- Reducers (Tables)
- A pure function, accepting two arguments, the previous state and an action with a type and optional data (payload) associated with the event
- Actions
- All interaction that causes a state update
- All relevant user events are dispatched as actions, flowing through the action pipeline defined by store
- Dispatch » Reducers » New State » Store
export const counter: Reducer<number> = (state: number = 0, action: Action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
};
- Source: Why React?
- JavaScript library for creating user interfaces (the V in MVC)
- Simple: Express how your app should look at any given point in time, React will automatically manage all UI updates when your underlaying data changes
- Declarative: React conceptually hits the „refresh“ button, and knows to only update the changed parts
- Build composable components: With React the only you do is build encapsulated components (easier code reuse, testing and separation of concerns)
Pros
- Extremely easy to write UI test cases (due to virtual DOM system)
- Reusability of components (even combine them)
- Plays well together with other libraries or frameworks
- Automatic UI updates when underlaying data changes
- Ease of debugging (Chrome Extension)
- Works nicely with CommonJS/AMD patterns
Cons
- Learning curve for beginners
- Integrating into a traditional MVC framework like rails would require some configuration
- Kind of verbose (isn’t as straight forward as pure HTML & JS
- Not a full framework (no router nor model management)
// main.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('app'));
// App.js
import React from 'react';
class App extends React.Component {
render() {
// JSX transpiled to
// return React.createElement('h1', null, 'Hello, World!');
return <h1>Hello, World!</h1>;
// there must be one root element
/*
return (
<div>
<h1>Hello, World!</h1>
<p>How are you?</p>
</div>
);
*/
}
}
export default App;
// stateless function component
const App = () => <h1>Hello, World!</h1>;
class App extends React.Component {
render() {
let greeting = this.props.greeting;
return <h1>{greeting}{exclamation}</h1>;
}
}
App.propTypes = {
greeting: React.PropTypes.string,
exclamation: React.PropTypes.string.isRequired
};
App.defaultProps = {
exclamation: '!'
};
ReactDOM.render(
<App greeting="Hello" />,
document.getElementById('app');
);
class App extends React.Component {
constructor() {
super();
this.state = {
greeting: 'Hello'
};
this.update = this.update.bind(this);
}
update(e) {
this.setState({greeting: e.target.value});
// this.setState({greeting: ReactDOM.findDOMNode(this.refs.header).value});
}
render() {
return (
<div>
<!--
<h1>{this.state.greeting}</h1>
<input type="text" onChange={this.update} />
-->
<Greeting ref="header" greeting={this.state.greeting} update={this.update} />
</div>
);
}
}
const Greeting = (props) => {
return (
<div>
<h1>{props.greeting}</h1>
<input type="text" onChange={props.update} />
</div>
)
};
class App extends React.Component {
render() {
return <Button>I <Heart /> React</Button>;
}
}
class Button extends React.Component {
render() {
return <button>{this.props.children}</button>;
}
}
const Heart = () => <span className="glyphicon glyphicon-heart"></span>;
class App extends React.Component {
constructor() {
super();
}
componentWillMount() {}
componentWillReceiveProps() {}
shouldComponentUpdate()
render() {}
componentDidUpdate()
componentDidMount() {}
componentWillUnmount() {}
}
let Mixin = InnerComponent => class extends React.Component {
constructor() {
super();
}
// ...
};
const Button = (props) => <button onClick={props.update}>{props.label}</button>;
const Label = (props) => <label onMouseMove={props.update}>{props.label}</label>;
let ButtonMixed = Mixin(Button);
let LabelMixed = Mixin(Label);
class App extends React.Component {
constructor() {
super();
this.state = {data: [/* ... */]};
}
render() {
let rows = this.state.data.map(person => {
return <PersonRow key={person.id} data={person} />;
});
return (
<table>
<tbody>{rows}</tbody>
</table>
);
}
}
const PersonRow = (props) => {
return (
<tr>
<td>{props.data.id}</td>
<td>{props.data.name}</td>
</tr>
);
};
- Source: Flux
- An application architecture for React utilizing a unidirectional data flow
- Three major parts: Dispatcher, Stores and Views (React components)
- When a user interacts with a React view, the view propagates an action through a central dispatcher, to the various stores that hold the application’s data and business logic, which updates all of the views that are affected
- Control is inverted with stores: the stores accept updates and reconcile them as appropriate, rather than depending on something external to update its data in a consistent way
- Unidirectional data flow: dispatcher, stores and views are independent nodes with distinct inputs and outputs; action creators are simple, discrete, semantic helper function that facilitate passing data to the dispatcher in the form of an action
- Source: Tutorial
- Uses native components instead of web components as building blocks
- Real mobile apps are built – no mobile web apps
- Instead of recompiling you can reload your app instantly
- Use native code when you need to
- Source: Redux, Three Principles
- Predictable state container
- Single source of truth: The application state is stored in an object tree within a single store
- State is read-only: Only way to change the state is to emit an action, an object describing what happened
- Changes are made with pure function: Pure reducers specify how the state tree is transformed by actions