我始终相信:任何一种新的技术的出现是为了解决原有的技术的缺陷
JavaScript 是一门非常优秀的编程语言
他比java的(面向对象/面向过程更加的灵活) 并且在很长的时间内并不会被另外一种语言所取代
Stack Overflow的创立者之一的 Jeff Atwood 在2007年提出了著名的 Atwood定律:任何使用JavaScrit实现的应用最终都会被JavaScript实现 现在看来 这句话正在被一步一步的被应验
- Web端一直用的都是javaScript
- 移动端使用的ReactNative Weex Uniapp等框架实现的跨平台开发 其内部也是使用的javascript
- 小程序端也离不开javaScript
- 桌面端应用程序我们可以借助于Electron来开发
- 服务器端开发可以借助于Node环境使用JavaScript来开发
但是javaScript本身也存在了很多缺点
- ES5以及之前使用的var关键字。关于作用域的问题
- 最初javaScript设计的数组类型 也并不是连续的内存空间
- 直到今天javaScript 也没有假如类型检测这一机制
为什么类型检测会在代码中有很大的重要性呢?
因为我们都知道 一个错误越早发现越好
- 能在编写代码时候被编译器发现问题 那就不要到编译期间暴露
- 能在编译期间发现的问题 那就不要在运行期间暴露
- 能在运行期间发现的问题 那就不要在上线了客户手里暴露出来
这个错误很大的原因就是在javascript中我们对传入的参数没有一个类型的限制 导致在运行期间才会被暴露 这就会造成了早期开发人员关于类型思维的缺失 导致我们写出来的代码 将会有不安全性
早期为了解决javaScript 类型约束上的缺陷 很多公司都推出了自己的增加类型的方案
- 2014年 Facebook退出了flow来对于javaScript的类型检测
- 同年 微软也推出了TypeScript 1.0 版本
- 结果显然而知。TypeScript战胜了
- vue2.0当时也采用的是flow的类型检测
- vue3.0版本全栈开始转向TypeScript了 基本上98.3% 使用了TypeScript进行了重构
- Angular在很早期就使用TypeScript进行了项目重构并且需要使用TypeScript来进行开发;
- 甚至Facebook公司一些自己的产品也在使用TypeScript;
认识TypeScript
- GitHub说法:TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
- TypeScript官网:TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. 解释一下:TypeScript是javaScript的超集,它可以变编译成普通的 干净的 完整的javascript代码
- 超集: TypeScript 是一个加强版的javascript 。javascript拥有的特性TypeScript也全部拥有 并且TypeScript它是紧跟随着ECMA规范的 所以ES6-7-8.。。所有方法它也是支持的
- 在语言方面 不仅仅增加了类型约束 还增加了语法的扩展 还有枚举类型(Enum) 元组类型(Tuple)接口 (Interface) 泛型 等等
- 并且TypeScript最后会变异成javascript 所以不用去担心浏览器兼容的问题 并且编译的时候并不需要babel这样的工具
搭建TypeScript环境
因为TypeScript它是最终是会转换成javaScript的所以他有两种方式
- 通过webpack/vite 搭建一个ts环境 首先需要通过ts-loader 在webpack中配置tsloader 在需要一个tsconfig.json 来约束配置ts具体
npm install ts-loader typescript -d
在 webpack 配置 tsloader
module: {
rules: [
{
test: /\.ts$/,
loader: 'ts-loader',
},
],
},
需要一个 tsconfig.json 来配置 ts
tsc --init
- 通过node里面一个ts-node库来进行编译ts代码 并且跑到node环境下面
npm install ts-node -g npm install tslib @types/node -g ts-node math.ts其内部原理 也是帮你把ts代码转换成了js
正常默认情况下 所有 ts 代码和 js 代码都在同一个作用域 会有名称重复定义方法一解决 使用 export{}当成一个模块 (模块有自己的作用域)
TypeScript中类型
- any类型 我们无法确认一个变量的类型 并且可能会发生一些变化
// 可以给any类型进行任何任何操作和赋值
let message: any = '222';
- unknown类型 用于描述类型不确定的
// any和unknown
//unknown类型只能赋值给any和unknown类型 防止拿到该值 不可以在其他地方再去给新创建的变量赋值
// any类型可以赋予给任何类型
// 错误
// let message1: string = result;
3.(函数)void类型 指一个函数如果没有返回值 那么他的返回值默认是void
// 3.void
function sum(): void {}
4.(函数)never类型 表示永远不会发生值/表示函数是一个死循环或者抛出异常 应用场景:在封装核心库的时候可以使用 逻辑判断最后使用never 这样的话 就可以检查逻辑判断是否少了定义逻辑 因为never是永远不会有值的 所以在逻辑里面永远不会走到这逻辑 如果这个变量接受了三种类型 所对应的逻辑就应该会有三种
// message:string|number联合类型
function handleMessage(message: string | number | boolean) {
switch (typeof message) {
case 'string':
console.log('string');
break;
case 'number':
console.log('number');
break;
default:
const check: never = message;
}
}
5.(数组)tuple类型:元祖类型 多种数组元素的组合[string,number,number]
const inf1o: [string, number, number] = ['tring', 11, 22];
console.log(inf1o);
// 可以从泛型的地方把any改编成泛型 T 他们默认吧传入的类型赋值给T
function useState(state: any) {
let currentState = state;
const changeState = (newState: any) => {
currentState = newState;
};
const tuple: [any, (newState: any) => void] = [currentState, changeState];
return tuple;
}
const [counter, setChange] = useState(10);
setChange(110);
6.函数的参数类型和返回值
// 给函数添加类型注解() => void
type MyFunction = () => void;
const foo2: () => void = () => {};
const foo3: MyFunction = () => {};
/***
* 6.函数的参数类型和返回值类型
* 也可以给返回值加上类型注解
* 在开发中一般都默认不写 因为他会类型推导的
* */
function su1m(num: number, num2: number): number {
return num + num2;
}
7.匿名函数的参数类型:如果函数可以在上下文环境中推导出来参数类型,这个时候可以不加该参数的类型注解
/***
* 匿名参数的参数类型
* 通常情况下 定一个函数的时候 都会给函数的参数加上一个类型注解
* 如果函数可以在上下文环境中推导出来参数类型 这个时候可以不加该参数的类型注解
* 比如item 他的上下文环境就是names1 names1里面的元素都是已经确认的类型注解 所以在itme就可以添加类型注解
* */
const names1 = ['abc', 'cba', 'nba'];
names1.forEach(item => {});
8.函数参数的对象类型
// 8.函数参数对象类型
// { x: number; y: number,z?:number } z就是可选类型在参数后面加入一个? z?
function messtage(parms: { x: number; y: number; z?: number }) {
console.log(parms.x);
console.log(parms.y);
}
message({ x1: 111, y2: 222 });
9.联合类型 允许我们使用多种运算符 从现有类型中构建出新的类型
// 09 联合类型
function parintId(id: string | number) {
// 这种判断为narrow :缩小
if (typeof id === 'string') {
// ts在判断里面确认了id为string
console.log(id.toLocaleUpperCase);
} else {
}
}
10.可选类型和联合类型的关系 当我们一个参数是一个可选类型的时候 它其实类似表示的是这个参数是 类型|undefined 的联合类型
// 10 可选类型和联合类型的关系
// 当我们一个参数的是一个可选类型的时候 它其实类似上表示的是在这个参数是 类型|undefined 的联合类型
function foooo(message?: string) {}
function n1ame(params: string | undefined) {}
11.类型别名
11.类型别名 type用于定义类型别名
type uniontype = string | number | boolean;
function foowi(parent: uniontype) {}
12.类型断言 (as):有时候ts无法获取到具体的类型信息 这个时候就需要类型断言 相对而言把广泛的类型 具体到指定的类型
// 类型断言
// 1.
const el = document.getElementById('xsh') as HTMLImageElement;
el.src;
// 2.
class Person {}
class student extends Person {
studding() {}
}
function foooo1(p: Person) {
// 可以通过断言 来获取到子类的方法
(p as student).studding();
}
const stu = new student();
foooo1(stu);
// 3; 这种方式可以转换类型 但是会造成类型混乱 不推荐
const shdag = '213';
const dhdh: number = shdag as unknown as number;
13.非空类型断言:当我们确认传入的参数肯定有值 就要使用非空类型断言 14.可选链 ?. :当对象属性不存在的话 会短路(不执行了)返回一个undefined 如果存在 会继续执行 !! 把一个其它类型的转换成boolean
// 非空类型断言 当使用可选类型参数的时候 如果使用这个变量 编译期间会进行自动检测是否使用这个变量 跳过ts编译的检查
function message031(message?: string) {
//1. 通过编译的话 确认有值
if (message) {
console.log(message.length);
}
// 2.非空断言
console.log(message!.length);
}
// 可选链
type Person01 = {
name: string;
friend?: {
name: string;
age?: number;
};
};
const info1: Person01 = {
name: '3123',
friend: {
name: '3123',
age: 23,
},
};
console.log(info1.friend?.name);
15.子面量类型 字符串也可以是类型注解 一般都是配合联合类型一起使用
子面量类型 配合联合类型
let align: 'left' | 'right' | 'center' = 'right';
type alignA = 'left' | 'right' | 'center';
let align1: alignA = 'left';
16.子面量推导 正常情况下没法把一个字符串类型传给一个子面量类型 这样就需要三种方式
- 第一种 给创建的对象设置类型注解
- 第二种 给传入的methd参数一个断言 确认该参数的类型
- 第三种 给创建的对象增加一个断言 为const。代表着里面的属性为只读
type Method = 'POST' | 'GET';
function request(url: string, method: Method) {}
// 第一种情况 给创建的对象设置到类型注解
type infoREq = {
url: string;
method: Method;
};
const infoREq: infoREq = {
url: '2222',
method: 'POST',
};
request(infoREq.url, infoREq.method);
// 第二种办法 给传入的method一个断言 确认类型注解
request(infoREq.url, infoREq.method as Method);
// 第三种办法 给创建的对象增加一个断言 为const 里面属性为只读
const infoREq1 = {
url: '2222',
method: 'POST',
} as const;
request(infoREq1.url, infoREq1.method);
17.类型缩小 在指定的执行路径中 我们可以缩小比声明时更小范围的类型 这个过程称之为类型缩小 常见的类型保护/类型缩小有以下几种
- typeof
- ===/!==
- instanceof
- in
- 等等等
// 1. typeof 检查返回类型 ts可以在typeof里面明确拿到该变量的类型(唯一性)
// 2.平等/ (===/!==/swich)
// 3.instanceof 来检查一个值是否另一个类的实例 (这个要创建对应的实例的)
function foowuy(params: 'string' | Date) {
if (params instanceof Date) {
}
}
function fooclass(p: student | Person) {
if (p instanceof student) {
}
}
// 4.in 指定属性是否在对象或其原型链中 (判断子面量的)
// 定义一种函数的类型注解
type Fish = {
swimming: () => void;
};
type Dog = {
running: () => void;
};
function walk(animal: Fish | Dog) {
if ('swimming' in animal) {
animal.swimming();
}
}
18.函数类型 函数作为参数 给它类型
函数类型
// 1.函数作为参数类型
function bars(fn: () => void) {}
// 2.定义常量时候 编写函数的类型
type funs = (a1: number, b1: number) => void;
const funsct: funs = (ai: number, b1: number) => {};
- this的推导 在明确的对象(对象内部使用this) this是默认能推导出来的
// 这是在对象中使用this ts可以默认推导出来this的指向 可以拿到name
const info0101 = {
name: 'xsh',
eatch() {
console.log(this.name);
},
};
// 如果函数放在全局里面 他是推导不出来this指向谁
function eatch1(this: { name1: string }) {}
const infoso = {
name1: 'xsh',
eatch: eatch1,
};
// 这会编译报错的因为它无法推导this 有可能this还会复制操作 call apply 需要给函数一个this的类型注解 这样还可以通过call/apply调用
infoso.eatch();
infoso.eatch.call({ name1: 'shh' });
总结:因为vue2中使用的optionsAPI 在里面的this用的地方很多 但是ts里面对this的支持很不友好 ,因为现在vue3中的CompositionAPI使用的是setup函数 该函数中已经没有了this的定义了 所以ts更适合在vue3中使用
20.函数的重载(解决联合类型不能解决的问题 比如无法确定返回值的类型) 函数的名称相同 但是参数不同的几个参数类型注解 就是函数的重载
// 函数的声明
function add(n1: number, n2: number): number;
function add(n1: string, n2: string): string;
//函数的执行 不能直接调用这个 因为他只是找到声明函数 只执行函数体而已
function add(n1: any, n2: any): any {
if (typeof n1 === 'string' && typeof n2 === 'string') {
return n1.length + n2.length;
} else {
return n1 + n2;
}
}
// 它会根据参数依次去函数声明去寻找对应的类型 然后去执行函数的执行体
add(10, 20);
add('xsd', 'jfh');
21.类的使用 类的成员修饰符
- publice 修饰的是任意都可以访问到的 共有的属性或方法 一般都是默认的
- private 仅仅在当前类内部才可以访问到的私有属性 通过我们方法
- protected 修饰的仅仅是在类本身以及子类中可见的 受保护的属性和方法
- readonly 只可以访问 不可以修改 一般情况下都在constructor 构造器中赋值 但是赋值之后不可以修改了 属性本身不可以修改 但是它是对象类型 可以修改对象中的属性值
- static 静态属性/方法 可以不用new创造实例 直接。调用 面对对象的三大特征
- 封装:把共有的属性封装到一起
- 继承:继承是多态的前提 子类里面使用父类方法/属性
- 多态:父类引用(类型)指向子类对象 在某一个方法里面 相同的类型但是返回的不是一个结果 目的:写出更加通用的代码
22.抽象类 abstract 我们可能会给一个函数的参数 也就是调用者传入一个父类的类型注解 然后通过多态来实现更加灵活的调用 但是父类并不会对某些方法进行实现 所以定义在父类中的方法 加上abstract的方法就是抽象方法
- 抽象方法必须放在抽象类中 也就是用abstract声明的类
- 继承父类的子类 必须要有这个方法的定义以及函数体 或者该类也是一个抽象类
- 抽象类不能被new 实例化
抽象类
function makeArea(parms: Shap1) {
return parms.getArea();
}
abstract class Shap1 {
abstract getArea(): number;
}
class Rectangle extends Shap1 {
private width: number = 0;
private height: number = 0;
constructor(width: number, height: number) {
super();
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
class aaa extends Shap1 {
private width: number = 0;
private height: number = 0;
constructor(width: number, height: number) {
super();
this.width = width;
this.height = height;
}
getArea() {
return this.width + this.height;
}
}
const circle = new Rectangle(10, 20);
const aaa111 = new aaa(10, 20);
makeArea(circle);
makeArea(aaa111);
typeScript中的接口(interface)
- 接口的声明
- 可以使用类型(type)别名来声明一个对象类型
- 也可以使用接口(interface)别名来声明
//23。 接口
type obj1Type = {
name: string;
age: number;
};
const inf111o: obj1Type = {
name: '222',
age: 29,
};
interface IIobj1Type {
readonly name: string;
age: number;
friden?: {
na1me: string;
};
}
const infosua: IIobj1Type = {
name: '111',
age: 298,
friden: {
na1me: '222',
},
};
- 索引类型 可以定义索引类型
- 接口定义函数类型(定义函数类型时候推荐使用type 定义对象类型的时候使用接口)
索引类型
interface IIndexLang {
[index: string]: string;
}
const InfoLang: IIndexLang = {
0: '11',
2: '444',
'222': '222',
};
// 定义函数类型
interface IcalcFn {
(n1: number, n2: number): number;
}
function addsa(fns: IcalcFn) {}
4.接口的继承(支撑多继承)通过extends关键字
接口的继承(支持多继承)
interface aaba {
name: string;
eat: (n1: number) => number;
}
interface a11aba {
nam1e: string;
aaa?: (n21: number) => number;
}
interface functs extends a11aba, aaba {
sno: string;
}
const infshhdj: functs = {
name: '222',
nam1e: '4444',
sno: 'string',
eat: function nam111e(n1: number): number {
return 111;
},
};
5.交叉类型(&) 组合一个多类型的 可以用交叉类型 也可以用接口的继承
type Mytype1 = aaba | a11aba;
type Mytype2 = aaba & a11aba;
6.可以通过class实现接口(继承只能实现单继承类 一个类可以实现多个接口)
// 类实现接口
class fish extends Shap1 implements aaba, a11aba {
name: string = '2';
nam1e: string = '3';
eat(n1: number) {
return 111;
}
aaa(n2: number) {
return n2;
}
getArea(): number {
return 11;
}
}
function swim(wsimer: aaba) {}
swim(new fish());
// 编写一些公共api :面向接口编程
// 所有实现了接口都可以传入进去 只要有包括这个接口即可
总结 type和interface的区别
- type:非对象类型的 并且别名不可以重复
- interface:可以定义重复名的接口 是把接口属性进行合并
- 枚举类型 枚举其实就是将一组可能出现的值 一个一个列举出来 定义在一个类型中 枚举允许开发者定义一组命名常量 常量可以是数字或者字符串
enum Direction {
LEFT = 100,
RIGHT,
TOP,
BOTTOM,
}
function rurnDirection(direction: Direction) {
switch (direction) {
case Direction.LEFT:
break;
default:
break;
}
}
rurnDirection(Direction.LEFT);
typeScript 中的泛型
- 类型参数化(外界可以决定调用的参数用什么类型) 定义函数时 函数不决定这些参数类型 而是通过者以参数的形式告知 我这里应该使用什么类型
// 里面的Type就是 调用参数的时候决定的
function su22m<Type>(n1: Type, n2: Type): Type {
return n1;
}
// 调用方式1 明确的传入类型
su22m<number>(20, 30);
// 调用方式2 明确的传入类型
su22m(39, 30);
- 泛型的多个定义
function sum0203<T1, E1>(n1: T1, n2: E1) {}
sum0203<string, number>('sjdjd', 11);
3.泛型接口 定义接口和泛型结合起来 并且可以给泛型一个默认值
// 33.泛型接口
interface Iperson<T = string, T1 = number> {
name: T;
age: T1;
}
const p111: Iperson<string, number> = {
name: 'xsh',
age: 12,
};
4.泛型类的使用
class point<T> {
name: T;
age: T;
old: T;
constructor(x: T, y: T, z: T) {
this.age = x;
this.name = y;
this.old = z;
}
}
const paps = new point<string>('111', '222', '333');
const paps1: point<string> = new point('111', '222', '333');
5.泛型类型的约束(extends) 继承一个子类型/该接口的属性条件
泛型的约束
// 使用关键字extends 继承一个接口的属性 所有调用者的参数必须有我接口定义的属性(子类型)来进行给泛型进行约束
interface Ilenght {
length: number;
}
function getLength<T extends Ilenght>(arg: T) {}
getLength('2312');
getLength({ length: 222 });
getLength(['sss']);
6.模块化 typescript 支持两种方式来控制我们的作用域
- 模块化 每个文件都是一个独立的模块 支持ES Module 也支持CommonJS
- 命名空间(内部模块) 通过namespace来声明一个命名空间
文件有自己的作用域 -模块化 -命名空间namespace
// 比如在命名空间内部导出(export)才可以在外部获取到
namespace time {
export function foo(n1: number) {}
function bar() {}
}
// 在本文件中使用
time.foo;
// 在外部文件需要给namespace加上导出
export namespace time1 {
export function foo(n1: number) {}
function bar() {}
}
import { time1 } from './index';
time1.foo;
7.类型的查找 如果一个变量/一个模块/第三方库只要被类型声明过的 就可以使用 否则就会报错
ts类型的管理和查找规则 ts一共有两种文件 .ts .d.ts
- .ts 一般这些文件都是最终输出成 .js文件的 也是我们通常写代码的地方
- .d.ts(.declare.ts) 一般都是用来类型声明的 它仅仅用来做类型检测 告知ts我们有哪些类型
那么ts会在三种地方寻找类型的声明文件他不会转换成js代码 不需要写实现
- 内置的声明文件 : 它是ts在安装的时候自带的 帮助我们内置了js运行时候标准化的api声明文件 eg:Math,Date等内置类型 也包括DOM API比如 Window Document 源代码实在ts/tree/main/lib下面
- 外部定义类型声明(手动安装第三方库) 比如axios(在他自己的库编写了.d.ts) 这个就可以用 在安装的包里面有.d.ts声明文件 所以它可以使用 比如lodash 他就是纯js写的 没有.d.ts声明文件 所以无法使用 这个时候需要去社区中寻找一个该包的是否有没有.d.ts文件
- 该库的GitHub地址:github.com/DefinitelyT…
- 该库查找声明安装方式的地址:www.typescriptlang.org/dt/search?s… .如我们安装react的类型声明: npm i @types/react --save-dev
- 自定义声明 (declare)
// 37.自定义声明声明文件
declare module 'lodash' {
export function join(arr: any[]);
}
// 38.声明变量/函数/类 只要声明的变量 可以在对应的实现 就可以在任何地方直接调用
declare let nameXSH: string;
declare function name213123(params: string);
declare class nshads {
name: string;
constructor(name: string, age: number);
}
// 声明文件 以.jpg结尾的文件都已经声明了 可以在其他地方导入这种后缀名的文件
declare module '*.jpg';
// 声明命名空间
declare namespace $ {
export function ajxisj(set: any): any;
}
// 可以全局调用
$.ajxisj('sss');
// html文件
let nameXSH = 12;
console.log(nameXSH);