javascript 动态弱类型语言
TypeScript的纯在,静态强类型语言诞生 类型思维
优缺点
优势:
- 提升代码质量
- 增强代码可维护性
- 提升开发效率
- 重塑类型思维
- …
成本.
- 思维转化
- 对接现有开发生态
- 项目迁移
- 接口,声明文件的维护
- ….
静态类型语言:在编译阶段确定所有变量的类型
动态类型语言:在执行阶段确认所有变量的类型
强类型语言:强制数据类型定义的语言,
也就说一旦定义了变量为某个数据类型,如果不通过强制类型转换,将无法更改类型
弱类型语言:数据类型可以被忽略的语言,
跟强类型相反
TS会在编码阶段进行类型检查
特性:
类型检查,
语言扩展,
工具属性:编译成javascript
基础篇
环境搭配
1.npm init -y 初始化项目生成package.json
1.1 淘宝镜像 npm config set registry https://registry.npm.taobao.org
查看配置 npm config get registry
查看proxy npm config get proxy 设置 npm config set proxy null
2.npm install typescript -g 全局安装typescript
3.tsc —init 初始化typescript 创建tsconfig.json
4. 编译ts文件 tsc ./index.ts
5. npm i webpack webpack-cli webpack-dev-server -D 安装webpack相关
npm i html-webpack-plugin -D 插件
npm i html-webpack-plugin -D 插件
npm install ts-loader typescript -D
…一堆插件
6. 生产,开发配置,package.json — script
"start": "webpack-dev-server --mode=development --config ./build/webpack.config.js",
"build": "wbepack --mode=production --config ./build/webpack.config.js",
类型注解 :相当于强类型语言中的类型声明
语法:(变量/函数) :type 联合语法
// todo
元祖 数量和类型已知的数组
普通枚举
枚举
// 常量枚举
any 如果变量为any 就跟js差不多, 不进行类型检查
null
let element: (HTMLElement | null) = document.getElementById(‘root’);
//非空断言 常用
element!.style.color = ‘green’;
// structNullChecks 则不能把null undefined 赋值为xxx
类型推导
//基本类型
let bool: boolean = true
let number: number = 1212
let str: string = '112121'
//引用类型-数组
let arr: number[] = [1, 2, 3]
//泛型
let arr2: Array<number> = [1, 2, 3]
//联合类型
let arr3: Array<number | string> = ['111', 222]
//元组 数量和类型已知的数组
let tuple: [number, string] = [1, 'string']
//元组越界 可以添加,但是不能访问
tuple.push('newString')
// console.log(tuple)
for (let A = 0; A < tuple.length; A++) {
console.log(tuple[A])
}
//函数 形参必须定义类型
let add = (x: number) => x
//对象
let obj: { x: number, y: number } = {
x: 11,
y: 22
}
//如果之定义了对象为object类型.不允许修改
obj.x = 333
// symbol
let s1: symbol = Symbol()
let s2: symbol = Symbol()
console.log(s1===s2) //false
//any类型 与js没有区别,
let x
x= 1
x='222'
x=[]
//never类型
let error =()=>{
throw new Error('')
}
let endless= ()=>{
while(true){}
}
枚举类型 :一组有名字的常量集合
1.枚举的成员都是只读,不允许修改
2.不同枚举成员是不允许比较
3.开发中可以把常量抽取出来用作枚举形式
数组枚举 ,字符串枚举, 异构枚举, 枚举成员 ,常量枚举
//数组枚举 反向映射
enum Num {
one = 3, //3 默认从0开始 如果设置了值,会递增
two,//3
three = 10,//10
four//11
}
console.log(Num)
//字符串枚举 枚举定义第一个字符要大写
enum Str {
success = '成功',
fail = '失败'
}
//异构枚举
enum Answer {
n,
y='Yes',
b= 'Bool',
}
//枚举成员 编译的时候已经知道结果
enum Char {
a,//没有定义值,默认数字0
b=Char.a,//枚举的引用
c=1+2,//表达式定义值
//computed计算成员,运行时才执行
d=Math.random(),
e ='123'.length
}
//常量枚举 编译阶段会被移除 ,不需要对象,只需要对象的值
const enum Month {
Jan,
Feb
}
let month = [Month.Jan,Month.Feb] //编译阶段不会又常量枚举
接口
理解:接口是一系列抽象方法的声明,是一些方法特征的集合,这些方法都应该是抽象的,需要由具体的类去实现,然后第三方就可以通过这组抽象方法调用,让具体的类执行具体的方法。
//定义接口里面的值
interface List {
readonly id: number //readonly 代表只读属性
name: string
age?: number //?代表可选属性
}
//引用接口
interface Result {
data: List[]
}
//传入参数是一个接口对象
function render(result: Result) {
result.data.forEach(item => {
console.log(item.id, item.name)
if (item.age) console.log(item.age)
})
}
let result = {
data: [
{id:1,name:'小明',age:20},
{id:2,name:'小红'},
]
}
render(result)
//定义不确定的接口,全部都是字符串
interface StringArray {
[index:number]:string
}
let charts:StringArray = ['A','B','C']
interface Names {
[x:string]:string
[z:number]:any
}
异构接口
//别名定义函数 接口定义
type Add = (x: number, y: number) => number
//实例定义
let addFun: Add = (a, b) => a + b
//调用
console.log(addFun(1, 2))
//混合类型的接口
interface Lib{
():void
version:string,
doSomething():void
}
function getLib(version:string){
let lib :Lib =(()=>{}) as Lib //断言
lib.version =version
lib.doSomething = () => {
console.log(`版本号:${lib.version}`)
}
return lib
}
let lib1 =getLib('1.0')
let lib2 =getLib('2.0')
lib2.doSomething()
函数定义汇总 自变量定义 别名定义 接口定义 剩余参数 函数重载
1.函数重载,编译的时候会创建函数重载列表
//函数定义
function add1(x: number, y: number): number {
return x + y
}
//自变量定义
let add2: (x: number, y: number) => number
//别名定义
type add3 = (x: number, y: number) => number
//接口定义
interface add4 {
(x: number, y: number): number
}
//调用函数参数个数必须相同,
// 可在参数中添加?设置为可选参数,可选参数必须在必选参数在后
function add5(x: number, y = 2, z: number, q = 1) {
return x + y + z + q
}
console.log(add5(1, undefined, 3))
//剩余参数
function add6(x: number, ...args: number[]) {
return x + args.reduce((prev, cur) => prev + cur)
}
console.log(add6(1, 2, 3, 4, 5, 6)) //21
//函数重载 利用参数类型实现重载
function heavyLoad(...args: number[]): number
function heavyLoad(...args: string[]): string
function heavyLoad(...args: any[]): any {
let first = args[0]
if (typeof first === 'number') {
return args.reduce((prev, cur) => prev + cur)
} else if (typeof first === "string") {
return args.join("_")
}
}
console.log(heavyLoad(1, 2, 3, 4, 5))
console.log(heavyLoad('1', '2', '3'))
类 class 会覆盖es6的类 创建,class 继承 extends
class Dog {
constructor(name: string) {
this.name = name
}
name: string
sayHi() {
console.log(`我叫${this.name}`)
}
run() {
}
}
//类的成员在实例上面,不在原型上
let dog = new Dog("旺财")
dog.sayHi()
//继承
class Husky extends Dog {
constructor(name: string, age: number) {
super(name)
this.age = age
}
age: number
play() {
console.log(`我叫${this.name},我会卖萌玩耍,我有年龄`)
}
sayHi1 = () => {
console.log('我有年龄' + this.age)
}
}
let husky = new Husky('哈稀奇', 14)
husky.sayHi1()
husky.sayHi()
husky.play()
修饰符
1.public
2.private
3.protected
4.readonly
5.static
抽象类, 只能被继承,不能被实例 多态
// 创建抽象类 相当于把公共的东西抽到同一个抽象类里面
abstract class Animal {
eat() {
console.log('eat')
}
// 抽象方法 继承抽象类一定要有实现
abstract sleep():void
}
class Dog1 extends Animal {
constructor(name: string) {
super()
this.name = name
}
name: string
sleep(): number {
console.log('Dog_sleep')
return 1
}
}
let dog1 =new Dog1("哈士奇")
dog1.eat()
dog1.sleep()
//多态实现
class Cat extends Animal{
sleep(): void {
console.log("cat_sleep")
}
}
let cat = new Cat()
let animals:Animal[]= [dog1,cat]
animals.forEach(item=>{
item.sleep()
})
//this.链式操作
class WorkFlow{
step1(){
return this
}
step2(){
return this
}
}
new WorkFlow().step1().step2()
//父类与子类都能链式操作
class MyFlow extends WorkFlow{
next(){
return this
}
}
new MyFlow().step2().next().next().step1()
类与接口之间的关系
- 类实现接口必须声明接口的所有成员
- 接口只能约束类的共有成员
- 接口之间能相互继承,多继承(,分割)
- 接口继承类
interface Human {
name: string
eat(): void
}
//类继承接口,需要把所有接口成员实现
class Asian implements Human {
constructor(name: string) {
this.name = name
}
name: string
eat(): void {
console.log("Eat_Fun")
}
sleep(): void {
console.log("sleep_fun")
}
}
interface Man extends Human {
run(): void
}
interface Child {
cry(): void
}
// 接口之间的继承关系
interface Boy extends Man, Child {
age: number
}
let boy: Boy = {
name: "杨明",
age: 27,
run() {},
eat() {},
cry() {
console.log(this.name + "crying")
return this
}
}
boy.cry()
//接口继承类
class Auto {
state:number =1
}
interface AutoInterface extends Auto{
//接口里面的成员
interfaceState: number
}
class C implements AutoInterface{
constructor(name:string,state:number) {
this.name = name
this.state =state
}
name:string
state:number
interfaceState =2
}
//实例
let c = new C("小明",1)
class Bus extends Auto implements AutoInterface{
constructor(interfaceState:number) {
super();
this.interfaceState =interfaceState
}
interfaceState:number
}
let bus = new Bus(1)
console.log(bus)
泛型(重要概念) 函数或者类支持多种数据类型
泛型理解:不需要预先确定的数据类型,具体的类型在使用的时候才确定
//泛型函数的定义 ,定义时不知道参数类型,返回值类型 但是两个类型必须一致
function log<Type>(value: Type): Type {
console.log(value)
return value
}
//1.调用
log<string>("小明") //调用前定义类型
log("小明")
//2.别名
type Log =<Type>(value:Type) => Type
let myLog:Log = log
myLog("小明")
//************************************************************************
//泛型接口
interface Log<Type = string> {
(value: Type): Type
}
let mylog :Log<number> = log
mylog(1)
泛型的好处:
- 函数和类可以轻松地支持多种类型,增强程序的扩展性
- 不必写多条函数重载,冗长的联合类型声明,增强代码可读性
-
泛型的约束
// 泛型函数
class Log <Type> {
run(value:Type) :Type{
console.log(value)
return value
}
}
let log1 = new Log<number>()
log1.run(1111)
let log2 =new Log()
log2.run({a:11})
//***************************************
// 泛型的约束
interface Length {
length:number
}
//传入泛型参数必须有length属性
function log<Type extends Length> (value:Type) :Type {
console.log(value, value.length)
return value
}
log([])
log("123")
类型检查机制
理解:TypeScript编译器在做类型检查时,所秉承的一些原则,以及表现出的一些行为
作用:辅助开发,提高开发效率 类型推断
- 类型兼容性
- 类型保护
类型推断
理解:不需要制定变量的类型(函数的返回值类型),TypeScript可以根据某些规则自动地为其推断出一个类型
1.基础类型推断
2.最佳通用类型推断
3.上下文类型推断//基础类型推断 从右到左推断
let any //any类型
let a = 1 //number
let b = "1231231" //string
let c1 = (x = 1) => x + 1 //number :number
//最佳通用类型推断
let arr1 = [1, null, "123"] //(number | null | string)[]
//上下文类型推断
window.onkeydown = (event) => {
//console.log(event.buttons)
}
类型兼容性
理解:当一个类型Y可以被赋值给另外一个类型X时,我们就可以说类型X兼容类型Y
X兼容Y X(目标类型) = Y(源类型)
源类型必须具备目标类型的属性 我的理解:如果右边包含左边,那么左边兼容右边
只有兼容类型才能被赋值
老师口诀
结构之间兼容:成员少的兼容成员多的
函数之间兼容:参数多的兼容参数少的 ```typescript let a1 :string =”aaa” a1 =null //关掉null类型检查的情况,a1兼容null
//接口兼容性 interface X { a:number b:number } interface Y { a:number b:number c:number d:number } let x1 :X ={ a:1, b:2 } let y1 :Y ={ a:10,b:11,c:12,d:13} x1 =y1 //右边包含左边,那么左边兼容右边,可赋值 console.log(x1) //{ a:10,b:11,c:12,d:13}
//函数兼容的3个条件 //1.参数个数 //2.参数类型 //3.返回值类型
//枚举类型的兼容性 enum Fruit { Apply, Banana } enum Color { Red ,Yellow } let fruit :Fruit.Apply = 3 //枚举类型兼容其他类型 let no:number = Fruit.Apply //枚举之间不兼容 //Type ‘Fruit.Apply’ is not assignable to type ‘Color.Red’. // let color:Color.Red = Fruit.Apply
//类的兼容性 //1.静态成员与构造函数是不参与比较 //2.如果有私有成员,就不兼容 //3.如果子类的继承父类,子类的实例与父类的实例是兼容的 class A { constructor(p:number) {} id:number =1 private name :string = “” } class B{ static c :number = 1 constructor(p:number ,b:number ) {} id:number = 2 private name :string = “” } let aa = new A(1) let bb = new B(1,1) class CC extends A{ age:number =27 } let cc =new CC(1) aa =cc //Property ‘age’ is missing in type ‘A’ but required in type ‘CC’. // cc =aa
//泛型兼容性
interface Empty
//泛型函数的兼容
let cs1 =
<a name="SUgrj"></a>
### 类型保护 instanceof in关键字 typeof 创建类型保护函数
理解: TypeScript能够在特定的区块中保证变量属于某种确定的类型<br /> 可以在此区块中放心的引用此类型的属性,或者调用此类型的方法
```typescript
enum Type {
Strong,
Week
}
class Java {
helloJava(){
console.log("hello_Java")
}
java:string
}
class JavaScript{
helloJavaScript(){
console.log("Hello_JavaScript")
}
javascript:string
}
function isJava(lang:Java |JavaScript):lang is Java {
return (lang as Java).helloJava !== undefined
}
function getLanguage(type:Type ,x:string|number) :Java|JavaScript{
let lang = type === Type.Strong ? new Java() :new JavaScript()
//instanceof //区块 -------------------------------
if (lang instanceof Java){
lang.helloJava()
}else{
lang.helloJavaScript()
}
//in关键字 //区块 -------------------------------
if("java" in lang){
lang.helloJava()
}else{
lang.helloJavaScript()
}
//typeof //区块 -------------------------------
if(typeof x === 'string'){
console.log(x.length)
}else{
console.log(x.toFixed(2))
}
//类型保护函数 //区块 -------------------------------
if(isJava(lang)){
lang.helloJava()
}else{
lang.helloJavaScript()
}
return lang
}
getLanguage(Type.Strong,"2222")
高级类型:交叉类型& ,联合类型
交叉类型理解:使用&字符,多个类型的并集合
联合类型理解:使用 | 当一个变量类型不明确时,可使用| 连接, 那么变量的类型为多个里面的一个 交集
interface DogInterface {
run():void
}
interface CatInterface {
jump():void
}
//交叉类型,实现需要交叉类型的并集合
let pet: DogInterface &CatInterface ={
run(){},
jump(){}
}
//------------------------------------------------
//联合类型
let value : string | number ="1212" || 123
//自变量联合类型
let b1 :"a" | "b" | "c" //字符串联合类型 b1的值只能是a,b,c
let b2 :1 | 2 | 3 //数字联合类型 b2的值只能是1,2,3
//------------------------------------------------
//对象联合类型
class Dog_1 implements DogInterface {
run(){}
eat(){}
}
class Cat_1 implements CatInterface{
jump(){}
eat(){}
}
enum Master { Boy, Girl }
function getPet(master :Master) {
let pet =master === Master.Boy? new Dog_1() :new Cat_1()
//对象联合类型只允许调用联合类型的共同成员 交集
pet.eat()
return pet
}
高级类型-索引类型 (有点模糊)
索引类型的操作符 keyof
let obj1 ={a:1, b:2, c:3 }
function getValues<T,K extends keyof T> (obj:T,keys:K[]):T[K][]{
return keys.map(key=>obj[key])
}
console.log(getValues(obj1,[ "a","b"])) //[1,2] 访问有的属性
console.log(getValues(obj1,[ "e","f"])) //[undefined,undefined] 访问有的属性 约束了
//索引类型 keyof T
interface Obj2 {
a:number
b:string
c:any
}
let key : keyof Obj2 = "a"
//索引访问操作符 T[K]
let value1 : Obj2['a'] = 1
//T extends U
高级类型-映射类型 旧的类型—>新的类型 同态类型
interface Obj {
a:string
b:number
c:boolean
}
//映射为只读的类型
type ReadOnlyObj = Readonly<Obj>
type PartialObj = Partial<Obj>
//Ts自带的类型
高级类型-条件类型 T extends U ? X : Y (微懂)
理解: 如果类型T可以被赋值类型U 那么类型就是X 类型 否者为Y类型
// T extends U ? X : Y
type TypeName <T> =
T extends string ?'string':
T extends number ? 'number':
T extends boolean? 'boolean':
T extends undefined? 'undefined':
T extends Function? 'function':
"object"
type T1 = TypeName<string>
type T2 = TypeName<number>
type T3 = TypeName<string[]>
//(A | B ) extends U ? X : Y
type T4 = TypeName<string | string[] | number> //string object number
装饰器
属性装饰器 target 是构造函数原型
static 静态属性装饰器 target是构造函数本身
实例方法 三个形参, target: any propertyKey: string descriptor: PropertyDescriptor
参数装饰器
执行顺序的规律
1.类装饰器是最后执行的,后写的类装饰器先执行
2.方法和就去参数中的装饰器先执行参数3.就去和属性装饰器,谁在前面先执行谁
一般人内往外执行先内后外
类比组件componentDidMount 先上后下先内后外一定要学会知识的
koa中间件redux中间件
工程篇
ES6 与CommonJs模块系统
ES6 导入导出
//a.ts
//单独导出
export let a = 1
//批量导出
let b = 2
let c = 3
export {b, c}
//导出接口
export interface p {
x: number
y: string
}
//导出函数
export function fun(){
//...
}
//别名导出
function fun1(){}
export { fun1 as fun2 }
//默认导出
export default function(){
console.log("default")
}
//引入外部模块,重新导出
export {str as hello} from './b'
//b.ts
export const str ="hello"
导入
//批量导入
import { a,b,c} from "./a"
//导入接口
import {p} from "./a"
//导入时起别名
import {fun2 as F} from "./a"
//导入模块中的所有成员,绑定在All上
import * as Al from "./a"
//不加{} 导入默认
import myFunction from "./a"
CommonJs导入导出
module.exports require
命名空间 解决全局污染问题 namespace 关键字
//a.ts
namespace Shape{
export function circle(x:number){
return x * x
}
}
//b.ts
/// <reference path="./a.ts" />
namespace Shape {
export function square(x: number) {
return x + x
}
}
// Shape.circle(2)
console.log(Shape.square(1));
//命名空间别名
import square = Shape.square
console.log(square(11));
声明合并
interface A {
x:number
foo(bar:number):number //5
foo(bar:'a'):string //2
}
interface A {
y:number
foo(bar:string):string //3
foo(bar:number[]):number[] //4
foo(bar:'b'):number //1
}
//接口声明合并 可以相同属性,但是类型要一样
let a :A = {
x:1,
y:1,
foo(bar: any) {
return bar
}
}
//命名空间,不允许重复定义相同名字属性
//命名空间可以与函数,类,枚举合并
//命名空间必须放在以上的后面
练习
- 数组对象声明 ```typescript type TTimeType = { state: string, duration: number
}
// 选择的时间周期
const selectTime: TTimeType[] = []
```