大前端进击之路|TypeScript入门基础
语言类型
类型安全
强类型
在语言层面限制函数的实参类型必须与形参类型相同,不允许任意的隐式类型转换。
弱类型
弱类型不限制实参的类型,允许任意的隐式类型转换。
funciton sum(a, b) {
console.log(a + b)
}
sum(1,2) // 打印 3
sum(1,'a') //打印 1a
类型系统
静态类型
一个变量声明时,它的类型就是明确的,声明过后,它的类型不允许再被修改。
动态类型
变量的类型随时可以改变,运行阶段才能明确变量类型,变量没有类型,变量存放的值是有类型的。
var a
a = 'hello'
console.log(a) // hello
a = 123
console.log(a) // 123
JavaScript自有类型系统的问题
通过上面我们了解了强类型和弱类型、动态类型和静态类型,我们可以知道JavaScript语言是弱类型、动态类型的。这是因为早期JavaScript编写的应用比较简单,没有编译环节。类似上面的例子我们写了一个两个数相加的sum函数,我们只是想要让两个数字相加,但是如果我们参数输入了字符串,得到的结果就会是字符串拼接。这就是弱类型的问题,让我们的代码不可靠,不能及时发现问题。
强类型的优势
错误更早暴露
编码更准确,代码更加智能
重构更可靠
减少不必要的类型判断
TypeScript基础
TypeScript是什么?
TypeScript是一种由微软开发的自由、开源的编程语言。它是JavaScript的一个超集,本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。任何一个JavaScript运行环境都支持它,TypeScript提供最新的和不断发展的JavaScript特性,功能更加强大,生态也更健全、更完善。
缺点:
项目初期,使用TypeScript会增加成本。
语言本身相对于JavaScript多出一些新的概念,增加学习成本。
但是TypeScript是渐进式的,我们会使用JavaScript的语法可直接上手TypeScript。
TypeScript初体验
安装TypeScript
npm install -g typescript
配置文件
生成配置文件tsconfig.json
tsc --init
// 在生成的配置文件中比较常用的配置选项
// target => 编译目标为es5
// lib => 标准库声明,这里我们引用了ES2015、DOM、ES2017
// outDir => 编译后文件存放位置
// rootDir => 文件根路径
编译TypeScript文件
// 如果我们配置好tsconfig.json文件后可直接使用tsc命令编译
tsc
// 如果我们没有配置好tsconfig.json文件
tsc xxx.ts
// 会给我们生成xxx.js
作用域问题
在TypeScript中会默认文件中的成员会作为全局成员,如果多个文件中有多个相同成员就会出现冲突。
解决办法:
使用IIFE提供独立作用域
(function () {
const a = 123
}())
在当前文件使用export,也就是把当前文件变成一个模块,模块具有单独的作用域。
const a = 123
export {}
基本数据类型
原始数据类型
基础类型分为string、number、boolean、null、undefined、Symbol、void。
const str:string = 'string'
const num:number = 1
const boo:boolean = true
const n:null = null
const u:undefined = undefined
const s:Symbol = Symbol()
const v:void = undefined
Object类型
Object是指除了原始数据类型以外的其它类型
const fun:object = function () {}
const obj:{name:string, age:number} = {
name : 'xm',
age : 25
}
Array类型
// 1.可以在元素类型后面街上[],表示由此类型元素组成的一个数组
let arr : number[] = [1,2,3]
// 2.也可以使用数组泛型
let arr : Array<number> = [1,2,3]
元祖Tuple
元祖类型表示一个规定好元素数量和类型的数组。
let arr : [string, number] = ['xm',20]
枚举Enum
使用枚举我们可以定义一些带名字的敞亮,可以清晰的表达意图或创建一组有区别的用例。分为数字枚举和字符串枚举。
数字枚举
enum Direction {
Up = 1,
Down,
Left,
Right
}
// Up的初始值为1,其余的成员会从1开始自动增长。
// Down为2,Left为3,Right为4
字符串枚举
字符串枚举并没有自增长的行为,但是可以很好的序列化。
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
函数类型
我们可以为一个函数的返回值设定类型
function sum(a: number, b: number): number {
return a + b
}
sum(1, 2)
// 我们为sum函数的两个参数设定为number类型,返回结果也设定位number类型
可选参数和默认参数
在TypeScript中我们可以在参数名旁使用?实现可选参数的功能,可选参数和默认参数必须跟在必须在最后
// 可选参数
function buildName(firstName: string, lastName?: string) {
if (lastName)
return firstName + " " + lastName;
else
return firstName;
}
// 默认参数
function buildName(firstName: string, lastName:string = "xm") {
// ...
}
任意类型any
任意类型我们也可以成它为弱类型,意思就是任意类型都可以。
let foo: any = 'string'
foo = 100
foo.bar()
隐式类型转换
在TypeScript中存在隐式类型转换,因此建议我们为每个变量都添加明确的类型。
let age = 18 // number
age = 'string' // 报错
let foo // 不声明类型
foo = 100 // 默认为任意类型any
foo = 'string' // 不报错
类型断言
类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构,没有运行时的影响,只是在编译阶段起作用。就好比我们告诉TypeScript,相信我,我知道这个变量的类型是什么,我知道自己正在干什么。
// 假定这个 nums 来自一个明确的接口
const nums = [110, 120, 119, 112]
const res = nums.find(i => i > 0)
const square = res * res
// 在这里会报错,因为TypeScript不确定res的类型是什么,
// const res: number | undefined
// 使用类型断言 两种方式是一样的
const num1 = res as number
const num2 = <number>res // 这种方式在JSX下不能使用
接口interface
定义一些结构,我们去使用这个接口就必须去遵守这些接口所定义的结构。
interface Post {
title: string
content?: string // ?代表可选成员
readonlys summary: string // 只读属性
}
function printPost (post: Post) {
console.log(post.title)
console.log(post.content)
}
printPost({
title: 'Hello TypeScript',
content: 'A javascript superset', // 可选
summary: 'hello' // 只读无法修改
})
// 动态成员
interface Cache {
[prop: string]: string
}
const cache: Cache = {}
cache.foo = 'value1'
cache.bar = 'value2'
类class
class Person {
// 类的属性需要使用的话,需要明确声明类型
name: string
age: number
constructor (name: string, age: number) {
this.name = name
this.age = age
}
sayHi (msy: string): void {
console.log(`我是${this.name},${msg}`)
}
}
类的访问修饰符
默认为public公共属性
private是私有属性,外部无法访问
protected受保护的,与private很相似,外部也无法访问,但是在派生类中可以被访问
readonly只读属性
构造函数也可以被设置为private、protected
class Person {
private name: string
protected readonly age: number
constructor (name: string, age: number) {
this.name = name
this.age = age
}
sayHi (msy: string): void {
console.log(`我是${this.name},${msg}`)
}
}
类和接口
interface Eat {
eat(foot: string): void
}
interface Run {
run(distance: number): void
}
class Person implements Eat, Run {
eat(food: string): void {
}
run(distance: number): void {
}
}
class Animal implements Eat {
eat(food: string): void {
}
}
抽象类
抽象类做为其它派生类的基类使用。它们一般不会直接被实例化。不同于接口,抽象类可以包含成员的实现细节。abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法。
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log('roaming the earch...');
}
// 抽象类中的抽象方法不包含具体实现并且必须在派生类中实现
abstract run (distance: number): void
}
泛型
function createNumberArray(length: number, value: number): number[] {
const arr = Array<number>(length).fill(value)
return arr
}
// 上面这个函数只能创建数字类型的数组,我们要是想要创建一个字符串类型的数组,又得写一个函数,这时候我们可以使用泛型
function createArray<T>(length: number, value: T): T[] {
const arr = Array<T>(length).fill(value)
return arr
}
const res = createNumberArray(3, 100)
const numArr = createArray(3, 'you')
const strArr = createArray(3, 100)
类型声明
import { camelCase } from 'lodash'
declare function camelCase (input: string): string
const res = camelCase('hello typed')
// 也可以使用npm install @types/lodash --dev 添加类型声明模块