JavaScript 是一门动态弱类型语言,对变量的类型非常宽容,而且不会在这些变量和他们的调用者之间建立结构化的契约。
TypeScript是拥有类型系统的JavaScript的超集,可以编译成纯JavaScript。
思维方式决定了编程习惯
编程习惯奠定了工程质量
工程质量划定了能力边界
强类型语言与弱类型语言
在强类型语言中,当一个对象从调用函数传递到被调用函数时,其类型必须与被调用函数中声明的类型兼容。
通俗定义:强类型语言不允许更改变量的数据类型,除非进行强制类型转换。
静态类型语言和动态类型语言
静态类型语言:在编译阶段确定所有变量的类型,如C++。
动态类型语言:在执行阶段确定所有变量的类型,如JavaScript。
静态类型语言 | 动态类型语言 |
---|---|
对类型极度严格 | 对类型非常管送 |
立即发现错误 | Bug可能隐藏数月甚至数年 |
运行时性能号 | 运行时性能差 |
自文档化 | 可读性差 |
动态类型语言的支持者认为:
总结:
因为JavaScript是动态类型语言,只有在执行阶段才能确定所有变量的类型,这使得我们在编写程序的时候,编辑器无法有效提示错误。
如以下错误,JavaScript就无法提示错误:
// 示例 1
const message = 'Hello World'
message.toLowerCase() // 无法得知mesaage是否存在toLowerCase() 方法
message() // 无法得知message() 是否是错误的写法
message.tollowerCase() // TypeScript 提示: Property 'tollowerCase' does not exist on type '"Hello World"'. Did you mean 'toLowerCase'?
// 示例 2
const user = {
name:"wuyanzu",
age:18
}
// TypeScript 提示: Property 'location' does not exist on type '{ name: string; age: number; }'.
// JavaScript 提示:未定义location
user.location
安装vscode、node.js、npm,在vscode终端中,安装TypeScript
PS C:\Users\wuyanzu > npm i typescript -g
added 1 package in 2s
执行ts、js文件
tsc helloworld.ts
node helloworld.js
解决ts和js冲突问题(编译后,函数名重复问题):
tsc --init // 生成tsconfig.json文件,并注释掉"strict": true,
实现自动编译:
tsc --watch // ts文件发生变化时,js文件自动编译
tsc --noEmitOnError --watch // ts发生错误时,不自动编译
变量类型声明
//TypeScript 可以推断出变量的类型,因此这时可以不用显示地声明明确变量的类型
let msg = 'hello world'
console.log(typeof(msg)) // "string"
msg = 'hello wu'
msg = 18 // Type 'number' is not assignable to type 'string'.
严格模式
// 配置文件 tsconfig.json
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
降级编译
// 配置文件 tsconfig.json
"target": "es2016", // 配置JavaScript适配的ea版本
let str: string = 'hello world'
let num: number = 100
let bool: boolean = true
// 数组声明的两种方式
let arr1: number[] = [1, 2, 3]
arr1 = ['a'] // Type 'string' is not assignable to type 'number'.
let arr2: Array<number> = [4, 5, 6]
arr2 = ['a'] // Type 'string' is not assignable to type 'number'.
// any 不进行任何类型检查
let obj1 = { // 系统推断出obj1的类型为"object"
x: 0
}
console.log(typeof(obj1)) // "object"
obj1.foo() // Property 'foo' does not exist on type '{ x: number; }'.
let obj2: any = {
x: 0
}
obj2.foo()
function sayHi (name: string): void{ // string为参数类型注释,void为返回值类型注释
console.log("Hello, " + name)
}
sayHi("wuyanzu") // "Hello, wuyanzu"
函数的参数类型声明、可传可不传参数
function allStarsVote(cn: {x: number, y?: number}) { // ?:表示该参数可传可不传
console.log('Uzi 的票数为: ' + cn.x)
console.log('XiaoHu 的票数为: ' + cn.y)
//console.log('XiaoHu 的票数为: ' + cn.y?.toPrecision(2)) // 在可不传参数调用方法时,可使用?.方式,等同于若为undefined则跳过
}
allStarsVote({x: 123, y: 456}) // "Uzi 的票数为: 123" "XiaoHu 的票数为: 456"
allStarsVote({x: 123}) // "Uzi 的票数为: 123" "XiaoHu 的票数为: undefined"
// 上下文类型,TypeScript会根据上下文推断出变量类型
const allStars = ['xiaohu', 'xiaoming', 'uzi']
allStars.forEach((item) => {
console.log(item.toLocaleUpperCase()) // TypeScript 可以根据上下文推断出item的类型
})
// 示例 1
function welcomePeople(x: string[] | string){
if (Array.isArray(x)){
console.log("Hello, " + x.join(' and '))
}else{
console.log("welcome long traveler" + x)
}
}
welcomePeople(['Uzi', 'Xiaohu'])
welcomePeople('Uzi')
// 示例 2
function sayHi(name: 'wu' | 'zhang' | 'li'){
}
sayHi('chen') // Argument of type '"chen"' is not assignable to parameter of type '"wu" | "zhang" | "li"'.
sayHi('li')
// 示例 3
function compare(a: string, b: string): -1 | 0 | 1 {
return a ===b ? 0 : a > b ? 1 : -1
}
type IdType = number | string // 使用type定义一个类型
function sayHi(x: IdType){
console.log(x)
}
sayHi(6)
sayHi('Uzi')
interface IdType {
x: number
y: number
}
function printCoord(pt: IdType){
console.log(pt)
}
printCoord({
x: 100,
y: 200
})
// 案例1 接口扩展
interface A {
name: string
}
interface B extends A {
honey: boolean
}
const foo1: B = {
name: 'winie',
honey: true
}
// 案例2 类型别名扩展
type C = {
name: string
}
type D = C & {
honey: boolean
}
const foo2: D = {
name: 'winie',
honey: true
}
// 案例3 向现有的类型添加字段(interface)
interface E {
name: string
}
interface E {
honey: boolean
}
const foo3: E = {
name: 'winie',
honey: true
}
// 案例4 向现有的类型添加字段(类型别名) 类型别名无法通过重名的类型别名添加字段,只能通过扩展方式添加
type F = {
name: string
}
type F = C & { // Duplicate identifier 'F'.
honey: boolean
}
与类型注释相同,类型断言由编辑器删除,不会影响代码的运行时行为
const myCanvas1 = document.getElementById('main_canvas') as HTMLCanvasElement
//const myCanvas2 = document.getElementById('main_canvas') // 报错了
const x = 'hello' as number // neither type sufficiently overlaps with the other.
const y = ('hell0' as unknown) as number
let s1 = 'hello world' // s1可以表示任何可能的字符串(重新赋值)
const s2 = 'hello world' // s2只能表示成字符串'hello world'
推断引起的异常
function handleRequest(url: string, method: 'POST' | 'GET' | 'GUESS'){
}
const req = {
url:"www.google.com",
method:'GET'
}
//handleRequest(req.url, req.method) // Argument of type 'string' is not assignable to parameter of type '"POST" | "GET" | "GUESS"'.
// 原因:req.method被推断为string方法,不满足 type '"POST" | "GET" | "GUESS"
// 解决方式1:
handleRequest(req.url, req.method as 'GET')
// 解决方式2:
const req2 = {
url:"www.google.com",
method:'GET' as 'GET'
}
handleRequest(req2.url, req2.method)
// 解决方式3:
const req3 = {
url:"www.google.com",
method:'GET' as 'GET'
} as const
handleRequest(req3.url, req3.method)
null 表示不存在
undefined 表示未初始化的值
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
console.log(c) // 1
console.log(Color)
{
"0": "Red",
"1": "Green",
"2": "Blue",
"Red": 0,
"Green": 1,
"Blue": 2
}
Symbols是不可改变且唯一的
let sym2 = Symbol("key");
let sym3 = Symbol("key");
sym2 === sym3; // false, symbols是唯一的
===
==
!==
!=
类型谓词是一种特殊的返回类型,它向Typescript编译器发出一个特定值是什么类型的信号。类型谓词总是附加到接受单个参数并返回布尔值的函数。类型谓词表示为 argumentName is Type
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
这里的 pet is Fish
就是类型谓词,pet
必须是函数的参数。当 isFinis()
调用并且返回 true 的时候,ts就会把该变量的类型范围缩小为某一具体类型。
type Fish = {
name: string,
swim: () => void
}
type Brid = {
name:string,
fly: () => void
}
const isFish = (pet: Fish | Brid): pet is Fish => {
return (pet as Fish).swim !== undefined
}
const getSmallPet = (): Fish | Brid => {
let brid: Brid = {
name: "sbrid",
fly(){
}
}
let fish: Fish = {
name:"sfish",
swim(){
}
}
return true ? brid : fish;
}
let pet = getSmallPet();
if (isFish(pet)){
pet.swim();
} else {
pet.fly();
}
const zoo: (Fish | Brid)[] = [getSmallPet(),getSmallPet()];
const underWater: Fish[] = zoo.filter(isFish);
const underWater2: Fish[] = zoo.filter(isFish) as Fish[];
xxx
11
type GreetFunction = (arg: string) => void // 定义一个函数类型
function greeter(fn:GreetFunction, arg1: string){
fn(arg1)
}
function printToConsole(arg: string){
console.log(arg) // "hello world"
}
greeter(printToConsole,"hello world") // greeter(printToConsole("a"),"hello world") 的写法是错误的,第一个参数只能是GreetFunction函数类型
function firstElement<Type>(arr: Type[]): Type | undefined{
return arr[0]
}
firstElement(['a', 'b', 'c'])
firstElement<number>([1, 2, 3])
泛型限制条件 extends
// 下文的extends并非真正的继承,而是简单的限制条件
function longest<Type extends { length: number }>(a: Type, b: Type){
if (a.length >= b.length){
return a
}else{
return b
}
}
longest([1, 2], [2, 3, 4])
longest("wuyanzu", "liangchaowei")
longest(10, 100) // Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
编写优秀通用函数的准则
函数参数
当为回调写一个函数类型时,永远不要写一个可选参数,除非你打算在不传递该参数的情况下调用函数。
函数重载签名、实现签名
// 原始类型
let bool: boolean = true
let num: number | undefined | null = 123
let str: string = 'abc'
// str = 123 Type '123' is not assignable to type 'string'.
// 数组
let arr1: number[] = [1, 2, 3]
let arr2: Array<number | string> = [1, 2, 3, '4']
// 元组
let tuple: [number, string] = [0, '1']
// tuple.push(2) 强烈不推荐
// console.log(tuple)
// tuple[2]
// 函数
let add = (x: number, y: number) => x + y
let compute: (x: number, y: number) => number
compute = (a, b) => a + b
// 对象
let obj: { x: number, y: number } = { x: 1, y: 2 }
obj.x = 3
// symbol
let s1: symbol = Symbol()
let s2 = Symbol()
// console.log(s1 === s2)
// undefined, null
let un: undefined = undefined
let nu: null = null
num = undefined
num = null
// void
let noReturn = () => {}
// any
let x
x = 1
x = []
x = () => {}
// never
let error = () => {
throw new Error('error')
}
let endless = () => {
while(true) {}
}
// 数字枚举
enum Role {
Reporter = 1,
Developer,
Maintainer,
Owner,
Guest
}
// console.log(Role.Reporter)
// console.log(Role)
// 字符串枚举
enum Message {
Success = '恭喜你,成功了',
Fail = '抱歉,失败了'
}
// 异构枚举 容易混乱,不推荐使用
enum Answer {
N,
Y = 'Yes'
}
// 枚举成员
// Role.Reporter = 0
enum Char {
// const member
a,
b = Char.a,
c = 1 + 3,
// computed member
d = Math.random(),
e = '123'.length,
f = 4
}
// 常量枚举 会被编译阶段移除
const enum Month {
Jan,
Feb,
Mar,
Apr = Month.Mar + 1,
// May = () => 5
}
let month = [Month.Jan, Month.Feb, Month.Mar]
// 枚举类型
enum E { a, b }
enum F { a = 0, b = 1 }
enum G { a = 'apple', b = 'banana' }
let e: E = 3
let f: F = 3
// console.log(e === f)
let e1: E.a = 3
let e2: E.b = 3
let e3: E.a = 3
// console.log(e1 === e2) 无法比较,类型不同
// console.log(e1 === e3) 可以比较,类型相同
let g1: G = G.a
let g2: G.a = G.a
总结:将程序中不容易记忆的硬编码,或者是未来可能改变的常量抽取出来,定义成枚举类型,提高程序的可读性和可维护性。