TypeScript入门基础

文章目录

    • 1 TypeScript介绍
      • 1.1 什么是TypeScript?
      • 1.2 为什么需要 TypeScript
      • 1.3 JS与TS的相关知识
      • 1.4 TypeScript特性
      • 1.5 TypeScript安装
    • 2 TypeScript的基本类型
      • 2.1 类型声明
      • 2.2 类型分类
        • 2.2.1 number
        • 2.2.2 string
        • 2.2.3 boolean
        • 2.2.4 字面量
        • 2.2.5 any
        • 2.2.6 unknown
        • 2.2.7 void
        • 2.2.8 never
        • 2.2.9 object
        • 2.2.10 array
        • 2.2.11 tuple
        • 2.2.12 enum
        • 2.2.13 联合类型
        • 2.2.14 交叉类型
        • 2.2.15 类型别名
        • 2.2.16 类型断言
    • 3 编译选项
      • 3.1 自动编译文件
      • 3.2 自动编译整个项目
      • 3.3 tsconfig.json的配置选项
        • 3.3.1 include
        • 3.3.2 exclude
        • 3.3.3 extends
        • 3.3.4 files
        • 3.3.5 compilerOptions
        • 3.3.6 其他配置
        • 3.3.7 严格检查
        • 3.3.8 额外检查
    • 4 使用Webpack打包TS代码
      • 4.1 初始化项目
      • 4.2 安装依赖
      • 4.3 配置webpack
        • 4.3.1 配置TS编译选项
        • 4.3.2 修改package.json配置
      • 4.4 配置babel
        • 4.4.1 安装依赖
        • 4.4.2 修改配置文件
        • 4.4.3 项目使用
      • 4.5 生产环境和开发环境分别配置
      • 4.6 编写代码
      • 4.7 配置命令打包代码
    • 5 面向对象
      • 5.1 类
      • 5.2 继承
      • 5.3 抽象
      • 5.4 接口
      • 5.5 封装
        • 5.5.1 静态属性和只读属性
        • 5.5.2 属性修饰符
        • 5.5.3 属性存取器
      • 5.6 泛型
    • 参考

1 TypeScript介绍

1.1 什么是TypeScript?

TypeScript简称TS,它是以JavaScript为基础构建的语言,是JavaScript的超集(TS是JS的升级版),扩展了JavaScript并添加了类型,可以在任何支持JavaScript的平台中执行。
TypeScript入门基础_第1张图片

TypeScript入门基础_第2张图片

1.2 为什么需要 TypeScript

JavaScript是弱类型语言,很多错误只有在运行时才会被发现,而TypeScript提供了一套静态检测机制,可以帮助我们在编译时就发现错误 。

1.3 JS与TS的相关知识

JS是一门标准的弱类型且动态类型的语言,所以JS灵活多变,缺失类型系统的可靠性。

TS是一门标准的强类型且静态类型的语言。

强类型VS弱类型

强类型:在语言层面就限制了函数的实参类型必须与形参类型相同

弱类型:语言层面不会限制实参的类型; 我们定义一个变量,不需要考虑它的类型

形参相当于函数中定义的变量,实参是在运行时的函数调用时传入的参数。

强类型语言的特点就是不允许程序在发生错误后继续执行

弱类型的一个特点就是在计算时,不同类型之间对使用者透明地对变量进行隐式转换。

相较于弱类型语言,强类型有着明显的优势

  • 错误更早暴露(编码阶段就能发现并避免类型异常);
  • 代码更智能,编码更准确(编辑器时时刻刻都知道变量的类型,作出提示);
  • 重构更牢靠(重构时,能及时暴露出相关代码异常信息,可及时准确的对耦合代码进行修改);
  • 减少不必要的类型判断(在编码阶段,参数类型已经确定,就不用再对类型做约定判断);

静态类型VS动态类型

静态类型语言:一个变量在声明时它的类型就是明确的,声明过后,它的类型就不可修改;

动态类型语言:在代码运行阶段才能明确变量的类型,而且变量的类型随时可以改变;

两者区别:是否允许随时修改变量的类型 ,

如何区分:一门语言在编译时报错,那么是静态类型,如果在运行时报错,那么是动态类型。

静态类型的好处:有利于代码的重构,可以在编译器编译时捕获错误。这样我们在编写代码的时候就能避免很多错误,提高编码的效率!

