第一章 TypeScript
一、簡介
TypeScript是JavaScript的超集
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5SO2UjNxE2M0IWY0QmN5YjZyYzX2EzNwIDMzEzLchDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
1、TypeScript優勢
(一)規避大量低級錯誤,避免時間浪費、省時;
(二)減少多人協作哦項目的成本,大型項目友好,省力;
(三)良好代碼提示,不用反複檔案跳轉或者翻文檔,省心;
2、TypeScript缺點
(一)與實際架構結合會有很多坑;
(二)配置學習成本高;
(三)TypeScript的類型系統比較複雜;
3、TypeScript 開發環境搭建
- 【Node.js(安裝與解除安裝)】
- 使用npm全局安裝typescript
- 進入指令行
- 輸入:npm i -g typescript
- 建立一個ts檔案
- 使用tsc對ts檔案進行編譯
- 進入指令行
- 進入ts檔案所在目錄
- 執行指令:tsc xxx.ts
4、flow
JavaScript的類型檢查器
安裝:npm i flow-bin -D
5、flow删除類型注釋
(一)flow-remove-types
npm i flow-remove-types -D
flow-remove-types src -d dist
(二)babel
npm i @babel/core @babel/cli @babel/preset-flow -D
babel src -d dist
二、快速入門
2、基本類型
- 類型聲明
- 類型聲明是TS非常重要的一個特點
- 通過類型聲明可以指定TS中變量(參數、形參)的類型
- 指定類型後,當為變量指派時,TS編譯器會自動檢查值是否符合類型聲明,符合則指派,否則報錯
- 簡而言之,類型聲明給變量設定了類型,使得變量隻能存儲某種類型的值
- 文法:
let 變量: 類型;
let 變量: 類型 = 值;
function fn(參數: 類型, 參數: 類型): 類型{
...
}
- 自動類型判斷
- TS擁有自動的類型判斷機制
- 當對變量的聲明和指派是同時進行的,TS編譯器會自動判斷變量的類型
- 是以如果你的變量的聲明和指派時同時進行的,可以省略掉類型聲明
- 類型:
類型 | 例子 | 描述 |
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中新增類型 |
- number
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let big: bigint = 100n;
- boolean
let isDone: boolean = false;
- string
let color: string = "blue";
color = 'red';
let fullName: string = `Bob Bobbington`;
let age: number = 37;
let sentence: string = `Hello, my name is ${fullName}.
I'll be ${age + 1} years old next month.`;
- 字面量
- 也可以使用字面量去指定變量的類型,通過字面量可以确定變量的取值範圍
let color: 'red' | 'blue' | 'black';
let num: 1 | 2 | 3 | 4 | 5;
- any
let d: any = 4;
d = 'hello';
d = true;
- unknown
let notSure: unknown = 4;
notSure = 'hello';
- void
let unusable: void = undefined;
- never
function error(message: string): never {
throw new Error(message);
}
- object(沒啥用)
let obj: object = {};
- array
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
- tuple
let x: [string, number];
x = ["hello", 10];
- enum
enum Color {
Red,
Green,
Blue,
}
let c: Color = Color.Green;
enum Color {
Red = 1,
Green,
Blue,
}
let c: Color = Color.Green;
enum Color {
Red = 1,
Green = 2,
Blue = 4,
}
let c: Color = Color.Green;
- 類型斷言
- 有些情況下,變量的類型對于我們來說是很明确,但是TS編譯器卻并不清楚,此時,可以通過類型斷言來告訴編譯器變量的類型,斷言有兩種形式:
- 第一種
let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;
- 第二種
let someValue: unknown = "this is a string";
let strLength: number = (<string>someValue).length;
3、編譯選項
- 自動編譯檔案
- 編譯檔案時,使用 -w 指令後,TS編譯器會自動監視檔案的變化,并在檔案發生變化時對檔案進行重新編譯。
- 示例:
tsc xxx.ts -w
- 自動編譯整個項目
- 如果直接使用tsc指令,則可以自動将目前項目下的所有ts檔案編譯為js檔案。
- 但是能直接使用tsc指令的前提時,要先在項目根目錄下建立一個ts的配置檔案 tsconfig.json
- tsconfig.json是一個JSON檔案,添加配置檔案後,隻需隻需 tsc 指令即可完成對整個項目的編譯
- 配置選項:
- include
- 定義希望被編譯檔案所在的目錄
- 預設值:[“**/*”]
- 示例:
"include":["src/**/*", "tests/**/*"]
- 上述示例中,所有src目錄和tests目錄下的檔案都會被編譯
- exclude
- 定義需要排除在外的目錄
- 預設值:[“node_modules”, “bower_components”, “jspm_packages”]
- 示例:
"exclude": ["./src/hello/**/*"]
- 上述示例中,src下hello目錄下的檔案都不會被編譯
- extends
- 定義被繼承的配置檔案
- 示例:
"extends": "./configs/base"
- 上述示例中,目前配置檔案中會自動包含config目錄下base.json中的所有配置資訊
- files
- 指定被編譯檔案的清單,隻有需要編譯的檔案少時才會用到
- 示例:
"files": [
"core.ts",
"sys.ts",
"types.ts",
"scanner.ts",
"parser.ts",
"utilities.ts",
"binder.ts",
"checker.ts",
"tsc.ts"
]
- 清單中的檔案都會被TS編譯器所編譯
- compilerOptions
- 編譯選項是配置檔案中非常重要也比較複雜的配置選項
- 在compilerOptions中包含多個子選項,用來完成對編譯的配置
- 項目選項
- target
- 設定ts代碼編譯的目标版本
- 可選值:
- ES3(預設)、ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ESNext
- 示例:
"compilerOptions": {
"target": "ES6"
}
- 如上設定,我們所編寫的ts代碼将會被編譯為ES6版本的js代碼
- lib
- 指定代碼運作時所包含的庫(宿主環境)
- 可選值:
- ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ESNext、DOM、WebWorker、ScriptHost …
- 示例:
"compilerOptions": {
"target": "ES6",
"lib": ["ES6", "DOM"],
"outDir": "dist",
"outFile": "dist/aa.js"
}
- module
- 設定編譯後代碼使用的子產品化系統
- 可選值:
- CommonJS、UMD、AMD、System、ES2020、ESNext、None
- 示例:
"compilerOptions": {
"module": "CommonJS"
}
- outDir
- 編譯後檔案的所在目錄
- 預設情況下,編譯後的js檔案會和ts檔案位于相同的目錄,設定outDir後可以改變編譯後檔案的位置
- 示例:
"compilerOptions": {
"outDir": "dist"
}
- 設定後編譯後的js檔案将會生成到dist目錄
- outFile
- 将所有的檔案編譯為一個js檔案
- 預設會将所有的編寫在全局作用域中的代碼合并為一個js檔案,如果module制定了None、System或AMD則會将子產品一起合并到檔案之中
- 示例:
"compilerOptions": {
"outFile": "dist/app.js"
}
- rootDir
- 指定代碼的根目錄,預設情況下編譯後檔案的目錄結構會以最長的公共目錄為根目錄,通過rootDir可以手動指定根目錄
- 示例:
"compilerOptions": {
"rootDir": "./src"
}
- allowJs
- 是否對js檔案編譯
- checkJs
- 是否對js檔案進行檢查
- 示例:
"compilerOptions": {
"allowJs": true,
"checkJs": true
}
- removeComments
- 是否删除注釋
- 預設值:false
- noEmit
- 不對代碼進行編譯
- 預設值:false
- sourceMap
- 是否生成sourceMap
- 預設值:false
- 嚴格檢查
- strict
- 啟用所有的嚴格檢查,預設值為true,設定後相當于開啟了所有的嚴格檢查
- alwaysStrict
- 總是以嚴格模式對代碼進行編譯
- noImplicitAny
- 禁止隐式的any類型
- noImplicitThis
- 禁止類型不明确的this
- strictBindCallApply
- 嚴格檢查bind、call和apply的參數清單
- strictFunctionTypes
- 嚴格檢查函數的類型
- strictNullChecks
- 嚴格的空值檢查
- strictPropertyInitialization
- 嚴格檢查屬性是否初始化
- 額外檢查
- noFallthroughCasesInSwitch
- 檢查switch語句包含正确的break
- noImplicitReturns
- 檢查函數沒有隐式的傳回值
- noUnusedLocals
- 檢查未使用的局部變量
- noUnusedParameters
- 檢查未使用的參數
- 進階
- allowUnreachableCode
- 檢查不可達代碼
- 可選值:
- true,忽略不可達代碼
- false,不可達代碼将引起錯誤
- noEmitOnError
- 有錯誤的情況下不進行編譯
- 預設值:false
4、webpack
- 通常情況下,實際開發中我們都需要使用建構工具對代碼進行打包,TS同樣也可以結合建構工具一起使用,下邊以webpack為例介紹一下如何結合建構工具使用TS。
- 步驟:
- 初始化項目
- 進入項目根目錄,執行指令
npm init -y
- 主要作用:建立package.json檔案
- 下載下傳建構工具
-
npm i -D webpack webpack-cli webpack-dev-server typescript ts-loader clean-webpack-plugin
- 共安裝了7個包
- webpack
- 建構工具webpack
- webpack-cli
- webpack的指令行工具
- webpack-dev-server
- webpack的開發伺服器
- typescript
- ts編譯器
- ts-loader
- ts加載器,用于在webpack中編譯ts檔案
- html-webpack-plugin
- webpack中html插件,用來自動建立html檔案
- clean-webpack-plugin
- webpack中的清除插件,每次建構都會先清除目錄
- 根目錄下建立webpack的配置檔案webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
optimization:{
minimize: false // 關閉代碼壓縮,可選
},
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"]
},
module: {
rules: [
{
test: /\.ts$/,
use: {
loader: "ts-loader"
},
exclude: /node_modules/
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title:'TS測試'
}),
]
}
- 根目錄下建立tsconfig.json,配置可以根據自己需要
{
"compilerOptions": {
"target": "ES2015",
"module": "ES2015",
"strict": true
}
}
- 修改package.json添加如下配置
{
...略...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"start": "webpack serve --open chrome.exe"
},
...略...
}
- 在src下建立ts檔案,并在并指令行執行
對代碼進行編譯,或者執行npm run build
來啟動開發伺服器npm start
5、Babel
- 經過一系列的配置,使得TS和webpack已經結合到了一起,除了webpack,開發中還經常需要結合babel來對代碼進行轉換以使其可以相容到更多的浏覽器,在上述步驟的基礎上,通過以下步驟再将babel引入到項目中。
- 安裝依賴包:
-
npm i -D @babel/core @babel/preset-env babel-loader core-js
- 共安裝了4個包,分别是:
- @babel/core
- babel的核心工具
- @babel/preset-env
- babel的預定義環境
- @babel-loader
- babel在webpack中的加載器
- core-js
- core-js用來使老版本的浏覽器支援新版ES文法
- 修改webpack.config.js配置檔案
...略...
module: {
rules: [
{
test: /\.ts$/,
use: [
{
loader: "babel-loader",
options:{
presets: [
[
"@babel/preset-env",
{
"targets":{
"chrome": "58",
"ie": "11"
},
"corejs":"3",
"useBuiltIns": "usage"
}
]
]
}
},
{
loader: "ts-loader",
}
],
exclude: /node_modules/
}
]
}
...略...
- 如此一來,使用ts編譯後的檔案将會再次被babel處理,使得代碼可以在大部分浏覽器中直接使用,可以在配置選項的targets中指定要相容的浏覽器版本。
第二章:面向對象
面向對象是程式中一個非常重要的思想,它被很多同學了解成了一個比較難,比較深奧的問題,其實不然。面向對象很簡單,簡而言之就是程式之中所有的操作都需要通過對象來完成。
- 舉例來說:
- 操作浏覽器要使用window對象
- 操作網頁要使用document對象
- 操作控制台要使用console對象
一切操作都要通過對象,也就是所謂的面向對象,那麼對象到底是什麼呢?這就要先說到程式是什麼,計算機程式的本質就是對現實事物的抽象,抽象的反義詞是具體,比如:照片是對一個具體的人的抽象,汽車模型是對具體汽車的抽象等等。程式也是對事物的抽象,在程式中我們可以表示一個人、一條狗、一把槍、一顆子彈等等所有的事物。一個事物到了程式中就變成了一個對象。
在程式中所有的對象都被分成了兩個部分資料和功能,以人為例,人的姓名、性别、年齡、身高、體重等屬于資料,人可以說話、走路、吃飯、睡覺這些屬于人的功能。資料在對象中被成為屬性,而功能就被稱為方法。是以簡而言之,在程式中一切皆是對象。
1、類(class)
要想面向對象,操作對象,首先便要擁有對象,那麼下一個問題就是如何建立對象。要建立對象,必須要先定義類,所謂的類可以了解為對象的模型,程式中可以根據類建立指定類型的對象,舉例來說:可以通過Person類來建立人的對象,通過Dog類建立狗的對象,通過Car類來建立汽車的對象,不同的類可以用來建立不同的對象。
- 定義類:
class 類名 {
屬性名: 類型;
constructor(參數: 類型){
this.屬性名 = 參數;
}
方法名(){
....
}
}
- 示例:
class Person{
name: string;
age: number;
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
sayHello(){
console.log(`大家好,我是${this.name}`);
}
}
- 使用類:
const p = new Person('孫悟空', 18);
p.sayHello();
2、面向對象的特點
- 封裝
- 對象實質上就是屬性和方法的容器,它的主要作用就是存儲屬性和方法,這就是所謂的封裝
- 預設情況下,對象的屬性是可以任意的修改的,為了確定資料的安全性,在TS中可以對屬性的權限進行設定
- 隻讀屬性(readonly):
- 如果在聲明屬性時添加一個readonly,則屬性便成了隻讀屬性無法修改
- TS中屬性具有三種修飾符:
- public(預設值),可以在類、子類和對象中修改
- protected ,可以在類、子類中修改
- private ,可以在類中修改
- 示例:
- public
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 = '豬八戒';// 可以通過對象修改
- 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 = '豬八戒';// 不能修改
- 屬性存取器
- 對于一些不希望被任意修改的屬性,可以将其設定為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屬性
- 靜态屬性
- 靜态屬性(方法),也稱為類屬性。使用靜态屬性無需建立執行個體,通過類即可直接使用
- 靜态屬性(方法)使用static開頭
- 示例:
class Tools{
static PI = 3.1415926;
static sum(num1: number, num2: number){
return num1 + num2
}
}
console.log(Tools.PI);
console.log(Tools.sum(123, 456));
- this
- 在類中,使用this表示目前對象
- 繼承
- 繼承時面向對象中的又一個特性
- 通過繼承可以将其他類中的屬性和方法引入到目前類中
- 示例:
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(){
console.log(`子類中的run方法,會重寫父類中的run方法!`);
}
}
const dog = new Dog('旺财', 4);
dog.bark();
- 在子類中可以使用super來完成對父類的引用
- 抽象類(abstract class)
- 抽象類是專門用來被其他類所繼承的類,它隻能被其他類所繼承不能用來建立執行個體
abstract class Animal{
abstract run(): void;
bark(){
console.log('動物在叫~');
}
}
class Dog extends Animals{
run(){
console.log('狗在跑~');
}
}
- 使用abstract開頭的方法叫做抽象方法,抽象方法沒有方法體隻能定義在抽象類中,繼承抽象類時抽象方法必須要實作
3、接口(Interface)
- 示例(檢查對象類型):
interface Person{
name: string;
sayHello():void;
}
function fn(per: Person){
per.sayHello();
}
fn({name:'孫悟空', sayHello() {console.log(`Hello, 我是 ${this.name}`)}});
- 示例(實作)
interface Person{
name: string;
sayHello():void;
}
class Student implements Person{
constructor(public name: string) {
}
sayHello() {
console.log('大家好,我是'+this.name);
}
}
4、泛型(Generic)
- 舉個例子:
function test(arg: any): any{
return arg;
}
- 上例中,test函數有一個參數類型不确定,但是能确定的時其傳回值的類型和參數的類型是相同的,由于類型不确定是以參數和傳回值均使用了any,但是很明顯這樣做是不合适的,首先使用any會關閉TS的類型檢查,其次這樣設定也不能展現出參數和傳回值是相同的類型
- 使用泛型:
function test<T>(arg: T): T{
return arg;
}
- 這裡的
就是泛型,T是我們給這個類型起的名字(不一定非叫T),設定泛型後即可在函數中使用T來表示該類型。是以泛型其實很好了解,就表示某個類型。<T>
- 那麼如何使用上邊的函數呢?
- 方式一(直接使用):
test(10)
- 使用時可以直接傳遞參數使用,類型會由TS自動推斷出來,但有時編譯器無法自動推斷時還需要使用下面的方式
- 方式二(指定類型):
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;
}
function test<T extends MyInter>(arg: T): number{
return arg.length;
}
- 使用T extends MyInter表示泛型T必須是MyInter的子類,不一定非要使用接口類和抽象類同樣适用。