typescript 学习笔记

一. 环境安装

使用 npm 全局安装 typescript

npm install -g typescript

安装完成后就可以使用 tsc 命令了

tsc --version

二. Hello world

Hello world 之前需要创建 tsconfig.json, 在其中定义编译时候的 严格类型检查编译选项, 和 Javascript 的版本.

{
    "compilerOptions": {
        "strict": true,
        "target": "ES5"
    }
}

创建 helloworld.ts

const greeting = 'hello world'
console.log(greeting)

编译并运行, 这里会使用当前工作目录的 tsconfig.json . 编译之后会生成 helloworld.js

tsc helloworld.ts
node helloworld.js
# hello world

你也可以指定某个位置 tsconfig.json 进行编译

// Compiles the TypeScript project located at the specified path.
tsc -p .\tsconfig.json

三. Javascript 基础知识

  1. 变量声明
    • var:
      没什么特别的,需要注意的是,var 声明变量的时候如果没有赋值, 那么默认值为 undefined
    • let:
      它和 var 的区别在于,它是有一个块级作用域
    • const:
      javascript 的常量,声明的时候必须赋一个初始值,并且不能再次赋值。同样也是块级作用域
    • 然后什么是块级作用域呢,块级作用域其实就是字面意思。块指的就是代码块,而变量的作用域的含义就是这个变量的可访问区间。 请看下面的例子:
let a = 1
const b = 2
{
        const b = 3
        const c = 4
        console.log(a, b, c)
}
console.log(a, b)
  1. 数据类型

    • Undefined

    • Null

    • Boolean

    • String:
      Javascript 使用 UTF-16 编码来表示一个字符。 UTF-16 以两个字节作为一个编码单元,每个字符使用一个编码单元或两个编码单元来表示。在底层存储中,字符串是由零个或多个16位无符号整数构成的有序序列。在获取字符串长度时,返回的是字符串中包含的编码单元的数量。

    • Number
      Javascript 使用双精度64位浮点数来表示数字, 因此所有数字本质上都是浮点数。Number.MAX_SAFE_INTEGER 是 Number 的最大值

    • Symbol
      每一个 Symbol 的值都是唯一且不可改变的。 Symbol() 函数可以想象成全局唯一标识符生成器, 每次调用都会生成一个完全不同的 Symbol 值。

    • Object:
      对象是属性的集合, Javascript 现在作为函数式编程语言,函数本身也是数据。 所以 Object 的属性只有两种,一种是数据属性, 另一种是存取器属性, 即 getter setter

  2. 字面量

    • Null字面量
      • null
    • 布尔字面量
      • true
      • false
    • 字符串字面量
      • ‘’
    • 数字字面量
      • 二进制字面量 0b 或 0B开头, 只包含数字 0 或 1
      • 八进制字面量 0o 或 0O 开头, 只包含数字 0 至 7
      • 十进制
      • 十六进制以 0x 或 0X 开头,可以包含数字 0 至 9 ,小写 a 至 f 以及 大写 A 至 F
    • 数组字面量 []
    • 对象字面量 {}
    • 模板字面量 ``

四. Javascript 进阶一点的知识

  1. BigInt:

    • BigInt 能表示任意精度的整数,尤其是大于 Number.MAX_SAFE_INTEGER 的数, 这也是引入 BigInt 的原因。
    • 严格模式下 BigInt 和 Number 的值总是不相等
    • 直接将 BigInt 转为 Number 可能会损失精度
  2. 展开运算符 …

    • … 可以将 数组, 对象 还有函数参数进行展开
  3. 解构赋值

    • 数组解构 let [a, b] = [1, 2]
    • 对象结构 let {a, b} = { a: 1, b: 2}
  4. 可选链运算符

    • 如果 obj 或者 fn 的值位 undefined 或 null 那么求值结果为 undefined 否则为调用的结果
    • 可选的静态属性访问 obj?.prop
    • 可选的计算属性访问 obj?.[expr]
    • 可选的函数调用或方法调用 fn?.()
  5. 空值合并运算符

    • 如果左边的值为 undefined 或 null 时返回右边的值, 否则返回左边的值
// 展开运算符的用法
let f: { a: number, b: number, c: number } = { a: 1, b: 2, c: 3 }
let g: { a: number, b: number, c: number } = { ...f }
console.log(g)