静态类型语言 动态类型语言
对类型极度严格 对类型非常宽松
立即发现错误 不能立即发现(单元测试)
运行时性能好 运行时性能差
自文档化 可读性差(工具生成文档)

TypeScript入门基础_第3张图片

1.4 TypeScript特性

  • 支持最新的JavaScript新特性(ES6-ES12),添加ES不具备的新特性
  • 支持代码静态检查,拥有了更加严格的语法和更强大的功能 ,TS可以在代码执行前就完成代码的检查,
    减小了运行时异常的出现的几率;
  • 同样的功能,TS的代码量要大于JS,但由于TS的代码结构更加清晰,变量类型更加明确,在后期代码的维护中TS却远远胜于JS;
  • 支持诸如C,C++,Java,Go等后端语言中的特性 (枚举、泛型、类型转换、命名空间、声明文件、类、接口等) ,有强大的开发工具以及丰富的配置选项。

1.5 TypeScript安装

安装Node:https://nodejs.org/zh-cn/

安装ts

 npm i -g typescript 

将ts文件编译为js文件(生成一个和ts文件名称相同的js文件),然后执行js文件

//编译ts文件
tsc XXX.ts
//运行js文件
node XXX.js

也可以安装ts-node直接运行ts文件 ,不需要编译为js

 /安装ts-node
npm i -g ts-node 
//直接运行ts文件
ts-node XXX.ts

2 TypeScript的基本类型

2.1 类型声明

类型声明给变量设置了类型,使得变量只能存储某种类型的值。

通过类型声明可以指定TS中变量(参数字、形参)的类型,指定类型后,当为变量赋值时,TS编译器会自动检查值是否符合类型声明,符合则赋值,否则报错。

语法如下:

// 声明一个变量
let 变量: 类型;

// 声明一个变量并给其赋值
let 变量: 类型 =;

// 声明一个函数
function fn(参数: 类型, 参数: 类型): 函数返回值类型{
    ...
}
// a 的类型设置为了number,在以后的使用过程中a的值只能是数字
let a: number;
a = 10;
a = 33;
a = 'hello'; // 此行代码会报错,因为变量a的类型是number,不能赋值字符串

自动类型判断:TS拥有自动的类型判断机制,当对变量的声明和赋值是同时进行的,TS编译器会自动判断变量的类型,所以变量的声明和赋值时同时进行可以省略掉类型声明。

:虽然可以省略类型,但声明类型对定义函数有极大的提升

let a = 1;
a = 'sj';//报错 不能将类型'string'分配给类型'number'

2.2 类型分类

类型 例子 描述
number 1, -33, 2.5 任意数字
string ‘hi’, “hi”,模板字符串`` 任意字符串
boolean true、false 布尔值true或false
字面量 其本身 限制变量的值就是该字面量的值
any * 任意类型
unknown * 类型安全的any
void 空值(undefined) 没有值(或undefined)
never 没有值 不能是任何值
object {name:‘孙悟空’} 任意的JS对象
array [1,2,3] 任意JS数组
tuple [4,5] 元组,TS新增类型,固定长度数组
enum enum{A, B} 枚举,TS中新增类型

2.2.1 number

number:任意

let a: number;
// a 的类型设置为了number,在以后的使用过程中a的值只能是数字
a = 10;
a = 33;
// a = 'hello'; // 此行代码会报错,因为变量a的类型是number,不能赋值字符串

2.2.2 string

string:任意字符串

let color: string = "blue";
color = 'red';

2.2.3 boolean

boolean:布尔值true或false

let isDone: boolean = false;

2.2.4 字面量

字面量:字面量不仅可以表示值,还可以表示类型,即所谓的字面量类型。也可以使用字面量去指定变量的类型,通过字面量可以确定变量的取值范围。

let a: 10; //a的值只能为10

//可以使用|来连接多个类型(|为联合类型表示取值可以为多种类型中的一种)
let b: 'male' | 'female'; //b的值只能二选一
b = 'male';
b = 'female';

let c: boolean | string;
c = true;
c = 'sd ';

2.2.5 any

any:任意类型

一个变量设置为any后相当于对该变量关闭了TS的类型检测,故不建议使用。声明变量如果不指定类型,则TS解析器会自动判断变量的类型为any(隐式的any)。

