Commit 1ebb2618 by zhao shuan

init

0 parents
- 在JavaScript的世界中,所有代码都 是单线程执行的
- 由于这个“缺陷”,导致 JavaScript 的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现
- 异步操作会在将来的某个时间点触发一个函数调用
- 主流的异步处理方案主要有:回调函数 (CallBack) 、 Promise 、 Generator 函数、 async/await。
# 回调函数
这是异步编程最基本、最直观的方式
```javascript
downloadAsync("http://example.com/file.txt", (text) => {
console.log(text);
});
```
# Promise
解决回调嵌套层数过多的问题
```javascript
downloadAsync("a.txt", (a) => {
downloadAsync("b.txt", (b) => {
downloadAsync("c.txt", (c) => {
console.log("Contents: " + a + b + c);
}, function(error) {
console.log("Error: " + error);
});
}, function(error) {
console.log("Error: " + error);
});
}, function(error) {
console.log("Error: " + error);
});
```
```javascript
downloadAsync("a.txt").then(() => {
return downloadAsync("b.txt");
}).then((b) => {
return downloadAsync("c.txt");
}).then((c) => {
console.log("Contents: " + a + b + c);
}).catch((error) => {
console.log("Error: " + error);
});
```
```javascript
var myPromise = new Promise((resolve, reject) => {
... //异步操作
if( success ) {
resolve(value);
} else {
reject(error);
}
});
```
```javascript
var myPromise = new Promise((resolve, reject) => {
resolve(1);
});
myPromise.then((value) => {
console.log("第" + value + "一次异步操作成功"); //第1次异步操作成功
return value+1;
}).then(function(value) {
console.log("第" + value + "一次异步操作成功"); //第2次异步操作成功
});
```
# Generator
Promise异步编程,可以很好地回避回调地狱。但Promise的问题是,不管什么样的异步操作,被Promise一包装,看上去都是一堆then,语义方面还不够清晰。因此更好的异步编程解决方案是ES6的Generator。
```javascript
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
console.log(g); // Generator {}
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 3, done: true }
console.log(g.next()); // { value: undefined, done: true }
```
调用Generator只会得到一个遍历器对象,仅此而已。并不会执行Generator函数。上例中var g = gen();,变量g是一个遍历器对象,即一个指向内部状态的指针对象,用于之后遍历yield定义的内部状态。
有了遍历器对象g之后,就可以使用next方法使指针依次移向下一个状态,即让函数从开头或上一次暂停的地方开始执行,执行到下一个yield或return语句为止。虽然yeild和next本质上是遍历器对象和操作指针,但你使用时可以将它们简单地理解为:
>Generator是分段执行的函数。yeild是暂停的标记。next用于继续执行。
## 自动遍历
```javascript
function* numbers () {
yield 1;
yield 2;
return 3;
yield 4;
}
//for...of
var gen1 = numbers();
for (let n of gen1) {
console.log(n); //1 2
}
//Array.from
console.log(Array.from(numbers())); // [1, 2]
//扩展运算符(…)
console.log([...numbers()]); // [1, 2]
//解构赋值
let [x, y] = numbers();
console.log(x); //1
console.log(y); //2
```
## 应用
```javascript
function delay(time, callback){
setTimeout(function(){
callback("sleep "+time);
},time);
}
delay(1000,function(msg){
console.log(msg);
delay(2000,function(msg){
console.log(msg);
});
});
//1秒后打印出:sleep 1000
//再过2秒打印出:sleep 2000
```
```javascript
function delay(time, callback){
setTimeout(function(){
callback("sleep "+time);
},time);
}
function run(genFunc) {
var g = genFunc(resume);
function resume(value) {
g.next(value);
}
g.next();
}
run(function* delayedMsg(resume) {
console.log(yield delay(1000, resume));
console.log(yield delay(2000, resume));
});
//1秒后打印出:sleep 1000
//再过2秒打印出:sleep 2000
```
# async/await
async函数就是Generator函数的语法糖,使得异步操作的流程更加清晰。
```javascript
function delay(time){
return new Promise(function(resolve) {
setTimeout(function(){
resolve("sleep "+time);
},time);
});
}
async function run() {
console.log(await delay(1000));
console.log(await delay(2000));
}
```
async函数的await命令后面是Promise对象(如果是原始类型的值,会自动将其转成Promise对象并立即将状态设成Resolved,效果等于同步操作)。进一步说,async函数完全可以看作多个异步操作,包装成的一个Promise对象,而await命令就是内部then命令的语法糖。因为await命令后面是Promise对象,需要考虑rejected的情况,毕竟谁也不能断言异步操作中不会出现异常,所以最好把await命令包进try…catch中:
```javascript
async function myAsyncFun() {
try {
await somePromise();
} catch (err) {
console.log(err);
}
}
//另一种写法
async function myAsyncFun() {
await somePromise().catch(function (err) {console.log(err);});
}
```
# 代码规范 - 命名
## 变量名和函数
使用驼峰(camelCase)命名变量和函数名
```typescript
let fooVar: string;
function barFunc(): void { }
```
## 类
使用帕斯卡(PascalCase)命名类名
```typescript
class Foo { }
```
使用驼峰(camelCase)命名类成员变量和方法
```typescript
class Foo {
bar: number;
baz() { }
}
```
## 接口
与类相似
```typescript
interface Foo {
foo: number;
baz(): void;
}
```
## 类型
与接口类似
```typescript
type T = Foo | Bar;
```
## 命名空间
使用帕斯卡(PascalCase)命名
## 枚举
使用帕斯卡(PascalCase)命名枚举类型和成员
```typescript
enum Color {
Red,
Blue
}
```
## 数组
声明数组时使用 ```let foos:Foo[];``` 而不是 ```let foos:Array<Foo>;```,便于阅读。
## 常量
模块级常,采用大写字母加下划线方式,如:
```typescript
export const UNITY_RES_BASE = "res/unity/LayaScene_prefabs/Conventional";
export const BASE_PLAYER_SCALE = 0.4;
export const BASE_WEAPON_SCALE = 0.6;
```
函数内部自用常量,同变量使用驼峰(camelCase)命名。(目的:防止自己不小心改变指向造成bug)
# 代码风格及注意事项
## 类型
能声明出类型的都要使用明确的类型,除非极特殊情况用`any`
工具中特殊的函数,如`function shallowCompare(obj1: any, obj2: any)`,否则能用函数优先考虑泛型。
## 修饰符
变量使用`let`声明,不使用`var`
不允许改变指向的变量用`const`;只读类型用`Readonly`/`DeepReadonly`
对于系统预定义常量对象使用`const``Readonly`结合,如:
```typescript
export const FLICK_ARR: Readonly<string[]> = ["break", "freeze"];
```
## 风格
1. 使用箭头函数代替匿名函数表达式。
2. 只在需要的时候才把箭头函数的参数括起来。比如
```typescript
x => x + x
(x, y) => x + y
<T>(x: T, y: T) => x === y
```
3. 总是使用`{}`把循环体和条件语句括起来。
4. 每个变量声明语句只声明一个变量。不要`let x = 1, y = 2;`,而是`let x = 1; let y = 2;`
5. 回调函数总是把参数和返回值函数声明出来,不要使用`Function`
6. 代码使用vs code工具格式化。
7. 一行过长的时候,要拆成多行。
8. 一个函数过长,考虑将其中某些代码块抽成函数。
9. 类的成员变量和函数优先考虑private,其次protected,谨慎public。
## 函数里的this
尽量不使用`bind`函数,因ts无法识别里面的`this`。使用箭头函数绑定this。
### this和箭头函数
JavaScript里,this的值在函数被调用的时候才会指定。 这是个既强大又灵活的特点,但是你需要花点时间弄清楚函数调用的上下文是什么。 但众所周知,这不是一件很简单的事,尤其是在返回一个函数或将函数当做参数传递的时候。
```typescript
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);
return {suit: this.suits[pickedSuit], card: pickedCard % 13};
}
}
}
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log("card: " + pickedCard.card + " of " + pickedCard.suit);
```
为了解决这个问题,我们可以在函数被返回时就绑好正确的this。 这样的话,无论之后怎么使用它,都会引用绑定的‘deck’对象。 我们需要改变函数表达式来使用ECMAScript 6箭头语法。 箭头函数能保存函数创建时的this值,而不是调用时的值:
```typescript
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() {
// NOTE: the line below is now an arrow function, allowing us to capture 'this' right here
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return {suit: this.suits[pickedSuit], card: pickedCard % 13};
}
}
}
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log("card: " + pickedCard.card + " of " + pickedCard.suit);
```
不幸的是,`this.suits[pickedSuit]`的类型依旧为`any`。 这是因为`this`来自对象字面量里的函数表达式。 修改的方法是,提供一个显式的`this`参数。 `this`参数是个假的参数,它出现在参数列表的最前面
```typescript
interface Card {
suit: string;
card: number;
}
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
// NOTE: The function now explicitly specifies that its callee must be of type Deck
createCardPicker: function(this: Deck) {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return {suit: this.suits[pickedSuit], card: pickedCard % 13};
}
}
}
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log("card: " + pickedCard.card + " of " + pickedCard.suit);
```
### 回调函数里的this
当你将一个函数传递到某个库函数里在稍后被调用时,你可能也见到过回调函数里的this会报错。 因为当回调函数被调用时,它会被当成一个普通函数调用,this将为undefined。 稍做改动,你就可以通过this参数来避免错误。
```typescript
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
```
```typescript
class Handler {
info: string;
onClickBad(this: Handler, e: Event) {
// oops, used this here. using this callback would crash at runtime
this.info = e.message;
}
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // error!
```
```typescript
class Handler {
info: string;
onClickGood(this: void, e: Event) {
// can't use this here because it's of type void!
console.log('clicked!');
}
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood);
```
```typescript
class Handler {
info: string;
onClickGood = (e: Event) => { this.info = e.message }
}
```
## 事件机制
```typescript
function onMouseDown() { }
element.on("mousedown", onMouseDown);
element.off("mousedown", onMouseDown);
element.emit("mousedown", mouseEvent);
```
```typescript
const emitter = new Emitter();
export namespace CameraEvents {
export const CHENG_STATE = emitter.createEvent<(state: ECameraState) => void>();
export const SET_OFFSET = emitter.createEvent<(factor: number, rotateX: number, position: Laya.Vector3) => void>();
export const SET_FACTOR = emitter.createEvent<(factor: number) => void>();
}
class XXX {
private disposable = new CompositeDisposable;
regiterEvents() {
this.disposable.add(CameraEvents.CHENG_STATE.on(() => {}));
this.disposable.add(CameraEvents.SET_OFFSET.on(() => {}));
}
unregisterEvents() {
this.disposable.dispose();
}
setCameraFactor() {
CameraEvents.SET_FACTOR.emit(2);
}
}
```
## 模块循环依赖
如果只是作为类型引用,严格来说不算依赖。
设计上应单向依赖,避免循环依赖。
- 使用事件机制解耦
- 耦合太深的写到一个模块
- 类型/模型单独放到模块中,不依赖任何其它模块
# Map & Set
## 使用索引类型
```typescript
let userInfo: {[index:string]: string} = {};
userInfo["name"] = "typescript";
userInfo["age"] = "14";
```
## Map类型
```typescript
let nameScore = new Map<string, number>([["Michael", 95], ["Bob", 75], ["Tracy", 85]]);
nameScore.get("Michael"); // 95
nameScore.has("Bob"); // 判断是否存在
nameScore.set("Adam", 70); // 添加新的
nameScore.set("Bob", 63); // 修改
nameScore.delete("Tracy"); // 删除
// 遍历
for (let item of nameScore) {
console.log(`name: ${item[0]}, score: ${item[1]}`);
}
```
## Set类型
```typescript
let s = new Set<number>([1, 2, 3]);
s.add(4); // 1, 2, 3, 4
s.add(2); // 1, 2, 3, 4
s.delete(3); // 1, 2, 4
```
# 泛型
```typescript
function echo(arg: any): any {
return arg;
}
```
```typescript
function echo<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("myString");
let output2 = identity("myString"); // type of output will be 'string'
```
## 泛型类
```typescript
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; };
```
## 泛型约束
```typescript
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
```
## 在泛型约束中使用类型参数
```typescript
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.
```
## 在泛型里使用类类型
```typescript
class BeeKeeper {
hasMask: boolean;
}
class ZooKeeper {
nametag: string;
}
class Animal {
numLegs: number;
}
class Bee extends Animal {
keeper: BeeKeeper;
}
class Lion extends Animal {
keeper: ZooKeeper;
}
function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}
createInstance(Lion).keeper.nametag; // typechecks!
createInstance(Bee).keeper.hasMask; // typechecks!
```
# 高级类型
## 交叉类型
交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。 例如,`Person & Serializable & Loggable`同时是`Person``Serializable``Loggable`。 就是说这个类型的对象同时拥有了这三种类型的成员。
## 联合类型
联合类型与交叉类型很有关联,但是使用上却完全不同。 偶尔你会遇到这种情况,一个代码库希望传入`number``string`类型的参数。 例如下面的函数:
```typescript
/**
* Takes a string and adds "padding" to the left.
* If 'padding' is a string, then 'padding' is appended to the left side.
* If 'padding' is a number, then that number of spaces is added to the left side.
*/
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
padLeft("Hello world", 4); // returns " Hello world"
```
如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员。
```typescript
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
function getSmallPet(): Fish | Bird {
// ...
}
let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim(); // errors
if ((<Fish>pet).swim) {
(<Fish>pet).swim(); // okay
}
```
## 类型保护
```typescript
function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}
if (isFish(pet)) {
pet.swim();
}
else {
pet.fly();
}
```
### typeof类型保护
`typeof`类型保护只有两种形式能被识别:`typeof v === "typename"``typeof v !== "typename"``"typename"`必须是`"number"``"string"``"boolean"``"symbol"`。 但是TypeScript并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。
```typescript
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
```
### instanceof类型保护
```typescript
interface Padder {
getPaddingString(): string
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) { }
getPaddingString() {
return Array(this.numSpaces + 1).join(" ");
}
}
class StringPadder implements Padder {
constructor(private value: string) { }
getPaddingString() {
return this.value;
}
}
function getRandomPadder() {
return Math.random() < 0.5 ?
new SpaceRepeatingPadder(4) :
new StringPadder(" ");
}
// 类型为SpaceRepeatingPadder | StringPadder
let padder: Padder = getRandomPadder();
if (padder instanceof SpaceRepeatingPadder) {
padder; // 类型细化为'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
padder; // 类型细化为'StringPadder'
}
```
## 类型别名
```typescript
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
}
else {
return n();
}
}
```
```typescript
type Alias = { num: number }
interface Interface {
num: number;
}
declare function aliased(arg: Alias): Alias;
declare function interfaced(arg: Interface): Interface;
```
类型别名不能被`extends``implements`(自己也不能`extends``implements`其它类型)。我们应该尽量去使用接口代替类型别名。如果你无法通过接口来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用类型别名。
# 模块
从ECMAScript 2015开始,JavaScript引入了模块的概念。TypeScript也沿用这个概念。
模块在其自身的作用域里执行,而不是在全局作用域里;这意味着定义在一个模块里的变量,函数,类等等在模块外部是不可见的,除非你明确地使用export形式之一导出它们。 相反,如果想使用其它模块导出的变量,函数,类,接口等的时候,你必须要导入它们,可以使用import形式之一。
模块是自声明的;两个模块之间的关系是通过在文件级别上使用imports和exports建立的。
## 导出
任何声明(比如变量,函数,类,类型别名或接口)都能够通过添加export关键字来导出。
ZipCodeValidator.ts
```typescript
export interface StringValidator {
isAcceptable(s: string): boolean;
}
export const numberRegexp = /^[0-9]+$/;
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
```
重新导出
ParseIntBasedZipCodeValidator.ts
```typescript
export class ParseIntBasedZipCodeValidator {
isAcceptable(s: string) {
return s.length === 5 && parseInt(s).toString() === s;
}
}
// 导出原先的验证器但做了重命名
export {ZipCodeValidator as RegExpBasedZipCodeValidator} from "./ZipCodeValidator";
```
AllValidators.ts
```typescript
export * from "./StringValidator"; // exports interface StringValidator
export * from "./LettersOnlyValidator"; // exports class LettersOnlyValidator
export * from "./ZipCodeValidator"; // exports class ZipCodeValidator
```
## 导入
```typescript
import { ZipCodeValidator } from "./ZipCodeValidator";
let myValidator = new ZipCodeValidator();
```
导入重命名
```typescript
import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";
let myValidator = new ZCV();
```
将整个模块导入到一个变量
```typescript
import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();
```
## 默认导出和导入
ZipCodeValidator.ts
```typescript
export default class ZipCodeValidator {
static numberRegexp = /^[0-9]+$/;
isAcceptable(s: string) {
return s.length === 5 && ZipCodeValidator.numberRegexp.test(s);
}
}
```
```typescript
import validator from "./ZipCodeValidator";
let myValidator = new validator();
```
## JavaScript模块
- Node.js(CommonJS)
- Require.js(AMD)
- isomorphic (UMD)
- SystemJS
- ECMAScript 2015 native modules
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!