let d: [number, number, number] = [1, 2, 3]
let e: [number, number, number] = [...d]
console.log(e)

function h(a: any, b: any, c: any) {
    console.log(a, b, c)
}
h(...e)

// 解构赋值的用法
let [i, j] = [1, 2]
console.log(i, j)
let { k, l } = { k: 1, l: 2 }
console.log(k, l)

// 可选链的用法
let obj: {
    props: string,
    method: Function
} = {
    props: 'prop',
    method: () => { console.log('method') }
}
console.log(obj?.props)
let expr: 'props' = 'props'
console.log(obj?.[expr])
obj?.method()

// 空值合并用法

console.log(null ?? undefined)

五. Typescript 类型系统的基础知识

  1. 类型注解
    • 类型注解用来明确标识类型。 写起来很简单就是冒号加某种具体类型。 用法也很简单,就是写在被修饰的实体之后。 示例如下:
const text: string = 'text'
// 字面量也是类型之一, 所以下面的写法一点问题也没有。
const text: 'text' = 'text'
  1. 类型检查

    • 严格类型检查
    • 非严格类型检查
  2. 原始类型

    • boolean
    • string
    • number
    • bigint
    • symbol 与 unique symbol, 这里之所有有 unique symbol 是因为 symbol 是没有字面量的, unique symbol 相当于 symbol 的字面量。你可能会想 symbol 的字面量不就是字符串么,但再仔细一想就能发现他们之间的区别。
    • undefined 和 null
    • void , void 只能用在函数,用来表示函数没有返回值。
  3. 枚举类型

    • 数值型枚举, 成员的值为 number 的枚举就是数值型枚举, 不显式的声明枚举的值, 那么值就会为 0 ,1,2,3
    • 字符串枚举, 成员的值为 string 的枚举就是数值型枚举
    • 异构型枚举, 成员的值既有 number 又有 string 的枚举
    • 常量枚举成员与计算枚举成员, 成员的值可以是确定的值, 也可以是一个函数。
    • 联合枚举类型, 枚举不光自己是一个类型, 枚举的成员也是类型
    • const 枚举类型, const枚举类型将在编译阶段被完全删除,并且在使用了const枚举类型的地方会直接将const枚举成员的值内联到代码中
// 数值型枚举
enum NumberEnum = {
    First,
    Second,
    Third
}
const numberEnumImpl :NumberEnum = NumberEnum.First

// 字符串枚举
enum StringEnum = {
    First: 'First',
    Second: 'Second',
    Third: 'Third'
}
const stringEnumImpl :StringEnum = StringEnum.First

// 联合枚举类型
const stringEnumImpl :StringEnum.Frist = StringEnum.First
  1. 字面量类型

    • boolean
    • string
    • number
    • bigint
    • 枚举成员类型,即联合枚举类型
  2. 顶端类型

    • any, 可以看成是所有类型的父类,一个标识符注明是 any 时,编译器就不会对他进行类型检查
    • unknown, 同样可以看成是所有类型的父类,但是 unknown 不能向下转型,也就是他不像 any 一样,可以看成是任意类的实例。
  3. 尾端类型

    • never, 可以看成是所有类型的子类。 也就是说他可以赋值给任意类型。更准确的来说 never 不能包含任意可能的值。any null undefined 也不行, 一个变量的类型如果是 never 那它真的是啥也不是。
  4. 数组类型

    • 简便数组类型表示法, 数据类型 加 数组字面量 比如 number[], (string|number)[]
    • 泛型数据类型表示法,Array 加 ElTypeName 表示某种类型, 实际应用中就是这样 Array
    • 只读数组
const a :number[] = [1, 2, 3]
const b: Array<number> = [1, 2, 3]
const a: readonly number[] = [1, 2, 3]
const a: ReadonlyArray<number> = [1, 2, 3]
  1. 元组类型
    • 元组类型是数组的子类, 它和数组的区别在于元组的长度是固定的。其实也并不完全是, 因为元组支持 剩余元素 写法, 当元组的长度不确定时 它的 length 的类型为 number, 其他情况下, 总是一个 number 的字面量。 然后元组类型的数据可以赋值给数组, 但是数组类型的数据不能赋值给元组
    • 可选元素
    • 剩余元素, 剩余元素表示,除了前面的几个元素,后面剩下的元素都是某种类型