let d: any = 4;
d = 'hello';
let e ;
e=1;
e=true;
//d的值为any,它可以赋值给任意变量
let s:string
s=d;//这样变量s的类型检测也关闭了

2.2.6 unknown

unknown:类型安全的any,不能直接赋值给其他变量

let e: unknown;
e="hello"
let f:string
f=e;//报错 类型unkonwn不能赋值给string类型

2.2.7 void

void:void表示没有任何类型,和其他类型是平等关系,不能直接赋值:

声明一个void类型的变量没有什么大用,我们一般也只有在函数没有返回值时去声明。

//void用来表示空
function fn(): void {
    return undefined//除了undefined的其他类型的返回值会报错
}

2.2.8 never

never:不能是任何值

//never 表示永远不会返回结果 连undefined都不能返回,一般用来报错
function fn1(): never {
 throw new Error('报错了');
}

2.2.9 object

object:任意的JS对象

//1.定义对象结构的类型声明
/*
语法:{属性名:属性值,属性名:属性值} 
在属性名后边加上?,表示属性是可选的
*/

let b: { name: string; age?: number };
b = { name: '孙悟空', age: 18 };

//[xxx:string]:any 表示任意类型的属性
let c: { name: string; [propName: string]: any };
c = { name: '猪八戒', age: 18, gender: '男' };

//2.定义函数结构的类型声明
//我们一般会限制函数有几个参数,参数的类型,返回值的类型,可以以一种类似箭头函数的方式来声明一个函数类型的变量
/* 
语法:(形参:类型,形参:类型...) => 返回值
*/

let d: (a: number, b: number) => number;
d = function (n1, n2): number {
  return n1 + n2;
};

2.2.10 array

array:任意JS数组

/*
语法:类型[]或Array<类型> 
*/
//如string[]表示字符串数组 (在元素类型后面接上[])  Array表示数字数组

let e: string[]
//or
let e: Array<string>;

2.2.11 tuple

tuple: 元组,TS新增类型,就是固定长度(数量)数组

元组中包含的元素,必须与声明的类型一致,数量要对应,且顺序也要一一对应

/* 
语法:[类型,类型,类型] 
*/

let h: [string,string,number];//里面两个字符串类型,一个数字
h = ['s', 'f',1];//必须对应两个字符串,一个数字,顺序也不能变化

2.2.12 enum

比如我们在定义一个对象的时候 let person = { name: ‘zs’, gender: ‘male’ }。像gender这种属性的取值一般只有两个,所以在存储的时候用数字来代替,可以提高效率,可以定义0表示女性,1表示男性

enum:枚举,TS中新增类型,把所有可能的情况全部列出来,适合用于多个值进行选择的场景,

//enum:结果是多个值进行选择
enum Sex {
  Male,
  Female,
}
let i: { name: string; sex: Sex };
i = {
  name: '张三',
  sex: Sex.Male,
};
console.log(i.sex === Sex.Male);//true

2.2.13 联合类型

联合类型:表示取值可以为多种类型中的一种,使用 | 分隔每个类型。

let h: string | number;
h = '张三';
h = 18;

2.2.14 交叉类型

交叉类型:是将多个类型合并为一个类,使用&,表示同时

let j: { name: string } & { age: number };
j = { name: '张三', age: 18 };

2.2.15 类型别名

类型别名:用来给一个类型起个新名字。 仅仅是给类型取了一个新的名字,并不是创建了一个新的类型,类型别名常用于联合类型

type Message = string | string[];
let greet = (message: Message) => {
  // ...
};

2.2.16 类型断言

类型断言:有些情况下,变量的类型对于我们来说是很明确,但TS编译器并不清楚,可以通过类型断言来告诉编译器变量的类型,断言有两种形式:

//类型断言,可以用来告诉编译器变量的实际类型
/* 
语法: (变量 as 类型) 或  < 类型 > 变量 
 */

let e: unknown;
e='ss' //e实际是字符串,但e的类型设置的是unkonwn
let f: string;
f=e//报错

f = e as string;//告诉ts编译器e就是字符串,这样就不会出现上述f=e报错的情况
// 或
f = <string>e;

3 编译选项

3.1 自动编译文件

编译文件时,使用 -w 指令后,TS编译器会自动监视文件的变化,并在文件发生变化时对文件进行重新编译。

tsc xxx.ts -w

3.2 自动编译整个项目

一个文件一个文件的编译太麻烦了,我们可以对整个项目进行编译

