代码规范 - 命名
变量名和函数
使用驼峰(camelCase)命名变量和函数名
let fooVar: string;
function barFunc(): void { }
类
使用帕斯卡(PascalCase)命名类名
class Foo { }
使用驼峰(camelCase)命名类成员变量和方法
class Foo {
bar: number;
baz() { }
}
接口
与类相似
interface Foo {
foo: number;
baz(): void;
}
类型
与接口类似
type T = Foo | Bar;
命名空间
使用帕斯卡(PascalCase)命名
枚举
使用帕斯卡(PascalCase)命名枚举类型和成员
enum Color {
Red,
Blue
}
数组
声明数组时使用 let foos:Foo[];
而不是 let foos:Array<Foo>;
,便于阅读。
常量
模块级常,采用大写字母加下划线方式,如:
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
结合,如:
export const FLICK_ARR: Readonly<string[]> = ["break", "freeze"];
风格
- 使用箭头函数代替匿名函数表达式。
- 只在需要的时候才把箭头函数的参数括起来。比如
typescript x => x + x (x, y) => x + y <T>(x: T, y: T) => x === y
- 总是使用
{}
把循环体和条件语句括起来。 - 每个变量声明语句只声明一个变量。不要
let x = 1, y = 2;
,而是let x = 1; let y = 2;
。 - 回调函数总是把参数和返回值函数声明出来,不要使用
Function
。 - 代码使用vs code工具格式化。
- 一行过长的时候,要拆成多行。
- 一个函数过长,考虑将其中某些代码块抽成函数。
- 类的成员变量和函数优先考虑private,其次protected,谨慎public。
函数里的this
尽量不使用bind
函数,因ts无法识别里面的this
。使用箭头函数绑定this。
this和箭头函数
JavaScript里,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);
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值,而不是调用时的值:
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
参数是个假的参数,它出现在参数列表的最前面
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参数来避免错误。
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
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!
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);
class Handler {
info: string;
onClickGood = (e: Event) => { this.info = e.message }
}
事件机制
function onMouseDown() { }
element.on("mousedown", onMouseDown);
element.off("mousedown", onMouseDown);
element.emit("mousedown", mouseEvent);
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);
}
}
模块循环依赖
如果只是作为类型引用,严格来说不算依赖。
设计上应单向依赖,避免循环依赖。
- 使用事件机制解耦
- 耦合太深的写到一个模块
- 类型/模型单独放到模块中,不依赖任何其它模块