天天看點

TypeScript入門及面向對象

第一章 TypeScript

一、簡介

TypeScript是JavaScript的超集

TypeScript入門及面向對象

1、TypeScript優勢

(一)規避大量低級錯誤,避免時間浪費、省時;

(二)減少多人協作哦項目的成本,大型項目友好,省力;

(三)良好代碼提示,不用反複檔案跳轉或者翻文檔,省心;

2、TypeScript缺點

(一)與實際架構結合會有很多坑;

(二)配置學習成本高;

(三)TypeScript的類型系統比較複雜;

3、TypeScript 開發環境搭建

  1. 【​​Node.js(安裝與解除安裝)​​】
  2. 使用npm全局安裝typescript
  • 進入指令行
  • 輸入:npm i -g typescript
  1. 建立一個ts檔案
  2. 使用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”, ​

​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。
  • 步驟:
  1. 初始化項目
  • 進入項目根目錄,執行指令​

    ​npm init -y​

  • 主要作用:建立package.json檔案
  1. 下載下傳建構工具
  • ​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中的清除插件,每次建構都會先清除目錄
  1. 根目錄下建立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測試'
        }),
    ]

}      
  1. 根目錄下建立tsconfig.json,配置可以根據自己需要
{
    "compilerOptions": {
        "target": "ES2015",
        "module": "ES2015",
        "strict": true
    }
}      
  1. 修改package.json添加如下配置
{
  ...略...
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "start": "webpack serve --open chrome.exe"
  },
  ...略...
}      
  1. 在src下建立ts檔案,并在并指令行執行​

    ​npm run build​

    ​對代碼進行編譯,或者執行​

    ​npm start​

    ​來啟動開發伺服器

5、Babel

  • 經過一系列的配置,使得TS和webpack已經結合到了一起,除了webpack,開發中還經常需要結合babel來對代碼進行轉換以使其可以相容到更多的浏覽器,在上述步驟的基礎上,通過以下步驟再将babel引入到項目中。
  1. 安裝依賴包:
  • ​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文法
  1. 修改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的子類,不一定非要使用接口類和抽象類同樣适用。

繼續閱讀