首先在项目根目录下创建一个ts的配置文件 tsconfig.json,然后就可以使用tsc指令,编译项目下的所有ts文件为js文件,当然也可以开启监视模式tsc -w监视所有的文件

3.3 tsconfig.json的配置选项

tsconfig.json配置总览

{
  "compilerOptions": {
    "target": "es5",   // 指定 ECMAScript 目标版本: 'ES5'
    "module": "commonjs",  // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "moduleResolution": "node",     // 选择模块解析策略
    "experimentalDecorators": true,  // 启用实验性的ES装饰器
    "allowSyntheticDefaultImports": true,   // 允许从没有设置默认导出的模块中默认导入。
    "sourceMap": true,   // 把 ts 文件编译成 js 文件的时候,同时生成对应的 map 文件
    "strict": true,  // 启用所有严格类型检查选项
    "noImplicitAny": true,  // 在表达式和声明上有隐含的 any类型时报错
    "alwaysStrict": true,  // 以严格模式检查模块,并在每个文件里加入 'use strict'
    "declaration": true,   // 生成相应的.d.ts文件
    "removeComments": true,   // 删除编译后的所有的注释
    "noImplicitReturns": true,  // 不是函数的所有返回路径都有返回值时报错
    "importHelpers": true,  // 从 tslib 导入辅助工具函数
    "lib": ["es6", "dom"],  // 指定要包含在编译中的库文件
    "typeRoots": ["node_modules/@types"],
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": [  
    "./src/**/*.ts"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "**/*.test.ts",
  ]
}

3.3.1 include

  • 指定需要编译的ts文件目录,是一个数组,其中 *表示任意文件 **表示任意目录
  • 默认值:["**/*"]
"include":["src/**/*", "test/**/*"] // 所有src目录和test目录下的文件都会被编译

3.3.2 exclude

  • 指定不需要被编译的文件目录
  • 默认值:["node_modules", "bower_components", "jspm_packages"]
"exclude": ["./src/hello/**/*"]  // src下hello目录下的文件都不会被编译

3.3.3 extends

  • 定义被继承的配置文件(和引入外部文件类似)

    "extends": "./configs/base" // 自动包含configs目录下base.json中的所有配置信息
    

3.3.4 files

  • 指定被编译文件的列表,只有需要编译的文件少时才会用到(和include类似)

  • 列表中的文件都会被TS编译器所编译

    "files": [
    "core.ts",
    "sys.ts",
    "types.ts",
    "scanner.ts",
    "parser.ts",
    "utilities.ts",
    "binder.ts",
    "checker.ts",
    "tsc.ts"
    ]
    

3.3.5 compilerOptions

重要的编译选项,在compilerOptions中包含多个子选项,用来完成对编译的配置

target:设置ts代码编译的目标版本

对于选项有哪些可选值,我们可以随便写一个值,编辑器会提示我们有哪些可选值

"compilerOptions": {
    //可选值: “ES3”(默认), “ES5”, “ES6”, “ES2015”, “ES2016”, “ES2017”, “ES2018”, “ES2019”, “ES2020”, “ES2021”, “ESNext”.
    "target": "ES6"//ts代码将会被编译为ES6版本的js代码
}

lib:指定代码运行时所包含的库(宿主环境),一般运行在浏览器环境下的,就不需要自己单独设置这个

"compilerOptions": {
//可选值:“ES5”, “ES6”, “ES2015”...很多
    "lib": ["ES6", "DOM"]
}

module:设置编译后代码使用的模块化系统

"compilerOptions": {
//可选值:“CommonJS”, “AMD”, “System”, “UMD”, “ES6”, “ES2015”, “ES2020”, “ESNext”, “None”, “es2022”, “node12”, “nodenext”
    "module": "CommonJS"
}

outDir:指定编译后文件的所在目录

"compilerOptions": {
    "outDir": "./dist"//编译过的js文件将会生成到dist目录。可以将源码与编译后的代码分开存放
}

outFile:将所有的文件编译为一个js文件

  • 默认会将所有的编写在全局作用域中的代码合并为一个js文件,如果 module 制定了 None、System 或 AMD 则会将模块一起合并到文件之中,这种合并由打包工具去做。
"compilerOptions": {
    "outFile": "./dist/app.js"
}

rootDir:指定代码的根目录,默认情况下编译后文件的目录结构会以最长的公共目录为根目录,通过rootDir可以手动指定根目录

