㈠ 整理涵蓋很全很廣的前端知識點
HTML、CSS相關
html5新特性、語義化
瀏覽器渲染機制、重繪、重排
網頁生成過程:
重排(也稱迴流): 當 DOM 的變化影響了元素的幾何信息( DOM 對象的位置和尺寸大小),瀏覽器需要重新計算元素的幾何屬性,將其安放在界面中的正確位置,這個過程叫做重排。 觸發:
重繪: 當一個元素的外觀發生改變,但沒有改變布局,重新把元素外觀繪制出來的過程,叫做重繪。 觸發:
重排優化建議:
transform 不重繪,不迴流 是因為 transform 屬於合成屬性,對合成屬性進行 transition/animate 動畫時,將會創建一個合成層。這使得動畫元素在一個獨立的層中進行渲染。當元素的內容沒有發生改變,就沒有必要進行重繪。瀏覽器會通過重新復合來創建動畫幀。
css盒子模型
所有 HTML 元素可以看作盒子,在CSS中, "box model" 這一術語是用來設計和布局時使用。 CSS 盒模型本質上是一個盒子,封裝周圍的 HTML 元素,它包括:邊距,邊框,填充,和實際內容。 盒模型允許我們在其它元素和周圍元素邊框之間的空間放置元素。
css樣式優先順序
!important>style>id>class
什麼是BFC?BFC的布局規則是什麼?如何創建BFC?BFC應用?
BFC 是 Block Formatting Context 的縮寫,即塊格式化上下文。 BFC 是CSS布局的一個概念,是一個環境,裡面的元素不會影響外面的元素。 布局規則:Box是CSS布局的對象和基本單位,頁面是由若干個Box組成的。元素的類型和display屬性,決定了這個Box的類型。不同類型的Box會參與不同的 Formatting Context 。 創建:浮動元素 display:inline-block position:absolute 應用: 1.分屬於不同的 BFC 時,可以防止 margin 重疊 2.清除內部浮動 3.自適應多欄布局
DOM、BOM對象
BOM(Browser Object Model) 是指瀏覽器對象模型,可以對瀏覽器窗口進行訪問和操作。使用 BOM,開發者可以移動窗口、改變狀態欄中的文本以及執行其他與頁面內容不直接相關的動作。 使 JavaScript 有能力與瀏覽器"對話"。 DOM (Document Object Model) 是指文檔對象模型,通過它,可以訪問 HTML 文檔的所有元素。 DOM 是 W3C (萬維網聯盟)的標准。 DOM 定義了訪問 HTML 和 XML 文檔的標准: "W3C 文檔對象模型(DOM)是中立於平台和語言的介面,它允許程序和腳本動態地訪問和更新文檔的內容、結構和樣式。" W3C DOM 標准被分為 3 個不同的部分:
什麼是 XML DOM ? XML DOM 定義了所有 XML 元素的對象和屬性,以及訪問它們的方法。 什麼是 HTML DOM? HTML DOM 定義了所有 HTML 元素的對象和屬性,以及訪問它們的方法。
JS相關
js數據類型、typeof、instanceof、類型轉換
閉包(高頻)
閉包是指有權訪問另一個函數作用域中的變數的函數 ——《JavaScript高級程序設計》
當函數可以記住並訪問所在的詞法作用域時,就產生了閉包,
即使函數是在當前詞法作用域之外執行 ——《你不知道的JavaScript》
原型、原型鏈(高頻)
原型: 對象中固有的 __proto__ 屬性,該屬性指向對象的 prototype 原型屬性。
原型鏈: 當我們訪問一個對象的屬性時,如果這個對象內部不存在這個屬性,那麼它就會去它的原型對象里找這個屬性,這個原型對象又會有自己的原型,於是就這樣一直找下去,也就是原型鏈的概念。原型鏈的盡頭一般來說都是 Object.prototype 所以這就是我們新建的對象為什麼能夠使用 toString() 等方法的原因。
特點: JavaScript 對象是通過引用來傳遞的,我們創建的每個新對象實體中並沒有一份屬於自己的原型副本。當我們修改原型時,與之相關的對象也會繼承這一改變。
this指向、new關鍵字
this 對象是是執行上下文中的一個屬性,它指向最後一次調用這個方法的對象,在全局函數中, this 等於 window ,而當函數被作為某個對象調用時,this等於那個對象。 在實際開發中, this 的指向可以通過四種調用模式來判斷。
new
作用域、作用域鏈、變數提升
繼承(含es6)、多種繼承方式
(1)第一種是以 原型鏈的方式來實現繼承 ,但是這種實現方式存在的缺點是,在包含有引用類型的數據時,會被所有的實例對象所共享,容易造成修改的混亂。還有就是在創建子類型的時候不能向超類型傳遞參數。
(2)第二種方式是使用 借用構造函數 的方式,這種方式是通過在子類型的函數中調用超類型的構造函數來實現的,這一種方法解決了不能向超類型傳遞參數的缺點,但是它存在的一個問題就是無法實現函數方法的復用,並且超類型原型定義的方法子類型也沒有辦法訪問到。
(3)第三種方式是 組合繼承 ,組合繼承是將原型鏈和借用構造函數組合起來使用的一種方式。通過借用構造函數的方式來實現類型的屬性的繼承,通過將子類型的原型設置為超類型的實例來實現方法的繼承。這種方式解決了上面的兩種模式單獨使用時的問題,但是由於我們是以超類型的實例來作為子類型的原型,所以調用了兩次超類的構造函數,造成了子類型的原型中多了很多不必要的屬性。
(4)第四種方式是 原型式繼承 ,原型式繼承的主要思路就是基於已有的對象來創建新的對象,實現的原理是,向函數中傳入一個對象,然後返回一個以這個對象為原型的對象。這種繼承的思路主要不是為了實現創造一種新的類型,只是對某個對象實現一種簡單繼承,ES5 中定義的 Object.create() 方法就是原型式繼承的實現。缺點與原型鏈方式相同。
(5)第五種方式是 寄生式繼承 ,寄生式繼承的思路是創建一個用於封裝繼承過程的函數,通過傳入一個對象,然後復制一個對象的副本,然後對象進行擴展,最後返回這個對象。這個擴展的過程就可以理解是一種繼承。這種繼承的優點就是對一個簡單對象實現繼承,如果這個對象不是我們的自定義類型時。缺點是沒有辦法實現函數的復用。
(6)第六種方式是 寄生式組合繼承 ,組合繼承的缺點就是使用超類型的實例做為子類型的原型,導致添加了不必要的原型屬性。寄生式組合繼承的方式是使用超類型的原型的副本來作為子類型的原型,這樣就避免了創建不必要的屬性。
EventLoop
JS 是單線程的,為了防止一個函數執行時間過長阻塞後面的代碼,所以會先將同步代碼壓入執行棧中,依次執行,將非同步代碼推入非同步隊列,非同步隊列又分為宏任務隊列和微任務隊列,因為宏任務隊列的執行時間較長,所以微任務隊列要優先於宏任務隊列。微任務隊列的代表就是, Promise.then , MutationObserver ,宏任務的話就是 setImmediate setTimeout setInterval
原生ajax
ajax 是一種非同步通信的方法,從服務端獲取數據,達到局部刷新頁面的效果。 過程:
事件冒泡、捕獲(委託)
event.stopPropagation() 或者 ie下的方法 event.cancelBubble = true; //阻止事件冒泡
ES6
Vue
簡述MVVM
MVVM 是 Model-View-ViewModel 縮寫,也就是把 MVC 中的 Controller 演變成 ViewModel。Model 層代表數據模型, View 代表UI組件, ViewModel 是 View 和 Model 層的橋梁,數據會綁定到 viewModel 層並自動將數據渲染到頁面中,視圖變化的時候會通知 viewModel 層更新數據。
談談對vue生命周期的理解?
每個 Vue 實例在創建時都會經過一系列的初始化過程, vue 的生命周期鉤子,就是說在達到某一階段或條件時去觸發的函數,目的就是為了完成一些動作或者事件
computed與watch
watch 屬性監聽 是一個對象,鍵是需要觀察的屬性,值是對應回調函數,主要用來監聽某些特定數據的變化,從而進行某些具體的業務邏輯操作,監聽屬性的變化,需要在數據變化時執行非同步或開銷較大的操作時使用
computed 計算屬性 屬性的結果會被緩存,當 computed 中的函數所依賴的屬性沒有發生改變的時候,那麼調用當前函數的時候結果會從緩存中讀取。除非依賴的響應式屬性變化時才會重新計算,主要當做屬性來使用 computed 中的函數必須用 return 返回最終的結果 computed 更高效,優先使用
使用場景 computed :當一個屬性受多個屬性影響的時候使用,例:購物車商品結算功能 watch :當一條數據影響多條數據的時候使用,例:搜索數據
v-for中key的作用
vue組件的通信方式
父子組件通信
父->子 props ,子->父 $on、$emit` 獲取父子組件實例 parent、 parent 、children Ref 獲取實例的方式調用組件的屬性或者方法 Provide、inject` 官方不推薦使用,但是寫組件庫時很常用
兄弟組件通信
Event Bus 實現跨組件通信 Vue.prototype.$bus = new Vue() Vuex
跨級組件通信
$attrs、$listeners Provide、inject
常用指令
雙向綁定實現原理
當一個 Vue 實例創建時,Vue會遍歷data選項的屬性,用 Object.defineProperty 將它們轉為 getter/setter並且在內部追蹤相關依賴,在屬性被訪問和修改時通知變化。每個組件實例都有相應的 watcher 程序實例,它會在組件渲染的過程中把屬性記錄為依賴,之後當依賴項的 setter 被調用時,會通知 watcher重新計算,從而致使它關聯的組件得以更新。
v-model的實現以及它的實現原理嗎?
nextTick的實現
vnode的理解,compiler和patch的過程
new Vue後整個的流程
思考:為什麼先注入再提供呢??
答:1、首先來自祖輩的數據要和當前實例的data,等判重,相結合,所以注入數據的initInjections一定要在 InitState 的上面。2. 從上面注入進來的東西在當前組件中轉了一下又提供給後代了,所以注入數據也一定要在上面。
vm.[Math Processing Error]mount(vm.mount(vm.options.el) :掛載實例。
keep-alive的實現
作用:實現組件緩存
鉤子函數:
原理: Vue.js 內部將 DOM 節點抽象成了一個個的 VNode 節點, keep-alive 組件的緩存也是基於 VNode 節點的而不是直接存儲 DOM 結構。它將滿足條件 (pruneCache與pruneCache) 的組件在 cache 對象中緩存起來,在需要重新渲染的時候再將 vnode 節點從 cache 對象中取出並渲染。
配置屬性:
include 字元串或正則表達式。只有名稱匹配的組件會被緩存
exclude 字元串或正則表達式。任何名稱匹配的組件都不會被緩存
max 數字、最多可以緩存多少組件實例
vuex、vue-router實現原理
vuex 是一個專門為vue.js應用程序開發的狀態管理庫。 核心概念:
你怎麼理解Vue中的diff演算法?
在js中,渲染真實 DOM 的開銷是非常大的, 比如我們修改了某個數據,如果直接渲染到真實 DOM , 會引起整個 dom 樹的重繪和重排。那麼有沒有可能實現只更新我們修改的那一小塊dom而不要更新整個 dom 呢?此時我們就需要先根據真實 dom 生成虛擬 dom , 當虛擬 dom 某個節點的數據改變後會生成有一個新的 Vnode , 然後新的 Vnode 和舊的 Vnode 作比較,發現有不一樣的地方就直接修改在真實DOM上,然後使舊的 Vnode 的值為新的 Vnode 。
diff 的過程就是調用 patch 函數,比較新舊節點,一邊比較一邊給真實的 DOM 打補丁。在採取 diff 演算法比較新舊節點的時候,比較只會在同層級進行。 在 patch 方法中,首先進行樹級別的比較 new Vnode 不存在就刪除 old Vnode old Vnode 不存在就增加新的 Vnode 都存在就執行diff更新 當確定需要執行diff演算法時,比較兩個 Vnode ,包括三種類型操作:屬性更新,文本更新,子節點更新 新老節點均有子節點,則對子節點進行 diff 操作,調用 updatechidren 如果老節點沒有子節點而新節點有子節點,先清空老節點的文本內容,然後為其新增子節點 如果新節點沒有子節點,而老節點有子節點的時候,則移除該節點的所有子節點 老新老節點都沒有子節點的時候,進行文本的替換
updateChildren 將 Vnode 的子節點Vch和oldVnode的子節點oldCh提取出來。 oldCh和vCh 各有兩個頭尾的變數 StartIdx和EndIdx ,它們的2個變數相互比較,一共有4種比較方式。如果4種比較都沒匹配,如果設置了 key ,就會用 key 進行比較,在比較的過程中,變數會往中間靠,一旦 StartIdx>EndIdx 表明 oldCh和vCh 至少有一個已經遍歷完了,就會結束比較。
你都做過哪些Vue的性能優化?
你知道Vue3有哪些新特性嗎?它們會帶來什麼影響?
更小巧、更快速 支持自定義渲染器 支持搖樹優化:一種在打包時去除無用代碼的優化手段 支持Fragments和跨組件渲染
模板語法99%保持不變 原生支持基於class的組件,並且無需藉助任何編譯及各種stage階段的特性 在設計時也考慮TypeScript的類型推斷特性 重寫虛擬DOM 可以期待更多的編譯時提示來減少運行時的開銷 優化插槽生成 可以單獨渲染父組件和子組件 靜態樹提升 降低渲染成本 基於Proxy的觀察者機制 節省內存開銷
檢測機制 更加全面、精準、高效,更具可調試式的響應跟蹤
實現雙向綁定 Proxy 與 Object.defineProperty 相比優劣如何?
React
1、react中key的作用,有key沒key有什麼區別,比較同一層級節點什麼意思?
2、你對虛擬dom和diff演算法的理解,實現render函數
虛擬DOM 本質上是 JavaScript 對象,是對 真實DOM 的抽象表現。 狀態變更時,記錄新樹和舊樹的差異 最後把差異更新到真正的 dom 中 render函數:
3、React組件之間通信方式?
Context 提供了一個無需為每層組件手動添加 props ,就能在組件樹間進行數據傳遞的方法.如果你只是想避免層層傳遞一些屬性,組件組合( component composition )有時候是一個比 context 更好的解決方案。 5. 組件組合缺點:會使高層組件變得復雜
4、如何解析jsx
5、生命周期都有哪幾種,分別是在什麼階段做哪些事情?為什麼要廢棄一些生命周期?
componentWillMount、componentWillReceiveProps、componentWillUpdate在16版本被廢棄,在17版本將被刪除,需要使用UNSAVE_前綴使用,目的是向下兼容。
6、關於react的優化方法
使用return null而不是CSS的display:none來控制節點的顯示隱藏。保證同一時間頁面的DOM節點盡可能的少。
不要使用數組下標作為key 利用 shouldComponentUpdate 和 PureComponent 避免過多 render function ; render 裡面盡量減少新建變數和bind函數,傳遞參數是盡量減少傳遞參數的數量。 盡量將 props 和 state 扁平化,只傳遞 component 需要的 props (傳得太多,或者層次傳得太深,都會加重 shouldComponentUpdate 裡面的數據比較負擔),慎將 component 當作 props 傳入
使用 babel-plugin-import 優化業務組件的引入,實現按需載入 使用 SplitChunksPlugin 拆分公共代碼 使用動態 import ,懶載入 React 組件
7、綁定this的幾種方式
8、對fiber的理解
9、setState是同步還是非同步的
10、Rex、React-Rex
Rex的實現流程
用戶頁面行為觸發一個 Action ,然後 Store 調用 Recer ,並且傳入兩個參數:當前 State 和收到的 Action 。 Recer 會返回新的 State 。每當 state 更新之後, view 會根據 state 觸發重新渲染。
React-Rex:
Provider :從最外部封裝了整個應用,並向 connect 模塊傳遞 store 。 Connect :
11、對高階組件的理解
高階組件是參數為組件,返回值為新組件的函數。 HOC 是純函數,沒有副作用。 HOC 在 React 的第三方庫中很常見,例如 Rex 的 connect 組件。
高階組件的作用:
12、可以用哪些方式創建 React 組件?
React.createClass()、ES6 class 和無狀態函數
13、 React 元素與組件的區別?
組件是由元素構成的。元素數據結構是普通對象,而組件數據結構是類或純函數。
Vue與React對比?
數據流:
react 主張函數式編程,所以推崇純組件,數據不可變,單向數據流,
vue 的思想是響應式的,也就是基於是數據可變的,通過對每一個屬性建立Watcher來監聽,當屬性變化的時候,響應式的更新對應的虛擬dom。
監聽數據變化實現原理 :
組件通信的區別:jsx和.vue模板。
性能優化
vuex 和 rex 之間的區別?
從實現原理上來說,最大的區別是兩點:
Rex 使用的是不可變數據,而 Vuex 的數據是可變的。 Rex 每次都是用新的 state 替換舊的 state ,而 Vuex 是直接修改
Rex 在檢測數據變化的時候,是通過 diff 的方式比較差異的,而 Vuex 其實和Vue的原理一樣,是通過 getter/setter 來比較的(如果看 Vuex 源碼會知道,其實他內部直接創建一個 Vue 實例用來跟蹤數據變化)
瀏覽器從輸入url到渲染頁面,發生了什麼?
網路安全、HTTP協議
TCP UDP 區別
Http和Https區別(高頻)
GET和POST區別(高頻)
理解xss,csrf,ddos攻擊原理以及避免方式
XSS ( Cross-Site Scripting , 跨站腳本攻擊 )是一種代碼注入攻擊。攻擊者在目標網站上注入惡意代碼,當被攻擊者登陸網站時就會執行這些惡意代碼,這些腳本可以讀取 cookie,session tokens ,或者其它敏感的網站信息,對用戶進行釣魚欺詐,甚至發起蠕蟲攻擊等。
CSRF ( Cross-site request forgery ) 跨站請求偽造 :攻擊者誘導受害者進入第三方網站,在第三方網站中,向被攻擊網站發送跨站請求。利用受害者在被攻擊網站已經獲取的注冊憑證,繞過後台的用戶驗證,達到冒充用戶對被攻擊的網站執行某項操作的目的。
XSS避免方式:
CSRF 避免方式:
DDoS 又叫分布式拒絕服務,全稱 Distributed Denial of Service ,其原理就是利用大量的請求造成資源過載,導致服務不可用。
㈡ LowDB 輕量級 JSON 本地資料庫
作為輕量級的本地存儲方式,對於構建不依賴伺服器的小型項目,用LowDB存儲和管理數據是十分理想的選擇。在Nodejs, Electron and browser等一些小型項目中經常能看到LowDB的身影。
https://github.com/typicode/lowdb
npm install lowdb
或者:
yarn add lowdb
const low = require('lowdb');
const FileSync = require('lowdb/adapters/FileSync'); // 有多種適配器可選擇
const adapter = new FileSync('db.json'); // 申明一個適配器
const db = low(adapter);
db.defaults({posts: [], user: {}, count: 0})
.write();
db.get('posts')
.push({id: 1, title: 'lowdb is awesome'})
.write()
db.set('user.name', 'typicode')
.write()
db.update('count', n => n + 1)
.write()
運行程序會在項目中添加db.json文件,裡面存儲了添加的數據:
{
"posts": [
{
"id": 1,
"title": "lowdb is awesome"
}
],
"user": {
"name": "typicode"
},
"count": 1
}
lowdb是基於lodash構建的,所以可以使用任何lodash強大的函數,比如: _.get() 和 _.find(),並且可以串聯地使用:
db.get('users')
.find({sex: 'male'})
.value()
函數 功能
low(adapter) 返回一個具有特定屬性和功能的 lodash chain
db.[...].write() / .value() 寫 / 讀數據
db.getState() / .setState() 獲取 / 設置資料庫的狀態
db._ 資料庫lodash的實例,可以利用這個添加自己的函數或者第三方的mixins,比如lodash-id
db._.mixin({
second: function(array) {
return array[1]
}
})
db.get('posts')
.second()
.value()
針對lowdb自帶的適配器:FileSync、FileAsync 和 LocalBrowser,有以下可選參數:
defaultValue: 文件不存在時的默認值;
serialize/deserialize: 寫之前和讀之後的操作。
const adapter = new FilSync('db.json',{
serialize: (data) => encrypt(JSON.stringify(data)),
deserialize: (data) => JSON.parse(decrypt(data))
})
可以直接使用lodash的函數進行查詢。需要注意的是有些操作可能會導致原數據被修改,為了避免這種誤操作,需要使用 .cloneDeep(),操作都是惰性的,只有調用 .value()或 .write()後才會正式執行。
檢查users是是否存在
db.has('users')
.value()
設置users
db.set('users', [])
.write()
排序、選擇
db.get('users')
.filter({sex: 'male'})
.sortBy('age')
.take(5)
.value()
獲取特定欄位
db.get('users')
.map('name')
.value()
獲取數量
db.get('users')
.size()
.value()
獲取特定信息
db.get('users[0].name')
.value()
更新信息
db.get('users')
.find({name: 'Tom'})
.assign({name: 'Tim'})
.write()
刪除信息
db.get('users')
.remove({name: 'Time'})
.write()
移除屬性
db.unset('users.name)
.write()
深拷貝
db.get('users')
.cloneDeep()
.value()
可以使用 shortid 和 lodash-id 為資料庫中的每一條記錄創建唯一的id索引,然後通過id檢索操作記錄:
const shortid = require('shortid')
const postId = db
.get('posts')
.push({ id: shortid.generate(), title: 'low!' })
.write()
.id
const post = db
.get('posts')
.find({ id: postId })
.value()
const lodashId = require('lodash-id')
const FileSync = require('lowdb/adapters/FileSync')
const adapter = new FileSync('db.json')
const db = low(adapter)
db._.mixin(lodashId)
// We need to set some default values, if the collection does not exist yet
// We also can store our collection
const collection = db
.defaults({ posts: [] })
.get('posts')
// Insert a new post...
const newPost = collection
.insert({ title: 'low!' })
.write()
// ...and retrieve it using its id
const post = collection
.getById(newPost.id)
.value()
low( ) 函數接受自定義的Adapter
class MyStorage {
constructor() {
// ...
}
read() {
// Should return data (object or array) or a Promise
}
write(data) {
// Should return nothing or a Promise
}
}
const adapter = new MyStorage(args)
const db = low(adapter);
==============================================
英文官網介紹,更加簡潔
Lowdb 3 is a pure ESM package. If you're having trouble importing it in your project, please read this.
You can use TypeScript to type check your data.
You can also add lodash or other utility libraries to improve lowdb.
For CLI, server and browser usage, see examples/ directory.
Lowdb has two classes (for asynchronous and synchronous adapters).
Calls adapter.read() and sets db.data .
Note: JSONFile and JSONFileSync adapters will set db.data to null if file doesn't exist.
Calls adapter.write(db.data) .
Holds your db content. If you're using the adapters coming with lowdb, it can be any type supported by JSON.stringify .
For example:
Adapters for reading and writing JSON files.
In-memory adapters. Useful for speeding up unit tests.
Synchronous adapter for window.localStorage .
Adapters for reading and writing text. Useful for creating custom adapters.
If you've published an adapter for lowdb, feel free to create a PR to add it here.
You may want to create an adapter to write db.data to YAML, XML, encrypt data, a remote storage, ...
An adapter is a simple class that just needs to expose two methods:
For example, let's say you have some async storage and want to create an adapter for it:
See src/adapters/ for more examples.
To create an adapter for another format than JSON, you can use TextFile or TextFileSync .
For example:
Lowdb doesn't support Node's cluster mole.
If you have large JavaScript objects ( ~10-100MB ) you may hit some performance issues. This is because whenever you call db.write , the whole db.data is serialized using JSON.stringify and written to storage.
Depending on your use case, this can be fine or not. It can be mitigated by doing batch operations and calling db.write only when you need it.
If you plan to scale, it's highly recommended to use databases like PostgreSQL or MongoDB instead.
㈢ 還在發愁寫API文檔推薦一款阿里騰訊都在用的API管理神器
作為一個前後端分離模式開發的團隊,我們經常會看到這樣的場景:前端開發和後端開發在一起熱烈的討論「你這介面參數怎麼又變了?」,「介面怎麼又不通了?」,「稍等,我調試下」,「你再試試..."。
那能不能寫好介面文檔,大家都按文檔來開發?很難,因為寫文檔、維護文檔比較麻煩,而且費時,還會經常出現 API 更新了,但文檔還是舊的,各種同步不一致的情況,從而耽擱彼此的時間。
之前我們團隊也遇到了同樣的問題,那麼作為研發團隊的負責人,我是如何帶領團隊解決這個問題的呢?
方法其實很簡單,如果能做到讓寫文檔/維護文檔這件事情的短期收益就能遠高於付出的成本,那麼所有問題都能迎刃而解,開發人員就會非常樂意去寫介面文檔。
要做到寫文檔和及時維護文檔的短期收益就能遠高於付出的成本,無非兩個方向:
鑒於此,我們設想如果有一款工具做到以下這些是不是就非常爽了?
總結下來,我們需要的就是這么一款工具:
為此,我們幾乎嘗遍了市面上所有相關的工具,但是很遺憾,沒有找到合適的。
於是,我們自己實現了一個Postman + Swagger + RAP + JMeter
這個工具就是 Apifox,經常很長一段時間不斷更新迭代後,我們基本上完全實現了最初的設想,幾乎完美解決了最開始遇到的所有問題,在公司內部大受歡迎。並且也形成了我們自己的最佳實踐。
沒錯,現在我們已經將Apifox產品化對外服務了,你們團隊也可以直接使用Apifox了。
官網:www.apifox.cn
Apifox = Postman + Swagger + Mock + JMeter
Apifox 是 API 文檔、API 調試、API Mock、API 自動化測試一體化協作平台。
通過一套系統、一份數據,解決多個系統之間的數據同步問題。只要定義好介面文檔,介面調試、數據 Mock、介面測試就可以直接使用,無需再次定義;介面文檔和介面開發調試使用同一個工具,介面調試完成後即可保證和介面文檔定義完全一致。高效、及時、准確!
節省研發團隊的每一分鍾!
如果你認為 Apifox 只做了數據打通,來提升研發團隊的效率,那就錯了。Apifox 還做了非常多的創新,來提升開發人員的效率。
通常一個介面會有多種情況用例,比如 正確用例 參數錯誤用例 數據為空用例 不同數據狀態用例。定義介面的時候定義好這些不同狀態的用例,介面調試的時候直接運行,非常高效。
可以獨立定義數據模型,介面定義時可以直接引用數據模型,數據模型之間也可以相互引用。同樣的數據結構,只需要定義一次即可多處使用;修改的時候只需要修改一處,多處實時更新,避免不一致。
使用 Apifox 調試介面的時候,系統會根據介面文檔里的定義,自動校驗返回的數據結構是否正確,無需通過肉眼識別,也無需手動寫斷言腳本檢測,非常高效!
Apifox 自動校驗數據結構
設置斷言:
Apifox 設置斷言
運行後,查看斷言結果:
先放一張圖對比下 Apifox 和其他同類工具 零配置 mock 出來的數據效果:
Apifox Mock 數據結果對比同類工具
可以看出 Apifox 零配置 Mock 出來的數據和真實情況是非常接近的,前端開發可以直接使用,而無需再手動寫 mock 規則。
「Apifox 如何做到高效率、零配置生成非常人性化的 mock 數據」
Apifox 項目可「在線分享」 API 文檔,分享出去的 API 文檔可設置為公開或需要密碼訪問,非常方便與外部團隊協作。
體驗地址:https://www.apipark.cn/s/ce387612-cfdb-478a-b604-b96d1dbc511b/http/5041285
根據介面模型定義,自動生成各種語言/框架(如 TypeScript、Java、Go、Swift、ObjectiveC、Kotlin、Dart、C++、C#、Rust 等)的業務代碼(如 Model、Controller、單元測試代碼等)和介面請求代碼。目前 Apifox 支持 130 種語言及框架的代碼自動生成。
更重要的是:你可以通過自定義代碼模板來生成符合自己團隊的架構規范的代碼,滿足各種個性化的需求。
介面調試
Apifox 多種主題色可選
㈣ TypeScript 入門指南
新系列 深入淺出TypeScript 來了,本系列至少20+篇。本文為第一篇,來介紹一下TypeScript 以及常見的類型。
TypeScript是一門由微軟推出的開源的、跨平台的編程語言。它是JavaScript的超集,擴展了 JavaScript 的語法,最終會被編譯為JavaScript代碼。
TypeScript的主要特性:
TypeScript 主要是為了實現以下兩個目標:
下面就來看看這兩個目標是如何實現的。
為什麼要給JavaScript加上類型呢?
我們知道,JavaScript是一種輕量級的解釋性腳本語言。也是弱類型、動態類型語言,允許隱式轉換,只有運行時才能確定變數的類型。正是因為在運行時才能確定變數的類型,JavaScript代碼很多錯誤在運行時才能發現。TypeScript在JavaScript的基礎上,包裝了類型機制,使其變身成為 靜態類型 語言。在 TypeScript 中,不僅可以輕易復用 JavaScript 的代碼、最新特性,還能使用可選的靜態類型進行檢查報錯,使得編寫的代碼更健壯、更易於維護。
下面是 JavaScript 項目中最常見的十大錯誤,如果使用 TypeScript,那麼在 編寫階段 就可以發現並解決很多 JavaScript 錯誤了:
類型系統能夠提高代碼的質量和可維護性,經過不斷的實踐,以下兩點尤其需要注意:
可以認為,在所有操作符之前,TypeScript 都能檢測到接收的類型(在代碼運行時,操作符接收的是實際數據;在靜態檢測時,操作符接收的則是類型)是否被當前操作符所支持。當 TypeScript 類型檢測能力覆蓋到所有代碼後,任意破壞約定的改動都能被自動檢測出來,並提出類型錯誤。因此,可以放心地修改、重構業務邏輯,而不用擔憂因為考慮不周而犯下低級錯誤。
在一些語言中,類型總是有一些不必要的復雜的存在方式,而 TypeScript 盡可能地降低了使用門檻,它是通過如下方式來實現的。
TypeScript 與 JavaScript 本質並無區別,我們可以將 TypeScipt 理解為是一個添加了類型註解的 JavaScript,為JavaScript代碼提供了編譯時的類型安全。
實際上,TypeScript 是一門「 中間語言 」,因為它最終會轉化為JavaScript,再交給瀏覽器解釋、執行。不過 TypeScript 並不會破壞 JavaScript 原有的體系,只是在 JavaScript 的基礎上進行了擴展。
准確的說,TypeScript 只是將JavaScript中的方法進行了標准化處理:
這段代碼在TypeScript中就會報錯,因為TS會知道a是一個數字類型,不能將其他類型的值賦值給a,這種類型的推斷是很有必要的。
上面說了,TypeScript會盡可能安全的推斷類型。我們也可以使用類型注釋,以實現以下兩件事:
在一些語言中,類型總是有一些不必要的復雜的存在方式,而 TypeScript 的類型是結構化的。比如下面的例子中,函數會接受它所期望的參數:
為了便於把 JavaScript 代碼遷移至 TypeScript,即使存在編譯錯誤,在默認的情況下,TypeScript 也會盡可能的被編譯為 JavaScript 代碼。因此,我們可以將JavaScript代碼逐步遷移至 TypeScript。
雖然 TypeScript 是 JavaScript 的超集,但它始終緊跟ECMAScript標准,所以是支持ES6/7/8/9 等新語法標準的。並且,在語法層面上對一些語法進行了擴展。TypeScript 團隊也正在積極的添加新功能的支持,這些功能會隨著時間的推移而越來越多,越來越全面。
雖然 TypeScript 比較嚴謹,但是它並沒有讓 JavaScript 失去其靈活性。TypeScript 由於兼容 JavaScript 所以靈活度可以媲美 JavaScript,比如可以在任何地方將類型定義為 any(當然,並不推薦這樣使用),畢竟 TypeScript 對類型的檢查嚴格程度是可以通過 tsconfig.json 來配置的。
在搭建TypeScript環境之前,先來看看適合TypeScript的IDE,這里主要介紹Visual Studio Code,筆者就一直使用這款編輯器。
VS Code可以說是微軟的親兒子了,其具有以下優勢:
因為 VS Code 中內置了特定版本的 TypeScript 語言服務,所以它天然支持 TypeScript 語法解析和類型檢測,且這個內置的服務與手動安裝的 TypeScript 完全隔離。因此, VS Code 支持在內置和手動安裝版本之間動態切換語言服務,從而實現對不同版本的 TypeScript 的支持。
如果當前應用目錄中安裝了與內置服務不同版本的 TypeScript,我們就可以點擊 VS Code 底部工具欄的版本號信息,從而實現 「use VS Code's Version」 和 「use Workspace's Version」 兩者之間的隨意切換。
除此之外,VS Code 也基於 TypeScript 語言服務提供了准確的代碼自動補全功能,並顯示詳細的類型定義信息,大大的提升了我們的開發效率。
1)全局安裝TypeScript:
2)初始化配置文件:
執行之後,項目根目錄會出現一個 tsconfig.json 文件,裡麵包含ts的配置項(可能因為版本不同而配置略有不同)。
可以在 package.json 中加入script命令:
3)編譯ts代碼:
TSLint 是一個通過 tslint.json 進行配置的插件,在編寫TypeScript代碼時,可以對代碼風格進行檢查和提示。如果對代碼風格有要求,就需要用到TSLint了。其使用步驟如下: (1)在全局安裝TSLint:
(2)使用TSLint初始化配置文件:
執行之後,項目根目錄下多了一個 tslint.json 文件,這就是TSLint的配置文件了,它會根據這個文件對代碼進行檢查,生成的 tslint.json 文件有下面幾個欄位:
這些欄位的含義如下;
在說TypeScript數據類型之前,先來看看在TypeScript中定義數據類型的基本語法。
在語法層面,預設類型註解的 TypeScript 與 JavaScript 完全一致。因此,可以把 TypeScript 代碼的編寫看作是為 JavaScript 代碼添加類型註解。
在 TypeScript 語法中,類型的標注主要通過類型後置語法來實現:「 變數: 類型 」
在 JavaScript 中,原始類型指的是 非對象且沒有方法 的數據類型,包括:number、boolean、string、null、undefined、symbol、bigInt。
它們對應的 TypeScript 類型如下:
JavaScript原始基礎類型TypeScript類型 numbernumber booleanboolean stringstring nullnull undefinendefined symbolsymbol bigIntbigInt
需要注意 number 和 Number 的區別:TypeScript中指定類型的時候要用 number ,這是TypeScript的類型關鍵字。而 Number 是 JavaScript 的原生構造函數,用它來創建數值類型的值,這兩個是不一樣的。包括 string 、 boolean 等都是TypeScript的類型關鍵字,而不是JavaScript語法。
TypeScript 和 JavaScript 一樣,所有數字都是 浮點數 ,所以只有一個 number 類型。
TypeScript 還支持 ES6 中新增的二進制和八進制字面量,所以 TypeScript 中共支持 2、8、10和16 這四種進制的數值:
字元串類型可以使用單引號和雙引號來包裹內容,但是如果使用 Tslint 規則,會對引號進行檢測,使用單引號還是雙引號可以在 Tslint 規則中進行配置。除此之外,還可以使用 ES6 中的模板字元串來拼接變數和字元串會更為方便。
類型為布爾值類型的變數的值只能是true或者false。除此之外,賦值給布爾值的值也可以是一個計算之後結果為布爾值的表達式:
在 JavaScript 中,undefined和 null 是兩個基本數據類型。在 TypeScript 中,這兩者都有各自的類型,即 undefined 和 null,也就是說它們既是實際的值,也是類型。這兩種類型的實際用處不是很大。
注意,第一行代碼可能會報一個tslint的錯誤: Unnecessary initialization to 'undefined' ,就是不能給一個變數賦值為undefined。但實際上給變數賦值為undefined是完全可以的,所以如果想讓代碼合理化,可以配置tslint,將" no-unnecessary-initializer "設置為 false 即可。
默認情況下,undefined 和 null 是所有類型的子類型,可以賦值給任意類型的值,也就是說可以把 undefined 賦值給 void 類型,也可以賦值給 number 類型。當在 tsconfig.json 的"compilerOptions"里設置為 "strictNullChecks": true 時,就必須嚴格對待了。這時 undefined 和 null 將只能賦值給它們自身或者 void 類型。這樣也可以規避一些錯誤。
BigInt是ES6中新引入的數據類型,它是一種內置對象,它提供了一種方法來表示大於 2- 1 的整數,BigInt可以表示任意大的整數。
使用 BigInt 可以安全地存儲和操作大整數,即使這個數已經超出了JavaScript構造函數 Number 能夠表示的安全整數范圍。
我們知道,在 JavaScript 中採用雙精度浮點數,這導致精度有限,比如 Number.MAX_SAFE_INTEGER 給出了可以安全遞增的最大可能整數,即 2- 1 ,來看一個例子:
可以看到,最終返回了true,這就是超過精讀范圍造成的問題,而 BigInt 正是解決這類問題而生的:
這里需要用 BigInt(number) 把 Number 轉化為 BigInt ,同時如果類型是 BigInt ,那麼數字後面需要加 n 。
在TypeScript中, number 類型雖然和 BigInt 都表示數字,但是實際上兩者類型是完全不同的:
symbol我們平時用的比較少,所以可能了解也不是很多,這里就詳細來說說symbol。
symbol 是 ES6 新增的一種基本數據類型,它用來表示獨一無二的值,可以通過 Symbol 構造函數生成。
注意:Symbol 前面不能加 new關鍵字,直接調用即可創建一個獨一無二的 symbol 類型的值。
可以在使用 Symbol 方法創建 symbol 類型值的時候傳入一個參數,這個參數需要是一個字元串。如果傳入的參數不是字元串,會先自動調用傳入參數的 toString 方法轉為字元串:
上面代碼的第三行可能會報一個錯誤:This condition will always return 'false' since the types 'unique symbol' and 'unique symbol' have no overlap. 這是因為編譯器檢測到這里的 s1 === s2 始終是false,所以編譯器提醒這代碼寫的多餘,建議進行優化。
上面使用Symbol創建了兩個symbol對象,方法中都傳入了相同的字元串,但是兩個symbol值仍然是false,這就說明了 Symbol 方法會返回一個獨一無二的值。Symbol 方法傳入的這個字元串,就是方便我們區分 symbol 值的。可以調用 symbol 值的 toString 方法將它轉為字元串:
在TypeScript中使用symbol就是指定一個值的類型為symbol類型:
在ES6中,對象的屬性是支持表達式的,可以使用於一個變數來作為屬性名,這對於代碼的簡化有很多用處,表達式必須放在大括弧內:
symbol 也可以作為屬性名,因為symbol的值是獨一無二的,所以當它作為屬性名時,不會與其他任何屬性名重復。當需要訪問這個屬性時,只能使用這個symbol值來訪問(必須使用方括弧形式來訪問):
在使用obj.name訪問時,實際上是字元串name,這和訪問普通字元串類型的屬性名是一樣的,要想訪問屬性名為symbol類型的屬性時,必須使用方括弧。方括弧中的name才是我們定義的symbol類型的變數name。
使用 Symbol 類型值作為屬性名,這個屬性是不會被 for…in遍歷到的,也不會被 Object.keys() 、 Object.getOwnPropertyNames() 、 JSON.stringify() 等方法獲取到:
雖然這些方法都不能訪問到Symbol類型的屬性名,但是Symbol類型的屬性並不是私有屬性,可以使用 Object.getOwnPropertySymbols 方法獲取對象的所有symbol類型的屬性名:
除了這個方法,還可以使用ES6提供的 Reflect 對象的靜態方法 Reflect.ownKeys ,它可以返回所有類型的屬性名,Symbol 類型的也會返回:
Symbol 包含兩個靜態方法, for 和 keyFor 。 1)Symbol.for()
用Symbol創建的symbol類型的值都是獨一無二的。使用 Symbol.for 方法傳入字元串,會先檢查有沒有使用該字元串調用 Symbol.for 方法創建的 symbol 值。如果有,返回該值;如果沒有,則使用該字元串新創建一個。使用該方法創建 symbol 值後會在全局范圍進行注冊。
上面代碼中,創建了一個iframe節點並把它放在body中,通過這個 iframe 對象的 contentWindow 拿到這個 iframe 的 window 對象,在 iframe.contentWindow上添加一個值就相當於在當前頁面定義一個全局變數一樣。可以看到,在 iframe 中定義的鍵為 TypeScript 的 symbol 值在和在當前頁面定義的鍵為'TypeScript'的symbol 值相等,說明它們是同一個值。
2)Symbol.keyFor() 該方法傳入一個 symbol 值,返回該值在全局注冊的鍵名:
看完簡單的數據類型,下面就來看看比較復雜的數據類型,包括JavaScript中的數組和對象,以及TypeScript中新增的元組、枚舉、Any、void、never、unknown。
在 TypeScript 中有兩種定義數組的方式:
以上兩種定義數組類型的方式雖然本質上沒有任何區別,但是更推薦使用第一種形式來定義。一方面可以避免與 JSX 語法沖突,另一方面可以減少代碼量。
注意,這兩種寫法中的 number 指定的是數組元素的類型,也可以在這里將數組的元素指定為其他任意類型。如果要指定一個數組里的元素既可以是數值也可以是字元串,那麼可以使用這種方式: number|string[] 。
在JavaScript中,object是引用類型,它存儲的是值的引用。在TypeScript中,當想讓一個變數或者函數的參數的類型是一個對象的形式時,可以使用這個類型:
可以看到,當給一個對象類型的變數賦值一個對象時,就會報錯。對象類型更適合以下場景:
在 JavaScript 中並沒有元組的概念,作為一門動態類型語言,它的優勢是支持多類型元素數組。但是出於較好的擴展性、可讀性和穩定性考慮,我們通常會把不同類型的值通過鍵值對的形式塞到一個對象中,再返回這個對象,而不是使用沒有任何限制的數組。TypeScript 的元組類型正好彌補了這個不足,使得定義包含固定個數元素、每個元素類型未必相同的數組成為可能。
元組可以看做是數組的擴展,它表示已知元素數量和類型的數組,它特別適合用來實現多值返回。確切的說,就是已知數組中每一個位置上的元素的類型,可以通過元組的索引為元素賦值::
可以看到,定義的arr元組中,元素個數和元素類型都是確定的,當為arr賦值時,各個位置上的元素類型都要對應,元素個數也要一致。
當訪問元組元素時,TypeScript也會對元素做類型檢查,如果元素是一個字元串,那麼它只能使用字元串方法,如果使用別的類型的方法,就會報錯。
在TypeScript 新的版本中,TypeScript會對元組做越界判斷。超出規定個數的元素稱作越界元素,元素賦值必須類型和個數都對應,不能超出定義的元素個數。
這里定義了介面 Tuple ,它繼承數組類型,並且數組元素的類型是 number 和 string 構成的聯合類型,這樣介面 Tuple 就擁有了數組類型所有的特性。並且指定索引為0的值為 string 類型,索引為1的值為 number 類型,同時指定 length 屬性的類型字面量為 2,這樣在指定一個類型為這個介面 Tuple 時,這個值必須是數組,而且如果元素個數超過2個時,它的length就不是2是大於2的數了,就不滿足這個介面定義了,所以就會報錯;當然,如果元素個數不夠2個也會報錯,因為索引為0或1的值缺失。
TypeScript 在 ES 原有類型基礎上加入枚舉類型,使得在 TypeScript 中也可以給一組數值賦予名字,這樣對開發者比較友好。枚舉類型使用enum來定義:
上面定義的枚舉類型的Roles,它有三個值,TypeScript會為它們每個值分配編號,默認從0開始,在使用時,就可以使用名字而不需要記數字和名稱的對應關系了:
除此之外,還可以修改這個數值,讓SUPER_ADMIN = 1,這樣後面的值就分別是2和3。當然還可以給每個值賦予不同的、不按順序排列的值:
我們可以將一個值定義為any類型,也可以在定義數組類型時使用any來指定數組中的元素類型為任意類型:
any 類型會在對象的調用鏈中進行傳導,即any 類型對象的任意屬性的類型都是 any,如下代碼所示:
需要注意:不要濫用any類型,如果代碼中充滿了any,那TypeScript和JavaScript就毫無區別了,所以除非有充足的理由,否則應該盡量避免使用 any ,並且開啟禁用隱式 any 的設置。
void 和 any 相反,any 是表示任意類型,而 void 是表示沒有類型,就是什麼類型都不是。這在 定義函數,並且函數沒有返回值時會用到 :
需要注意: void 類型的變數只能賦值為 undefined 和 null ,其他類型不能賦值給 void 類型的變數。
never 類型指永遠不存在值的類型,它是那些 總會拋出異常 或 根本不會有返回值的函數表達式的返回值 類型,當變數被永不為真的類型保護所約束時,該變數也是 never 類型。
下面的函數,總是會拋出異常,所以它的返回值類型是never,用來表明它的返回值是不存在的:
never 類型是任何類型的子類型,所以它可以賦值給任何類型;而沒有類型是 never 的子類型,所以除了它自身以外,其他類型(包括 any 類型)都不能為 never 類型賦值。
上面代碼定義了一個立即執行函數,函數體是一個死循環,這個函數調用後的返回值類型為 never,所以賦值之後 neverVariable 的類型是 never 類型,當給neverVariable 賦值 123 時,就會報錯,因為除它自身外任何類型都不能賦值給 never 類型。
基於 never 的特性,我們可以把 never 作為介面類型下的屬性類型,用來禁止操作介面下特定的屬性:
可以看到,無論給 props.name 賦什麼類型的值,它都會提示類型錯誤,這就相當於將 name 屬性設置為了只讀 。
unknown 是TypeScript在3.0版本新增的類型,主要用來描述類型並不確定的變數。它看起來和any很像,但是還是有區別的,unknown相對於any更安全。
對於any,來看一個例子:
上面這些語句都不會報錯,因為value是any類型,所以後面三個操作都有合法的情況,當value是一個對象時,訪問name屬性是沒問題的;當value是數值類型的時候,調用它的toFixed方法沒問題;當value是字元串或數組時獲取它的length屬性是沒問題的。
當指定值為unknown類型的時候,如果沒有 縮小類型範圍 的話,是不能對它進行任何操作的。總之,unknown類型的值不能隨便操作。那什麼是類型範圍縮小呢?下面來看一個例子:
這里由於把value的類型縮小為Date實例的范圍內,所以進行了value.toISOString(),也就是使用ISO標准將 Date 對象轉換為字元串。
使用以下方式也可以縮小類型範圍:
關於 unknown 類型,在使用時需要注意以下幾點:
在實際使用中,如果有類型無法確定的情況,要盡量避免使用 any,因為 any 會丟失類型信息,一旦一個類型被指定為 any,那麼在它上面進行任何操作都是合法的,所以會有意想不到的情況發生。因此如果遇到無法確定類型的情況,要先考慮使用 unknown。
㈤ TypeScript 系統入門到項目實戰
### 課程介紹
TS在構建大型應用上的優勢,以及與JS的完美互通,讓TS未來一片光明,從0到1系統學習,把TS真正應用到框架和項目中。
在迷你「全棧」項目中學習TypeScript,以TypeScript完整串聯前後端
基礎與實戰「融合」,將知識講解融於項目開發中,讓你更快的掌握TS工程開發所必須的知識點
### 目錄
第1章 課程導學
本章主要介紹課程的知識大綱,學習前提,講授方式及預期收獲。
1-1 都2020了,還不抓緊學TypeScript?
第2章 TypeScript 基礎語法入門
本章主要幫助大家理解 TypeScript 可以解決的問題和所帶來的優勢,幫助大家理解 TS 中的各種靜態類型,包括:函數,數組,元組,類,抽象類介面等,迅速幫助大家理解 TS 的基礎使用方式和語法。
2-1 安裝 VsCode 編輯器
2-2 TypeScript的定義
2-3 TypeScript帶來了什麼優勢
2-4 TypeScript基礎環境搭建
2-5 靜態類型的深度理解
2-6 基礎類型和對象類型
2-7 類型註解和類型推斷
2-8 函數相關類型
2-9 基礎語法復習
2-10 數組和元組
2-11 Interface介面
2-12 類的定義與繼承
2-13 類中的訪問類型和構造器
2-14 靜態屬性,Setter和Getter
2-15 抽象類
2-16 作業節
第3章 使用 TypeScript 編寫爬蟲工具
本章將帶大家使用 TypeScript 編寫一個獲取網站課程銷量的爬蟲工具,過程中對上一章節學習的 TypeScript 基礎知識進行實踐鞏固,同時藉助 TypeScript 中的 OOP 編程方式,給大家講解部分面向對象開發中的設計模式。
3-1 爬蟲概述及正版密鑰獲取 (04:48)
3-2 使用SuperAgent和類型定義文件獲取頁面內容 (18:43)
3-3 使用cheerio進行數據提取 (12:32)
3-4 爬取數據的結構設計和存儲 (18:00)
3-5 使用組合設計模式優化代碼 (21:21)
3-6 單例模式實戰復習 (07:24)
3-7 TypeScript的編譯運轉過程的進一步理解 (21:04)
3-8 作業節
第4章 TypeScript 語法進階
本章將給大家講解更多的 TypeScript 進階語法以及原理性知識。包括如何進行 TypeScript 編譯過程的配置,聯合類型,類型保護,枚舉類型,泛型等知識點。同時也給大家擴展講解了類型定義文件的使用及編寫方式等內容。通過本章的學習,大家基本可以掌握 TypeScript 中絕大部分的語法知識點。
4-1 TypeScript中的配置文件(上)
4-2 TypeScript中的配置文件(下)
4-3 作業節
4-4 聯合類型和類型保護
4-5 Enum 枚舉類型
4-6 函數泛型
4-7 類中的泛型以及泛型類型
4-8 命名空間-namespace(上)
4-9 命名空間-namespace(下)
4-10 import對應的模塊化-缺代碼
4-11 使用 Parcel 打包 TS 代碼
4-12 描述文件中的全局類型(上)
4-13 描述文件中的全局類型(下)
4-14 模塊代碼的類型描述文件
4-15 作業節
4-16 泛型中keyof語法的使用
第5章 使用 Express 框架開發數據爬取及展示介面
本章將在 Express 框架中使用 TypeScript 的語法進行登陸,數據爬取和展示介面的開發,過程中對之前的基礎語法進行實踐鞏固,同時講解以 JavaScript 作為開發語言的框架中使用 TypeScript 會遇到的問題以及解決方案。
5-1 Express 基礎項目結構搭建
5-2 使用TS編寫常規express代碼遇到的問題
5-3 擴展解決 Express 的類型定義文件問題
5-4 登陸功能的開發
5-5 統一介面數據結構,優化代碼
第6章 TypeScript 高級語法
本章主要講解 TypeScript 中,裝飾器和元數據的語法知識,包括類裝飾器,方法裝飾器,屬性裝飾器和參數裝飾器在本章中都會詳細講解,通過本章的學習,大家基本上完成了對 TypeScript 所有重點語法的學習。
6-1 類的裝飾器(1)
6-2 類的裝飾器(2)
6-3 方法裝飾器
6-4 訪問器的裝飾器
6-5 屬性的裝飾器
6-6 參數裝飾器
6-7 裝飾器實際使用的小例子
6-8 reflect-metadata
6-9 裝飾器的執行順序
6-10 作業節
第7章 Express 項目代碼改良
結合上一章學習的裝飾器和元數據的語法知識,本章將通過面向對象的方式,對之前的介面代碼進行全面重構,最終幫大家編寫出和當前主流 Node 框架風格一致的後端應用代碼,對上一章的知識點進行實戰鞏固,同時幫助大家理解 Node 框架設計背後的原理性知識。
7-1 創建控制器和裝飾器
7-2 通過裝飾器實現項目路由功能
7-3 多種請求方法裝飾器的生成
7-4 .中間件裝飾器的編寫
7-5 .代碼結構優化
7-6 .練習題:如何在一個方法上使用多個裝飾器
第8章 使用 React 編寫爬取數據的展示頁面
本章將帶大家使用TS的語法進行前端 React 代碼的開發,過程中會給大家講解在 React 和 Rex 等前端核心框架上如何正確巧妙的使用TypeScript。本章的最後,我們將產出一個完整的爬蟲項目,並通過可視化的方式,對爬取到的數據進行展示。
8-1 初始化 React 項目
8-2 編寫登陸表單
8-3 類型及路由的使用
8-4 前後端代碼聯調及登陸跳轉邏輯開發
8-5 登陸退出功能完整優化
8-6 數據爬取功能打通及 Echarts 的使用
8-7 折線圖數據處理及展示
8-8 介面數據類型的冗餘定義問題
8-9 通過 Typescript 簡化前後端協作模式
第9章 課程總結
本章將對整個項目所學習到的知識點進行總結,並給出大家進一步深入學習 TS 的方法和技巧。
9-1 課程總結及後續學習方法推薦
### 獲取方式:TypeScript 系統入門到項目實戰
㈥ TypeScript 速成教程
Typescript 是 javascript 的類型超集,旨在簡化大型 JavaScript 應用程序的開發。Typescript 加入了常見的概念例如 類(classes),泛型(generics),介面(interfaces)和靜態類型(static types)並允許開發人員使用靜態檢查和代碼重構等工具。
為什麼在意 Typescript
現在問題仍然是為什麼你應該優選使用 Typescript。這有一些關於為什麼 javascript 開發者應該考慮學習 Typescript 的原因。
靜態類型
Javascript 是動態類型的,這意味著直到在運行時實例化時,它不知道變數的類型,這可能導致項目中的問題和錯誤。Typescript 加入了對 Javascript 靜態類型支持如果你正確的使用它處理由變數類型的錯誤設定引起的錯誤。您仍然可以完全控制輸入代碼的嚴格程度,或者甚至根本不使用類型。
更好的 IDE 支持
Typescript 相比 Javascript 一個更大的優勢是更好的 IED 支持包括了來自 Typescript 編譯器智能,實時的提示,調試以及更多功能。這里還有一大堆擴展進一步 提升你的 Typescript 開發體驗。
應用新的 ECMAScript 特性
Typescript 使您可以使用最新的 ECMAScript 功能,並將它們轉換到您選擇的 ECMAScript 目標。這意味著您可以使用最新的工具開發應用程序,而無需擔心瀏覽器支持。
什麼時候你該使用它
到目前為止,我們應該知道為什麼 Typescript 是有用的以及如何改善我們的開發體驗。但它並不是解決所有問題的方法,當然也不能阻止你自己編寫可怕的代碼。那麼讓我們來看看你應該在哪裡使用 Typescript。
當你擁有一個很大的代碼庫時
Typescript 是大型代碼庫的一個很好的補充,因為它可以幫助您防止許多常見錯誤。這尤其適用於多個開發人員工作在同一項目之中。
當你項目成員早已知道靜態類型語言時
另一個明顯使用 Typescript 的場景是當你和你的團隊已經知道靜態類型的語言像 Java 和 C# 不想改為編寫 Javascript。
設置/建立
要設置 typescript,我們只需要使用 npm 包管理器安裝它並創建一個新的 Typescript 文件。
安裝完成之後我們可以繼續探尋 Typescript 提供給我們的語法和功能特性。
類型
現在讓我們來看看 Typescript 所提供的類型:
數值(Number)
Typescript 所有的值類型都是浮點數。所有的數字包括二進制和十六進制都是數值類型。
字元串(String)
與其他語言一樣,Typescript 使用 String 數據類型來保存文本數據。
你還可以用反引號來應用多行字元串並嵌入表達式。
布爾類型(Boolean)
Typescript 支持所有的基本數據類型,布爾類型,值必須為 true 或者 false。
指定類型
現在我們已經有了基本的數據類型,我們可以看看你如何在 Typescript 中指定類型。基本上,您只需要在名稱和冒號後面寫出變數的類型。
單一類型
這里例子為我們如何為變數指定字元串數據類型
所有其他數據類型也是這樣使用。
多類型
你仍然可以通過 | 操作符為你的變數指定多個數據類型:
這里我們使用|為變數分配兩種類型。現在我們可以在其中存儲字元串和數值。
類型檢測
現在讓我們看看我們如何檢查我們的變數是否具有正確的類型。我們有多種選擇,但在這里我只展示了兩個最常用的選項。
Typeof
typeof 僅僅知道基本類型。這意味著它只能檢查變數是否是我們上面定義的數據類型之一。
在此示例中,我們創建一個字元串類型變數並使用 typeof 命令檢查 str 是否為 Number 類型(始終為 false)。然後我們列印是否是數值。
Instanceof
instanceof 運算符與 typeof 幾乎相同,只是它還可以檢查 javascript 尚未定義的自定義類型。
在這里,我們創建一個自定義類型,我們稍後將在本文中討論,然後創建它的實例。之後,我們檢查它是否真的是 Human 類型的變數,如果是,則在控制台中列印。
類型斷言
有時我們還需要將變數轉換為特定的數據類型。這經常發生在你已經指定了一個泛型類型像 any 並且你想使用它具體的類型的方法。
有很多選擇可以解決這個問題,但在這里我只分享其中兩個。
As 關鍵字
通過在變數名之後使用 as 關鍵字跟隨具體的數據類型來轉換變數的類型。
這里我們將 str 變數轉換為字元串,以便我們可以使用 length 屬性(如果您的 TSLINT 設置允許,甚至可以在沒有轉換的情況下工作)。
> 操作符
我們也可以使用 > 運算符,它與 as 關鍵字具有完全相同的效果,只有語法差異。
此代碼塊與上面的代碼塊具有完全相同的功能。它只是語法不同。
數組
Typescript 中的數組是相同對象的集合,可以用兩種不同的方式創建。
創建數組
使用 []
我們可以通過指定類型後跟 [] 來定義數組對象,以表示它是一個數組。
在這個例子中,我們創建一個字元串數組,它包含三個不同的字元串值。
使用泛型數組
我們還可用指定 Array 定義泛型數組
這里我們創建一個數值數組,它包含 5 個不同的數字。
多(混合)類型數組
此外,我們還可以使用 | 操作符將多個類型分配給單個數組。
此例中我們創建了一個數值可以包含字元串和數值。
多維數組
Typescript 還允許我們定義多維數組,這意味著我們可以將數組保存在另一個數組中。我們可以通過使用多個[]運算符來創建一個多維數組。
這里我們創建一個包含另一個數字數組的數組。
元組(Tupels)
元組基本類似數組但有一點不同。我們可以定義每個位子上儲存數據的類型。這意味著我們可以通過方括弧內的枚舉來限制固定索引位置的類型。
在此列中,我們定義了一個簡單的元組,在索引 0 位置上指定為數值類型,在索引為 1 位置上指定為字元串類型。這意味著如果我們嘗試在此索引上放置另一種數據類型,則會拋出錯誤。
以下是非法元組的示例:
枚舉(Enums)
與大多數其他面向對象編程語言一樣,Typescript 中的枚舉允許我們定義一組命名常量。 Typescript 還提供基於數值和基於字元串的枚舉。使用 enum 關鍵字定義 Typescript 中的枚舉。
數值枚舉
首先,我們將查看數值枚舉,其中我們將鍵值與索引匹配。
上面,我們定義了數值枚舉將 Playing 初始化為 0,Paused 為 1 等等。
我們也可以將初始化器留空,而 Typescript 會從零開始自動索引它。
字元串枚舉
定義字元串枚舉也十分簡單,我們只需要在定義的每個枚舉值後初始化字元串值。
這里我們通過使用字元串初始化我們的狀態來定義字元串枚舉。
對象(Objects)
Typescript 中的對象是包含一組鍵值對的實例。這些值可以是變數,數組甚至函數。它也被視為表示非基本類型的數據類型。
我們可以使用大括弧創建一個對象:
這里我們創建了一個 human 對象包含三個不同的鍵值對。
我們可以為對象加入方法:
自定義類型
Typescript 還允許我們自定義類型,以便於我們後續重用。要創建自定義類型,我們只需要使用 type 關鍵字並定義我們的類型。
在此示例中,我們定義了一個名為 Human 包含三個屬性的自定義類型。現在讓我們看看如何創建這種類型的對象。
在這里,我們創建自定義類型的實例並設置所需的屬性。
方法參數和返回類型
Typescript 允許我們為方法參數和返回值指定數據類型。現在讓我們看一下使用 Typescript 定義函數的語法。
這里我們有兩個示例函數,它們都具有定義類型的參數。我們還看到在結束括弧後定義返回類型。
現在我們可以像普通的 javascript 一樣調用我們的函數,但編譯器會檢查我們是否為函數提供了正確的參數。
可選屬性
Typescript 允許我們為方法(註:介面等同樣可以定義可選屬性)定義可選屬性。我們通過 ? 操作符定義。
在這個例子中,lastName 是一個可選參數,這意味著當我們不提供調用函數時,我們不會從編譯器中獲得錯誤。
這表示 2 個示例都被視為正確的。
默認值
我們使用可選屬性的第二種方法是為它指定一個默認值。我們可以通過直接在函數頭部賦值來實現。
在此例我中我們 lastName 賦予了默認值這意味著我們不必每次調用方法時提供它。
介面(Interfaces)
讓我們看個例子讓定義更加清晰:
可選屬性
在 Typescript 中,有時並不是所有介面屬性都是必需的。可以使用 ? 運算符在屬性後面將其設置為可選。
在這里,我們創建一個具有一個普通和一個可選屬性的介面,該屬性是使用 ? 運算符。這就是我們兩個人初始化都有效的原因。
只讀屬性
我們的介面中一些屬性也應該只在首次創建對象時修改賦值。我們可以通過將 readonly 關鍵字放在我們的屬性名稱之前來指定此功能。
在此示例中,id 屬性是只讀的,在創建對象後無法更改。
模塊(Barrels Moles)
Barrels 允許我們在一個更方便的模塊中匯總多個導出模塊。
我們僅需要創建一個新文件,它將導出我們項目中的多個模塊 (譯者註:根據 ECMAScript 定義一個文件定義一個模塊,此處可能表示模塊聚合(類似庫等的入口文件))。
之後我們可以通過這個便利的單一導入語句引入這些模塊。
泛型(Generics)
泛型允許我們創建兼容廣泛類型而不是單一類型的組件。這使得我們的組件「 開放」和復用。
現在您可能想知道為什麼我們不只是使用任何( any )類型來使組件接受多種類型而不是單一類型。讓我們看一個例子更好地了解。
我們想要一個簡單的假函數(mmy function),它返回傳入的參數:
然而 any 是通用的,某種程度它能接受所有類型參數但有一個很大的區別。我們丟失了我們傳入的參數是什麼類型以及返回值是什麼類型。
所以讓我們來看看,我們如何接受所有類型並知道它返回值的類型。
這里我們使用泛型參數 T,因此我們可以捕獲變數類型並在以後使用它。我們還使用它作為返回參數類型,它允許我們在檢查代碼時看到相應的類型。
更多詳細介紹你可以查看Charly Poly關於Generics and overloads的文章
訪問修飾符(Access Modifiers)
訪問修飾符控制我們類成員的可訪問性。 Typescript 支持三種訪問修飾符 - 公共的(public),私有的(private)和受保護的(protected)。
公共的
公共成員可以在任何地方訪問,沒有任何限制 這也是標准修飾符,這意味著您不需要使用 public 關鍵字為變數添加前綴。
私有的
私有成員只能在其定義的類中能訪問。
受保護的
保護成員只能在其定義的類及其子類中訪問。
TSLINT
TSLINT 是 Typescript 的標准 linter,可以幫助我們編寫干凈,可維護和可讀的代碼。它可以使用我們自己的 lint 規則,配置和格式化程序進行自定義。
設置
首先我們需要安裝 Typescript 和 tslint,我們可以全局安裝和局部安裝:
之後,我們可以使用 TSLINT CLI 在我們的項目中初始化 TSLINT。
現在我們有了 tslint.json 文件,我們可以開始配置我們的規則了。
配置
TSLINT 允許使用配置我們自己的規則並自定義代碼的外觀。默認情況下,tslint.json 文件看起來像這樣,只使用默認規則。
我們可以通過將它們放在 rules 對象中來添加其他規則。
有關所有可用規則的 概述,您可以查看官方文檔。
結論
恭喜您一路走到最後!希望此篇文章幫助您理解 Typescript 的基礎知識以及如何在項目中使用。
如果您發現這個有用,請考慮推薦並與其他開發人員共享。也可以訪問我的網站學習更多。https://www.icoderoad.com
如果您有任何問題和反饋,在以下評論中讓我知道。
㈦ TypeScript簡單入門(三):TypeScript原始數據類型表達
原始數據類型包括:布爾值、數值、字元串、 null 、 undefined 以及 ES6 中的新類型 Symbol 和 ES10 中的新類型 BigInt 。
TypeScript聲明變數時需要指定數據類型具體表達式
以構造函數方式:
編譯後
其中 0b1010 和 0o744 是 ES6 中的二進制和八進製表示法,它們會被編譯為十進制數字。
模板字元串
其中 ` 用來定義 ES6 中的模板字元串,${expr} 用來在模板字元串中嵌入表達式。
JavaScript 沒有空值(Void)的概念,在 TypeScript 中,可以用 void 表示沒有任何返回值的函數:
聲明一個 void 類型的變數沒有什麼用,因為你只能將它賦值為 undefined 和 null
在 TypeScript 中,可以使用 null 和 undefined 來定義這兩個原始數據類型:
與 void 的區別是,undefined 和 null 是所有類型的子類型。也就是說 undefined 類型的變數,可以賦值給 number 類型的變數:
而 void 類型的變數不能賦值給 number 類型的變數:
Symbols是ES6新增的原始數據類型,ts中使用時需要先配置
1、配置tsconfig.json
"lib": ["es6"], 需要dom時還要將"dom"添加進lib,如:console.log語句
2、特性
(1)不能和其他值計算,如加鍵、字元串拼接等
(2)可以調用.toString()返回字元串
(3)本身作為true類型存在
(4)類似for in遍歷時,不會遍歷symbol屬性
可通過Object.getOwnPropertySymbols(對象)/Reflect.ownKeys(obj)獲取symbol屬性
3、語法
bigint 數據類型是用來表示那些已經超出了 number 類型最大值的整數值,對於總是被詬病的整數溢出問題,使用了 bigint 後將完美解決。
bigint 是一種基本數據類型(primitive data type)。
JavaScript 中可以用 Number 表示的最大整數為 2^53 - 1,可以寫為 Number.MAX_SAFE_INTEGER。如果超過了這個界限,可以用 BigInt來表示,它可以表示任意大的整數。
在一個整數字面量後加 n 的方式定義一個 BigInt,如:10n 或者調用函數 BigInt():
BigInt 與 Number 的不同點:
BigInt 不能用於 Math 對象中的方法。
BigInt 不能和任何 Number 實例混合運算,兩者必須轉換成同一種類型。
BigInt 變數在轉換為 Number 變數時可能會丟失精度。
參考
原始數據類型
TypeScript BigInt
TypeScript Symbol
㈧ 第三節:TypeScript對象類型
在 JavaScript 中,我們分組和傳遞數據的基本方式是通過對象。在 TypeScript 中,我們通過 對象類型 來表示它們。
而TypeScript的核心原則之一是對值所具有的的結構進行類型檢查,
之前的數據類型中已經了解,如何限定變數的類型
例如:
如果變數的值不是基本數據類型的值,而是一個對象,可以使用 object 類型
這種寫法只能限定變數是object 類型, 但是沒法明確表明或限定對象內部屬性以及值類型,
也就是說示例中name可是是任意數據類型;屬性也不限定於 name 和 age
如果要限定對象屬性值的類型,就需要使用字面量的方式進行類型注釋
這樣的寫法雖然達到了限定對象內部結構, 但同時也帶來了另外的問題,如屬性過多,或多次復用相同類型注釋.
因此可以定義介面或 類型別名來定義統一的對象屬性限制
通過介面命名對象類型
或者使用類型別名來命名對象類型
但是也需要注意到,在給使用了介面和類型別名時, 變數值接受對象中的屬性必須和介面或類型別名中定義的屬性一致.,多了少了編譯時都會報錯
為了讓對象類型更具靈活性, 對象類型中每個屬性都可以指定幾件事:
很多時候,在處理對象類型的時候, 某些屬性可能並不一定存在. 這個時候就需要用到可選屬性.
可選屬性通過在屬性名稱末尾添加 ? 來將這些屬性標記為可選
例如:
可選屬性在進行檢測時,可選屬性在實現上可有可無,這樣就提升了對象類型使用的靈活性.
例如:
在TypeScript中對象屬性也可以標記為 readonly 只讀屬性
只讀屬性雖然不會在運行時更改任何行為.但在類型檢查期間無法寫入只讀標記的屬性
通過在屬性名前添加 readonly 來標記只讀屬性
例如:
此時 name 屬性標記為只讀屬性, 當你嘗試修改只讀屬性值時, TypeScript 報錯,提示你不可修改
使用 readonly 修飾符並不一定意味著一個值完全不可修改, 也就是說屬性屬性值是一個引用類型的值,比如對象
readonly 只是表示當前屬性本身不能被重寫, 但屬性值是引用類型, 引用類型內部的值是完全可變的
例如:
示例中只讀屬性 friend 內部的屬性 name 被修改了, 沒有任何報錯.
因為TypeScript在檢查這些類型是否兼容時不會考慮兩種類型內部的屬性是否有 readonly 標記存在, 所以 readonly 屬性也可以通過別名來更改, 也就是說將有隻讀屬性的類型重新分配給沒有隻讀屬性的類型
有的時候你並不提前知道類型屬性的所有屬性名, 但你確實知道屬性值的類型
在這樣的情況下, 你可以使用索引簽名來描述可能值的類型,
例如:
示例中所以簽名的意思,表示使用 number 類型的索引獲取值時,返回 string 類型
TypeScript索引簽名可以同時支持兩種類型的索引器.
雖然字元串索引簽名是描述'字典'模式的強大方式, 但TypeScript 還是強制所有的屬性與其返回的類型匹配
例如:
示例中, age 屬性的類型是 number 類型, 將會出現錯誤,因為與索引簽名沖突,
其實也很好理解, 因為在使用 age 屬性時, 無論通過, obj.age , 還是 obj["age"] , 其都符合索引簽名的模式, 返回的值類型應該是 string 類型, 可是你有明確的聲明了 age 屬性的返回值是明確的 number 類型
此時TypeScript不知道使用索引簽名的規則來檢查值類型,還是具體羅列 age 屬性的類型來檢查值類型
這種問題,可以通過給索引簽名使用聯合類型解決
最後你 還可以在索引簽名上使用只讀屬性 readonly , 表示不可以給索引分配值
例如:只讀索引簽名
在實際使用時,一個類型有可能是其他類型的具體版本的類型很常見,
簡單說就是, 一個類型只有另外一個類型中的部分信息
例如:
有一下兩個類型:
人員基本信息,包含姓名,年紀信息
具體學生信息: 包含除了姓名,年紀外還有學號,班級等信息
示例中, StudentPerson 包含 Person 所有的屬性信息, 也可以說是 Person 類型更詳細的類型,
試想一下,如果每個定義包含 name , age 屬性以及其他不同屬性類型時,我們都像示例中把 name , age 屬性重新定義一遍.
這樣的使用方式會導致 name , age 屬性大量重復
解決這樣重復聲明一個類型中所有的屬性,我們就可以使用 extends 關鍵字擴展原有類型, 並添加新的屬性
關鍵字 extends 允許我們有效的從其他命名類型復製成員, 並添加我們想要的新成員
同樣 interface 介面也允許從多個介面中擴展新的類型
這樣我們就可以使用 WorkPerson 介面來注釋一個具有姓名,年紀, 工作信息,工號屬性對象的類型
interface 允許我們通過 extends 擴展其他類型來構建新的類型
TypeScript中還為類型別名提供了另外一種稱之為交叉類型的類型擴展方式. 主要用於組合現有類型
使用 & 運算符定義交叉類型
示例中,通過交叉類型組合 Colorful 並 Circle 生成一個新的類型別名,類型別名同時具有前兩個類型的所有屬性
也可以在類型注釋的時候使用交叉類型
理解兩者的主要區別,方便我們在使用時做出取捨
通用對象類型:就是需要定義一個可以通用類型,
例如:定義一個Box類型,具有 contents 屬性, 但是屬性的值可能是 string , number , 等各種類型
首先會想到的是屬性值類型使用聯合類型
但聯合類型也僅僅是羅列我們已知的類型, 在使用場景下可能並不通用, 例如值也有可能是其他對象類型呢
此時也許會考慮 any 任何類型
any類型可以工作,但是可能會導致意外事故發生
也可以嘗試定義 unknown 類型
使用unknown類型就意味著我們需要進行類型檢查,或者使用類型斷言
不過 unknow 也不是特別安全, 比較安全的做法是為每一種類型添加一個介面
但這意味著如果是給函數參數使用, 我們需要創建不同函數或函數重載, 才能對這些進行操作
這樣的處理方式不僅繁瑣, 而且之後需要需要新的類型還需要引入新的類型和重載, 因為我們contents類型和重載實際上都是相同的,
最好的處理方式,就是我們創建一個聲明類型參數的泛型,
其實就是將類型定義為像函數參數或變數一樣, 類型參數就可以在多個地方使用, 通過傳遞具體類型,讓使用類型參數的地方全部指代當前具體類型
此時當我們在使用 Box 類型注釋時,必須給出一個類型參數來代替 Type
此時會將Box視為類型模板,其中 Type 為佔位符將被其他類型替換,
Box 類型可以重復使用, Type 可以用任何類型代替, 這意味著當我們需要一個新類型 Box 是, 我們根本不要在 聲明一個新的 Box 類型, 我們只需要傳遞不同的類型替換 Type 即可
這也意味著,如果將類型用在函數參數上,我們可以通過使用泛型函數來避免重載
示例中, 我們並沒有限定obj的類型, 但是在傳遞參數後, TypeScript根據入參推斷出 Type 是一個string 類型, 因此函數的第二個參數也必須是一個字元串類,否則TypeScript將發出錯誤警告
例如:如下調用函數
類型的別名也是可以通用的
例如
Box 介面也可以使用類型別名來替換
由於類型別名與介面不同,類型別名不僅僅可以描述對象,還可以使用類型別名來編寫其他類型的通用幫助類型
㈨ 如何在 TypeScript 中創建自定義類型
介紹
TypeScript 是 JavaScript 語言的擴展,它使用 JavaScript 運行時和編譯時類型檢查器。
這種組合允許開發人員使用完整的 JavaScript 生態系統和語言功能,同時還添加可選的靜態類型檢查、枚舉數據類型、類和介面。這些特性為開發人員提供了 JavaScript 動態特性的靈活性,但也允許更可靠的代碼庫,其中可以在編譯時使用類型信息來檢測可能在運行時導致錯誤或其他意外行為的問題。
額外的類型信息還提供了更好的代碼庫文檔,並在文本編輯器中改進了 IntelliSense(代碼完成、參數信息和類似的內容輔助功能)。隊友可以准確地確定任何變數或函數參數的預期類型,而無需通過實現本身。
准備工作
要遵循本教程,我們將需要:
1)、一個環境,我們可以在其中執行 TypeScript 程序以跟隨示例。要在本地計算機上進行設置,我們將需要以下內容。
2)、如果你不想在本地機器上創建 TypeScript 環境,你可以使用官方的 TypeScript Playground 來跟隨。
3)、我們將需要足夠的 JavaScript 知識,尤其是 ES6+ 語法,例如解構、rest 運算符和導入/導出。有關JavaScript的更多主題信息,建議閱讀我們的 JavaScript 系列教程。
4)、本教程將參考支持 TypeScript 並顯示內聯錯誤的文本編輯器的各個方面。這不是使用 TypeScript 所必需的,但確實可以更多地利用 TypeScript 功能。為了獲得這些好處,我們可以使用像 Visual Studio Code 這樣的文本編輯器,它完全支持開箱即用的 TypeScript。我們也可以在 TypeScript Playground 中嘗試這些好處。
本教程中顯示的所有示例都是使用 TypeScript 4.2.2 版創建的。
創建自定義類型
自定義類型語法
在 TypeScript 中,創建自定義類型的語法是使用 type 關鍵字,後跟類型名稱,然後使用類型屬性分配給 {} 塊。採取以下措施:
語法類似於對象文字,其中鍵是屬性的名稱,值是該屬性應具有的類型。這定義了一個 Programmer 類型,它必須是一個對象,其 name 鍵保存一個字元串值,並且 knownFor 鍵保存一個字元串數組。
如前面的示例所示,我們可以使用 ; 作為每個屬性之間的分隔符。也可以使用逗號、, 或完全省略分隔符,如下所示:
使用自定義類型與使用任何基本類型相同。添加一個雙冒號,然後添加我們的類型名稱:
ada 常量現在將通過類型檢查器而不會引發錯誤。
如果我們在任何完全支持 TypeScript 的編輯器中編寫此示例,例如在 TypeScript Playground 中,編輯器將建議該對象期望的欄位及其類型,如下面的動畫所示:
如果我們使用 TSDoc 格式(一種流行的 TypeScript 注釋文檔樣式)向欄位添加註釋,則在代碼完成中也建議使用它們。使用以下代碼並在注釋中進行解釋:
注釋描述現在將與欄位建議一起出現:
TypeScript 編譯器 (tsc) 將顯示錯誤 2322:
如果我們省略了我們的類型所需的任何屬性,如下所示:
TypeScript 編譯器將給出錯誤 2741:
添加原始類型中未指定的新屬性也會導致錯誤:
在這種情況下,顯示的錯誤是 2322:
嵌套自定義類型
我們還可以將自定義類型嵌套在一起。想像一下,我們有一個 Company 類型,它有一個符合 Person 類型的 manager 欄位。我們可以像這樣創建這些類型:
然後,我們可以像這樣創建一個 Company 類型的值:
我們可以省略 manager 常量中的類型,因為它與 Person 類型具有相同的形狀。當我們使用與 manager 屬性類型所期望的形狀相同的對象時,TypeScript 不會引發錯誤,即使它沒有明確設置為 Person 類型。
以下不會引發錯誤:
我們甚至可以更進一步,直接在company對象字面量中設置manager:
所有這些場景都是有效的。
如果在支持 TypeScript 的編輯器中編寫這些示例,我們會發現編輯器將使用可用的類型信息來記錄自己。對於前面的示例,只要我們打開 manager 的 {} 對象文字,編輯器就會期望一個name類型的字元串屬性:
現在,我們已經完成了一些使用固定數量的屬性創建我們自己的自定義類型的示例,接下來,我們將嘗試向我們的類型添加可選屬性。
可選屬性
要將可選屬性添加到類型,請添加 ? 屬性的修飾符。使用前面部分中的 Programmer 類型,通過添加以下突出顯示的字元將 knownFor 屬性轉換為可選屬性:
在這里我們要添加 ? 屬性名稱後的修飾符。這使得 TypeScript 將此屬性視為可選的,並且在我們省略該屬性時不會引發錯誤:
這將毫無錯誤地通過。
既然,我們已經知道如何向類型添加可選屬性,那麼,現在該學習如何創建一個可以容納無限數量的欄位的類型了。
可索引類型
在這里,我們使用大括弧 ({}) 中的類型定義塊創建一個普通類型,然後以 [key: typeOfKeys]: typeOfValues 的格式添加一個特殊屬性,其中 typeOfKeys 是該對象的鍵應具有的類型, typeOfValues 是這些鍵的值應該具有的類型。
然後,我們可以像任何其他類型一樣正常使用它:
使用可索引類型,我們可以分配無限數量的屬性,只要它們與索引簽名匹配,索引簽名是用於描述可索引類型的鍵和值的類型的名稱。在這種情況下,鍵具有字元串類型,值具有任何類型。
還可以將始終需要的特定屬性添加到可索引類型中,就像使用普通類型一樣。在以下突出顯示的代碼中,我們將狀態屬性添加到我們的數據類型:
這意味著數據類型對象必須有一個帶有布爾值的狀態鍵才能通過類型檢查器。
現在,我們可以創建具有不同數量元素的對象,我們可以繼續學習 TypeScript 中的數組,它可以具有自定義數量的元素或更多。
創建元素數量或更多的數組
使用 TypeScript 中可用的數組和元組基本類型,我們可以為應該具有最少元素的數組創建自定義類型。在本節中,我們將使用 TypeScript 剩餘運算符...來執行此操作。
想像一下,我們有一個負責合並多個字元串的函數。此函數將採用單個數組參數。這個數組必須至少有兩個元素,每個元素都應該是字元串。我們可以使用以下內容創建這樣的類型:
MergeStringsArray 類型利用了這樣一個事實,即我們可以將 rest 運算符與數組類型一起使用,並將其結果用作元組的第三個元素。這意味著前兩個字元串是必需的,但之後的其他字元串元素不是必需的。
如果一個數組的字元串元素少於兩個,它將是無效的,如下所示:
TypeScript 編譯器在檢查此數組時將給出錯誤 2322:
到目前為止,我們已經從基本類型的組合中創建了自己的自定義類型。在下一節中,我們將通過將兩個或多個自定義類型組合在一起來創建一個新類型。
組合類型
在這里我們將介紹兩種組合類型的方法。這些將使用聯合運算符傳遞符合一種或另一種類型的任何數據,並使用交集運算符傳遞滿足兩種類型中所有條件的數據。
Unions
unions是使用 | 創建的 (pipe) 運算符,它表示可以具有聯合中任何類型的值。舉個例子:
在此代碼中,ProctCode 可以是字元串或數字。以下代碼將通過類型檢查器:
unions類型可以從任何有效 TypeScript 類型的聯合中創建。
Intersections
我們可以使用相交類型來創建一個全新的類型,該類型具有相交在一起的所有類型的所有屬性。
例如,假設我們有一些公共欄位始終出現在 API 調用的響應中,然後是某些端點的特定欄位:
在這種情況下,所有響應都將具有 status 和 isValid 屬性,但只有用戶響應將具有附加的用戶欄位。要使用交集類型創建特定 API 用戶調用的結果響應,請結合使用 StatusResponse 和 GetUserResponse 類型:
ApiGetUserResponse 類型將具有 StatusResponse 中可用的所有屬性以及 GetUserResponse 中可用的屬性。這意味著數據只有在滿足兩種類型的所有條件時才會通過類型檢查器。以下示例將起作用:
另一個示例是資料庫客戶端為包含連接的查詢返回的行類型。我們將能夠使用交集類型來指定此類查詢的結果:
稍後,如果我們使用 fetchRowsFromDatabase() 函數,如下所示:
生成的常量joinedRows 必須有一個role 屬性和一個name 屬性,它們都保存字元串值,以便通過類型檢查器。
使用模板字元串類型
從 TypeScript 4.1 開始,可以使用模板字元串類型創建類型。這將允許我們創建檢查特定字元串格式的類型,並為我們的 TypeScript 項目添加更多自定義。
要創建模板字元串類型,我們使用的語法與創建模板字元串文字時使用的語法幾乎相同。但是,我們將在字元串模板中使用其他類型而不是值。
想像一下,我們想創建一個傳遞所有以 get 開頭的字元串的類型。我們可以使用模板字元串類型來做到這一點:
myString 將在此處通過類型檢查器,因為字元串以 get 開頭,然後是一個附加字元串。
如果我們將無效值傳遞給我們的類型,例如以下 invalidStringValue:
TypeScript 編譯器會給我們錯誤 2322:
使用模板字元串創建類型可幫助我們根據項目的特定需求自定義類型。在下一節中,我們將嘗試類型斷言,它為其他無類型數據添加類型。
Using Type Assertions
如果我們想讓我們的代碼在這些場景中是類型安全的,我們可以使用類型斷言,這是一種將變數類型更改為另一種類型的方法。通過在變數後添加 as NewType 可以實現類型斷言。這會將變數的類型更改為 as 關鍵字之後指定的類型。
舉個例子:
value 的類型為 any,但是,使用 as 關鍵字,此代碼將 value 強制為 string 類型。
注意:要斷言 TypeA 的變數具有 TypeB 類型,TypeB 必須是 TypeA 的子類型。幾乎所有的 TypeScript 類型,除了 never,都是 any 的子類型,包括 unknown。
實用程序類型
在前面的部分中,我們查看了從基本類型創建自定義類型的多種方法。但有時我們不想從頭開始創建一個全新的類型。有時最好使用現有類型的一些屬性,甚至創建一個與另一種類型具有相同形狀但所有屬性都設置為可選的新類型。
使用 TypeScript 提供的現有實用程序類型,所有這些都是可能的。本節將介紹其中一些實用程序類型;有關所有可用的完整列表,請查看 TypeScript 手冊的實用程序類型部分。
所有實用程序類型都是通用類型,我們可以將其視為接受其他類型作為參數的類型。可以通過使用 語法向其傳遞類型參數來識別通用類型。
Record
Record 實用程序類型可用於以比使用之前介紹的索引簽名更簡潔的方式創建可索引類型。
在我們的可索引類型示例中,我們具有以下類型:
我們可以使用 Record 實用程序類型而不是像這樣的可索引類型:
Record 泛型的第一個類型參數是每個鍵的類型。在以下示例中,所有鍵都必須是字元串:
第二個類型參數是這些鍵的每個值的類型。以下將允許值是任何值:
Omit
Omit 實用程序類型可用於基於另一種類型創建新類型,同時排除結果類型中不需要的一些屬性。
假設我們有以下類型來表示資料庫中用戶行的類型:
如果在我們的代碼中,我們要檢索除 addressId 之外的所有欄位,則可以使用 Omit 創建沒有該欄位的新類型:
Omit 的第一個參數是新類型所基於的類型。第二個是我們要省略的欄位。
如果我們在代碼編輯器中將滑鼠懸停在 UserRowWithoutAddressId 上,我們會發現它具有 UserRow 類型的所有屬性,但我們省略了這些屬性。
我們可以使用字元串聯合將多個欄位傳遞給第二個類型參數。假設我們還想省略 id 欄位,我們可以這樣做:
Pick
Pick 實用程序類型與 Omit 類型完全相反。我們無需說出要省略的欄位,而是指定要從其他類型使用的欄位。
使用我們之前使用的相同 UserRow:
假設我們只需要從資料庫行中選擇電子郵件鍵。我們可以像這樣使用 Pick 創建這樣的類型:
Pick 這里的第一個參數指定了新類型所基於的類型。第二個是我們想要包含的鍵。
這將等同於以下內容:
我們還可以使用字元串聯合來選擇多個欄位:
Partial
使用相同的 UserRow 示例,假設我們想創建一個新類型,該類型與我們的資料庫客戶端可以用來將新數據插入用戶表中的對象相匹配,但有一個小細節:我們的資料庫具有所有欄位的默認值,所以,我們是不需要通過其中任何一個。
為此,我們可以使用 Partial 實用程序類型來選擇性地包括基本類型的所有欄位。
我們現有的類型 UserRow 具有所需的所有屬性:
要創建所有屬性都是可選的新類型,我們可以使用 Partial 實用程序類型,如下所示:
這與擁有這樣的 UserRowInsert 完全相同:
實用程序類型是一個很好的資源,因為它們提供了一種比從 TypeScript 中的基本類型創建類型更快的方法來構建類型。
總結
創建我們自己的自定義類型來表示我們自己的代碼中使用的數據結構,可以為我們的項目提供靈活且有用的 TypeScript 解決方案。除了從整體上提高我們自己代碼的類型安全性之外,將我們自己的業務對象類型化為代碼中的數據結構將增加代碼庫的整體文檔,並在與團隊成員一起工作時改善我們自己的開發人員體驗相同的代碼庫。
㈩ TypeScript 變數聲明
一直以來我們都是通過 var 關鍵字定義JavaScript變數。
大家都能理解,這里定義了一個名為 a 值為 10 的變數。
我們也可以在函數內部定義變數:
並且我們也可以在其它函數內部訪問相同的變數。
上面的例子里, g 可以獲取到 f 函數里定義的 a 變數。 每當 g 被調用時,它都可以訪問到 f 里的 a 變數。 即使當 g 在 f 已經執行完後才被調用,它仍然可以訪問及修改 a 。
這里很容易看出一些問題,里層的 for 循環會覆蓋變數 i ,因為所有 i 都引用相同的函數作用域內的變數。 有經驗的開發者們很清楚,這些問題可能在代碼審查時漏掉,引發無窮的麻煩。
快速的猜一下下面的代碼會返回什麼:
介紹一下, setTimeout 會在若干毫秒的延時後執行一個函數(等待其它代碼執行完畢)。
好吧,看一下結果:
很多JavaScript程序員對這種行為已經很熟悉了,但如果你很不解,你並不是一個人。 大多數人期望輸出結果是這樣:
還記得我們上面提到的捕獲變數嗎?
讓我們花點時間思考一下這是為什麼。 setTimeout 在若干毫秒後執行一個函數,並且是在 for 循環結束後。 for 循環結束後, i 的值為 10 。 所以當函數被調用的時候,它會列印出 10 !
一個通常的解決方法是使用立即執行的函數表達式(IIFE)來捕獲每次迭代時 i 的值:
這種奇怪的形式我們已經司空見慣了。 參數 i 會覆蓋 for 循環里的 i ,但是因為我們起了同樣的名字,所以我們不用怎麼改 for 循環體里的代碼。
主要的區別不在語法上,而是語義,我們接下來會深入研究。
這里我們定義了2個變數 a 和 b 。 a 的作用域是 f 函數體內,而 b 的作用域是 if 語句塊里。
關於 暫時性死區 的更多信息,查看這里Mozilla Developer Network.
在一個嵌套作用域里引入一個新名字的行為稱做 屏蔽 。 它是一把雙刃劍,它可能會不小心地引入新問題,同時也可能會解決一些錯誤。 例如,假設我們現在用 let 重寫之前的 sumMatrix 函數。
這個版本的循環能得到正確的結果,因為內層循環的 i 可以屏蔽掉外層循環的 i 。
通常 來講應該避免使用屏蔽,因為我們需要寫出清晰的代碼。 同時也有些場景適合利用它,你需要好好打算一下。
因為我們已經在 city 的環境里獲取到了 city ,所以就算 if 語句執行結束後我們仍然可以訪問它。
回想一下前面 setTimeout 的例子,我們最後需要使用立即執行的函數表達式來獲取每次 for 循環迭代里的狀態。 實際上,我們做的是為獲取到的變數創建了一個新的變數環境。 這樣做挺痛苦的,但是幸運的是,你不必在TypeScript里這樣做了。
會輸出與預料一致的結果:
這很好理解,它們引用的值是 不可變的 。
除非你使用特殊的方法去避免,實際上 const 變數的內部狀態是可修改的。 幸運的是,TypeScript允許你將對象的成員設置成只讀的。 介面一章有詳細說明。
使用最小特權原則,所有變數除了你計劃去修改的都應該使用 const 。 基本原則就是如果一個變數不需要對它寫入,那麼其它使用這些代碼的人也不能夠寫入它們,並且要思考為什麼會需要對這些變數重新賦值。 使用 const 也可以讓我們更容易的推測數據的流動。
跟據你的自己判斷,如果合適的話,與團隊成員商議一下。
Another TypeScript已經可以解析其它 ECMAScript 2015 特性了。 完整列表請參見 the article on the Mozilla Developer Network。 本章,我們將給出一個簡短的概述。
最簡單的解構莫過於數組的解構賦值了:
這創建了2個命名變數 first 和 second 。 相當於使用了索引,但更為方便:
作用於函數參數:
你可以在數組里使用 ... 語法創建剩餘變數:
當然,由於是JavaScript, 你可以忽略你不關心的尾隨元素:
或其它元素:
你也可以解構對象:
這通過 o.a and o.b 創建了 a 和 b 。 注意,如果你不需要 c 你可以忽略它。
注意,我們需要用括弧將它括起來,因為Javascript通常會將以 { 起始的語句解析為一個塊。
你可以在對象里使用 ... 語法創建剩餘變數:
你也可以給屬性以不同的名字:
這里的語法開始變得混亂。 你可以將 a: newName1 讀做 " a 作為 newName1 "。 方向是從左到右,好像你寫成了以下樣子:
令人困惑的是,這里的冒號 不是 指示類型的。 如果你想指定它的類型, 仍然需要在其後寫上完整的模式。
默認值可以讓你在屬性為 undefined 時使用預設值:
現在,即使 b 為 undefined , keepWholeObject 函數的變數 wholeObject 的屬性 a 和 b 都會有值。
但是,通常情況下更多的是指定默認值,解構默認值有些棘手。 首先,你需要在默認值之前設置其格式。
其次,你需要知道在解構屬性上給予一個默認或可選的屬性用來替換主初始化列表。 要知道 C 的定義有一個 b 可選屬性:
要小心使用解構。 從前面的例子可以看出,就算是最簡單的解構表達式也是難以理解的。 尤其當存在深層嵌套解構的時候,就算這時沒有堆疊在一起的重命名,默認值和類型註解,也是令人難以理解的。 解構表達式要盡量保持小而簡單。 你自己也可以直接使用解構將會生成的賦值表達式。
展開操作符正與解構相反。 它允許你將一個數組展開為另一個數組,或將一個對象展開為另一個對象。 例如:
這會令 bothPlus 的值為 [0, 1, 2, 3, 4, 5] 。 展開操作創建了 first 和 second 的一份淺拷貝。 它們不會被展開操作所改變。
你還可以展開對象:
search 的值為 { food: "rich", price: "$", ambiance: "noisy" } 。 對象的展開比數組的展開要復雜的多。 像數組展開一樣,它是從左至右進行處理,但結果仍為對象。 這就意味著出現在展開對象後面的屬性會覆蓋前面的屬性。 因此,如果我們修改上面的例子,在結尾處進行展開的話:
那麼, defaults 里的 food 屬性會重寫 food: "rich" ,在這里這並不是我們想要的結果。
對象展開還有其它一些意想不到的限制。 首先,它僅包含對象 自身的可枚舉屬性。 大體上是說當你展開一個對象實例時,你會丟失其方法:
其次,TypeScript編譯器不允許展開泛型函數上的類型參數。 這個特性會在TypeScript的未來版本中考慮實現。