智能合约开发——TypeScript 基础(全)

2022-10-28 16:50:42 浏览数 (1)

TS 准备

首先我们准备一个目录,使用 dos 进入到某目录,当然你直接 vs 打开终端执行也是没有问题的:

执行以下命令安装 typescrip(不用进入目录,直接安装即可):

代码语言:javascript复制
npm install -g typescript

我是已经安装过了:

接着你可以初始化项目(这里需要进入文件夹了,毕竟是你的项目目录对吧,初始化项目就是以当前目录默认初始化):

代码语言:javascript复制
tsc --init

此时你的目录中会出现一个 tsconfig 文件:

里面存储的是一些 ts 的config ,暂时不用理,接下来就可以开始 ts 的编程了。

一、TS 编译

安装好 nodejs 之后,新建一个 01.ts 文件:

编写代码:

代码语言:javascript复制
console.log("HI") 

由于 ts 最终将会编译生成 js,你在 ts 中直接写 js 是没有问题的(并且ts不是一个全新的语言,是基于 js 的),在终端中输入 node 01.ts:

此时将会输出 HI~。

二、变量定义及编译

在 ts 变量定中需要指定的这个变量类型,这个是跟 js 大有不同,又或者说这该指定类型贯穿了整个 ts。

更改 01 代码:

代码语言:javascript复制
let age: number = 18;
console.log(age)

以上代码中定义了一个 age变量,类型为 number,赋值为 18,此时在变量名之后有一个冒号,冒号右侧就是对应的这个变量的类型,最终使用等于号赋值为 18。

此时由于我们私用的是 ts 的语法,你直接使用 node 话会错误,需要使用 tsc-node 命令对 ts 文件进行编译:

若你使用 node 命令就不是编译了,此时将会报错:

使用 node 表示运行 js 文件,而不是 编译 ts 文件。

在创建一个变量的时候,也可以不指定类型,直接给定一个值,这个时候 ts 会完成类型的自动推导(跟go类似),这样就知道这个变量是什么值了,例如:

代码语言:javascript复制
let age = 11;

三、生成 js 文件

在 ts 中,还可以直接使用 tsc 对 ts 编译生成 js 文件:

执行命令后,将会在当前目录下生成一个 js 文件,并且这个 js 文件是对应 ts 的 js 代码:

在此我们一定要注意, ts 你可以理解为是一种强制性的“规范语法”框架,在该“语法框架”下需要严谨的对某些动作进行处理,但最终的本质还是 js。

四、变量、数组

4.1 一般数据类型

ts 中类型有 number、string、boolean、symbol、数组、map 等,在此只介绍常见类型。

以下是对应这些类型的示例:

代码语言:javascript复制
let age: number = 18;
let authorName: string = '1_bit';
let boolVal: boolean = true;

//数组
let strs: string[] = ["1_bit", "blog", "author"];
let strOrNumber: (string | number)[] = ["1_bit", 11, "blog", "author", 22];

以上的示例中,主要看数组的定义,例如 let strs: string[] = ["1_bit", "blog", "author"];,在这段代码中 strs 是数组名,冒号后就是对应的类型约束,在此是 string,而方括号 “ [] ” 则表示这是一个数组,在等于号右侧则是这个数组中的值。

已经理解了基本的数组的 ts 数组,在查看对应的 strOrNumber 数组,在这个代码中,用圆括号包裹了 string 和 number 并且中间使用了“或”运算的 “ | ” 链接,这是表示这个数组中可以存储 string 以及 number 类型,所以在数组中可以看到 string 和 number 类型的数据都存在。

4.2 元组

学过 python 的小伙伴对元组应该很熟悉,在 ts 中的元组跟 python 中的不是很一样,在 ts 中定义的元组你可以理解为固长、固定类型的数组,可以指定某个位置的类型以及指定整个数组的长度,以下为元组示例:

代码语言:javascript复制
let postion: [number, number];
postion = [0.45, 0.50];