"compilerOptions": {
"rootDir": "./src"
}

3.3.6 其他配置

//是否对js文件编译,默认值:false
"allowJs":false,
    
//是否对js文件进行语法检查,默认值:false
"checkJs":false

//是否删除注释,默认值:false
"removeComments":false

//不生成编译后的文件,默认值:false
"noEmit":false

//当有错误的时候不生成编译后的文件,默认值:false
"noEmitOnError":false

//是否生成sourceMap,默认值:false
"remove":false

3.3.7 严格检查

//是否启用所有的严格检查(总开关),设置true后相当于开启了所有的严格检查,后面的检查不需要在写了,默认值:false
"strict":false

//是否总是以严格模式对代码进行编译,默认值:false
"alwaysStrict":false

//是否允许隐式的 any 类型,默认值:false
"noImplicitAny":false

//是否允许隐式的 this,默认值:false
"noImplicitThis":false

//严格检查bind、call和apply的参数列表,默认值:false
"strictBindCallApply":false

//严格检查函数的类型,默认值:false
"strictFunctionTypes":false

//检查是否存在空值,默认值:false  可以用a?.b判断a是否是空值
"strictNullChecks":false

//严格检查属性是否初始化,默认值:false
"strictPropertyInitialization":false

3.3.8 额外检查

//是否检查switch语句包含正确的break
"noFallthroughCasesInSwitch":false

//是否检查函数没有隐式的返回值
"noImplicitReturns":false

//是否检查未使用的局部变量
"noUnusedLocals":false

//是否检查未使用的参数
"noUnusedParameters":false

//检查不可达代码;true:忽略不可达代码,false:不可达代码将引起错误
"allowUnreachableCode":false

4 使用Webpack打包TS代码

通常情况下,实际开发中我们都需要使用构建工具对代码进行打包;TS同样也可以结合构建工具一起使用,下边以webpack为例介绍一下如何结合构建工具使用TS;

4.1 初始化项目

通过执行命令 npm init -y 初始化一个项目并创建package.json文件

使用tsc --init 创建ts的配置文件tsconfig.json

创建src/index.ts文件,用来编写ts代码

4.2 安装依赖

安装以下依赖包

npm i -D webpack webpack-cli webpack-dev-server typescript ts-loader clean-webpack-plugin
  • webpack:构建工具webpack

  • webpack-cli:webpack的命令行工具

  • webpack-dev-server:webpack的开发服务器

  • typescript:ts编译器

  • ts-loader:ts加载器,用于在webpack中编译ts文件

  • html-webpack-plugin:webpack中html插件,用来自动创建html文件

  • clean-wepack-plugin:webpack中的清除插件,每次构建都会先清除目录

4.3 配置webpack

根目录下创建webpack的配置文件webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
  // 指定入口文件
  entry: "./src/index.ts",

  // 开发模式使用,方便查错误
  devtool: "inline-source-map",

  // 配置服务器
  devServer: {
    contentBase: "./dist",
  },

  // 指定打包文件所在目录
  output: {
    path: path.resolve(__dirname, "dist"),
    //打包后的文件
    filename: "bundle.js",
    environment: {
      arrowFunction: false, // 关闭webpack的箭头函数,可选
    },
  },

  // 用来设置引用模块
  resolve: {
   extensions: [".ts", ".js"],//以ts js结尾的文件都可以作为模块使用
  },

  // 配置webpack的loader(打包使用的模块)
  module: {
      //加载规则
    rules: [
      {
          //规则生效的文件
        test: /.ts$/,//以ts结尾的文件
          //要使用的loader
        use: {
          loader: "ts-loader",
        },
          //排除的文件
        exclude: /node_modules/,
      },
    ],
  },

  // 配置webpack的插件
  plugins: [
     // 构建前清除目录
    new CleanWebpackPlugin(),
      //自动创建html文件
    new HtmlWebpackPlugin({
      template: "./src/index.html",
    }),
  ],
};

4.3.1 配置TS编译选项

根目录下创建tsconfig.json,配置可以根据自己需要

{
   "compilerOptions": {
       "target": "ES2015",
       "module": "ES2015",
       "strict": true
   }
}

4.3.2 修改package.json配置

修改package.json添加如下配置

