簡(jiǎn)介
typescript:javasscript的超集 ,添加了類型系統(tǒng)的 JavaScript,適用于任何規(guī)模的項(xiàng)目。
我們都知道JavaScript是一種弱類型的語(yǔ)言。而TypeScript增強(qiáng)了它的類型。
TypeScript 的[核心設(shè)計(jì)理念]:在完整保留 JavaScript 運(yùn)行時(shí)行為的基礎(chǔ)上,通過(guò)引入靜態(tài)類型系統(tǒng)來(lái)提高代碼的可維護(hù)性,減少可能出現(xiàn)的 bug。
| TypeScript | JavaScript |
|---|---|
| JavaScript 的超集用于解決大型項(xiàng)目的代碼復(fù)雜性 | 一種腳本語(yǔ)言,用于創(chuàng)建動(dòng)態(tài)網(wǎng)頁(yè) |
| 可以在編譯期間發(fā)現(xiàn)并糾正錯(cuò)誤 是靜態(tài)類型 | 作為一種解釋型語(yǔ)言,只能在運(yùn)行時(shí)發(fā)現(xiàn)錯(cuò)誤,是動(dòng)態(tài)類型 |
| 強(qiáng)類型,支持靜態(tài)和動(dòng)態(tài)類型 | 弱類型,沒(méi)有靜態(tài)類型選項(xiàng) |
| 最終被編譯成 JavaScript 代碼,使瀏覽器可以理解 | 可以直接在瀏覽器中使用 |
| 支持模塊、泛型和接口 | 不支持模塊,泛型或接口 |
| 社區(qū)的支持仍在增長(zhǎng),而且還不是很大 | 大量的社區(qū)支持以及大量文檔和解決問(wèn)題的支持 |
動(dòng)態(tài)類型是指在運(yùn)行時(shí)才會(huì)進(jìn)行類型檢查
靜態(tài)類型是指編譯階段就能確定每個(gè)變量的類型
1. 安裝typescript
npm install -g typescript
編譯ts文件(生成對(duì)應(yīng)的js文件,)
tsc xxx.ts
2. 數(shù)據(jù)類型
原始數(shù)據(jù)類型:Boolean , String , Number , null , undefined ,Symbol
其他數(shù)據(jù)類型: Arrsy , Enum , Any , Never , Object , Void , Unknow , Tuple
- Symbol
es6中新增的一個(gè)類型,表示獨(dú)一無(wú)二的值.Symbol 值通過(guò)Symbol()函數(shù)生成。這就是說(shuō),對(duì)象的屬性名現(xiàn)在可以有兩種類型,一種是原來(lái)就有的字符串,另一種就是新增的 Symbol 類型。凡是屬性名屬于 Symbol 類型,就都是獨(dú)一無(wú)二的,可以保證不會(huì)與其他屬性名產(chǎn)生沖突。
let a = Symbol()
let b = Symbol('b')
let d = Symbol()
a == b // false
- unknow ,any
在ts中any被成為全局超級(jí)類型,可以逃避類型檢測(cè)
unknow也是另一種超級(jí)類型,但是unknow類型只能被賦值 unknow類型 和any類型
let value:unknown ;
let value2: any =value
let value3: boolean = value // Type 'unknown' is not assignable to type 'boolean
- tuple
一般數(shù)組是由同種類型的值組成,但是元組可以滿足數(shù)組中含有不同類型的要求,
let data:[boolean , number , string] = [true ,123,'111']
3. 類型推論
如果沒(méi)有明確的指定類型,那么 TypeScript 會(huì)依照類型推論(Type Inference)的規(guī)則推斷出一個(gè)類型。
let c = 12345;
console.log(typeof c) // number
c = 'hahahha' // Error: Type 'string' is not assignable to type 'number'
4. 聯(lián)合類型
取值可以為多種類型中的一種
let a :number | string | boolean;
a = 4
a = 'www'
a = true
當(dāng) TypeScript 不確定一個(gè)聯(lián)合類型的變量到底是哪個(gè)類型的時(shí)候,我們只能訪問(wèn)此聯(lián)合類型的所有類型里共有的屬性或方法:
5. class
雖然 JavaScript 中有類的概念,但是可能大多數(shù) JavaScript 程序員并不是非常熟悉類,這里對(duì)類相關(guān)的概念做一個(gè)簡(jiǎn)單的介紹。
- 類(Class):定義了一件事物的抽象特點(diǎn),包含它的屬性和方法
- 對(duì)象(Object):類的實(shí)例,通過(guò)
new生成 - 面向?qū)ο螅∣OP)的三大特性:封裝、繼承、多態(tài)
- 封裝(Encapsulation):將對(duì)數(shù)據(jù)的操作細(xì)節(jié)隱藏起來(lái),只暴露對(duì)外的接口。外界調(diào)用端不需要(也不可能)知道細(xì)節(jié),就能通過(guò)對(duì)外提供的接口來(lái)訪問(wèn)該對(duì)象,同時(shí)也保證了外界無(wú)法任意更改對(duì)象內(nèi)部的數(shù)據(jù)
- 繼承(Inheritance):子類繼承父類,子類除了擁有父類的所有特性外,還有一些更具體的特性
- 多態(tài)(Polymorphism):由繼承而產(chǎn)生了相關(guān)的不同的類,對(duì)同一個(gè)方法可以有不同的響應(yīng)。比如
Cat和Dog都繼承自Animal,但是分別實(shí)現(xiàn)了自己的eat方法。此時(shí)針對(duì)某一個(gè)實(shí)例,我們無(wú)需了解它是Cat還是Dog,就可以直接調(diào)用eat方法,程序會(huì)自動(dòng)判斷出來(lái)應(yīng)該如何執(zhí)行eat - 存取器(getter & setter):用以改變屬性的讀取和賦值行為
- 修飾符(Modifiers):修飾符是一些關(guān)鍵字,用于限定成員或類型的性質(zhì)。比如
public表示公有屬性或方法 - 抽象類(Abstract Class):抽象類是供其他類繼承的基類,抽象類不允許被實(shí)例化。抽象類中的抽象方法必須在子類中被實(shí)現(xiàn)
- 接口(Interfaces):不同類之間公有的屬性或方法,可以抽象成一個(gè)接口。接口可以被類實(shí)現(xiàn)(implements)。一個(gè)類只能繼承自另一個(gè)類,但是可以實(shí)現(xiàn)多個(gè)接口
class Greeter {
// 靜態(tài)屬性
static cname: string = "Greeter";
// 成員屬性
greeting: string;
// 構(gòu)造函數(shù) - 執(zhí)行初始化操作
constructor(message: string) {
this.greeting = message;
}
// 靜態(tài)方法
static getClassName() {
return "Class name is Greeter";
}
// 成員方法
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
那么成員屬性與靜態(tài)屬性,成員方法與靜態(tài)方法有什么區(qū)別呢?可以直接看一下編譯生成的 ES5 代碼:
var Greeter = (function () {
// 構(gòu)造函數(shù) - 執(zhí)行初始化操作
function Greeter(message) {
this.greeting = message;
}
// 靜態(tài)方法
Greeter.getClassName = function () {
return "Class name is Greeter";
};
// 成員方法
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
// 靜態(tài)屬性
Greeter.cname = "Greeter";
return Greeter;
}());
var greeter = new Greeter("world");
greeter.getClassName() // Property 'getClassName' does not exist on type 'Greeter'. Did you mean to access the static member 'Greeter.getClassName' instead?
Greeter.getClassName() // Class name is Greeter
私有字段
class Person {
#name: string;
constructor(name: string) {
this.#name = name;
}
greet() {
console.log(`Hello, my name is ${this.#name}!`);
}
}
let semlinker = new Person("Semlinker");
semlinker.#name;
// ~~~~~
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.
與常規(guī)屬性(甚至使用 private 修飾符聲明的屬性)不同,私有字段要牢記以下規(guī)則:
私有字段以 # 字符開(kāi)頭,有時(shí)我們稱之為私有名稱;
每個(gè)私有字段名稱都唯一地限定于其包含的類;
不能在私有字段上使用 TypeScript 可訪問(wèn)性修飾符(如 public 或 private);
私有字段不能在包含的類之外訪問(wèn),甚至不能被檢測(cè)到。
抽象類
使用 abstract 關(guān)鍵字聲明的類,我們稱之為抽象類。抽象類不能被實(shí)例化,因?yàn)樗锩姘粋€(gè)或多個(gè)抽象方法。所謂的抽象方法,是指不包含具體實(shí)現(xiàn)的方法:
abstract class Person {
constructor(public name: string){}
abstract say(words: string) :void;
}
// Cannot create an instance of an abstract class.(2511)
const lolo = new Person(); // Error
抽象類不能被直接實(shí)例化,我們只能實(shí)例化實(shí)現(xiàn)了所有抽象方法的子類。
abstract class Person {
constructor(public name: string){}
// 抽象方法
abstract say(words: string) :void;
}
class Developer extends Person {
constructor(name: string) {
super(name);
}
say(words: string): void {
console.log(`${this.name} says ${words}`);
}
}
const lolo = new Developer("lolo");
lolo.say("I love ts!"); // lolo says I love ts!
6. implements / extends
- implements
實(shí)現(xiàn), 一個(gè)新的類,從父類或者接口實(shí)現(xiàn)所有的屬性和方法,同時(shí)可以重寫屬性和方法,包含一些新的功能 - extends
繼承,一個(gè)新的接口或者類,從父類或者接口繼承所有的屬性和方法,不可以重寫屬性,但可以重寫方法
interface Test {
x:number;
y:number
}
class TestDemo implements Test{
x = 1;
y = 2;
sayHello(){
console.log(`${this.x}--${this.y}say hello`)
}
}
class Test2Demo extends TestDemo{
y = 4
sayHello(){
console.log(`${this.x}--${this.y}say hello 2`)
}
}
const a = new Test2Demo()
const b = new TestDemo()
a.sayHello();
b.sayHello()
//1--4say hello 2
//1--2say hello
- 只有類才能實(shí)現(xiàn)和繼承類
- 可以多實(shí)現(xiàn)和多繼承
7. 泛型
泛型(Generics)是指在定義函數(shù)、接口或類的時(shí)候,不預(yù)先指定具體的類型,而在使用的時(shí)候再指定類型的一種特性。
function createArray(length: number, value: any): Array<any> {
let result = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
無(wú)法精準(zhǔn)定位返回的數(shù)據(jù)類型,any可以是任意類型
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray<string>(3, 'x'); // ['x', 'x', 'x']
這樣可以精準(zhǔn)推算出返回的數(shù)據(jù)類型
其中 T 代表 Type,在定義泛型時(shí)通常用作第一個(gè)類型變量名稱。但實(shí)際上 T 可以用任何有效名稱代替。除了 T 之外,以下是常見(jiàn)泛型變量代表的意思:
K(Key):表示對(duì)象中的鍵類型;
V(Value):表示對(duì)象中的值類型;
E(Element):表示元素類型。
- 泛型接口
當(dāng)然也可以使用含有泛型的接口來(lái)定義函數(shù)的形狀:
interface CreateArrayFunc {
<T>(length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc;
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
- 泛型類
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};
- 泛型約束
在函數(shù)內(nèi)部使用泛型變量的時(shí)候,由于事先不知道它是哪種類型,所以不能隨意的操作它的屬性或方法:所有可以對(duì)泛型進(jìn)行約束,必須含有特定的屬性
function loggingIdentity<T extends TypeConstraint>(arg: T): T {
console.log(arg.length);
console.log(arg.personName);
return arg
}
loggingIdentity({personName:'hahah' ,length:4}) // 4 , 'hahah'
- 在泛型約束中使用類型參數(shù)
可以聲明受另一個(gè)類型參數(shù)約束的類型參數(shù)。
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a"); 1
getProperty(x, "m"); //Error Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.
8. 裝飾器
8.1 裝飾器是什么
- 它是一個(gè)表達(dá)式
- 該表達(dá)式被執(zhí)行后,返回一個(gè)函數(shù)
- 函數(shù)的入?yún)⒎謩e為 target、name 和 descriptor
- 執(zhí)行該函數(shù)后,可能返回 descriptor 對(duì)象,用于配置 target 對(duì)象
8.2 裝飾器的分類
- 類裝飾器(Class decorators)
- 屬性裝飾器(Property decorators)
- 方法裝飾器(Method decorators)
- 參數(shù)裝飾器(Parameter decorators)
需要注意的是,若要啟用實(shí)驗(yàn)性的裝飾器特性,你必須在命令行或 tsconfig.json 里啟用 experimentalDecorators 編譯器選項(xiàng):
Class decorators
// Class decorators
function classDecorator<T extends {new(...args:any[]):{}}>(constructor: T){
return class extends constructor{
newProperty ='new property';
hello = 'override';
}
}
function classFactoryDecorator(num:number){
return function(constructor:Function){
constructor.prototype.luckyNumber = Math.floor(Math.random() * num)
}
}
@classDecorator
@classFactoryDecorator(20)
class TestClass {
property = "property";
hello: string;
constructor(m: string) {
this.hello = m;
}
}
let greeting = new TestClass('hah');
console.log(greeting.hello , greeting.property , greeting.luckyNumber)
// "override", "property", 9
Property decorators
// Property decorators
function Min(num:number){
return function(target:Object , properrtyKey:string){
let value:string;
const setter = function(newVal:string){
if(newVal.length < num){
throw new Error(`Your password should be bigger than ${num}`)
}
value = newVal
}
const getter = function(){
return value
}
Object.defineProperty(target , properrtyKey , {
get:getter,
set:setter
})
}
}
class Password{
@Min(4)
password:string;
constructor(password:string){
this.password = password
}
}
let password = new Password('121212')
console.log(password.password)
let password2 = new Password('12') // Error :Your password should be bigger than 4
Method decorators
// declare type MethodDecorator = <T>(target:Object, propertyKey: string | symbol,
// descriptor: TypePropertyDescript<T>) => TypedPropertyDescriptor<T> | void;
function logger(target:Object , propertyKey:string , descriptor :PropertyDescriptor){
console.log(`${propertyKey}is working`)
}
function doubleResult(){
return function(target:Object , propertyKey:string , descriptor :PropertyDescriptor){
return {
value: function(...args:any[]){
var result = descriptor.value.apply(this, args) * 2;
return result;
}
}
}
}
class MethodDecoratorClass {
newName:string ;
constructor(newName:string){
this.newName = newName;
}
@logger
changeName(name:string){
this.newName = name
}
@doubleResult()
sum(x:number , y:number){
return x + y
}
}
let demo = new MethodDecoratorClass('hello');
demo.changeName('hahahh')
console.log(demo.newName)
console.log(demo.sum(1 ,2))
// [LOG]: "changeNameis working"
// [LOG]: "hahahh"
// [LOG]: 6
Parameter decorators
function notNull(target: any, propertyKey: string, parameterIndex: number) {
console.log("param decorator notNull function invoked ");
Validator.registerNotNull(target, propertyKey, parameterIndex);
}
function validate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("method decorator validate function invoked ");
let originalMethod = descriptor.value;
//wrapping the original method
descriptor.value = function (...args: any[]) {//wrapper function
if (!Validator.performValidation(target, propertyKey, args)) {
console.log("validation failed, method call aborted: " + propertyKey);
return;
}
let result = originalMethod.apply(this, args);
return result;
}
}
class Validator {
private static notNullValidatorMap: Map<any, Map<string, number[]>> = new Map();
//todo add more validator maps
static registerNotNull(target: any, methodName: string, paramIndex: number): void {
let paramMap: Map<string, number[]> = this.notNullValidatorMap.get(target);
if (!paramMap) {
paramMap = new Map();
this.notNullValidatorMap.set(target, paramMap);
}
let paramIndexes: number[] = paramMap.get(methodName);
if (!paramIndexes) {
paramIndexes = [];
paramMap.set(methodName, paramIndexes);
}
paramIndexes.push(paramIndex);
}
static performValidation(target: any, methodName: string, paramValues: any[]): boolean {
let notNullMethodMap: Map<string, number[]> = this.notNullValidatorMap.get(target);
if (!notNullMethodMap) {
return true;
}
let paramIndexes: number[] = notNullMethodMap.get(methodName);
if (!paramIndexes) {
return true;
}
let hasErrors: boolean = false;
for (const [index, paramValue] of paramValues.entries()) {
if (paramIndexes.indexOf(index) != -1) {
if (!paramValue) {
console.error("method param at index " + index + " cannot be null");
hasErrors = true;
}
}
}
return !hasErrors;
}
}
class Task {
@validate
run(@notNull name: string): void {
console.log("running task, name: " + name);
}
}
console.log("-- creating instance --");
let task: Task = new Task();
console.log("-- calling Task#run(null) --");
//task.run(null);
console.log("----------------");
console.log("-- calling Task#run('test') --");
task.run("test");
// [LOG]: "param decorator notNull function invoked "
// [LOG]: "method decorator validate function invoked "
// [LOG]: "-- creating instance --"
// [LOG]: "-- calling Task#run(null) --"
// [LOG]: "----------------"
// [LOG]: "-- calling Task#run('test') --"
// [LOG]: "running task, name: test"
9.namespace
隨著方法屬性的增多,以便于在記錄它們類型的同時(shí)還不用擔(dān)心與其它對(duì)象產(chǎn)生命名沖突。 因此,我們把屬性和方法包裹到一個(gè)命名空間內(nèi),而不是把它們放在全局命名空間下。
namespace Test {
export interface TestInterface{
location?:string
}
export const personName:string = 'hahahah';
export const getPersonName = () => {
return personName
}
}
let test:Test.TestInterface;
let nameNew = Test.getPersonName();
console.log(nameNew) // 'hahahah'
9.JSX
JSX是一種嵌入式的類似XML的語(yǔ)法。 它可以被轉(zhuǎn)換成合法的JavaScript,盡管轉(zhuǎn)換的語(yǔ)義是依據(jù)不同的實(shí)現(xiàn)而定的。 JSX因React框架而流行,但也存在其它的實(shí)現(xiàn)。 TypeScript支持內(nèi)嵌,類型檢查以及將JSX直接編譯為JavaScript。
想要使用JSX必須做兩件事:
- 給文件一個(gè)
.tsx擴(kuò)展名 - 啟用
jsx選項(xiàng)
TypeScript具有三種JSX模式:preserve,react和react-native。 這些模式只在代碼生成階段起作用 - 類型檢查并不受影響。 在preserve模式下生成代碼中會(huì)保留JSX以供后續(xù)的轉(zhuǎn)換操作使用(比如:Babel)。 另外,輸出文件會(huì)帶有.jsx擴(kuò)展名。 react模式會(huì)生成React.createElement,在使用前不需要再進(jìn)行轉(zhuǎn)換操作了,輸出文件的擴(kuò)展名為.js。 react-native相當(dāng)于preserve,它也保留了所有的JSX,但是輸出文件的擴(kuò)展名是.js。
10. interface和type的區(qū)別
- typeb不能聲明合并
interface Window {
title: string;
}
interface Window {
ts: TypeScriptAPI;
}
// 合并后的 Window 接口
// {
// title: string;
// ts: TypeScriptAPI;
// }
- type可以用來(lái)創(chuàng)建聯(lián)合類型,交叉類型等復(fù)雜類型
- type支持類型映射
- type可以用于定義元組和基本類型
總結(jié): interface 用于定義對(duì)象的結(jié)構(gòu),因?yàn)榭梢詫?shí)現(xiàn)類的繼承和實(shí)現(xiàn),支持聲明合并,type適合定義更復(fù)雜和靈活的類型。
2. 提取公共屬性
- keyof , Extract提取共有的鍵
- 利用提取的共有鍵,構(gòu)建新類型
type CommKey<T, U> = Extract<keyof T , keyof U>
type CommonProperties<T , U> = Pick<T ,CommonKeys<T,U>>
type A = {
name: string;
age: number;
location: string;
};
type B = {
name: string;
age: number;
gender: string;
};
// 提取共有鍵
type CommonKeys<T, U> = Extract<keyof T, keyof U>;
// 基于共有鍵生成新類型
type CommonProperties<T, U> = Pick<T, CommonKeys<T, U>>;
// 應(yīng)用到具體類型 A 和 B 上
type ABCommonProperties = CommonProperties<A, B>;
// 測(cè)試
const example: ABCommonProperties = {
name: "Alice",
age: 30,
// location: "NY", // Error: 'location' is not a common property
// gender: "female" // Error: 'gender' is not a common property
}