0%

Angular 2 学习笔记之 EcmaScript6 & TypeScript

Angular 2 学习笔记之 EcmaScript6 & TypeScript

EcmaScript6

Classes

类是在 ES6 一个新功能,用来描述对象的蓝图,使 ECMAScript 中的原型继承模型的功能更像是一个传统的基于类的语言。

1
2
3
4
5
6
7
8
class Hamburger {
constructor() {
// This is the constructor.
}
listToppings() {
// This is a method.
}
}

Object

一个对象是通过使用 new 关键字创建的一个类的实例。

1
2
let burger = new Hamburger();
burger.listToppings();

Refresher on ‘this’

在 Javascript 中, this 的指向一直是个经久不衰的话题。传统的基于类的语言中的 this 关键字通常指向类的当前实例。然而在 Javascript 中, this 则指向当前调用的上下文,因此可能指向任何其他值而不是当前对象。在函数内部,this的值取决于函数被调用的方式。在严格模式下,如果 this未在执行的上下文中定义,那它将会默认为 undefined

在一个 Javascript 的类中,我们通常使用 this 关键字来指向当前类的实例。

1
2
3
4
5
6
7
8
9
class Toppings {
...

formatToppings() { /* implementation details */ }

list() {
return this.formatToppings(this.toppings);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(function() {

'use strict';

var myObject = {
foo: 'bar',
someMethod: function() {
console.log(this.foo);
}
};

function someMethod() {
console.log(this);
}

myObject.someMethod(); // myObject
someMethod(); // undefined

})();

在线实例:

JS Bin on jsbin.com

1
2
3
4
5
6
7
8
9
10
class ServerRequest {
notify() {
...
}
fetch() {
getFromServer(function callback(err, data) {
this.notify(); // this is not going to work
});
}
}

在线实例:

JS Bin on jsbin.com

Changing Caller Context

JavaScript 可以在调用的时候使用以下任一一种方法改变 this 的指向。

  • Function.prototype.call(object [,arg, …]) // 立即调用
  • Function.prototype.bind(object [,arg, …]) // 稍后调用
  • Function.prototype.apply(object [,argsArray]) // 立即调用

Arrow Function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Toppings {
toppings;

constructor(toppings) {
this.toppings = Array.isArray(toppings) ? toppings : [];
}

outputList(){
this.toppings
.forEach((topping, i) => console.log(topping, i + '/' + this.toppings.length)) // `this` works
}
}

var ctrl = new Toppings(['cheese','letture']);

ctrl.outputList();

View

Template Strings

1
2
3
4
5
6
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`);

View

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

1
2
3
4
5
6
7
8
var i;
for(i = 0; i < 10; i++) {
var j = i;
let k = i;
}

console.log(j); // 9
console.log(k); //undefined
1
2
3
for(var x=0; x<5; x++) {
setTimeout(()=>console.log(x), 0)
}
1
2
3
for(let y=0; y<5; y++) {
setTimeout(()=>console.log(y), 0)
}

View

…spread and …rest

1
2
3
const add = (a, b) => a + b;
let args = [3, 5];
add(...args); // same as `add(args[0], args[1])`, or `add.apply(null, args)`
1
2
let cde = ['c', 'd', 'e'];
let scale = ['a', 'b', ...cde, 'f', 'g']; // ['a', 'b', 'c', 'd', 'e', 'f', 'g']
1
2
let mapABC  = { a: 5, b: 6, c: 3};
let mapABCD = { ...mapABC, d: 7}; // { a: 5, b: 6, c: 3, d: 7 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
1
2
3
4
5
6
function print(a, b, c, ...more) {
console.log(more[0]);
console.log(arguments[0]);
}

print(1, 2, 3, 4, 5);

View

Destructuring

1
2
3
4
5
let foo = ['one', 'two', 'three'];

let one = foo[0];
let two = foo[1];
let three = foo[2];

into

1
2
3
let foo = ['one', 'two', 'three'];
let [one, two, three] = foo;
console.log(one); // 'one'
1
2
3
4
5
6
7
8
9
10
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');

View

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:

1
2
3
4
5
6
7
8
9
10
<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

1
npm install -g typescript

Without TS:

1
2
3
4
5
6
function add(a, b) {
return a + b;
}

add(1, 3); // 4
add(1, '3'); // '13'

With TS:

1
2
3
4
5
6
7
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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');
1
2
3
4
5
6
7
8
9
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

1
2
3
4
class Person {
name: string;
nickName?: string;
}

Interface

1
2
3
4
5
6
7
8
9
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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'
1
2
3
4
5
6
7
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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, tsc lets 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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'
1
2
3
4
5
6
7
8
9
10
11
12
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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

1
2
3
4
5
6
7
8
9
10
11
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.