{
   ...
   "scripts": {
       "test": "echo \"Error: no test specified\" && exit 1",
       "build": "webpack",
       "start": "webpack serve --open chrome.exe"
   },
   ...
}

4.4 配置babel

除了webpack,开发中还经常需要结合babel来对代码进行转换以使其可以兼容到更多的浏览器,通过以下步骤可以将babel引入到项目中。

4.4.1 安装依赖

安装以下依赖包

npm i -D @babel/core @babel/preset-env babel-loader core-js
  • @babel/core:babel 的核心工具
  • @babel/preset-env:babel 的预定义环境
  • babel-loader:babel 在webpack中的加载器
  • core-js:core-js 用来使老版本的浏览器支持新版ES语法

4.4.2 修改配置文件

修改webpack.config.js配置文件

这样修改后,使用ts编译后的文件将会再次被babel处理,使得代码可以在大部分浏览器中直接使用,可以在配置选项的targets中指定要兼容的浏览器版本。

module: {
  rules: [
    {
      //test指定的是规则生效的文件
      test: /.ts$/,
      //要使用的loader
      use: [
        //配置babel
        {
          //指定加载器
          loader: "babel-loader",
          // 设置babel
          options: {
            // 设置预定义的环境
            presets: [
              [
                // 指定环境的插件
                "@babel/preset-env",
                // 配置信息
                {
                  // 要兼容的目标浏览器  
                  targets: {
                    chrome: "58",
                    ie: "11",
                  },
                  // 指定corejs的版本
                  corejs: "3",
                  // 使用corejs的方式 "usage" 表示按需加载
                  useBuiltIns: "usage",
                },
              ],
            ],
          },
        },
        {
          loader: "ts-loader",
        },
      ],
      exclude: /node_modules/,
    },
  ];
}

4.4.3 项目使用

在src下创建ts文件,并在并命令行执行npm run build对代码进行编译;

或者执行npm start来启动开发服务器;

4.5 生产环境和开发环境分别配置

以上是一些基本的配置,但是在实际开发中,webpack在配置开发环境与生产环境时,配置的有些东西不太相同,所以我们应该分开写我们生产环境和开发环境的webpack配置

所以我们就在根目录下创建build文件夹存放我们的webpack配置文件

TypeScript入门基础_第4张图片

安装webpack-merge

//基于公共配置通过webpack-merge合并开发或者生产环境的特有配置,生成完整的开发或者生产环境配置
npm i -D webpack-merge

基本配置webpack.base.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/index.ts",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
    environment: {
      arrowFunction: false, // 关闭webpack的箭头函数,可选
    },
  },
  resolve: {
    extensions: [".js", ".ts"],
  },
  module: {
    rules: [
      {
        test: /.ts$/,
        use: [
          {
            loader: "ts-loader",
          },
        ],
        exclude: /node_modules/,
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",
    }),
  ],
};

开发环境配置webpack.dev.config.js

module.exports = {
  devtool: "inline-source-map",
};

生产环境配置webpack.pro.config.js

const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
  plugins: [new CleanWebpackPlugin()],
};

配置主文件webpack.config.js

const { merge } = require("webpack-merge");
const baseConfig = require("./webpack.base.config");
const devConfig = require("./webpack.dev.config");
const proConfig = require("./webpack.pro.config");

module.exports = (env, argv) => {
  let config = argv.mode === "development" ? devConfig : proConfig;
  return merge(baseConfig, config);
};

4.6 编写代码

index.ts

const box = document.querySelector('#app')
const hello: string = 'Hello TS'

if (box !== null) {
  box.innerHTML = hello
}

index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>TS & webpack</title>
</head>

<body>
  <div id="app"></div>
</body>

</html>

4.7 配置命令打包代码

修改package.json添加如下配置

{
  ......
  "scripts": {
    "start": "webpack-dev-server --mode=development --config ./build/webpack.config.js",
    "build": "webpack --mode=production --config ./build/webpack.config.js"
  },
  ......
}

在src下创建ts文件,并在并命令行执行npm run build对代码进行编译,执行npm start来启动开发服务器

5 面向对象

面向对象(OOP Object-Oriented Programming)是一种优秀的程序设计方法,它的基本思想是使用类、对象、继承、封装、消息等基本概念进行程序设计。

对象 就是一个自包含的实体,用一组可识别的特性(属性)和行为(方法)来标识。程序中的对象就是对现实世界中具体实体的抽象。

