Vue3
基本全部使用 TypeScript
來進行重寫,盡管你可能覺得要學的東西越來越多了,但是作為程序員,如果沒有保持對新鮮事物的好奇和接納,很可能兩年后你就必須要加入外賣大軍了!話不多說,一起走進 TypeScript
的世界吧!
學習資料
TypeScript 中文文檔地址 | TypeScript 線上學習視頻
什么是 TypeScript
進入官網,映入眼簾的第一句就給出了解釋:TypeScript
是 JavaScript
類型的超集,它可以編譯成純 JavaScript
。所謂超集意思就是:js
有的東西,我 ts
有,js
沒有的,我 ts
還有。這樣說可能有點糊,我們先來看一個栗子。
熟悉 js
的小伙伴都知道當我們聲明了一個變量后,比如賦值了一個字符串,但是后續仍然可以將其再賦值成其它類型的數據。例如:
let a = '123' // a 是 string 類型
a = 123 // 將 a 改變成 number 類型
console.log(typeof a) // number
我們嘗試用 ts
寫上面這段代碼
let b = 123
b = '123' // 編輯器提示錯誤:不能將類型 string 分配給類型 number
其實如果用 ts
的規范來寫上面這段代碼應該下面這樣的
let b: number = 123 // 規定了 b 是 number 類型,如果賦值給 string 就會直接提示報錯
b = 456
但是如果我們把這段代碼直接拿到瀏覽器中,就會報錯,因為瀏覽器不認識 TypeScript
,我們寫的所有 ts
代碼都需要編譯成純 js
代碼然后才能被瀏覽器識別,我們可以在 TypeScript 官網練習 將上面代碼復制到左邊,右邊就會自動生成與其對應的 js
代碼。
繼續學習前,我們來看看 TypeScript
相對于 JavaScript
帶來了什么優勢,為什么未來我們一定要學 TypeScript
呢?我們先用 JavaScript
來寫一段代碼:
function demo (data) {
return data.x * 2 + data.y * 2
}
demo()
上面這段 js
代碼咋一看好像沒錯,但是如果我們放到瀏覽器中運行就會發現報錯信息 Cannot read property 'x' of undefined(無法讀取未定義的屬性 x)
。我們在調用 demo()
的時候沒有傳參,導致代碼真正運行過程中報錯,但是它不會再編輯器中給我們報錯提示, 只有當代碼運行了才會報錯。
我們使用 ts
改造一下上述代碼:
function tsDemo(data: { x: number, y: number }) {
return data.x * 2 + data.y * 2
}
tsDemo() // 編輯器直接報錯提醒:應有一個參數,但獲得 0 個
假如我們隨便傳一個不符合規定的參數,編輯器也會直接給我們錯誤提醒:
tsDemo(10) // 類型 number 的參數不能賦值給類型 {x: number, y: number} 的參數
只有我們正確傳入了對應的類型參數,編輯器才會讓我們通過,并且在函數中調用 data
編輯器就會直接將其下面的 x、y
給我們提示出來:
tsDemo({ x: 10, y: 10 }) // ok
TypeScript
相對于 JavaScript
優勢總結:
1、
ts
編寫代碼就會提示你的代碼是否編寫正確,而不需要像js
等到代碼運行時才發現錯誤。
2、ts
有更好的編輯器語法提示,寫起代碼來更舒服。
3、類型的聲明可以讓我們直觀看到我們代碼里潛在的語義,代碼可讀性更好。
TypeScript
基礎環境搭建
一般使用 npm
來安裝,當然前提肯定需要你裝了 node
,然后使用如下命令:
npm install -g typescript
這樣我們就全局安裝了 typescript
,此時我們怎么應該運行 ts
文件的代碼呢?我們知道在 node
環境下運行 js
文件只需要使用 node demo.js
就行了,但是 ts
文件不能直接在瀏覽器和 node
環境下運行,所以我們需要先將 ts
轉成 js
文件:
tsc demo.ts
然后我們就會發現文件夾下多了一個 demo.js
文件,這個文件就是由 demo.ts
編譯過來的。 接下來我們使用 node demo.js
就可以運行了。但是如果我們每次都需要先轉一道 js
然后在運行會顯的十分繁瑣,所以我們可以安裝 ts-node
,命令行輸入如下指令:
npm install -g ts-node
此時我們就可以直接使用如下指令直接輸出 ts
文件的結果:
ts-node demo.ts
靜態類型的深度理解
在 TypeScript
中如果我們做了如下定義
const num: number = 123
我們不僅僅是將常量 num
永久定義成了 number
類型,而且我們在使用常量 num
的時候還可以直接使用 number
類型下面的所有方法,編輯器都會給我們最友好的提示。又比如我們定義了一個對象類型:
interface Point {
x: number;
y: number;
}
const point: Point = {
x: 3,
y: 4
}
point // 編輯器直接就會有 x 和 y 的提示
point.z // 類型 Point 上不存在屬性 z
如果我們看到一個變量是靜態類型,不僅僅意味著這個變量類型不能修改,還意味著這個變量的屬性和方法基本也就確定了,編輯器在使用靜態類型時就會給我們很好的語法提示。
TypeScript 的使用
為了讓程序有價值,我們需要能夠處理最簡單的數據單元:數字,字符串,結構體,布爾值等。 TypeScript
支持與 JavaScript
幾乎相同的數據類型,此外還提供了實用的枚舉類型方便我們使用。
基礎類型
日常開發中常見的基礎類型一般有:
- boolean
- number
- string
- void
- undefined
- symbol
- null
// boolean
let isDone: boolean = false
// number 注意 es6 還支持2進制和8進制
let age: number = 10
let binaryNumber: number = 0b1111
// string,注意es6新增的模版字符串也是沒有問題的
let firstName: string = 'viking'
let message: string = `hello, ${firstName}`
// 奇葩兄弟二人組,undefined 和 null
let u: undefined = undefined
let n: null = null
// 注意 undefined 和 null 是所有類型的子類型。
// 也就是說 undefined 類型的變量,可以賦值給 number 類型的變量:
let num: number = undefined
在實際使用中,其實單獨的 ts
的 null
、undefined
不是很常用,因為聲明一個 null
或者 undefined
的變量沒有意義,這種數據類型的聲明也只是在 js
中使用,比如程序初始時,先聲明一個 null
的變量,后續的代碼邏輯中再改成其它數據類型。
如果我們在編程階段還不清楚類型的變量到底會指定為哪一個類型,那應該怎么辦呢?在這種情況下,我們不希望類型檢查器對這些值進行檢查而是直接讓它們通過編譯階段的檢查。 那么我們可以使用 any
類型來標記這些變量:
let notSure: any = 4
notSure = 'maybe a string'
notSure = true
notSure.myName
notSure.getName()
當然,如果我們知道一個變量可能只是 number 和 string 類型的其中一種,我們也可以不使用 any
而是用下面這種語法:
// num 可以是 number 類型也可以是 string 類型
let num: number | string = 123
num = '456'
類型注解 和 類型推斷
類型注解指的是我們在定義變量的時候就會去告訴 ts
這個變量是什么類型:
let count: number = 123 // 指定 count 是 number 類型
類型推斷是指當我們未使用類型注解時,ts 會自動去嘗試分析變量的類型:
const firstName = 1
const lastName = 2
// 自動推斷出 total 為 number 類型,因為它是 2 個數字的和
const total = firstNumber + lastNumber
所以如果 ts 能夠自動分析變量類型,我們就什么也不需要做,但是如果 ts 無法分析變量類型的話,我們就要使用類型注解了。栗子如下:
function getTotal(firstNumber, lastNumber) {
return firstNumber + lastNumber
}
const result = getTotal(1, 2)
此時 ts
就無法推斷出 result
是什么類型,只為給它分配一個 any
類型,所以像這種時候,我們就應該使用類型注解:
function getTotal(firstNumber: number, lastNumber: number) {
return firstNumber + lastNumber
}
const result = getTotal(1, 2)
此時 result
就會被推斷為 number
類型,符合我們預期的結果。
所以在未來使用
ts
的過程中我們就是希望變量和屬性能夠類型固定,能夠推斷的就讓它推斷,不能推斷的我們告訴它就行了。
函數類型
和 JavaScript
一樣,TypeScript
函數可以創建有名字的函數和匿名函數。 你可以隨意選擇適合應用程序的方式,不論是定義一系列 API
函數還是只使用一次的函數。
通過下面的例子可以迅速回想起這兩種 JavaScript
中的函數:
// 命名函數
function add(x, y) {
return x + y;
}
// 匿名函數
let myAdd = function(x, y) { return x + y; };
我們首先來嘗試為一個命名函數添加類型:
// 約定函數傳入兩個值都是 number 類型,且函數的返回值也為 number 類型
function add(x: number, y: number): number {
return x + y
}
add(1, 2) // OK
add(1, 2, 3) // Error 應有 2 個參數,但獲得 3 個
add(1, '2') // Error 類型 'string' 的參數不能賦值給類型 'number' 的參數
我們約定了 add()
的形參只能傳入兩個數字類型,并且它的返回值也是 nunmber
類型。你也許會奇怪,根據類型推斷來說,我們不需要給函數的返回值加上類型注解,可以通過類型推斷默認推斷出兩個數字的和肯定也是 number
類型。但是如果我們不小心在上述函數中寫入了如下代碼:
function add(x: number, y: number) {
return x + y + ''
}
const result = add(1, 2) // result 變成了 string 類型,編輯器未報錯提醒
此時我們不小心在函數結尾加了一個空字符串,結果導致不能推斷出正確的類型,所以為了保證代碼的嚴謹性,對于函數類型來說,我們一般在結尾處給其進行類型注解。
在函數的形參中,如果我們希望傳遞的第三個參數為可選參數,如果有就用,沒有就不用,那么我們可以這樣來寫:
// ?: number 代表這是一個可選參數,有就用,沒有就不用
function add(x: number, y: number, z?: number): number {
if (typeof z === 'number') {
return x + y + z
} else {
return x + y
}
}
add(1, 2) // ok
add(1, 2, 3) // ok
add(1, 2, 3, 4) // error 應該有 2-3 個參數,但獲得 4 個
如果我們希望這個命名函數沒有返回值,那么我們就可以為這個函數給定 void
類型:
function add(x: number, y: number): void {
return x + y // error 不能將類型 number 分配給類型 void
}
const result = add(1, 2)
日常開發中,我們經常會使用對象解構的方式進行傳參,如果我們使用解構去傳參,那么我們應該怎么樣來定義對應的類型呢?你可能會這樣寫:
function add({ x: number, y: number }) { // error
return x + y
}
const result = add({ x: 1, y: 2 })
編輯器直接給我們報錯了,正確的寫法應該如下:
function add({ x, y }: { x: number, y: number }): number {
return x + y
}
const result = add({ x: 1, y: 2 })
命名函數其實在 ES6
之后我們基本很少寫了,現在的開發中,你可能進場這樣寫一個函數:
const add = (x: number, y: number): number => {
return x + y
}
此時聰明的你會發現,
add
好像就直接是函數類型,我們試著在編輯器中把鼠標一如到add
上面會發現它的類型為:const add:(x: number, y: number) => number
,所以函數不僅輸入輸出有類型,它自己本身也是有類型的。
此時如果我們把 add
賦值給一個 string
類型就會報錯
let add2: string = add // error 不能將函數類型分配給 string 類型
此時我們應該將 add2
定義為函數類型才能接收 add
,那么問題來了,怎么給常量定義一個函數類型呢,我們先跟著編輯器的提示寫:
let add2: (x: number, y: number) => number = add // ok
感覺寫著寫著你會不會蒙圈,感覺定義類型就像在寫一個箭頭函數,這個時候我們要始終記得兩點:
1、在
ts
中凡是在:
后面都是在聲明類型,和實際的代碼邏輯沒有任何關系,而=
后面跟的是函數的具體實現,也就是函數體。
2、定義函數類型時,后面的 => 不是箭頭函數,而是 ts 中聲明函數類型返回值的方法,在上述栗子中它只是代表這個函數類型的返回值是一個number
類型。
數組
TypeScript
像 JavaScript
一樣可以操作數組元素。 有兩種方式可以定義數組。 第一種,可以在元素類型后面接上 []
,表示由此類型元素組成的一個數組:
//最簡單的方法是使用「類型 + 方括號」來表示數組
let numberArr: number[] = [1, 2, 4]
let stringArr: string[] = ['1', '2', '3']
如果數組中是一個對象類型,那么我們應該如下定義:
let objArr: { name: string, age: number }[] = [{
name: 'cc',
age: 18
}]
當然,因為我們約束了類型中只能有 name
和 age
兩個字段,所以此時 objArr
就被限定了只能有 name
和 age
兩個屬性,如果我們隨意添加一個新的屬性,編輯器就會給出報錯提醒。如果我們想在類型約束中給出更多的字段,我們肯定不能用上面那種寫法,此時我們可以使用類型別名 type
,具體怎么用呢?
// 使用 type 定義一個 User 類型
type User = {
name: string;
age: number;
sex: string
}
// 使用 User 類型
let objArr: User[] = [{
name: 'cc',
age: 18,
sex: '男'
}]
當然前面說了定義數組有兩種方式,其實第二種方式就是是使用數組泛型,Array<元素類型>
。當然泛型算是 ts
中比較難懂的一個概念,我們這里先羅列出來用法,后面說到泛型在來具體講解:
// 使用數組泛型定義變量的類型
let list: Array<number> = [1, 2, 3]
元組
元組和數組很像,但是元組更具象,它表示一個已知元素數量和類型的數組
,各元素的類型不必相同。我們先寫個栗子:
// 這表示一個 [] 里面只能是 string 類型或者 number 類型
const arr: (string | number)[] = ['aa', 'bb', 18]
但是上述栗子中如果我們希望這個數組只有 3 個值,并且這 3 個值的類型就是 string
string
number
,并且這三個值的類型順序也不能顛倒哦。那么我們就可以使用元組:
const arr: [string, string, number] = ['aa', 'bb', 18] // ok
const arr: [string, string, number] = ['aa', 18, 'bb'] // error
元素的應用場景,一般向 csv
格式中的數據都是知道表頭是姓名、性別、年齡等信息就可以使用元組:
const teacherList: [string, string, number][] = [
['cc', 'male', 18],
['wc', 'female', 28]
]
interface 接口
這應該是最重要的一個基礎語法了,我們項目中用的最多的就是 interface
,我們先通過一個小栗子來看看它到底怎么用:
const getPersonName = (person: { name: string }): void => {
console.log(person.name)
}
const setPersonName = (person: { name: string }, name: string): void => {
person.name = name
}
const person: { name: string } = {
name: 'cc'
}
getPersonName(person)
setPersonName(person, 'wc')
上述代碼中我們定義了兩個函數和一個常量,我們發現有一個對象類型 {name: string}
我們寫了 3 遍,那么我們能不能把這個對象類型抽離出來公用呢,聰明的你肯定想到了使用 類型別名 type
,那么我們使用 type
改造一下上面的代碼:
// 使用類型別名 type
type Person = {
name: string
}
const getPersonName = (person: Person): void => {
console.log(person.name)
}
const setPersonName = (person: Person, name: string): void => {
person.name = name
const person: Person = {
name: 'cc'
}
除了 type
我們是否還有其它方法改造這段代碼呢?接下來就要介紹 interface
了,那我們就使用 interface
繼續改造:
interface Person {
name: string
}
const getPersonName = (person: Person): void => {
console.log(person.name)
}
const setPersonName = (person: Person, name: string): void => {
person.name = name
}
const person: Person = {
name: 'cc'
}
聰明的你應該發現貌似
interface
和type
好像用法都差不多,但是在 ts 中通用性的規范是如果能用接口去表述一些類型的話就要優先去使用接口,實在不行我們才使用類型別名 type
。
接下來我們在 interface
中使用 ?
可選參數和 readonly
只讀參數:
interface Person {
readonly name: string, // name 只能讀不能修改
age?: number // age 可有可無
}
const setPersonName = (person: Person, name: string): void => {
person.name = name // error 無法分配到 name,因為它是只讀屬性
}
const person: Person = {
name: 'cc'
}
但是我們可能會遇到這種情況,就是我們定義的常量 person
雖然規定了 Person
類型,但是如果這個常量還有性別、身高等屬性,而我們的接口類型定義中又不知道未來還會有哪些屬性,怎么樣防止 ts
效驗錯誤呢?還是看栗子:
interface Person {
name: string,
age?: number
}
const person: Person = { // error Person 類型中未指定 sex 和 height
name: 'cc',
sex: 'male',
height: 180
}
此時我們可以在 Person
類型中使用如下定義:
interface Person {
name: string,
age?: number,
[propName: string]: any
}
此時錯誤提示就被關閉了,它代表 Person
這個類型除了 name
和 age
以外還可以有其它的屬性,屬性的名字是一個字符串類型就行,屬性的值可以是任意類型。當然一個接口里面除了有屬性還可以有方法:
interface Person {
name: string,
age?: number,
[propName: string]: any,
say(): string // 必須有 say 方法,返回一個 string 類型
}
const person: Person = {
name: 'cc',
sex: 'male',
height: 180,
say() {
return '111'
}
}
implements
在面向對象中,一個類只能繼承另外一個類,有時候不同類之間有一些共同的特性,而一個類想使用一個接口去做類的一些屬性約束時就要使用 implements
關鍵字,這時候我們就可以把這些特性提取成接口然后用 implements
關鍵詞來實現,這樣就可以提高靈活性。舉個栗子:
// car 和 cellphone 兩個類有相同的特性,但是沒有公共父類,這時候可以把相同特性抽離成接口
interface Radio {
switchRadio(trigger: boolean): void
}
class Car implements Radio {
switchRadio(trigger: boolean) {}
}
class Cellphone implements Radio {
switchRadio(trigger: boolean) {}
}
interface Battery {
checkBatteryStatus(): void
}
// 要實現多個接口,我們只需要中間用逗號隔開即可
class Cellphone implements Radio, Battery {
switchRadio() {
}
checkBatteryStatus() {}
}
接口繼承
接口之間也可以實現相互繼承,這讓我們能夠從一個接口里復制成員到另一個接口里,可以更靈活地將接口分割到可重用的模塊里。
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
// Square 繼承了 Shape 和 PenStroke
interface Square extends Shape, PenStroke {
sideLength: number;
}
// 既然 demo 使用了 Square 那么就必須有 color、penWidth、sideLength 屬性
const demo: Square = {
color: '#ccc',
penWidth: 20,
sideLength: 10
}
當然接口不僅可以定義成一個對象,還可以定義成一個函數:
interface Test {
// 定義一個函數類型接收兩個 number 類型的形參同時返回 number 類型
(firstNumber: number, lastNumber: number): number;
}
const total: Test = (firstNumber: number, lastNumber: number): number => {
return firstNumber + lastNumber
}
const result1 = total(1, 2)
class 類 -- TypeScript
首先我們來定義一個類:
class Person {
name: string;
constructor(message: string) {
this.name = message
}
getName() {
return this.name;
}
}
const person = new Person('cc')
console.log(person.getName()) // cc
我們再次定義一個 Teacher
類來繼承父類 Person
// 使用 extends 構建繼承關系
class Teacher extends Person {
getTeacherName() {
return 'Teacher'
}
}
const teacher = new Teacher('dd')
console.log(teacher.getName()) // dd
console.log(teacher.getTeacherName()) // Teacher
使用繼承之后,子類的實例可以直接訪問父類上的方法。如果子類上面也有 getName
方法,那么就會優先執行子類上的 getName()
,如下栗子:
class Teacher extends Person {
getTeacherName() {
return 'Teacher'
}
getName() {
return 'cc'
}
}
const teacher = new Teacher('dd')
console.log(teacher.getName()) // cc
假如我們希望在子類重寫的方法中再次調用父類的這個方法,就可以使用 super
關鍵字,如下栗子:
class Teacher extends Person {
getTeacherName() {
return 'Teacher'
}
getName() {
return super.getName() + 'ee'
}
}
const teacher = new Teacher('dd')
console.log(teacher.getName()) // ddee
當子類的方法將父類的方法覆蓋之后,如果我們仍需調用父類的這個方法,就可以通過
super
進行調用。
公共,私有與受保護的修飾符
了解了類的基本用法之后,我們來認識類中的訪問類型,類中的訪問類型基本分為以下三種:
-
Public
:修飾的屬性或方法是共有的,不僅可以自己訪問,且實例對象和子類都能訪問,如不定義訪問類型默認類型就是Publice
-
Private
:修飾的屬性或方法是私有的,只有自己可以訪問,實例對象和子類都不能訪問 -
Protected
:修飾的屬性或方法是受保護的,只有自己和自己的子類中可以訪問,實例對象無法訪問
通過栗子我們來瞅瞅 Private
的用法:
class Animal {
name: string;
constructor(name: string) {
this.name = name
}
private run() {
return `${this.name} is running`
}
eat() {
return this.run()
}
}
const snake = new Animal('snake')
console.log(snake.run()) // error run 為私有屬性,只能在 Animal 類中使用
console.log(snake.eat()) // 所以我們又在其中定義 eat 方法,通過 eat 調用 run
我們再來瞅瞅 Protected
的用法,其實也很簡單,跟著栗子看看就行:
class Animal {
name: string;
constructor(name: string) {
this.name = name
}
protected eat() {
return this.name + ' eat'
}
}
const snake = new Animal('snake')
console.log(snake.eat()) // error 只能在類和子類中訪問,實例對象無法訪問
class Dog extends Animal {
myEat() {
return super.eat() // 子類內部可以調用 eat 方法
}
}
const dog = new Dog('dog')
console.log(dog.myEat())
構造器 constructor
接下來認識一下構造器 constructor
,其實 ts
中的類基本和 js es6
中的類用法相同,constructor
方法是類的默認方法,通過 new
命令生成對象實例時,自動調用該方法。一個類必須有 constructor
方法,如果沒有顯式定義,一個空的 constructor
方法會被默認添加。
class Person {
name: string;
constructor(name: string) {
this.name = name
}
}
const person = new Person('cc')
console.log(person.name)
constructor
接收的形參就是實例化傳過來的值,然后我們通過 this.name
將其賦值給類中的 name
屬性,那么我們能不能將 constructor
接收的形參直接初始化為類中的全局變量呢,我們只需要這樣寫就行了:
class Person {
constructor(public name: string) {
}
}
const person = new Person('cc')
console.log(person.name)
如果父類有構造器,而子類也有自己的構造器時編輯器就會報錯,代碼如下:
class Teacher extends Person {
constructor() {} // 報錯
sayHi() {
console.log(this.name)
}
}
子類中聲明構造器的時候,必須要調用 super()
去把父類的構造器也調用一下,當然這里我們只通過 super()
調用父類的構造器也不行,因為父類的構造器中要求我們傳入一個 name
,所以我們子類的 super()
中也要加入傳參才行。栗子如下:
class Person {
constructor(public name: string) {
}
}
class Teacher extends Person {
constructor(public age: number) {
super('zz');
}
sayHi() {
console.log(this.name)
}
}
const person = new Person('cc')
const teacher = new Teacher(18)
console.log(teacher.name) // zz
console.log(teacher.age) // 18
當然,如果我們父類的 constructor
沒有接收任何參數,子類的構造器也必須調用 super()
只是此時可以不用傳遞對應的參數:
class Person {
constructor() {
}
}
class Teacher extends Person {
constructor(public age: number) {
super()
}
}
存儲器 (getters / setters)
此時你有沒有一個疑問,private
和 protected
這些訪問類型存在的意義是什么呢?或者說我們在什么地方會用到呢?其實 private
和 protected
一般結合著存儲器來用,好吧?前面那個我都沒弄明白,怎么又突然來一個存儲器的概念?TypeScript
支持通過 getters/setters
來截取對對象成員的訪問。 它能幫助你有效的控制對對象成員的訪問。我們還是用栗子來說話吧:
class Person {
constructor(private _name: string) { }
get name() {
return this._name + ' wc'
}
}
const person = new Person('cc')
console.log(person.name) // 'cc wc'
在 Person
類中我們有一個私有屬性 _name
(一般私有屬性的定義我們都會在屬性前面加上 _
表示這是一個私有屬性),我們不希望外界能直接訪問到這個私有屬性,所以此時我們可以通過 getters
像外曝光這個數據,然后實例對象可以直接訪問到,使用 getters
之后此時實例就不是函數調用的方式訪問,而是直接通過屬性調用方式訪問。在 get
的過程中,我們可以對私密數據進行加密處理,不讓外界猜到基礎數據的值。
我們想重新給這個私有屬性賦值,那么我們就可以使用 setters
,在 setters
中我們可以解析新設置的值,讓私有屬性的值等于解析過后的值:
class Person {
constructor(private _name: string) { }
get name() {
return this._name
}
set name(name: string) {
const realName = name.split(' ')[0]
this._name = realName
}
}
const person = new Person('cc')
person.name = 'cc wc'
console.log(person.name) // cc
靜態屬性
到目前為止,我們只討論了類的實例成員,那些僅當類被實例化的時候才會被初始化的屬性。 我們也可以創建類的靜態成員,這些屬性存在于類本身上面而不是類的實例上。
我們可以給類的 constructor
上設置一個 private
,因為每個類實例之后都會自動執行 constructor()
,而現在它上面設置了私有屬性,那么這個類就不能進行實例化了,但是我們卻可以通過 static
關鍵字直接訪問這個類上的方法,而不是實例上的方法,舉個栗子:
class Person {
private constructor() { }
static eat() { // 使用 static 關鍵字將方法直接定義在類上
console.log('eat...')
}
}
const person = new Person('cc') // 無法實例化
console.log(Person.eat()) // 直接訪問類上的方法
延伸思考:是否可以在類上實現單例模式呢?
class Person {
private constructor(public name: string) { }
// 定義一個私有屬性 instance 將其類型設置為 Person
private static instance: Person
static getInstance() {
if (!this.instance) {
this.instance = new Person('cc')
}
return this.instance
}
}
const person1 = Person.getInstance()
const person2 = Person.getInstance()
console.log(person1.name) // cc
console.log(person2.name) // cc
抽象類
抽象類做為其它派生類的基類使用。 它們一般不會直接被實例化。 不同于接口,抽象類可以包含成員的實現細節。 abstract
關鍵字是用于定義抽象類和在抽象類內部定義抽象方法。這是官網給出的解釋,看這句話我們大概能知道抽象類前面都要加上 abstract
關鍵字,然后就是它不能直接實例化。
這里我們用栗子來說明,假如有多個圖像類,它們都有計算面積的方法:
// 3 個圖形類應該都有一個公有的方法,比如說計算面積的方法
class Circle {
getArea() { }
}
class Square {
getArea() { }
}
class Triangle {
getArea() { }
}
但是這三個圖形計算面積的方法確實不一樣的,他們并不能完全抽離成一個方法,此時我們就可以使用抽象類。抽象類中建立一個公用的抽象方法,抽象方法不負責具體的實現,只是做一個定義。如下栗子:
// 建一個抽象圖形類
abstract class Geom {
width: number;
// 建一個抽象的圖形面積實現方法,但是具體實現不能在抽象方法里寫
abstract getArea(): number
}
new Geom() // 報錯,不能直接實例化
然后我們在圖形類出繼承這個抽象類:
class Circle extends Geom {
// 此時類中必須要有 getArea() 并且給其一個返回值,不然就會報錯
getArea() {
return 123
}
}
const circle = new Circle()
circle.width = 100
circle.getArea()
如果我們實例化這個 Circle
類,就會發現這個實例上有 width
屬性和 getArea()
可以直接調用,有沒有感覺它和 extends
還有接口中的 implements
傻傻分不清,感覺功能都好相似,當然具體的功能差異我們在后面繼續來講。
突然發現這篇筆記居然已經寫了這么長了,泛型、枚舉等一些進階語法還沒開始寫。不過基礎語法就先寫到這里吧,后面在寫進階篇,文章整理僅供學習參考,如有錯誤,歡迎指正!