一尾流莺在此预祝大家虎年大吉 ~ !

JavaScript 是弱类型语言, 很多错误只有在运行时才会被发现,而 TypeScript 提供了一套静态检测机制, 可以帮助我们在编译时就发现错误。
随着前端工程化的发展,TypeScript 起到了相当大的作用,可以帮助团队开发避免很多错误的发生。
虽然使用 TypeScript 会让代码量增加,但是会让代码变的更加健壮,更好维护。另外,它的类型推断和语法提示功能属实是可以大幅度提升开发效率的。
比如你写了一个很长的变量,你要么忘了怎么拼的,要么怕写错去复制粘贴,但是有了 TypeScript 你可能只敲了一个字符,整个变量都出来了。
所以说懒才是生产力,这也是我喜欢 TypeScript 的一大原因。

可以先 声明变量 再 进行赋值,赋值只可以是 声明时定义的类型,否则会报错
let a: number;
let b: string;
a = 3;//true
a = '3';//false
b = 'hello';//true
b = 5;//false可以在 声明变量的同时进行赋值 ,赋值只可以是 声明时定义的类型,否则会报错
let c: boolean = false;
c = true;//true
c = 'hello';//false如果变量的声明和赋值是同时进行的,并且没有定义类型, TS 可以根据声明时的类型自动对变量进行类型检测
let d = false;
d = true;//true
d = 123; //false可以使用 | 来连接多个类型 (联合类型),变量值只可以是联合类型中的一种
let b: 'male' | 'female';
b = 'male';//true
b = 'female';//true
b = 'hello'; //false
let c: boolean | string;
c = true;//true
c = 'aa';//true
c = 3;//false当 不确定 一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里 共有的属性或方法:
length 不是 string 和 number 的共有属性,所以会报错。
访问 string 和 number 的共有方法 toString() 是没问题的:
function fn(something: string | number): number {
return something.length;//false
return something.toString();//true
}联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型:
第二行的 myFavoriteNumber 被推断成了 string,访问它的 length 属性不会报错。
而第四行的 myFavoriteNumber 被推断成了 number,访问它的 length 属性时就报错了。
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length); // true
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); // false any 表示任意类型,可以任意赋值一个变量,设置类型为 any 后,相当于对该变量关闭了 TS 的类型检测
可以显式或者隐式的设置类型为 any,只声明,不赋值 ,TS 解析器会自动判断变量类型为 any
在 任意值上 访问 任何属性 都是允许的,也允许调用 任何方法,可以认为,声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是 任意值
//显式any
let d: any;
d = 10;
d = 'aa';
d = false;
//隐式any
let e; unknown 表示未知类型的值 实际上就是一个类型安全的 any
let f: unknown;
f = 'hello';any 类型的变量 可以赋值给任意变量 any 会影响其他变量的类型检测
unknown 不可以赋值给其他类型的变量 否则报错
let a:any;
let b:string;
let c:unknown;
b = a;//true 同时关闭了b的类型检测
b = c;//false 可以先进行类型检测,再把 unknown 类型的值赋值给其他类型
let s: string;
if (typeof f === 'string') {
s = f;
}类型断言 告诉解析器变量的实际类型
两种写法
//写法一
s = f as string;
//写法二
s = <string>f;设置函数返回值的类型
function fn1(): string {
return 'hello';//true
return 3;//false
}void 用来表示空,以函数为例,表示没有返回值的函数
function fn2(): void {
return;
} never 表示永远不会返回结果
function fn3(): never {
throw new Error('错误了');
}object 表示一个 js 对象
let h: object;
h = {};
h = function () {}; 但是 js 中万物皆对象,所以检测对象几乎没有什么意义
主要是为了限制对象中的属性,而不是限制是不是一个对象
{} 用来指定对象中包含哪些属性,
属性后面加 ? 表示属性是可选属性 [] 表示任意属性
[propName: string] 任意属性名 any 任意类型
let i: { name: string; age?: number; [propName: string]: any };
i = { name: 'hzw' };
i = { name: 'hzw', age: 13 };
i = { name: 'hzw', sad: 'asd' };定义函数结构
语法:function fn(形参1:类型,形参2:类型,...)=>类型,参数不符合要求,过多,过少,都会报错
let k: (a: number, b: number) => number;
k = (n1, n2) => {
return n1 + n2;
};string[] 表示字符串数组 Array<string>
number[] 表示数值数组 Array<number>
任意类型 Array<any>
let m: string[];
let n: Array<number>;
let o:Array<any>;
m = ['a', 'b', 'c'];
n = [1,2,3];
o = ['a',1]数组的一些方法的参数也会根据数组在定义时约定的类型进行限制:push 方法只允许传入 number 类型的参数,但是却传了一个 "8" 类型的参数,所以报错了。
let arr: number[] = [1, 1, 2, 3, 5];
arr.push('8');//false元组 即固定长度的数组,元素过多或者过少都会报错
但是可以数组方法 push 向元组内添加元素,被称之为"越界元素",但是被添加的元素只能是元组中定义的类型,否则报错
let n: [string, number];
n = ['asd', 1];//true
n = ['asd', 1,'cc'];//false
n.push(1)//true
n.push("vv")//true
n.push(true)//falseenum 枚举类型 适用于在几个值当中选择的情况
enum Gender {
Male,
Famale,
}
let o: { name: string; gender: Gender };
o = {
name: 'hzw',
gender: Gender.Male,
};
console.log(o.gender === Gender.Male); //true& 表示同时
let p: { name: string } & { age: number };
p = { name: 'hzw', age: 14 };类型的别名
给类型起一个别名 用 myType 代替 1 | 2 | 3 | 4 | 5
var z: 1 | 2 | 3 | 4 | 5;
let l: 1 | 2 | 3 | 4 | 5;
type myType = 1 | 2 | 3 | 4 | 5;
let q: myType;
q = 1;使用 tsc 命令对 TS 文件进行编辑,编译文件时,使用 -w 指令后,TS 编译器会自动监视文件的变化,并在文件发生变化时对文件进行重新编译。
示例:
tsc xxx.ts -w如果直接使用 tsc 指令,则可以自动将当前项目下的所有 ts 文件编译为 js 文件。
但是能直接使用tsc命令的前提时,要先在项目根目录下创建一个ts的配置文件 tsconfig.json
tsconfig.json 是一个 JSON 文件,添加配置文件后,只需 tsc 命令即可完成对整个项目的编译
tsc["**/*"]** 表示任意目录 * 表示任意文件//表示根目录下src目录下任意目录任意文件
"include": [
"./src/**/*"
],ts 文件不需要被编译["node_modules", "bower_components", "jspm_packages"]//表示不编译根目录下src目录下test目录下任意目录任意文件
"exclude":[
"./src/test/**/*"
],//当前配置文件中会自动包含根目录下base.json中的所有配置信息
"extends": "./base.json", //只会编译根目录下01目录下的hello.ts
"files": ["./01/hello.ts"],compilerOptions 中包含多个子选项,用来完成对编译的配置TS 被编译为的 ES 版本ES3(默认)、ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ESNext(最新版本的ES)//我们所编写的ts代码将会被编译为ES6版本的js代码
"compilerOptions": {
"target": "ES6"
}CommonJS、UMD、AMD、System、ES2020、ESNext、None"compilerOptions": {
"module": "CommonJS"
}ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ESNext、DOM、WebWorker、ScriptHost ......"compilerOptions": {
"lib": ["ES6", "DOM"],
}outDir 用来指定编译后文件所在的目录rootDir 用来指定代码的根目录js 文件会和 ts 文件位于相同的目录,设置 outDir 后可以改变编译后文件的位置"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
}outFile 后,所有的全局作用域中的代码会合并到同一个文件中module 制定了 None、System 或 AMD 则会将模块一起合并到文件之中"compilerOptions": {
"outFile": "dist/app.js"
}js 文件进行编译,默认为 falsejs 代码是否符合语法规范,默认为 falsefalsefalsefalse"compilerOptions": {
"allowJs": true,
"checkJs": true,
"removeComments": false,
"noEmit": false,
"noEmitOnError": true
}true,设置后相当于开启了所有的严格检查any 类型thisbind、call 和 apply 的参数列表switch 语句包含正确的 breaktrue,忽略不可达代码;false,不可达代码将引起错误通常情况下,实际开发中我们都需要使用构建工具对代码进行打包;
TS 同样也可以结合构建工具一起使用,下边以 webpack 为例介绍一下如何结合构建工具使用 TS;
进入项目根目录,执行命令 npm init -y,创建 package.json 文件
npm i
webpack
webpack-cli
webpack-dev-server
typescript
ts-loader
clean-webpack-plugin
html-webpack-plugin
-D共安装了7个包:
webpackwebpack 的命令行工具webpack 的开发服务器ts 编译器ts 加载器,用于在 webpack 中编译 ts 文件webpack 中 html 插件,用来自动创建 html 文件webpack 中的清除插件,每次构建都会先清除目录根目录下创建 webpack 的配置文件 webpack.config.js
// 引入处理路径的模块
const path = require('path');
// 引入自动生成html的插件
const HTMLWebpackPlugin = require('html-webpack-plugin');
// 引入每次打包之前清除dist目录的插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
// webpack所有的配置信息都写在modules.exports中
module.exports = {
optimization:{
minimize: false // 关闭代码压缩,可选
},
// 指定入口文件
entry: './src/index.ts',
// 指定打包文件所在的目录
output: {
// 指定打包后文件的目录
path: path.resolve(__dirname, 'dist'),
// 打包后文件的名字
filename: 'bundle.js',
// 禁止使用箭头函数 可选
environment: {
arrowFunction: false,
},
},
// 指定webpack打包时要使用的模块
module: {
// 指定要加载的规则
rules: [
{
// test指定的是规则生效的文件
test: /.ts$/,
// 要使用的loader处理ts文件
use: [
'ts-loader',
],
// 要排除的文件
exclude: /node_modules/,
},
],
},
// 配置webpack插件
plugins: [
//自动生成html
new HTMLWebpackPlugin({
// 自定义title
title: '自定义title',
// 配置模板 打包后的html以该文件为模板
template: './src/index.html',
}),
//自动删除dist目录下的文件
new CleanWebpackPlugin(),
],
// 用来设置哪些文件可以作为模块被引入
resolve: {
extensions: ['.ts', '.js'],
},
};根目录下创建 tsconfig.json,配置可以根据自己需要
{
"compilerOptions": {
//用来指定TS被编译为的ES版本
"target": "ES2015",
//用来指定要使用的模块化的解决方案
"module": "ES2015",
//所有严格检查的总开关
"strict": true
}
}"scripts": {
//通过webpack进行打包
"build": "webpack",
//通过webpack启动网络服务器,并使用谷歌浏览器打开 支持热更新
"start": "webpack serve --open chrome.exe"
},在 src 下创建 ts 文件,并在并命令行执行 npm run build 对代码进行编译;
或者执行 npm start 来启动开发服务器;
webpack,开发中还经常需要结合 babel 来对代码进行转换,以使其可以兼容到更多的浏览器TS 在编译时也支持代码转换,但是只支持简单的代码转换,对于例如:Promise 等 ES6 特性,TS 无法直接转换,这时还要用到 babel 来做转换;npm i -D @babel/core @babel/preset-env babel-loader core-js共安装了4个包,分别是:
babel 的核心工具babel 的预定义环境babel 在 webpack 中的加载器core-js 用来使老版本的浏览器支持新版 ES 语法 // 配置babel
{
// 指定加载器
loader: 'babel-loader',
// 设置babel
options: {
// 设置预定义的环境
presets: [
[
// 指定环境插件
'@babel/preset-env',
// 配置信息
{
// 指定浏览器版本
targets: {
chrome: '58',
ie: '11',
},
// 指定corejs版本
corejs: '3',
// 使用corejs的方式 表示usage按需加载
useBuiltIns: 'usage',
},
],
],
},
},如此一来,使用 ts 编译后的文件将会再次被 babel 处理,使得代码可以在大部分浏览器中直接使用.
使用 类型声明 来描述一个对象的类型
type myType = {
name: string;
age: number;
};
const person1: myType = {
name: 'hzw',
age: 18,
}; interface myObiect {
name: string;
age: number;
}
const person2: myObiect = {
name: 'hzw',
age: 18,
};类型声明和接口的区别:
类型声明不可以重复写 接口可以重复写,内容会自动合并
interface myObiect {
name: string;
}
interface myObiect {
age: number;
}
const person2: myObiect = {
name: 'hzw',
age: 18,
};interface myClass {
name: string;
bark(): void;
}定义类时,可以使类去实现一个接口 使用 implements 关键字
class class1 implements myClass {
name: string;
constructor(name: string) {
this.name = name;
}
bark() {
console.log('旺旺旺');
}
}使用接口定义函数的类型
interface ISum {
(x: number, y: number): number;
}
let add: ISum = (x: number, y: number): number => {
return x + y;
};定义一个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定)此时泛型便能够发挥作用;
举个例子,下面这段代码 test 函数有一个参数类型不确定,但是能确定的时其返回值的类型和参数的类型是相同的;
由于类型不确定所以参数和返回值均使用了 any,但是很明显这样做是不合适的:
首先使用 any 会关闭 TS 的类型检查,其次这样设置也不能体现出参数和返回值是相同的类型;
function test(arg: any): any{
return arg;
}function test<T>(arg: T): T{
return arg;
}这里的<T>就是泛型,那么如何使用上边的函数呢?
使用时可以直接传递参数使用,类型会由 TS 自动推断出来,但有时编译器无法自动推断时还需要使用下面的方式
test(10)也可以在函数后手动指定泛型;
test<number>(10)可以同时指定多个泛型,泛型间使用逗号隔开
function test<T, K>(a: T, b: K): K{
return b;
}
test<number, string>(10, "hello");类中同样可以使用泛型:
class MyClass<T>{
prop: T;
constructor(prop: T){
this.prop = prop;
}
}也可以对泛型的范围进行约束
使用 T extends MyInter 表示泛型 T 必须是 MyInter 的子类,不一定非要使用接口类和抽象类同样适用;
interface MyInter{
length: number;
}
function test<T extends MyInter>(arg: T): number{
return arg.length;
}class 关键字来定义一个类class Person {}class Person {
name: String = 'hzw';
age: number = 18;
}
const hzw = new Person();
console.log(hzw.name);//hzw
console.log(hzw.age);18static 关键字定义静态属性 通过类访问class Person {
static age: number = 20;
}
console.log(Person.age);//20readonly 关键字定义只读属性static 和 readonly 关键字定义静态只读属性,static 只能在 readonly 前面class Person {
readonly color: string = 'red';
static readonly height: string = '50px';
}class Person {
sayHello(): void {
console.log('hello');
}
}
const hzw = new Person();
hzw.sayHello();//hellostatic 关键字定义静态方法 通过类访问class Person {
static sayHello(): void {
console.log('hello,static');
}
}
Person.sayHello();//hello,staticclass Dog {
constructor() {
console.log("构造函数调用了")
}
}
const dog1 = new Dog();//构造函数调用了this 向新建的对象中添加属性class Dog {
name: String;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}this 表示当前的实例,在实例方法中可以通过 this 来表示当前调用方法的对象class Dog {
name: String;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
bark(): void {
//在实例方法中可以通过this来表示当前调用方法的对象
console.log(this);
}
}
const dog1 = new Dog('小黑', 3);
dog1.bark();//小黑: Dog {name: "小黑", age: 3}通过继承可以将多个类中共有的代码写在一个父类中,这样只需要写一次即可让所有的子类都同时拥有父类中的属性
Animal class Animal {
name: String;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
bark(): void {
console.log(`动物在叫~~`);
}
}DogDog 类继承 Animal 类Animal 被称为父类,Dog 被称为子类class Dog extends Animal {
}
const dog = new Dog('旺财', 3);
console.log(dog);// Dog {name: "旺财", age: 3}
dog.bark();//动物在叫~~CatCat 类继承 Animal 类class Cat extends Animal {
}
const cat = new Cat('小白', 4);
console.log(cat);// Cat {name: "小白", age: 4}
cat.bark();//动物在叫~~子类可以添加自己独有的属性和方法
class Dog extends Animal {
run() {
console.log(`${this.name}在跑~~~`);
}
}
const dog = new Dog('旺财', 3);
dog.run();//旺财在跑~~~如果子类添加了父类相同的方法,子类方法会覆盖父类,被称之为方法重写
class Dog extends Animal {
bark(): void {
console.log('旺旺旺');
}
}
const dog = new Dog('旺财', 3);
dog.bark();//旺旺旺在类的方法中,super 表示当前类的父类,在子类中可以使用 super 来完成对父类的引用
class Animal {
name: String;
constructor(name: string) {
this.name = name;
}
bark(): void {
console.log(`动物在叫~~`);
}
}
//在类的方法中 super表示当前类的父类
class Dog extends Animal {
bark() {
super.bark();
}
}
const dog = new Dog('旺财');
dog.bark();//动物在叫~~子类继承父类时,如果子类中也定义了构造方法,必须调用父类的构造方法!
//在类的方法中 super表示当前类的父类
class Dog extends Animal {
age: number;
constructor(name: string, age: number) {
//调用父类的构造函数
super(name);
this.age = age;
}
}abstract 开头的类被称为抽象类abstract 开头的方法叫做抽象方法,抽象方法没有方法体只能定义在抽象类中,继承抽象类时抽象方法必须要重写,否则报错 abstract class Animal {
name: String;
constructor(name: string) {
this.name = name;
}
abstract bark(): void;
}
class Dog extends Animal {
bark() {
console.log('旺旺旺');
}
}默认情况下,对象的属性是可以任意的修改的,为了确保数据的安全性,在 TS 中可以对属性的权限进行设置
TS 中属性具有三种修饰符:
public(默认值),可以在类、子类和对象中修改protected ,可以在类、子类中修改private ,可以在类中修改class Person{
public name: string; // 写或什么都不写都是public
public age: number;
constructor(name: string, age: number){
this.name = name; // 可以在类中修改
this.age = age;
}
sayHello(){
console.log(`大家好,我是${this.name}`);
}
}
class Employee extends Person{
constructor(name: string, age: number){
super(name, age);
this.name = name; //子类中可以修改
}
}
const p = new Person('孙悟空', 18);
p.name = '猪八戒';// 可以通过对象修改class Person{
protected name: string;
protected age: number;
constructor(name: string, age: number){
this.name = name; // 可以修改
this.age = age;
}
sayHello(){
console.log(`大家好,我是${this.name}`);
}
}
class Employee extends Person{
constructor(name: string, age: number){
super(name, age);
this.name = name; //子类中可以修改
}
}
const p = new Person('孙悟空', 18);
p.name = '猪八戒';// 不能修改
//Property 'name' is protected and only accessible within class 'Person' and its subclasses.class Person{
private name: string;
private age: number;
constructor(name: string, age: number){
this.name = name; // 可以修改
this.age = age;
}
sayHello(){
console.log(`大家好,我是${this.name}`);
}
}
class Employee extends Person{
constructor(name: string, age: number){
super(name, age);
this.name = name; //子类中不能修改
//Property 'name' is private and only accessible within class 'Person'.
}
}
const p = new Person('孙悟空', 18);
p.name = '猪八戒';// 不能修改
//Property 'name' is private and only accessible within class 'Person'.private,直接将其设置为 private 将导致无法再通过对象修改其中的属性setter 方法,设置属性的方法叫做 getter 方法class Person{
private _name: string;
constructor(name: string){
this._name = name;
}
//get 属性名
get name(){
return this._name;
}
//set 属性名
set name(name: string){
this._name = name;
}
}
const p1 = new Person('孙悟空');
// 实际通过调用getter方法读取name属性
console.log(p1.name);
// 实际通过调用setter方法修改name属性
p1.name = '猪八戒'; name:stringthis.name=nameclass Person{
constructor(public name: string){
}
}
//等价于
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}到这为止,你就可以使用 ts 进行初步的开发了。但是对于 ts 来说,掌握这些是远远不够的。
再给大家推荐两个学习 ts 的网站。