const enumImpl :[number, number] = [1, 2]
const enumOptionalImpl :[number, number?] = [1]
const enumResidueImpl :[number, ...string[]] = [1, 2, 3]
  1. 对象类型
    • Object, javascript 万物皆对象, 严格来说其实是万物都有 prototype 属性,所以只要有 prototype 属性,基本上可以视为 Object 类型的实例。
    • object, javascript 万物皆对象,但是 typescript 有原始数据类型和非原始数据类型之分, 他们的差别有点像 java 的基本数据类型类型和包装数据类型的区别。但是因为 javascript 万物皆对象, 原始数据类型也是 Object, 所以为了区别原始数据类型和非原始数据类型增加了一个 object 类型。 这个类型用来表示非原始数据类型。
    • 对象类型字面量, 通过对象类型字面量定义对象的类型
    • 弱类型
    • 多余属性, 实际应用中 有可能出现没有声明类型的属性, 被称为多余属性。
// Object 源码
interface Object {
     /**
      * The initial value of Object.prototype.constructor
      * is the standard built-in Object constructor.
      */
     constructor: Function;

     /**
      * Returns a string representation of an object.
      */
     toString(): string;

     /**
      * Returns a date converted to a string using the
      * current locale.
      */
     toLocaleString(): string;

     /**
      * Returns the primitive value of the specified object.
      */
     valueOf(): Object;

     /**
      * Determines whether an object has a property with
      * the specified name.
      * @param v A property name.
      */
     hasOwnProperty(v: PropertyKey): boolean;

     /**
      * Determines whether an object exists in another
      * object's prototype chain.
      */
     isPrototypeOf(v: Object): boolean;

     /**
     * Determines whether a specified property is enumerable.
     * @param v A property name.
     */
     propertyIsEnumerable(v: PropertyKey): boolean;
}
// object
let obj:object;

// 下面的语句没有问题
obj = { x: 0, y: 0} 
// 下面的语句会报错
// console.log(obj.x)
// 下面的语句也会报错 null 不是 object
// obj = null

// 只能访问 Object.prototype 的方法
console.log(obj.valueOf())
// 对象字面量
let obj: {
    x: number,
    // 计算属性的类型
    ['y']:number,
    // 默认属性类型any
    z, 
    // 可选的属性
    a?: number,
    // 只读属性
    readonly b: number,
    // 方法签名
    sayHello(name: string) :string,
} = {
    x:1,
    ['y']: 2,
    b: 3,
    z: null,
    sayHello: (name)=>{
        return name
    },
}
  1. 函数类型
    • 常规参数类型, 正常情况下 在参数的后面写冒号加某个类型,即可定义参数类型
    • 可选参数类型, 和元组的可选元素写法相似, 类型后面加个问号就ok
    • 默认参数类型, 如果参数有默认值, 那么编译器会自动计算参数类型, typescript 不允许一个参数既是可选参数又是默认参数
    • 剩余参数类型
      • 数组类型的剩余参数, 如果剩余参数的类型都一样, 那么 :number[] 这样就OK, 这时数组类型的剩余参数
      • 元组类型的剩余参数, 如果剩余参数的类型都不一样
    • 解构参数类型
    • 返回值类型
    • 函数类型字面量,因为javascript 万物皆对象,所以可以像定义一个对象的类型一样定义函数的类型,然后先定义函数的类型, 再创建函数对象的实例。 这里说实话有点夸张…
    • 调用签名, 除了可以用函数字面量的方式定义函数的类型,因为函数本身也是对象, 所以可以用对象字面量的方式定义函数的类型, 然后因为对象具有多个属性, 所以 typescript 定义的函数类型不光可以执行方法体,还能有其他的函数属性。
    • 构造函数, javascript 中构造函数也是一种特殊的函数,这部分有点不好理解, 你们自行查资料吧
    • 重载函数, 就算没有 class ,函数本身也可以多态。 你可以创建多个参数列表不同,但名称相同的函数,通过传入不同的参数执行不同的方法。这里属实太夸张了。
    • 函数中 this 的类型, 通常是 any。 但是编译器在某些选项打开的情况下会阻止 any 类型的使用。 这种情况需要在函数参数列表声明参数的类型