console.log(postion)
console.log(postion[0])

以上代码中 let postion: [number, number]; 为定义了一个元组,但是并没有进行初始化,接下来直接给 postion 定义了一个值[0.45, 0.50],随后使用 console.log 对其打印。

在此我们可以看到,数组的定义方式是之直接在变量的冒号后面使用一个类型加方括号“[]”定义一个数组,在元组中则是在方括号内编写对应的数值类型。

运行后结果如下:

4.3 any

在 ts 中不推荐使用 any 类型,你用了不就等于跟原本的 js 没啥区别了吗?若真的有必要使用 any 推荐临时使用,别贯穿项目。

以下是any 的错误示范(语法正确,使用错误):

代码语言:javascript复制
let age: any;
age = "str";
console.log(age)
console.log(typeof age)

结果如下:

五、自定义类型(别名)

在 ts 中,若一个数组的类型经常使用,可以定义一个类型别名,在使用这个类型时即可简短的通过这个别名表示这个类型:

代码语言:javascript复制
type StuT = (string | number)[]
let stus: StuT = ["1_bit", 13, "Xi", 26]

以上示例定义了一个 type 类型,这个类型为 (string | number)[] 表示是一个字符串与数组的数组类型,接着在下一行代码中直接创建了一个 stus 变量,指定为 StuT 类型,并且赋值有字符串和数字的值。

六、函数

6.1 基本函数

ts 语言中的自定义函数跟 go 中的有点相似,例如如下是一个 ts 中的自定义函数:

代码语言:javascript复制
function sayHi(name: string, age: number): string {
    return "Hi "   name   " you "   age   " age";
}
let xiName: string = "Xi";
let xiAge: number = 19;
console.log(sayHi(xiName, xiAge));

在以上示例中 sayHi 是一个自定义函数,跟js 语法一样,使用function 定义一个函数,在 sayHi 函数中有两个参数,一个是 name 还有一个是 age,name 参数是一个 string 类型,age 是一个 number 类型,参数的声明在此也是不同的,使用了 ts 的语法;还有一点不同的是在参数之后使用一个冒号说明了当前函数的返回值为一个 string,在此需要注意,这是 ts 的语法。

接着我创建了两个边路 xiName 以及 xiAge,用此当做参数传入到 sayHi 之中,此时使用 ts-node 命令编译运行我们的 ts 文件:

6.2 箭头函数

函数的编写还有另外一种使用箭头函数的方式:

代码语言:javascript复制
const sayHi = (name: string, age: number): string => {
    return "Hi "   name   " you "   age   " age";
}
let xiName: string = "Xi";
let xiAge: number = 19;
console.log(sayHi(xiName, xiAge));

6.3 箭头函数加强版

函数还有另外一种定义方式,可能大家都不是特别想去学习:

代码语言:javascript复制
const sayHi: (name: string, age: number) => string = (name, age) => {
    return "Hi "   name   " you "   age   " age";
}

其实这个形式可以看成两个部分:

其中前半部分是为这个 sayHi 声明一个类型,这个 sayHi 的类型是 (name: string, age: number) 由于需要指定这个函数返回值,所以使用 => string 表示当前函数返回值为 string。

接着我们再看第二个部分:

这一部分就直接当做函数的内容即可,而 (name, age) 表示这个函数接收两个参数,把之前所说明的 name 和 age 两个变量传入到函数,所以在此处并不用使用类型对其进行约束,在之前已经做好约束了,最后箭头函数右侧则是函数体。

函数返回值若为空则使用 void。

6.4 可选参数

可选参数在 ts 中表示这个参数可传或不传,非必须参数,例如我们现在修改以上示例完成需求“姓名必传、年龄和身高为可选,传入年龄或身高需要对应的对其进行显示”。

若完成这个需求那么必然会有一个函数对某个值进行判断,此时做一个检测的函数:

代码语言:javascript复制
function isUndefine(arg: (number | string | undefined)): boolean {
    let check: boolean = true;
    if (!arg) {
        check = false;
    }
    return check;
}

以上检测 agr 参数,参数因为需要时 number、string、undefined 类型的其中一个,所以在此处设定了这个参数的类型范围,接着有一个 Boolean 的返回值,在函数体中给了一个变量 check 为 Boolean,初始值为 true,只要 arg 不存在那么久 fase 即可,最后返回 check。

接着我们修改 sayHi 函数,修改后调用检测参数的函数,完成内容的拼接:

代码语言:javascript复制
const sayHi: (name: string, age?: number, height?: number) => string = (name, age, height) => {
    let sayStr = 'Hi '   name;
    if (isUndefine(age)) {
        sayStr  = " you "   age   " age";
    }
    if (isUndefine(height)) {
        sayStr  = " you "   age   " age "   "height "   height;
    }
    return sayStr;
}

此时两个 if 判断 age 或者 height 是否存在,随后进行拼接即可,最后返回了sayStr 这个字符串。

在这里我们可以看到,参数 age、height 在参数名后添加了一个问号,这个问号就是表示可选参数。

最终完整代码如下:

代码语言:javascript复制
function isUndefine(arg: (number | string | undefined)): boolean {
    let check: boolean = true;
    if (!arg) {
        check = false;
    }
    return check;
}

const sayHi: (name: string, age?: number, height?: number) => string = (name, age, height) => {
    let sayStr = 'Hi '   name;
    if (isUndefine(age)) {
        sayStr  = " you "   age   " age";
    }
    if (isUndefine(height)) {
        sayStr  = " you "   age   " age "   "height "   height;
    }
    return sayStr;
}
let xiName: string = "Xi";
let xiAge: number = 19;
console.log(sayHi(xiName, xiAge, 170));

最终传入不同的参数将会显示不同的结果:

七、对象

在 ts 中使用对象分为声明和定义,声明就像类型一样进行使用,定义则是赋予这个对象的值:

代码语言:javascript复制
let stu: {
    name: string;
    age: number;
    height: number;
    getName(): string;
    getAge(): number;
    setName(name: string): void;
    setAge(age: number): void;

} = {
    name: 'Xi',
    age: 11,
    height: 170,
    setName(name) {
        this.name = name;
    },
    setAge(age) {
        this.age
    },
    getName() {
        return this.name;
    },
    getAge() {
        return this.age
    },
}

stu.setName("1_bit")
console.log(stu.getName())

以上实例中定义了一个 stu 的对象,其中包含 name、age、height 变量以及 getName 等方法,这些都可以看做是这个对象的“类型”,随后赋值为这些“类型”的初始化,并且设定函数的实现。

在此一定要注意,你既然声明了,那就必须要实现(若删除一个成员变量的初始化),否则会报错:

运行结果如下:

八、类

8.1 类的基本使用

在 ts 中定义一个类方式很简单,使用 class 例如如下示例:

代码语言:javascript复制
class Stu {
    public readonly name: string;
    public age: number;
    private nickname: string = '';
    private readonly color = 'yellow';

    constructor(_name: string, _age: number) {
        this.name = _name;
        this.age = _age;
    }

    public setNickName(_nick: string) {
        this.nickname = _nick;
    }

    public getNickName() {
        return this.nickname;
    }
}

以上示例中使用 class 创建了一个 stu 类,在这个类中定义了几个成员变量,在这几个成员变量中使用了 public、private 对其进行修饰,public 表公开都可以调用,private 私有,自由类内部可以调用,除了这些常规的修饰之外还有一个 readonly,readonly 表示当前字段不允许修改,若你想对其进行改动那么将会报错:

在此还需要注意,这些变量你都需要对其进行初始化,否则将会报错。

在类中还有一个构造方法,ts 是支持构造方法的;在使用成员变量时需要使用 this 对其指向,直接使用变量名将会出错。

ts冷笑话:在 class 中创建“函数”不需要使用 function,因为他是方法。

