強類型與弱類型
在函數參數上
強類型要求入參必須按照類型來傳
fun(int num){}
func(num){}
強類型語言中不允許任意的資料隐式類型轉換,而弱類型是允許的
如node 是弱類型
> '100'-50
50
> Math.floor(true)
1
在python中
>>> '100' - 50
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for -: 'str' and 'int'
在語言層面的類型限制
弱類型的問題
- 必須在運作階段才能發現代碼中的類型異常
*TypeError: obj.foo is not a functionconst obj = {}; obj.foo();
- 類型不确定造成的典型問題,如果使用強類型語言則不會有這個問題,如要求是數字類型,傳入字元串類型則文法不通過
function sum(a,b){ return a+b; } console.log(sum(1,2)); //3 console.log(sum(1,'2')); //12
- 對象屬性會轉為字元串,若不知道此特點則很奇怪
const obj = {} obj[true] = 123; console.log(obj['true'])
君子約定有隐患,強制要求有保障
強類型的優勢
- 錯誤更早暴露
-
代碼更隻能,編碼更準确
如成員的名稱校驗 element.innerHtml;
-
重構更牢靠
工具類中的方法改名,強類型在編譯期間就會提示錯誤,弱類型則不會報錯,需要在運作階段才會報錯
- 減少不必要的類型判斷
function sum(a,b){ if(typeof a !== 'number' || typeof b !== 'number'){ return 'type is not number' } return a + b; }
靜态類型語言與動态類型語言
- 靜态類型語言:一個變量聲明時它的類型就已明确了,不能修改了
- 動态類型:在運作階段才能夠明确變量類型,變量類型可以随時發生變化
如JavaScript中
var foo = 100;
foo='你好'; //ok
cosnole.log(foo)
可以了解為動态類型語言中的變量是沒有類型的,變量中存放的值是有類型的.
JavaScript是弱類型的動态類型語言
腳本語言,沒有編譯環節.即便設計成靜态類型語言也無意義,靜态類型語言在編譯的時候會做靜态類型檢查,js不需要
flow
JavaScript的類型檢查工具(Facebook推出的工具)
通過代碼中使用類型注解的方式來表明變量/參數應該是什麼類型的
function square(n: number){
retrun n*n;
}
square('100') //類型異常檢查
在運作之前通過babel或官方子產品去除注解
flow快速上手
flow是以npm子產品的方式去工作的
初始化
yarn init --yes
添加子產品到該項目
yarn add flow-bin --dev
安裝完後可以在node_modules/.bin檔案夾内發現flow執行檔案,也就是說可以在指令行中執行flow
//@flow
function square(n: number){
retrun n*n;
}
square('100') //類型異常檢查
必須在檔案的第一行以注釋的形式添加@flow的标記,flow才會檢測這個檔案
//初始化産生.flowconfig配置檔案
yarn flow init
// 執行flow 第一次執行慢一點,因為要啟動一個背景服務去接收檔案
yarn flow
//完成編碼後 結束服務
yarn flow stop
編譯去除注解
flow注解是在js中編譯是會報錯的
安裝官方的移除注解子產品(簡單快捷)
yarn add flow-remove-types --dev
//執行 yarn flow-remove-types 源代碼所在目錄(.是目前目錄) -d 輸出目錄
yarn flow-remove-types . -d dist
這樣是src目錄下
yarn flow-remove-types src -d dist
babel
//安裝 核心子產品 和 babel的cli工具(可以用babel指令去編譯) preset-flow包含轉換flow類型注解的插件
yarn add @babel/core @babel/cli @babel/preset-flow --dev
在項目中添加.babelrc檔案
{
"presets":["@babel/preset-flow"]
}
通過yarn運作指令
yarn babel src -d dist
也能移除flow的類型注解
開發工具插件
Flow Language Support
flow類型推斷
//@flow
function square(n){
return (n*n); //根據計算 類型推斷應該是數字
}
square('你好')
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-HlIhdpHy-1627926188756)(C:\Users\Kid\AppData\Roaming\Typora\typora-user-images\image-20210701005036991.png)]
flow類型注解
//@flow
let num:number = 123; //在變量後面加冒号 類型名稱規定該變量是什麼類型的
num = 'Str' //報錯
// 函數括号後添加冒号 類型名稱 限定該函數的傳回值類型,若無傳回值是 undefined 則類型應該是void
function fun():number{
return 123; //正常
return '123'; //報錯
}
flow原始類型
//@flow
// 原始類型
const a:string = '字元串';
const b:number = Infinity // 100 NAN 數字類型
const c:boolean = false // true
const d:null = null
const e:void = undefined //flow中的undefined是用void表示
const f:symbol = Symbol();
any 表示任易類型,盡量避免使用,不安全,例如兩數相加時 字元串拼接的結果
flow數組類型
flow中支援2中數組類型的表示方法
/**
* 數組類型
* @flow
*/
// 第一種
//<數組中每個元素的類型> 泛型
const arr1:Array<number> = [1,2,3,4]; //這裡出現字元串不是數字類型則會報錯
// 第二種
const arr2:number[] = [1,2,3]
// 元組 固定長度的數組 指定每個元素的類型
const foo:[string,number] = ['1',2];
flow對象類型
/**
* 對象類型
* @flow
*/
//表示目前對象中必須要有foo和bar的成員,而且類型分别為string和number
const obj1:{foo:string,bar:number} = {
foo:'字元串',
bar:123
}
// 如果其中的成員是非必須的,可以在成員名稱後面加 ? 表示可有可無的
const obj2:{foo?:string,bar:any} = {
bar:'第二個參數'
}
// 指定鍵的類型和值的類型 以下是鍵必須是字元串類型,值必須是數字類型
const obj3:{[string]:number} = {
'1':1
};
flow函數類型
/**
* 函數類型
* @flow
*/
// 類似于箭頭函數的函數簽名的方式指定回調函數的參數類型與傳回值
function foo(callback:(number, string)=>void) {
// 表示 規定callback的參數1是數字類型 參數2是字元串類型 箭頭後面是傳回值類型 void代表不傳回
callback(123,'123')
}
foo( function (number, string){
console.log(number);
console.log(string);
})
flow特殊類型
字面量類型/聯合類型/或類型
/**
* 特殊類型
* @flow
*/
// a變量後面的字元串隻能被此字元串指派,賦其他值就會報錯
const a:'你好' = '你好'
// 應用可以被多個或的字元串指派
const status: 'success' | 'error' | 'danger' = 'error'
// type關鍵字 給類型做别名
type StringOrNumber = string | number;
// 利用此類型别名作為變量
const b:StringOrNumber = '字元串' //100
// maybe類型
// 想要給一個類型 允許賦null或者undefined的時候
const d:?number = null
// 在類型前面加問好相當于如下
const e:number|null|void = undefined
mixed 和 any
mixed接收任易類型的資料,相當于所有類型的聯合類型
差別:any是弱類型 而mixed任然是強類型;
/**
* mixed 和 any類型
* @flow
*/
// string|number|boolean ....
function fun(value:mixed){
// 強類型文法上會報錯
if( typeof value === 'string'){
value.substr(1)
}
if( typeof value === 'number'){
value*value
}
}
fun('string')
fun(123)
// --------------------------------------
function funAny(value:any){
// 弱類型文法上不會報錯
value.substr(1)
value*value
}
funAny('string')
funAny(123)
一般用mixed 更為安全;用any是為了相容一些老代碼;
flow 類型小結
以上是比較常用的類型,如遇到其他的類型可到
或者
查詢相關的類型資訊
flow運作環境API
因為JavaScript不是獨立工作的,例如運作在node環境/浏覽器環境;浏覽器中有DOM和BOM,node中有各種子產品
/**
* 運作環境 API
* @flow
*/
// HTMLElement類型 如果沒找到對應的元素會傳回null,類型不對應會報錯
const element: HTMLElement | null = document.getElementById('app')
vscode中在HTMLElement 右擊轉到定義能顯示對應的聲明
TypeScript
typescript是基于JavaScript的一門程式設計語言 包含(JavaScript、類型系統、es6+)
即使什麼特性不知道也可以使用JavaScript的文法使用,例如隻想使用es的新特性,使用typescript也是很好的選擇
typescript最低能編譯到es3版本的代碼,相容性好;任何JavaScript運作環境都支援typescript進行開發
缺點:語言多了很多概念(如接口/泛型/枚舉等);但是typescript是漸進式的,什麼特性不知道也能當JavaScript使用;周期短的小項目增加一些成本(如類型聲明單獨編寫);
typescript快速上手
yarn初始化項目
yarn init --yes
然後添加typescript子產品 作為開發依賴使用
yarn add typescript --dev
.bin目錄下的tsc檔案是用來編譯typescript的指令
/**
* typescript快速上手
*/
const hello = (name: string) => {
console.log(`Hello,${name}`)
}
hello('TypeScript')
hello(100) //這裡會報錯
使用yarn tsc指令編譯
yarn tsc 檔案名.ts
編譯後生成一個新的js檔案,轉換成了ES3标準的文法;ts檔案内可以使用flow,如果文法不通過,編譯也會報錯
typescript的配置檔案
yarn tsc --init
生成了一個tsconfig.json檔案
target 改為es2015輸出結果中就不會進行es6轉換了
module commonjs子產品 導入導出為require 和 export的方式
outDir編譯完後輸出的路徑如"dist"
rootDir:typescript源代碼路徑
sourceMap: true開啟源代碼映射,調試的時候使用sourcemap調試
strict:true 開啟嚴格模式 每個變量都要聲明對應的類型 哪怕是any的類型
項目根目錄運作
yarn tsc
進行編譯
typescript的原始類型
/**
* typescript原始類型
*/
const a:string = '字元串'
const b:number = 100
const c:boolean = true
// 非嚴格模式下 或 strictNullChecks配置為false的時候 與flow 的差別是可以為null
const d:string = null
// void一般用于标記函數無傳回值的時候傳回undefined的類型
const e:void = undefined
const f:null = null
const g:undefined = undefined
const h:symbol = Symbol() //這裡會報錯 根據提示 将配置檔案tsconfig.json中的target 改為 es2015 或更高版本
标準庫就是内置對象所對應的聲明,typescript中使用内置對象就必須要引入對應的聲明,否則報錯
target為es5就不支援es6的特性,是以文法會報錯,1.可以改目标的标準庫;2.在lib:[“es2015”] 增加
浏覽器的BOM和DOM合為"DOM" 即lib:[“es2015”,“DOM”]
中文報錯
編譯使用 --locale zh-cn
yarn tsc --locale zh-cn
vscode中設定 TypeScript Locale 為:zh-CN即可
一般不建議設定vscode報錯為中文,因為報錯通過英文查找比較好找到解決辦法;
作用域問題
編譯的時候是所有檔案全局進行編譯的,是以不同檔案下的全局變量相同名稱會報錯
/**
* 作用域
*/
//别的檔案有該變量名則會報錯
const a = 123;
//放在函數的作用域中則不會報錯
(function() {
const a = 2;
})
// 所有的成員就會變成這個子產品作用域中的局部成員了;
export {}
export {}實際很少用到,絕大多數情況下,每個檔案都會以子產品的形式去工作
Object類型
- 對象類型不僅僅是對象
- 對象内的屬性可以指定類型,不能多也不能少.專業的方式是使用接口
/**
* 對象類型
*/
export {} //確定和其他檔案沒有成員沖突
const foo:object = null //function(){} //[] //{}
const obj:{num:number,str:string} = {num:1,str:'字元串',more:"多出來會報錯"} //少了num和str也會報錯
數組類型
類型注解與flow一樣
/**
* 數組類型
*/
export {} //確定和其他檔案沒有成員沖突
const arr1: Array<number> = [1,23,3]
const arr2:number[] = [1,2,3]
function sum(...args:Array<number>){
let result = args.reduce((pre,cur)=>pre+cur,0);
console.log("結果",result)
}
// sum(1,23,3,"4") //有字元串非數字類型報錯
sum(1,23,3)
元組類型
明确元素數量與元素類型的數組
/**
* 元組類型
*/
export {} //確定和其他檔案沒有成員沖突
const arr:[string,number] = ['你好',123];
// const arr2:[number,string] = ['1',2,3] //多了元素或着類型不對也會報錯
// Object.entries()方法傳回一個給定對象自身可枚舉屬性的鍵值對數組
// 此方法源碼也是使用元組類型 :[string, number]
Object.entries({
a:123,
b:456
})
枚舉類型
JavaScript中沒有該類型,一般使用對象
枚舉聲明 使用等号指定;不用等号指定,從0開始累加;其中一個指定了具體的值,後面會根據前面的值進行累加
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-t5e3GlRv-1627926188758)(C:\Users\Kid\AppData\Roaming\Typora\typora-user-images\image-20210721005239599.png)]
/**
* 枚舉類型
*/
export {} //確定和其他檔案沒有成員沖突
// const postStatus = {
// success:1,
// err:2,
// ing:0,
// }
// 枚舉聲明 使用等号指定
// enum postStatus{
// success = 1,
// err = 2,
// ing = 0,
// }
// 不用等号指定,從0開始累加
// enum postStatus{
// success, //0
// err = 44,
// ing, //45
// }
// 字元串初始化必須給每個成員指定值
// 增加const常量聲明,則不能用索引的方式使用
const enum postStatus{
success = 'aaa',
err = 'bbb',
ing = 'ccc',
}
const post={
title:"稽核中",
statu:postStatus.ing, //1 稽核成功 2 稽核失敗
}
// 編譯後
var post = {
title: "稽核中",
statu: "ccc" /* ing */, //1 稽核成功 2 稽核失敗
};
// 具體的數值 名稱以注釋的方式出現
函數類型
函數定義的方式
1.函數聲明
2.函數表達式
/**
* 函數類型
*/
export {} //確定和其他檔案沒有成員沖突
// 參數名稱後面加一個小問号,表示該參數是可選參數
// es6特性可以給參數賦預設值,賦預設值後就不是必選參數了
// 接收任意個數的參數 使用es6的rest操作符
function fun1(a:number = 10, b?:number,...rest:number[]):string {
return "圓括号後面冒号類型表示傳回值的類型"
}
fun1(1,2)
// fun1(1,2,3) //報錯,參數個數不一緻
fun1(1) //文法通過
// fun1('1') //報錯類型不一緻
// -------------------------------表達式
const func2 = function(a:number,b:number):string{
return '字元串'
}
const func3: (a: number, b: number) => string = function(a:number,b:number):string{
return '字元串'
}
任易類型
any 類型是不安全的,文法上不同類型指派不會報錯,除非相容以前的老代碼
/**
* 任易類型
*/
export {} //確定和其他檔案沒有成員沖突
function stringify(value:any){
return JSON.stringify(value);
}
stringify(123);
stringify('123');
stringify(true)
let foo:any = 'string';
foo = 123;
foo.bar();
隐式類型推斷
最好給每個變量添加明确的類型注解,更有利于代碼的閱讀
/**
* 隐式類型推斷
*/
export {} //確定和其他檔案沒有成員沖突
let age = 18; //被typescript推斷為數字類型
age = 'String'; // 報錯 不能将類型“string”配置設定給類型“number”
// 如果typescript不能推斷一個變量類型,則是any類型
let foo; //any類型
foo = 123;
foo = '123';
類型斷言
類型斷言不是類型轉換,是遍以上的概念,不是代碼運作時的概念
/**
* 類型斷言 */
export {} //確定和其他檔案沒有成員沖突
// 假定nums是來自接口
const nums = [110,120,119,112,undefined]
// find 尋找數組中符合條件的第一個元素,沒有傳回undefined;空數組不執行;不改變原數組
const res = nums.find(i=>i>200); //有可能會傳回undefined
// const square = res * res; //如果傳回的undefined 這裡則會報錯
//類型斷言 為數字類型 推薦使用該方式
const num1 = res as number;
const num2 = <number>res; //JSX下不能使用
typescript接口
一種抽象的概念,限制對象的結構,如預定一個對象具體有哪些成員,成員的類型
typescript接口在編譯運作後并沒有别的意義,隻是在編寫時候的限制作用
/**
* 接口 */
export {} //確定和其他檔案沒有成員沖突
// interface關鍵字 聲明一個接口 這裡是沒有括号的()
interface Post {
title:string; // 可以使用逗号分割, 更标準文法是分号分割; 和JavaScript中的分号一樣可以省略
content:string,
subtitle?:string, //可選成員? 後加問号表示該成員可有可無
readonly summary:string, //隻讀成員,初始化後不可修改
}
// 設定post參數類型為Post接口
function printPost(post:Post) {
console.log(post.title);
console.log(post.content);
}
printPost({
title:"标題",
content:"内容",
summary: '初始内容'
// more:123 //多了會報錯
})
const newObj:Post = {
title:"标題",
content:"内容",
summary: '初始内容'
}
// newObj.summary = '二次修改' // 報錯:無法配置設定到 "summary" ,因為它是隻讀屬性
//----------------------------------
interface Cache {
[keyName:string]: string //keyName是自定義的 :string表示key的類型為string : 值的類型
}
const obj:Cache ={
"這":"值"
}
可選成員 後面 ? 問号表示
隻讀成員 前面 readonly表示
接口鍵值對類型 {[鍵名:鍵類型]:值類型}
類
es6增加的"類"的特性typescript中都能使用,typescript中的類還有新增的特性
在typescript中要明确聲明類的屬性,而不是構造函數中動态添加;
在typescript中類的屬性必須要有初始值,可以在=号後面指派,或者在構造函數中初始化
類的屬性在使用前必須要聲明,目的是為了給屬性的類型進行标注
/**
* 類
*/
export {} //確定和其他檔案沒有成員沖突
class Person{ //聲明類 這裡是沒有括号的()
//es2016 即es7中的文法
name: string = '初始值';
age: number;
constructor(name:string,age:number){ //構造函數
this.name = name; //報錯 類型“Person”上不存在屬性“name”
this.age = age;
}
sayHi(msg: string): void{
console.log(`這是${this.name},${this.age}`)
}
}
通路修飾符
public 公共的,預設的修飾符
private 私有的,在類的内部通路
protected 受保護的,類的内部和子類中通路
/**
* 類的通路修飾符
*/
export {} //確定和其他檔案沒有成員沖突
class Person{ //聲明類
public name: string = '初始值'; // 預設是public的修飾符
private age: number;
protected gender: string;
constructor(name:string,age:number){ //構造函數
this.name = name;
this.age = age;
}
sayHi(msg: string): void{
console.log(`這是${this.name},${this.age}`)
}
}
const tom = new Person('tom',18);
// console.log( tom.age ) //報錯 屬性“age”為私有屬性,隻能在類“Person”中通路
// console.log( tom.gender ) //報錯 屬性“gender”受保護,隻能在類“Person”及其子類中通路。
// Student 繼承了 Person
class Student extends Person{
constructor(name:string,age: number){
super(name,age);
console.log(this.gender) // 不報錯
}
}
類 隻讀屬性 readonly
如果隻讀屬性要修飾的成員已經有通路修飾符了, readonly要跟在通路修飾符的後面
/**
* 類的隻讀屬性
*/
export {} //確定和其他檔案沒有成員沖突
class Person{ //聲明類
protected readonly gender: string; //readonly修飾的隻讀屬性不能再修改
constructor(){ //構造函數
this.gender = '男'; //要麼在聲明的時候初始化要麼在構造函數初始化
}
}
// Student 繼承了 Person
class Student extends Person{
constructor(){
super();
this.gender = '女'; //報錯 無法配置設定到 "gender" ,因為它是隻讀屬性。
}
}
類與接口
interface 聲明一個接口,接口内的方法是不做具體實作的
implements 實作一個接口,必須要有接口對應的成員,否則報錯
implements Eat,Run 逗号間隔實作多接口
接口最好細分一些,一個接口代表一個能力;讓一個類實作多個接口較為合理
/**
* 類與接口
*/
export {} //確定和其他檔案沒有成員沖突
interface Eat{
eat(food: string): void
};
interface Run{
run(way:string):void
}
class Person implements Eat,Run {
eat(food:string):void{
console.log(`優雅的進餐${food}`);
};
run(myWay:string):void{
console.log(`人類直立行走在${myWay}`);
}
}
抽象類
在某種程度上與接口類似,可以限制子類中必須要有某個成員;
不同的是抽象類能夠包含具體的實作,而接口不行;
一般比較大的類使用抽象類,如:動物
使用abstract聲明一個抽象類,聲明後隻能被繼承不能通過new的方式建立執行個體;
抽象類中能定義抽象方法 abstract fun() 抽象方法也不需要寫方法體;子類必須實作父類的抽象方法
/**
* 抽象類
*/
export {} //確定和其他檔案沒有成員沖突
abstract class Eat{
eat(food: string): void{
console.log(`大多數動物用嘴巴吃${food}`);
};
// 抽象方法,繼承該抽象類的類都必須實作該方法
abstract run(mode: string):string;
};
class Person extends Eat {
otherEat(food:string):void{
console.log(`優雅的進餐${food}`);
};
run(myWay:string):string{
let str = `人類直立行走在${myWay}`
console.log(str);
return str;
}
}
泛型
定義函數/接口/類的時候不去指定類型,使用的時候再指定具體的類型
目的:極大程度的複用代碼
/**
* 泛型
*/
export {} //確定和其他檔案沒有成員沖突
// 建立一個指定長度數字類型的數組
function createNumberArray(length,value:number):number[]{
let arr = Array(length).fill(value);
return arr;
}
// 但是如果要建立一個字元串的數組以上就不符合了 是以我們使用泛型;
// <T>表示泛型 T也可以是其他的值,一般用T表示
function createArray<T>(length,value:T):T[]{
// 指定數組内的元素是什麼類型,否則是any類型
let arr = Array<T>(length).fill(value)
return arr;
}
createArray(3,100)//[100,100,100]
類型聲明
實際開發中難免用到第三方的npm子產品;可以嘗試安裝對應子產品的類型聲明 一般是@types/子產品名
如loadsh子產品(工具子產品) 、 query-string(解析query的字元串)
yarn add lodash
yarn add query-string
沒有對應的類型聲明可以用declare語句聲明;declare語句聲明可以在函數原來定義的時候通過declare聲明這個函數的入參和傳回值類型
.d.ts是typescript中用來類型聲明的檔案
/**
* 類型的聲明
*/
import {camelCase} from 'loadsh'
const qs = require('query-string')
// declare 聲明函數的參數與傳回值類型
declare function camelCase(str: string): string
const str = camelCase('www.baidu.com?a=1&c=false')
console.log(str) //wwwBaiduComA1CFalse
const query = qs.parse('www.baidu.com?a=1&c=false');
console.log(query) //
export {} //確定和其他檔案沒有成員沖突