EcmaScript6
Classes
类是在ES6一个新功能,用来描述对象的蓝图,使ECMAScript中的原型继承模型的功能更像是一个传统的基于类的语言。
class Hamburger { constructor() { // This is the constructor. } listToppings() { // This is a method. } }
Refresher on 'this'
(function() { 'use strict'; var myObject = { foo: 'bar', someMethod: function() { console.log(this); } }; function someMethod() { console.log(this); } myObject.someMethod(); someMethod(); // undefined })();
class ServerRequest { notify() { ... } fetch() { getFromServer(function callback(err, data) { this.notify(); // this is not going to work }); } }
Arrow Function
class Toppings {
toppings;
constructor(toppings) {
this.toppings = Array.isArray(toppings) ? toppings : [];
}
outputList(){
// this.toppings.forEach(function(topping, i) {
// console.log(topping, i + '/' + this.toppings.length); // no this
// })
this.toppings
.forEach((topping, i) => console.log(topping, i + '/' + this.toppings.length)) // `this` works
}
}
var ctrl = new Toppings(['cheese','letture']);
ctrl.outputList();
Template Strings
var name = 'Sam';
var age = 42;
console.log('hello my name is ' + name + ', and I am ' + age + ' years old');
console.log(`hello my name is ${name}, and I am ${age} years old`);
Inheritance
JavaScript's inheritance works differently from inheritance in other languages, which can be very confusing. ES6 classes provide a syntactic sugar attempting to alleviate the issues with using prototypical inheritance present in ES5. Our recommendation is still to avoid using inheritance or at least deep inheritance hierarchies. Try solving the same problems through delegation instead.
Constants and Block Scope Veribles
var i;
for(i = 0; i < 10; i++) {
var j = i;
let k = i;
}
console.log(j); // 9
console.log(k); //undefined
for(var x=0; x<5; x++) {
setTimeout(()=>console.log(x), 0)
}
for(let y=0; y<5; y++) {
setTimeout(()=>console.log(y), 0)
}
...spread and ...rest
const add = (a, b) => a + b;
let args = [3, 5];
add(...args); // same as `add(args[0], args[1])`, or `add.apply(null, args)`
let cde = ['c', 'd', 'e'];
let scale = ['a', 'b', ...cde, 'f', 'g']; // ['a', 'b', 'c', 'd', 'e', 'f', 'g']
let mapABC = { a: 5, b: 6, c: 3};
let mapABCD = { ...mapABC, d: 7}; // { a: 5, b: 6, c: 3, d: 7 }
function addSimple(a, b) {
return a + b;
}
function add(...numbers) {
return numbers[0] + numbers[1];
}
addSimple(3, 2); // 5
add(3, 2); // 5
// or in es6 style:
const addEs6 = (...numbers) => numbers.reduce((p, c) => p + c, 0);
addEs6(1, 2, 3); // 6
function print(a, b, c, ...more) {
console.log(more[0]);
console.log(arguments[0]);
}
print(1, 2, 3, 4, 5);
Destructuring
let foo = ['one', 'two', 'three'];
let one = foo[0];
let two = foo[1];
let three = foo[2];
into
let foo = ['one', 'two', 'three'];
let [one, two, three] = foo;
console.log(one); // 'one'
let myModule = {
drawSquare: function drawSquare(length) { /* implementation */ },
drawCircle: function drawCircle(radius) { /* implementation */ },
drawText: function drawText(text) { /* implementation */ },
};
let {drawSquare, drawText} = myModule;
drawSquare(5);
drawText('hello');
Modules
ES6 also introduces the concept of a module, which works similar to other languages. Defining an ES6 module is quite easy: each file is assumed to define a module and we specify its exported values using the export keyword.
Loading ES6 modules is a little trickier. In an ES6-compliant browser you use the System keyword to load modules asynchronously. To make our code work with current browsers, however, we will use SystemJS library as a polyfill:
<script src="/node_module/systemjs/dist/system.js"></script>
<script>
var promise = System.import('app')
.then(function() {
console.log('Loaded!');
})
.then(null, function(error) {
console.error('Failed to load:', error);
});
</script>
TypeScript
Angular 2 is built in TypeScript
npm install -g typescript
Without TS:
function add(a, b) {
return a + b;
}
add(1, 3); // 4
add(1, '3'); // '13'
With TS:
function add(a: number, b: number) {
return a + b;
}
add(1, 3); // 4
// compiler error before JS is even produced
add(1, '3'); // '13'
Types
- boolean (true/false)
- number integers, floats, Infinity and NaN
- string characters and strings of characters
- [] Arrays of other types, like number[] or boolean[]
- {} Object literal
- undefined not set
- enum enumerations like { Red, Blue, Green }
- any use any type
- void nothing
let isDone: boolean = false;
let height: number = 6;
let name: string = "bob";
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
enum Color {Red, Green, Blue};
let c: Color = Color.Green;
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean
function showMessage(data: string): void {
alert(data);
}
showMessage('hello');
function logMessage(message: string, isDebug?: boolean) {
if (isDebug) {
console.log('Debug: ' + message);
} else {
console.log(message);
}
}
logMessage('hi'); // 'hi'
logMessage('test', true); // 'Debug: test'
Using a ? lets tsc know that isDebug is an optional parameter. tsc will not complain if isDebug is omitted.
Classes
class Person {
name: string;
nickName?: string;
}
Interface
interface Callback {
(error: Error, data: any): void;
}
function callServer(callback: Callback) {
callback(null, 'hi');
}
callServer((error, data) => console.log(data)); // 'hi'
callServer('hi'); // tsc error
interface PrintOutput {
(message: string): void; // common case
(message: string[]): void; // less common case
}
let printOut: PrintOutput = (message) => {
if (Array.isArray(message)) {
console.log(message.join(', '));
} else {
console.log(message);
}
}
printOut('hello'); // 'hello'
printOut(['hi', 'bye']); // 'hi, bye'
interface Action {
type: string;
}
let a: Action = {
type: 'literal'
}
Shapes
Underneath TypeScript is JavaScript, and underneath JavaScript is typically a JIT (Just-In-Time compiler). Given JavaScript's underlying semantics, types are typically reasoned about by "shapes". These underlying shapes work like TypeScript's interfaces, and are in fact how TypeScript compares custom types like classes and interfaces.
interface Action {
type: string;
}
let a: Action = {
type: 'literal'
}
class NotAnAction {
type: string;
constructor() {
this.type = 'Constructor function (class)';
}
}
a = new NotAnAction(); // valid TypeScript!
Despite the fact that Action and NotAnAction have different identifiers, tsclets us assign an instance of NotAnAction to a which has a type of Action. This is because TypeScript only really cares that objects have the same shape. In other words if two objects have the same attributes, with the same typings, those two objects are considered to be of the same type.
Decorators
Property Decorators
function Override(label: string) {
return function (target: any, key: string) {
Object.defineProperty(target, key, {
configurable: false,
get: () => label
});
}
}
class Test {
@Override('test') // invokes Override, which returns the decorator
name: string = 'pat';
}
let t = new Test();
console.log(t.name); // 'test'
function ReadOnly(target: any, key: string) {
Object.defineProperty(target, key, { writable: false });
}
class Test {
@ReadOnly // notice there are no `()`
name: string;
}
const t = new Test();
t.name = 'jan';
console.log(t.name); // 'undefined'
Class Decorators
function log(prefix?: string) {
return (target) => {
// save a reference to the original constructor
var original = target;
// a utility function to generate instances of a class
function construct(constructor, args) {
var c : any = function () {
return constructor.apply(this, args);
}
c.prototype = constructor.prototype;
return new c();
}
// the new constructor behavior
var f : any = function (...args) {
console.log(prefix + original.name);
return construct(original, args);
}
// copy prototype so instanceof operator still works
f.prototype = original.prototype;
// return new constructor (will override original)
return f;
};
}
@log('hello')
class World {
}
const w = new World(); // outputs "helloWorld"
In the example log is invoked using @, and passed a string as a parameter, @log() returns an anonymous function that is the actual decorator.
The decorator function takes a class, or constructor function (ES5) as an argument. The decorator function then returns a new class construction function that is used whenever World is instantiated.
This decorator does nothing other than log out its given parameter, and its target's class name to the console.
Parameter Decorators
function logPosition(target: any, propertyKey: string, parameterIndex: number) {
console.log(parameterIndex);
}
class Cow {
say(b: string, @logPosition c: boolean) {
console.log(b);
}
}
new Cow().say('hello', false); // outputs 1 (newline) hello
The above demonstrates decorating method parameters. Readers familiar with Angular 2 can now imagine how Angular 2 implemented their @Inject() system.