// 通常情况
function add(x: number, y=0, z?: number, ...args: number[]):void{

}
// 通过函数字面量定义
let add :(x: number, y:any, z?: number, ...args: number[]) => void
add = (x, y, z, a, b) => {}

// 通过调用签名定义, 可以看到, 这个函数 add 多了一个 times 属性
let add: {
    (x: number, y: any, z?: number, ...args: number[]): void,
 times: number
} = fuc
function  fuc(x:number, y:number, z?:number) {
 console.log(x, y, z)
}
fuc.times = 1
  1. 接口

    • 因为 javascript 万物皆对象, 所以对象类型字面量,类,甚至函数,它们几个其实本质都一样。然后接口和其他几个的主要区别是, javascript 其实没有接口,在编译生成后的代码中不包含任何的接口代码。而且接口只能表示对象,不能表示原始类型。
    • 接口声明 interface 关键字
    • 属性签名, 当 interface 定义的接口为某个对象时,接口定义的类型声明为属性签名, 即对象属性的类型声明
    • 调用签名, 当 interface 定义的接口为某个函数时,接口定义的类型声明为调用签名,即这个函数调用时的类型参数,参数列表,及返回值
    • 构造签名, 当 interface 定义的接口为某个类时,接口内部定义的构造函数
    • 方法签名, 当 interface 定义的接口为某个类时,接口内部定义的方法
    • 索引签名, javascript 支持使用索引去访问对象的属性,比如正常 a.b 这样是直接访问属性, 但是 a[‘b’] 通常也可以, 就是因为 javascript 自动为这个对象创建了一个索引。 typescript 可以给这个索引创建类型声明,称为索引签名。一个接口只能创建一种索引签名。 而索引签名一共有两种,分为字符串索引签名和数值索引签名
    • 可选属性与方法
    • 只读属性和方法
    • 接口的继承, 接口可以使用 extends 关键字进行继承。
  2. 类型别名

    • 类型别名能够定义一个类型别名, 如 type AliasName = Type。 要注意避免出现递归别名的情况
    • 类的定义
      • 类声明, class className {}
      • 类表达式, const classNmae = class {}
    • 成员变量
    • 成员函数
    • 成员存取器,
      • get
      • set
    • 索引成员
    • 成员可访问性
      • public
      • protected
      • private
    • 构造函数
    • 参数成员, 在类的构造方法中,给参数一个访问控制修饰符, 该形式参数就成了参数成员,进而会被声明未类的成员变量
    • 继承
      • typescript 的继承是单继承
      • 子类必须调用父类的构造函数,否则将不能正确地实例化
      • typescript 允许接口继承类
    • 实现接口, 一个类可以实现多个接口
    • 静态成员, static 关键字,typescript 的静态成员可以使用访问控制修饰符进行修饰
    • 抽象类和抽象方法
    • this 类型
    • 类类型

六. Typescript 类型系统的高阶一点的知识

  1. 泛型, 泛型其实很简单, 就是用 <> 包裹一个单词 T 表示泛型,你也可以使用其他的标识符, 这个 T 表示某一个类型,它和 any 和 unknow 类似,都可以有任意类型的含义。 不同的是它这个任意类型只在代码编写时是任意的,编译之后,运行时,这个泛型就会被某个具体的类型所代替。 好比传入参数是一个泛型 T ,那么在代码块作用域中,所有的 T 的类型都是传入参数时的那个 T 的类型。然后在运行时传入参数有了确定的值, 并可以推算出类型, 所有的 T 也就有了确定的类型。
  2. 局部类型
  3. 联合类型
  4. 交叉类型
  5. 索引类型
  6. 映射对象类型
  7. 条件类型
  8. 内置工具类型
  9. 类型查询
  10. 类型断言
  11. 类型细化

七. 深入了解 Typescript 类型系统

  1. 子类型兼容性
  2. 赋值兼容性
  3. 类型推断
  4. 类型放宽
  5. 命名空间
  6. 模块
  7. 外部声明
  8. 使用声明文件
  9. 模块解析
  10. 声明合并
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!