一个事物到了程序中就变成了一个对象,在程序中所有的对象都被分成了两个部分:数据和功能,以人为例,人的姓名、性别等属于数据,人可以说话、走路这些属于人的功能。数据在对象中被称为属性,而功能就被称为方法。简而言之,在程序中一切皆是对象。

5.1 类

就是具有相同属性和功能的对象的抽象的集合,可以理解为对象的模型。一般是先创建 ,然后通过实例化 来得到 对象

定义类

class Cat{
  // 属性的类型声明,在TS中必须有初始值,或者在构造函数中初始化
  name: string
  age: number
  
  // constructor构造函数会在对象创建时调用
  constructor(name: string) {
    // 初始化属性,this表示当前调用对象
    this.name = name
    this.age = 3
  }
  
  // 方法
  sayHi(): void{
    // 在方法中可以通过this表示当前调用方法的对象
    console.log(`喵,我是 ${this.name}`)
  }
}

使用类

let Tom = new Cat("Tom") // 创建Cat类的实例
console.log(Tom) // Cat: { "name": "Tom", "age": 3 } (实例上只有属性)
Tom.say() // "喵,我是Tom" (方法在原型上,所以调用可以通过原型链访问到)

5.2 继承

继承Inheritance):在继承机制下形成有层级的类,使得低层级的类可以延用高层级类的特征和方法。

继承的实现方式:

  • 实现继承:直接使用基类公开的属性和方法,无需额外编码。
  • 接口继承:仅使用接口公开的属性和方法名称,需要子类实现。

继承的作用:

  • 复用代码,减少类的冗余代码。

  • 使得类与类之间产生关系,为多态的实现打下基础。

创建一个猫类的父类 Animal

class Animal{
  name: string
  age: number
  construcor(name: string){
    this.name = name
    this.age = 3
  }
  say(){
    console.log('...')
  }
}

使用 extends 关键字来继承 Animal 类,在子类中使用 super 完成对父类的引用

class Cat extends Animal {
  color: string
  construcor(name: string, color: string){
    super(name)//调用父类的构造函数
    this.color = color
  } 
}

通过继承可以将其他类中的属性和方法引入到当前类中 (在不修改类的情况下完成对类的扩展)

class Animal{
    name: string;
    age: number;
    constructor(name: string, age: number){
        this.name = name;
        this.age = age;
    }
}

// 继承自动物类
class Dog extends Animal{
    // 增加新方法
    bark(){
        console.log(`${this.name}在汪汪叫!`);
    }
}

const dog = new Dog('旺财', 4);
dog.bark();

发生继承时,如果子类中的方法会替换掉父类中的同名方法,这就称为方法的重写

class Animal{
    name: string;
    age: number;
    constructor(name: string, age: number){
        this.name = name;
        this.age = age;
    }
    run(){
        console.log(`父类中的run方法!`);
    }
}

class Dog extends Animal{
    bark(){
        console.log(`${this.name}在汪汪叫!`);
    }
    // 重写父类的run方法
    run(){
        console.log(`子类中的run方法,会重写父类中的run方法!`);
    }
}

const dog = new Dog('旺财', 4);
dog.bark();

5.3 抽象

抽象类abstract class)是专门用来被其他类所继承的类,它只能被其他类所继承不能用来创建实例

abstract class Animal {
  eat() {
    console.log('eat')
  }
  abstract sleep(): void
}

不能直接实例化抽象类,通常需要我们创建子类继承基类,然后可以实例化子类

class Dog extends Animal {
  name: string
  constructor(name: string){
    super()
    this.name = name
  }
  run(){}
  sleep(){
    console.log('dog sleep')
  }
}

抽象类中可以添加抽象方法,使用abstract开头的方法叫做抽象方法,抽象方法没有方法体只能定义在抽象类中,继承抽象类时抽象方法必须要实现

abstract class Animal{
    abstract run(): void; // 抽象方法
    bark() {
        console.log('动物在叫~');
    }
}

class Dog extends Animals{
    run() {
        console.log('狗在跑~');
    }
}

5.4 接口

接口(Interface)是对行为的抽象,而具体如何行动需要由类(class)去实现(implement)

接口主要负责定义一个类的结构,可以在定义类的时候去限制类的结构,其中所有的属性都不能有实际的值,所有的方法都是抽象方法。接口可以去限制一个对象的接口,对象只有包含接口中定义的所有属性和方法时才能匹配接口。

