1. ts 是 js 的超集(功能更多)
2. ts 编写完要翻译成 js 来运行
3. ts 具有强类型语言的优点,又有脚本语言免编译见效快的优点(更易懂)
4. ts 支持接近完美的代码提示
5. ts 可以重构(适合大型项目)
6. ts 符合 ES6 标准(未来趋势)
7. 编译代码 -- npm install -g typescript , tsc xx.ts
8. 类型注解 -- 一种轻量级的为函数或变量添加约束的方式
// 希望 greeter函数接收一个字符串参数
function greeter(person: string) {
return "Hello, " + person;
}
9. 接口 -- 用来描述一个拥有某些类型字段的对象( 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约并让代码看起来更好理解)
// 属性接口
interface FullName{
firstName:string; //注意;结束
secondName:string;
}
function printName(name:FullName){
// 必须传入对象 firstName secondName
console.log(name.firstName+'--'+name.secondName);
}
// printName('1213'); //错误
var obj={ /*传入的参数必须包含 firstName secondName*/
firstName:'张',
secondName:'三'
};
printName(obj)
// 可选接口
interface FullName{
firstName:string;
secondName?:string; // 属性可选(可传可不传)
}
function getName(name:FullName){
console.log(name)
}
getName({
firstName:'firstName'
})
// 函数类型接口:描述函数类型,给接口定义一个调用签名,它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型!
interface SomeInterface {
(arg1: string, arg2: string): boolean;
}
let someFunc: SomeInterface
someFunc = function (arg1: string, arg2: string) {
const res = arg1.search(arg2)
return res > -1;
}
console.log(someFunc('weast','east')); // true
// 对于函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配
let someFunc2: SomeInterface;
someFunc2 = function (x: string, y: string): boolean {
const res = x.search(y);
return res > -1;
}
console.log(someFunc2('weast', 'east')); // true
// 可索引接口 数组,对象(不常用)
interface UserArr{
//定义索引key为number类型,索引值为string类型
[index:number]:string
}
var arr1:UserArr;
arr1=["aa","bb","cc"];
var arr2: UserArr
arr2={1:"hello",2:"world"};
console.log(arr1);
console.log(arr2);
// 类类型接口 (对类的规范 ,这种类型的接口在传统面向对象语言中最为常见)
// 这种接口与抽象类比较相似,但是接口只能含有为抽象方法、成员属性。
实现类中必须实现接口中所有的抽象方法和成员属性。
interface Animal{
name:string;
eat(s:string):string;
}
//实现接口使用implements关键字,继承 使用extends关键字
//狗类
class Dog implements Animal{
name:string;
constructor(name:string){
this.name=name;
}
//实现接口中抽象方法
eat(s){
return this.name+"吃肉:"+s;
}
}
//猫类
class Cat implements Animal{
name:string;
constructor(name:string){
this.name=name;
}
//实现接口中抽象方法
eat(s){
return this.name+"吃鱼:"+s;
}
}
var dog=new Dog("tom");
var cat=new Cat("kitty");
console.log(dog.eat("五花肉"));
console.log(cat.eat("乌鳢鱼"));
// 接口扩展:接口可以继承接口
interface Animal{
eat():void;
}
interface Person extends Animal{
work():void;
}
class Programmer{
public name:string;
constructor(name:string){
this.name=name;
}
coding(code:string){
console.log(this.name+code)
}
}
class Web extends Programmer implements Person{
constructor(name:string){
super(name)
}
eat(){
console.log(this.name+'喜欢吃馒头')
}
work(){
console.log(this.name+'写代码');
}
}
var w=new Web('小李');
// w.eat();
w.coding('写ts代码');
3. 类 -- 只是 js 里常用的基于原型面向对象编程的简写
// 注意 -- 类和接口可以一起共作; 在构造函数的参数上使用public等同于创建了同名的成员变量。
class Student {
fullName: string;
constructor(public firstName, public middleInitial, public lastName) {
this.fullName = firstName + " " + middleInitial + " " + lastName;
}
}
interface Person {
firstName: string;
lastName: string;
}
function greeter(person : Person) {
return "Hello, " + person.firstName + " " + person.lastName;
}
let user = new Student("Jane", "M.", "User");
document.body.innerHTML = greeter(user);
// 属性的get和set访问器, 存取器,也就是get,set方法
// 只带有 get不带有 set的存取器自动被推断为 readonly
class Person {
constructor() {
}
private _name: string;
public get name() {
return this._name;
}
public set name(name: string) {
this._name = name;
}
}
let person = new Person();
// person._name = "apple"; // 无法访问到_name变量
person.name = "apple";
console.log(person.name); // 输出 apple
// 静态属性(即是通过类型而不是实例就可以访问的属性)
class User {
// 通过static关键字可以声明类型的静态属性
static sex_type = ['male', 'female']; // 静态属性
name: string;
sex: string;
constructor(_name: string) {
this.name = _name;
}
sayHello(): string {
return `Hello,${this.name}!`;
}
}
let user = new User('John Reese');
user.name = 'Root';
user.sex = User.sex_type[1]; // 类成员的静态属性我们可以直接调用
// 类的继承
// 基类
class Animal {
name: string;
constructor(theName: string) {
this.name = theName;
}
eat() {
console.log(`${this.name} 吃食物。`);
}
}
// 子类继承基类
class Dog extends Animal {
constructor(theName: string) {
super(theName);
}
eat() {
super.eat();
console.log('并且吃的是狗粮。');
}
}
class People extends Animal {
constructor(theName: string) {
super(theName);
}
// 子类重写基类方法
eat() {
console.log(`${this.name} 拒绝吃狗粮。`);
}
}
let animal = new Animal('动物');
animal.eat();
let dog: Animal;
dog = new Dog('狗');
dog.eat();
let people: Animal;
people = new People('人类');
people.eat();
// 抽象类 abstract 修饰, 里面可以没有抽象方法。但有抽象方法(abstract method)的类必须声明为抽象类(abstract class)
abstract class Animal{
public name:string;
constructor(name:string){
this.name=name;
}
//抽象方法 ,不包含具体实现,要求子类中必须实现此方法
abstract eat():any;
//非抽象方法,无需要求子类实现、重写
run(){
console.log('非抽象方法,不要子类实现、重写');
}
}
class Dog extends Animal{
//子类中必须实现父类抽象方法,否则ts编译报错
eat(){
return this.name+"吃肉";
}
}
class Cat extends Animal{
//子类中必须实现父类抽象方法,否则ts编译报错
eat(){
return this.name+"吃鱼";
}
}
var dog =new Dog("tom");
var cat=new Cat("kitty");
console.log(dog.eat());
console.log(cat.eat());
// 通过abstract关键字声明抽象类和抽象方法,子类继承抽象类后,需要实现抽象方法。同样的,抽象类不能被实例化
//多态 ,一种事物的不同表现形态。如下 先声明变量f是Animal类型,具体是Dog还是Cat,在 new 对象时才知道
//如果是 Dog,则f.eat()调用的是Dog类中的eat方法;如果是Cat,则f.eat()调用的是Cat类中的eat方法,这就是多态!!!
var f:Animal;//声明变量为Animal类型
//f=new Dog("sunny");
f=new Cat("sunny");
console.log(f.eat());
===================修饰符===================
1. 默认为 public
2. 当成员被标记为 private 时,它就不能在声明它的类的外部访问
class Animal {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
let a = new Animal('Cat').name; //错误,‘name’是私有的
3. protected 和 private 类似,但是,protected 成员在派生类中可以访问
class Animal {
protected name: string;
constructor(theName: string) {
this.name = theName;
}
}
class Rhino extends Animal {
constructor() {
super('Rhino');
}
getName() {
console.log(this.name) //此处的name就是Animal类中的name
}
}
4. 构造函数也可以被标记为 protected。这意味着这个类不能再包含它的类外被实例化,但是能被继承,也就是可以在派生类中被 super 执行
class Animal {
protected name: string;
protected constructor(theName: string) {
this.name = theName;
}
}
//Rhino能够继承Animal
class Rhino extends Person {
private food: string;
constructor(name: string, food: string) {
super(name);
this.food = food;
}
getFood() {
return `${this.name} love this ${this.food}`
}
}
let rhino = new Rhino('zhao', 'banana');
===================基础类型===================
1. 基础类型 -- 布尔值,数字,字符串,数组,元组 Tuple,枚举,Any,Void,Null 和 Undefined,Never,Object,类型断言
2. 字符串的模板字符串用法(被反引号包围( `
),并且以${ expr }
这种形式嵌入表达式)
let name: string = `Gene`;
let age: number = 37;
let sentence: string = `Hello, my name is ${ name }.
I'll be ${ age + 1 } years old next month.`;
3. 数组的定义
// 第一种 -- 在元素类型后面接上 [],表示由此类型元素组成的一个数组
let list: number[] = [1, 2, 3];
// 第二种 -- 使用数组泛型,Array<元素类型>
let list: Array<number> = [1, 2, 3];
4. 元组 Tuple(表示一个已知元素数量和类型的数组,各元素的类型不必相同)
let x: [string, number];
x = ['hello', 10]; // OK
x = [10, 'hello']; // Error
5. 枚举(一个命名元素的集合,使用枚举类型可以为一组数值赋予友好的名字)
// 默认情况下,枚举是基于 0 的,也就是说第一个值是 0,后面的值依次递增
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2];
console.log(colorName); // 显示'Green'因为上面代码里它的值是2
最佳实践 -- 首个枚举项值设置为 1;尽量不要为枚举项手动设置值;不要为枚举项设置字符串数据类型;
6. Any(为不清楚类型的变量指定一个类型)
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean
// 当你只知道一部分数据的类型时,any类型也是有用的。 比如,你有一个数组,它包含了不同的类型的数据:
let list: any[] = [1, true, "free"];
list[1] = 100;
7. null和undefined (默认情况下null和undefined是所有类型的子类型,可以把null和undefined赋值给number类型的变量)
let x: number;
x = 1;
x = null; //正确
// 启用 --strictNullChecks
let y: number;
y = 1;
y = null; //错误
8. void类型 (使用void表示没有任何类型,例如一个函数没有返回值,意味着返回void)
function hello(): void{
alert('hello Angular');
}
9. never类型(其他类型(包括null和undefined)的子类型,代表从不会出现的值)
// never类型的变量只能被never类型所赋值,在函数中通常表示为抛出异常或无法执行到终止点
let x: never;
let y: number;
//报错
x = 123;
//正确
y = x;
10. 类型断言(好比其它语言里的类型转换,但是不进行特殊的数据检查和解构)
// “尖括号”语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
// as语法 (推荐)
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
==================函数====================
1. 函数定义
/*普通函数*/
function add(a:number,b:number):number{
return a+b;
}
add(1,2);
/*匿名函数*/
var add1=function (a:number,b:number):number {
return a+b;
}
add1(3,4);
2. 默认参数,可选参数
// 可选参数要放在最后面
function run5(age:number,name?:string):string{
if(name){
return `${name}--${age}`
}else{
return `${age}`
}
}
run5(20)
run5(20,'aa')
run5('aa',20)
3. 剩余参数
function sum(a:number,b:number,c:number):number{
return a+b+c;
}
alert(sum(1,2,3));
function sum2(...result:number[]):number{
var sum =0;
for(var i =0;i<result.length;i++){
sum+=result[i]
}
return sum;
}
alert(sum2(1,2,3,4));
4. 函数重载
function getInfo(name: string): string;
function getInfo(age: number): string;
function getInfo(str: any): any {
if (typeof str === 'string') {
return '我叫' + str;
} else {
return '我的年龄是' + str;
}
}
alert(getInfo('aa'))
alert(getInfo(11))
======================================
1. 泛型 -- 可以支持不特定的数据类型
2. 要求 -- 传入的参数和返回的参数一致 (类型变量)
// 不同于使用 any,它不会丢失信息
// T表示泛型,具体什么类型是调用这个方法的时候决定的
function identity<T>(arg: T): T {
return arg;
}
// 我们给identity添加了类型变量T。 T帮助我们捕获用户传入的类型(比如:number),之后我们就可以使用这个类型。 之后我们再次使用了 T当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。 这允许我们跟踪函数里使用的类型的信息。
// 第一种是,传入所有的参数,包含类型参数
let output = identity<string>("myString"); // type of output will be 'string'
// 第二种方法更普遍。利用了类型推论 -- 即编译器会根据传入的参数自动地帮助我们确定T的类型:
let output = identity("myString"); // type of output will be 'string'
3. 泛型变量
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
// 泛型函数loggingIdentity,接收类型参数T和参数arg,它是个元素类型是T的数组,并返回元素类型是T的数组
// 也可以这么实现
function loggingIdentity<T>(arg: Array<T>): Array<T> {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
4. 泛型类型
// 泛型函数的类型与非泛型函数的类型没什么不同,只是有一个类型参数在最前面,像函数声明一样:
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <T>(arg: T) => T = identity;
5. 泛型类
// 泛型类看上去与泛型接口差不多。 泛型类使用( <>)括起泛型类型,跟在类名后面。
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; };
// 类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型
6. 泛型约束
// 定义一个接口来描述约束条件。 创建一个包含 .length属性的接口,使用这个接口和extends关键字来实现约束:
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;
}
loggingIdentity(3); // Error, number doesn't have a .length property
// 我们需要传入符合约束类型的值,必须包含必须的属性:
loggingIdentity({length: 10, value: 3});
===================设计模式===================
1. 简单工厂模式 -- 把同类型产品对象的创建集中到一起,通过工厂来创建,添加新产品时只需加到工厂里即可,也就是把变化封装起来,同时还可以隐藏产品细节(要new多个同一类型对象时可以考虑使用简单工厂)
// 注意:对象需要继承自同一个接口。
enum GunType{
AK,
M4A1,
}
interface Shootable{
shoot();
}
abstract class Gun implements Shootable{ // 抽象产品 - 枪
abstract shoot();
}
class AK47 extends Gun{ //具体产品 - AK47
shoot(){
console.log('ak47 shoot.');
}
}
class M4A1 extends Gun{ //具体产品 - M4A1
shoot(){
console.log('m4a1 shoot.');
}
}
// 枪工厂
class GunFactory{
static createGun(type: GunType): Gun{
switch(type){
case GunType.AK:
return new AK47();
case GunType.M4A1:
return new M4A1();
default:
throw Error('not support this gun yet');
}
}
}
GunFactory.createGun(GunType.AK).shoot();
GunFactory.createGun(GunType.M4A1).shoot();
//输出
ak47 shoot.
m4a1 shoot.
// GunFactory工厂 根据类型来创建不同的产品,使用的时候只需要引入这个工厂和接口即可。
// 这样就把变化封装到了工厂中,如果以后要支持狙击枪,只需要加个实现Gun接口的Sniper类就可以了。
2. 工厂方法模式 -- 把工厂抽象出来,让子工厂来决定怎么生产产品, 每个产品都由自己的工厂生产(当产品对象需要进行不同的加工时可以考虑工厂方法)
// 这不是所谓的简单工厂的升级版,两者有不同的应用场景
interface Shootable{
shoot();
}
abstract class Gun implements Shootable{ // 抽象产品 - 枪
abstract shoot();
}
class AK47 extends Gun{ //具体产品 - AK47
shoot(){
console.log('ak47 shoot.');
}
}
class M4A1 extends Gun{ //具体产品 - M4A1
shoot(){
console.log('m4a1 shoot.');
}
}
abstract class GunFactory{ //抽象枪工厂
abstract create(): Gun;
}
class AK47Factory extends GunFactory{ //Ak47工厂
create(): Gun{
let gun = new AK47(); // 生产Ak47
console.log('produce ak47 gun.');
this.clean(gun); // 清理工作
this.applyTungOil(gun);// Ak47是木头枪托,涂上桐油
return gun;
}
private clean(gun: Gun){
//清洗
console.log('clean gun.');
}
private applyTungOil(gun: Gun){
//涂上桐油
console.log('apply tung oil.');
}
}
class M4A1Factory extends GunFactory{ //M4A1工厂
create(): Gun{
let gun = new M4A1(); // 生产M4A1
console.log('produce m4a1 gun.');
this.clean(gun); // 清理工作
this.sprayPaint(gun); // M4是全金属,喷上漆
return gun;
}
private clean(gun: Gun){
//清洗
console.log('clean gun.');
}
private sprayPaint(gun: Gun){
//喷漆
console.log('spray paint.');
}
}
let ak47 = new AK47Factory().create();
ak47.shoot();
let m4a1 = new M4A1Factory().create();
m4a1.shoot();
//output
produce ak47 gun.
clean gun.
apply tung oil.
ak47 shoot.
produce m4a1 gun.
clean gun.
spray paint.
m4a1 shoot.
/*
可以看到Ak47和M4A1在生产出来后的处理不一样,Ak需要涂桐油,M4需要喷漆,用简单工厂就比较难做到,所以就每个产品都弄个工厂来封装各自己的生产过程。
另外的好处是当加入其他枪比如沙漠之鹰时,再加一个产品和产品工厂就好了,并不需要改变现有代码,算是做到了遵守开闭原则。
缺点也明显,增加一个产品就需要多加两个类,增加了代码复杂性
*/
3. 抽象工厂模式 -- 同样隐藏了具体产品的生产,不过生产的是多种类产品(当需要生产的是一个产品族,并且产品之间或多或少有关联时可以考虑抽象工厂方法)
// 和工厂方法的区别,工厂方法是一个产品, 而抽象工厂是产品族,线和面的区别
// 继续用枪,外加子弹
interface Shootable{
shoot();
}
abstract class Gun implements Shootable{ // 抽象产品 - 枪
private _bullet: Bullet;
addBullet(bullet: Bullet){
this._bullet = bullet;
}
abstract shoot();
}
class AK47 extends Gun{ //具体产品 - AK47
shoot(){
console.log(`ak47 shoot with ${this._bullet}.`);
}
}
class M4A1 extends Gun{ //具体产品 - M4A1
shoot(){
console.log(`m4a1 shoot with ${this._bullet}.`);
}
}
abstract class Bullet{ // 抽象子弹
abstract name: string;
}
class AkBullet{ // AK 子弹
name: string = 'ak bullet';
}
class M4Bullet{ // m4a1 子弹
name: string = 'm4a1 bullet';
}
abstract class ArmFactory{ //抽象军工厂
abstract createGun(): Gun;
abstract createBullet(): Bullet;
}
class AK47Factory extends ArmFactory{
createGun(): Gun{
let gun = new AK47(); // 生产Ak47
console.log('produce ak47 gun.');
this.clean(gun); // 清理工作
this.applyTungOil(gun);// Ak47是木头枪托,涂上桐油
return gun;
}
private clean(gun: Gun){
//清洗
console.log('clean gun.');
}
private applyTungOil(gun: Gun){
//涂上桐油
console.log('apply tung oil.');
}
createBullet(): Bullet{
return new AkBullet();
}
}
class M4A1Factory extends ArmFactory{ //M4A1工厂
createGun(): Gun{
let gun = new M4A1(); // 生产M4A1
console.log('produce m4a1 gun.');
this.clean(gun); // 清理工作
this.sprayPaint(gun); // M4是全金属,喷上漆
return gun;
}
private clean(gun: Gun){
//清洗
console.log('clean gun.');
}
private sprayPaint(gun: Gun){
//喷漆
console.log('spray paint.');
}
createBullet(): Bullet{
return new M4Bullet();
}
}
//使用
function shoot(gun: Gun, bullet: Bullet) // 使用生产的枪和子弹
{
gun.addBullet(bullet);
gun.shoot();
}
let akFactory = new AK47Factory();
shoot(akFactory.createGun(), akFactory.createBullet());
let m4a1Factory = new M4A1Factory();
shoot(m4a1Factory.createGun(), m4a1Factory.createBullet());
//输出
produce ak47 gun.
clean gun.
apply tung oil.
add bullet: ak bullet
ak47 shoot with ak bullet.
produce m4a1 gun.
clean gun.
spray paint.
add bullet: m4a1 bullet
m4a1 shoot with m4a1 bullet.
======================================
1. ccc中使用 ts 的属性类型声明
const LEVEL = cc.Enum({EASY:1,HARD:2});
@ccclass
export class Game extends cc.Component {
// Label
@property(cc.Label)
labelVar: cc.Label = null;
@property
text:string = "hello";
// 整型
@property(cc.Integer)
intVar: number = 0;
// 浮点型
@property(cc.Float)
floatVar: number = 0;
// 布尔型
@property(cc.Boolean)
boolVar: boolean = false;
// 节点
@property(cc.Node)
nodeVar: cc.Node = null;
// 节点数组
@property([cc.Node])
nodeArrVar: Array<cc.Node> = [];
// 预制体
@property(cc.Prefab)
prefabVar: cc.Prefab = null;
// 点
@property(cc.Vec2)
vec2Var: cc.Vec2 = cc.v2();
// 自定义节点
@property(Player)
palyerVar: Player = null;
// 重点来了,自定义枚举
/**
* 全局变量
* const LEVEL = cc.Enum({EASY:1,HARD:2});
*/
@property({
type:LEVEL
})
enumVa = LEVEL.EASY;
}
======================================