接着创建一个对象:

代码语言:javascript复制
const XiaoMing = new Stu("XiaoMing", 18);
XiaoMing.setNickName("MM");
console.log(XiaoMing.getNickName());

接着使用 tc 命令编译运行:

在 ts 中类的继承使用 extends:

代码语言:javascript复制
class Stu1 extends Stu {
    public getName(): string {
        return this.name
    }
}

继承后在 Stu1将会拥有 Stu 的成员变量和方法,使用和正常一个对象使用一致:

代码语言:javascript复制
const XiaoMing1 = new Stu1("XiaoM", 18);
console.log(XiaoMing1.getName());

8.2 函数重载

8.3 类的兼容性

以下是一个示例,在 ts 中相同成员变量的类可以互相兼容:

代码语言:javascript复制
class Stu1 {
    name: string;
    height: number;
    constructor() {
        this.name = "xiaoM";
        this.height = 170;
    }
}

class Stu2 {
    name: string;
    height: number;
    constructor() {
        this.name = "xiaoY";
        this.height = 160;
    }
}

const xiaoY: Stu1 = new Stu2()

若此时 Stu2 中多了一个成员变量呢:

此时 Stu1 还是兼容于 Stu2 的,少的可以兼容于多的,若反过来是不可以的:

当然多个方法也没问题:

九、接口

9.1 接口的一般使用

在 ts 中支持接口,使用关键字 interface:

代码语言:javascript复制
interface stuI {
    height: number;
    name: string;
    getName(): string;
}

以上定义了一个接口,若需要某些变量为该接口类型,那么就需要对接口中的变量进行初始化和实现方法:

代码语言:javascript复制
let stu: stuI = {
    height: 170,
    name: "1_bit",
    getName() {
        return this.name;
    }
}

实现方法很简单,就是 let 一个变量后指定接口为类型,随后使用花括号对其中的内容进行初始化。

接口还可以继承,例如如下示例:

代码语言:javascript复制
interface stuI1 extends stuI {
    color: string,
    getHeight(): number
}

以上 stuI1 继承 stuI,并且继承了其中的内容,在 stuI1 接口中可以为接口本身编写 stuI1 的内容。

使用也如下使用示例(需要完全实现接口、父接口的中的内容):

代码语言:javascript复制
let stu1: stuI1 = {
    height: 170,
    name: "1_bit",
    color: "yello",
    getName() {
        return this.name;
    },
    getHeight() {
        return this.height
    }
}

最后调用:

代码语言:javascript复制
console.log(stu.getName());
console.log(stu1.getHeight());

9.2 交叉类型与同名成员变量

交叉类型其实就是多个接口进行合并,如下示例:

代码语言:javascript复制
interface BBB { name: string }
interface CCC { age: number }
interface DDD { height: number }

type BCD = BBB & CCC & DDD
let val: BCD = {
    name: 'ccc',
    age: 123,
    height: 12121
}

交叉类型直接使用 & 对某个类型进行定义即可,之后的变量在使用这个类型的时候就必须实现这些接口中的内容。

若接口中有同名,那么那么变量就是 never 类型,表示不可能,永远不可能。

9.2 交叉类型与同名函数参数

在同名函数中的参数同名但类型不同的情况下可以理解为同时兼容于两个类型,例如:

代码语言:javascript复制
interface CCC {
    age: number;
    getVl(key: number): string
}
interface DDD {
    height: number;
    getVl(key: string): string
}

以上两个接口新增了一个同名方法 getVl,参数同名却参数类型不同,此时就可以当成是 getVl(key: (number | string)),例如如下代码:

代码语言:javascript复制
type BCD = BBB & CCC & DDD
let val: BCD = {
    name: 'ccc',
    age: 123,
    height: 12121,
    getVl(key: (number | string)) {
        console.log(key)
        return "1";
    }
}

val.getVl(233);

十、泛型 后面有时间再补

0 人点赞