示例(检查对象类型)

    // 描述一个对象的类型,类型别名type
    type myType = {
        name: string,
        age: number
    };
    
    // 接口用来定义一个类结构,用来定义一个类中应该包含哪些属性和方法,可以当成类型声明去使用
    interface myInterface {
        name: string;
        age: number;
    }

    //接口可以重复声明,相当于一个接口中有name age gender三个属性
    interface myInterface {
        gender: string;
    }

    const obj: myInterface = {
        name: 'sss',
        age: 111,
        gender: '男'
    };

示例(实现)

   // 接口可以在定义类的时候去限制类的结构,其中所有的属性都不能有实际的值,所有的方法都是抽象方法
    interface myInter {
        name: string;
        sayHello(): void;
    }

    //定义类时,可以使类去实现一个接口,使类满足接口的要求
    class MyClass implements myInter {
        name: string;

        constructor(name: string) {
            this.name = name;
        }

        sayHello() {
            console.log('大家好~~');
        }
    }

5.5 封装

对象实质上就是属性和方法的容器,它的主要作用就是存储属性和方法,这就是所谓的封装,默认情况下,对象的属性是可以任意的修改的,为了确保数据的安全性,在TS中可以对属性的权限进行设置

封装的作用

  • 可隐藏实体实现的细节
  • 提高安全性,设定访问控制,只允许具有特定权限的使用者调用
  • 简化编程,调用方无需知道功能是怎么实现的,即可调用

5.5.1 静态属性和只读属性

静态属性(static):也称为类属性。使用静态属性无需创建实例,通过类即可直接使用,静态属性使用 static 开头

只读属性(readonly):如果在声明属性时添加一个readonly,则属性便成了只读属性无法修改

示例

class Tools{
    readonly a =10
    static PI = 3.1415926;
}

const tool =  new Tools();
tool.a//10 只读属性不可以修改
Tools.PI//静态属性可以直接获取,3.1415926

5.5.2 属性修饰符

TS中属性具有三种修饰符

  • public(默认值):修饰的属性可以在任意位置修改
  • protected (受保护成员),可以在类或子类的内部修改
  • private (私有成员):修饰的属只能在类的内部修改

public修饰符示例

class Person{
    // public可以不写
    public name: string; 
    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 = '猪八戒';// 可以通过对象修改

protected修饰符示例

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 = '猪八戒';// 不能修改

private修饰符示例

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; //子类中不能修改
    }
}

const p = new Person('孙悟空', 18);
p.name = '猪八戒';// 不能修改

5.5.3 属性存取器

对于一些不希望被任意修改的属性,可以将其设置为private,直接将其设置为private将导致无法再通过对象修改其中的属性。

那么我们可以在类中定义一组读取、设置属性的方法使得私有属性private可以被外部访问,这种对属性读取或设置的属性被称为属性存取器

读取属性的方法叫做setter方法,设置属性的方法叫做getter方法。

class Person{
    private _name: string;

    constructor(name: string){
        this._name = name;
    }

    get name(){
        return this._name;
    }

    set name(name: string){
        this._name = name;
    }

}

const p1 = new Person('孙悟空');
console.log(p1.name); // 通过getter读取name属性
p1.name = '猪八戒';   // 通过setter修改name属性

5.6 泛型

定义一个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定),此时泛型就可以表示这个类型。

举个例子

function test(arg: any): any{
	return arg;
}

test函数有一个参数类型不确定,但是能确定的时其返回值的类型和参数的类型是相同的,由于类型不确定所以参数和返回值均使用了any,但是很明显这样做是不合适的,首先使用any会关闭TS的类型检查,其次这样设置也不能体现出参数和返回值是相同的类型

使用泛型

function test<T>(arg: T): T{
	return arg;
}

这里的就是泛型,T是我们给这个类型起的名字(不一定非叫T),设置泛型后即可在函数中使用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;
    }
}

除此之外,也可以对泛型的范围进行约束

interface MyInter{
    length: number;
}

//使用T extends MyInter表示泛型T必须是MyInter的子类,不一定非要使用接口类和抽象类同样适用。
function test<T extends MyInter>(arg: T): number{
    return arg.length;
}

参考

B站尚硅谷视频

你可能感兴趣的:(TS,JS,typescript,前端,javascript)