导读:本文章按照TypeScript中文网章节设置,会对官方文档补充一些案例(个人理解学习,仅供参考)欢迎交流。
本章内容:基础类型、变量声明、接口、类、函数、泛型、枚举、类型推论、类型兼容性。
1、基础类型
布尔类型boolean
let isTrue:boolean = true;
let isFalse:boolean = false;
数字类型number 支持十进制,十六进制,二进制,八进制字面量
let num1:number = 10;
// 十六进制是:0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f
// 其中字母的大小写并不敏感
// 在程序中开头使用0x作为前缀表示十六进制数
let num2:number = 0xF01d;
// 在程序中开头使用0b作为前缀表示二进制数
let num3:number = 0b0101;
// 在程序中开头使用0o作为前缀表示八进制数,这里八进制的写法会和其他地方略有不同
let num4:number = 0o123;
字符串
// 字符串,双引号和单引号都是支持的
let str:string = "string";
let str1:string = 'string';
// 模板字符串(支持插入变量,换行,和表达式)
let str2 : string = `string${num1}
string${num2 + num3}
string${num3}`
数组
// 直接在元素类型后拼接[]
let list1 : number[] = [1,2,3]
// 使用数组泛型
let list2 : Array<number> = [1,2,3]
元组
元素数量已知,元素类型不必一致
let tuple1: [string,number,string] = ['s',1,'a']
enum枚举
enum color{
red,
green,
blue
}
console.log(color[0],color[1],color[2])
// 重写定义数值
enum colorT{
red = 1,
green = 4 ,
blue
}
console.log(colorT[1],colorT[4],colorT[5])
any任意类型
let any1:any = "1111"
// any类型避开类型检查,可以调用任意方法
any1.toString()
void没有类型
// void没有类型
function fn1():void{
// return 1 不能有返回值
}
let void1 : void
let null1 : null = null
// let void1 : void = null; 中文文档中说null可以赋值,但是实测不行
console.log(typeof(void1),typeof(null1))//undefined object
let void2 : void = undefined;
null、 undefined、 never
// null undefined
// null 是一个表示“没有值”的特殊关键字。
// 它可以赋值给任何类型,包括原始类型和对象类型,作为它们的默认值。
// null 通常用于初始化一个变量,表示它当前没有指向任何对象。
// undefined 是一个表示变量已声明但尚未被赋值的值。
// 它也是一个原始类型,但与 null 不同,undefined 不能赋值给非原始类型(如对象、数组、函数等)。
// undefined 在JavaScript中是一个常见的值,用于表示属性或变量未被赋值。
// never
// 表示永远不存在的值,任何类型都不是never的子类型,只有never类型可以赋值给never
function error():never{
throw new Error("cccccccccc")
}
let never1 : never = (()=>{
return error()
})()
let void3 :void = never1
object
// object
// object表示非原始类型,也就是除number,string,boolean,symbol,null或undefined之外的类型
类型断言
// 类型断言
// 通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。
let any2:any = "8888888888888888"
let num5:number = (any2 as string).length;
// 若是我们数据类型,可以参用强制类型转化
let num6:number = any2 as number;
let num7:number = <number>any2;
2、变量声明
var和let的区别
// 函数(var)作用域和块级作用域
function isUseVar(isUse:boolean){
if(isUse){
var x = '11';
}
// return x;
}
isUseVar(true);//11 正常运行,var不限制于块级作用域
isUseVar(false);//undefined var声明可以省略,所以x是没有赋值的
// 重定义及屏蔽
// var可以在一个域内重复声明相同的变量(名),且覆盖之前
function f1(){
var x;
let y;
var x;
// let y;
if(true){
var x;
let y;//与父域中的y不冲突
}
}
// 变量提升
//var 声明的变量会发生变量提升,这意味着变量可以在声明之前使用,但只会被赋值为 undefined。
let和const的区别
// const 与 let的区别
const c1 = 1;//不能再重新赋值
const c2 = {name:1}
// c2 = {name:2} error
c2.name = 2;//引用类型,const指向的是该类型的内存地址,可以改变它内部的数值
解构数组
// 解构数组
// 赋值
let arr1 = [1,2];
let [first,second] = arr1;
console.log(first);
console.log(second);
// 定义参数类型
function f2([first,second]:[number,number]){
console.log(first);
console.log(second);
}
f2([1,3])
// 余量赋值
{
let [first,...rest] = [1,2,3]
console.log(first);
console.log(rest);
}
结构对象
{
let obj = {name:1,age:2,id:3}
let {name , age , id } = obj;
console.log(name)
console.log(age)
console.log(id)
}
//属性重命名
{
let obj = {a:1,b:'bb'}
let {a:newa,b:newb} = obj
console.log(newa)
console.log(newb)
}
//设置属性类型
{
let obj = {a:1,b:'bb'}
let {a:newa,b:newb}:{a:number,b:string} = obj
console.log(newa)
console.log(newb)
}
// 设置默认值
{
function f(obj:{a:string,b?:number}){
let {a,b=1} = obj
console.log(b)
}
f({a:'aa'})
f({a:'aa',b:2})
}
// 函数声明
{
function f3({a,b = 2} = {a:1}){
console.log(a,b)
}
f3({a:3})
f3()
}
展开
//浅拷贝
{
class C {
p = 12;
m() {
}
}
let c = new C();
let clone = { ...c };
clone.p; // ok
//clone.m(); // error!
}
3、接口
接口写法
{
// interface关键字声明
interface In1{
name:string;
}
const in1:In1 = {name:"kk"}
}
接口的属性:可选属性,只读属性
// 可选属性
{
interface In2{
name?:string;
age?:number;
}
const in2:In2 = {name:"kk"}
}
// 只读属性
{
interface In3{
readonly name:string;
}
const in3:In3 = {name:'KK'}
// in3.name = 'aa'
}
接口的属性类型:函数类型,可索引类型、类类型
// 函数类型
{
interface In5{
(source:string,subString:string):boolean;
}
let in5:In5;
in5 = function (a,b){
return true
}
}
// 可索引类型
{
interface In5{
// [索引:索引签名]:索引返回值;
[index:number]:string;
[name:string]:string;
}
/**
* 索引签名有两种类型:number,和string
* 但是签名类型为number的索引返回值类型必须是string
* 类型的子类
* 原因:number类型的索引前面实际也是转化为string类型去索引,所以string签名类型返回值要包含number类型的返回值类型
*/
}
// 类类型
{
interface In6{
time_:Date;
setTime(d:Date):string;
}
class In6C implements In6{
time_: Date;
setTime(d: Date): string {
throw new Error("Method not implemented.");
}
constructor(){
this.time_ = new Date()
}
}
}
接口的继承
// 继承接口
{
interface In8{
name:string;
}
interface In9 extends In8{
age:number;
}
const c9:In9 = {age:1,name:"a"}
const c10 = <In9>{}
console.log(c10.name)
c10.name = 'a'
console.log(c10.name)
}
混合类型
// 混合类型
{
interface In10{
(index:number):string;
reset():void;
id:number;
}
function getIn10():In10{
let in10 = <In10>function(index){
}
in10.reset = function(){
}
in10.id = 1
return in10
}
let in10 = getIn10()
}
接口继承类
// 接口基础类
//创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现
{
class Cl6{
private txt:string;
}
interface In11 extends Cl6{
test():any;
}
class Cl7 extends Cl6 implements In11{
test() {
throw new Error("Method not implemented.");
}
}
}
4、类
类写法
class Animal{
move(d:number = 0){
console.log("Animal")
}
}
继承
{
// 超类
class Animal{
move(d:number = 0){
console.log("Animal")
}
}
// 子类
class Dog extends Animal{
bark(){
console.log("dog")
}
}
const dog = new Dog()
dog.move()
}
super关键字
// super
//必须调用 super(),它会执行基类的构造函数。 而且,在构造函数里访问 this的属性之前,我们 一定要调用 super()。 这个是TypeScript强制执行的一条重要规则。
{
class Animal{
constructor( private name:string){
}
}
class Snake extends Animal{
constructor (private id:string){
super("name")
}
}
class Cat extends Animal{
constructor(name:string){
super(name)
}
}
}
关键字:public、protected、private、readonly
// public.private.protected
{
class Animal{
public name:string;
private age:number = 1;
protected id:number = 1;
constructor(name:string){
this.name = name;
}
}
class Dunk extends Animal{
constructor(name:string){
super(name)
}
}
}
// protected可以用于构造方法,只能被继承不能实例化
{
class Animal{
protected constructor(name:string){
}
}
class Dog extends Animal{
constructor(name:string){
super(name)
}
}
// new Animal("nnnn")无法实例化
}
存取器
// 存取器
{
class Animal{
private _name:string;
constructor(name:string){
this._name = name;
}
set name(name:string){
this._name = name
}
get name():string{
return this._name;
}
}
const cat = new Animal("dog");
cat.name = 'cat';
console.log(cat.name);
}
静态属性:学习静态属性后,大家就应该清晰类中的静态属性和实例化属性的区别
// 静态属性
{
class Animal{
static live = {name:'animal',cell:'max'};
getNewAnimal(live:{name:string,cell:string}){
let newName = live.name + Animal.live.name;
let newCell = live.cell + Animal.live.cell;
return {name:newName,cell:newCell};
}
}
const newAnimal = new Animal();
console.log(newAnimal.getNewAnimal({name:"lonng",cell:"38X"}))
}
抽象类
//抽象类
//抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。 abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法
{
abstract class Animal{
abstract sound():void;
}
}
构造函数
// 构造函数
{
class Zoo{
//定义一个静态属性
static desc = "hello zoo";
// 需要实例化的属性
stick:number = 1;
//
walk(){
if(this.stick>0){
return "zoo stick"+this.stick;
}else{
return Zoo.desc;
}
}
}
// 按照常用的方式实例化
const zoo1 = new Zoo();
zoo1.stick = 100;
console.log(zoo1.walk())
// 我们通过赋予构造函数类型
let zoo2 = Zoo;
// zoo2.stick = 100 类型“typeof Zoo”上不存在属性“stick"
// 所以zoo2只有Zoo的静态属性,但是并没有stick可以实例化的属性
console.log("zoo2.desc",zoo2.desc)
let zoo3:typeof Zoo = Zoo;
zoo3.desc = "hello zoo3";
console.log("zoo3.desc",zoo3.desc)
}
// 把类当作接口使用
{
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
let point2d:Point = {x:1,y:2}
}
5、函数
函数写法
// 函数
function add(x:number,y:number):number{
return ( x + y )
}
匿名函数
// 匿名函数
let add2 = function (x:number,y:number):number{
return x+y;
}
函数的类型
//函数的类型定义
let add2Pro:(xValue:number,yValue:number)=>number = function (x:number,y:number):number{
return x+y;
}
类型推断
// 类型推断
let add2Plus:(xValue:number,yValue:number)=>number = function(x,y){
return x+y;
}
可选参数/默认参数
// 可选参数和默认参数
{
// 我们设置一个起名函数,第一个是姓,第二个和第三个是名(可能是两个字,也会是一个字)
//可选参数必须跟在必须参数后面
function setName(first:string,second:string,third?:string):string{
return first + second + (third ? third : '');
}
console.log(setName('xue','jin'));
// 这里我们默认姓为xue
//在所有必须参数后面的带默认初始化的参数都是可选的,与可选参数一样,在调用函数的时候可以省略
//与普通可选参数不同的是,带默认值的参数不需要放在必须参数的后面。 如果带默认值的参数出现在必须参数前面,用户必须明确的传入 undefined值来获得默认值。
function setName2(second:string,third?:string,first = 'xue'):string{
return first + second + (third ? third : '');
}
console.log(setName2('jin'));
}
剩余参数
//剩余参数
// 说到剩余参数就能想到js中的arguments,看看和ts有什么不一样
{
function setName3(first:string = 'xue',...rest:string[]){
return first + rest.join('');
}
console.log(setName3(undefined,'jin','chi'));
}
this
{
let dog = {
name:['二哈','博美'],
getName: function (){
// return function(x:number){
// return this.name[x]
// }
// 'this' implicitly has type 'any' because it does not have a type annotation.
return (x:number)=>{
return this.name[x]
}
}
}
let bomei = dog.getName();
console.log("=======",bomei(1))
}
this参数
{
interface Dog{
name:string[];
getName(this: Dog):(x:number)=>string;
}
let dog:Dog = {
name:['二哈','博美'],
getName: function (this:Dog){
// return function(x:number){
// return this.name[x]
// }
// 'this' implicitly has type 'any' because it does not have a type annotation.
return (x:number)=>{
return this.name[x]
}
}
}
let bomei = dog.getName();
console.log("=======",bomei(1))
}
重载
{
enum Suit{
"hearts",
"spades",
"clubs",
"diamonds"
}
type Card = {
suit:Suit,
card:number
}
function pickCard(x:Card[]):number;
function pickCard(x:number):Card;
function pickCard(x:any):any{
if(typeof x == 'object'){
return Suit[x.suit]
}else if(typeof x == 'number'){
let pickCard = Math.floor(x / 13)
return {suit:pickCard,card: x % 13}
}
}
const cards:Card[] = [{suit:Suit['hearts'],card:2}]
let p1 = pickCard(cards)
console.log(p1)
let p2 = pickCard(15)
console.log(p2.card,Suit[p2.suit])
}
6、泛型
泛型的定义
泛型提供了一种方式,使得函数、接口或类能够接收一个或多个类型参数,这些参数在定义时不需要指定,而是在使用时确定。
function fn<T>(x:T):T{
return x
}
泛型变量
function t2<T>(x:T[]):T[]{
return x
}
function t3<T>(x:Array<T>):T[]{
return x
}
泛型类型
function t4<T>(arg:T):T{
return arg;
}
let t4_: <T>(arg:T)=>T =t4;
// 对象字面量
let t4_2: {<T>(arg:T):T} = t4;
// 再变为接口类型
interface T4{
<T>(arg:T):T;
}
let t4_3 : T4 = t4;
//
interface T4_<T>{
(arg:T):T;
}
let t4_4 :T4_<number> = t4;
}
泛型类
/ 泛型类
//类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。
{
class T5<T>{
title:T;
add: (x:T,y:T)=>T;
}
let t5 = new T5<number>();
}
泛型约束
interface limitT{
length:number;
}
function getLength<T extends limitT>(x:T):number{
return x.length;
}
在泛型使用类类型
function t7<T>(c : {new():T}){
return new c()
}
7、枚举
数字枚举
{
enum e1{
a = 1,
b,
c,
d
}
}
字符串枚举
{
enum e2{
a = "a",
b= "b",
c= "c",
d= "d",
}
}
异构枚举
---------文档中不建议使用
计算的和常量成员
// 一个枚举表达式字面量(主要是字符串字面量或数字字面量)
// 一个对之前定义的常量枚举成员的引用(可以是在不同的枚举类型中定义的)
// 带括号的常量枚举表达式
// 一元运算符 +, -, ~其中之一应用在了常量枚举表达式
// 常量枚举表达式做为二元运算符 +, -, *, /, %, <<, >>, >>>, &, |, ^的操作对象。 若常数枚举表达式求值后为 NaN或 Infinity,则会在编译阶段报错。
运行时的枚举,枚举是在运行时真正存在的对象
enum e3{
X,Y,Z
}
function f2(obj:{X:number}){
return obj.X
}
f2(e3)
双向映射
enum Enum{
A
}
let a = Enum.A
let A1 = Enum[a]
8、类型推论
类型推论是指编译器根据变量的初始化值自动推断其类型的过程。类型推论可以减少代码中显式类型声明的需求,使代码更加简洁。
9、类型兼容性
// 引用类型的兼容型,比较结构/属性
// 函数 比较参数和返回值
// 函数参数双向协变
{
enum EventType { Mouse, Keyboard }
interface Event { timestamp: number; }
interface MouseEvent extends Event { x: number; y: number }
interface KeyEvent extends Event { keyCode: number }
function listenEvent(eventType: EventType, handler: (n: Event) => void) {
/* ... */
}
// Unsound, but useful and common
listenEvent(EventType.Mouse, (e: Event) => console.log((<MouseEvent>e).x + ',' + (<MouseEvent>e).y));
// Undesirable alternatives in presence of soundness
listenEvent(EventType.Mouse, (e: Event) => console.log((<MouseEvent>e).x + ',' + (<MouseEvent>e).y));
listenEvent(EventType.Mouse, <(e: Event) => void>((e: MouseEvent) => console.log(e.x + ',' + e.y)));
// Still disallowed (clear error). Type safety enforced for wholly incompatible types
// listenEvent(EventType.Mouse, (e: number) => console.log(e));
}
//可选参数及剩余参数
{
function invokeLater(args: any[], callback: (...args: any[]) => void) {
/* ... Invoke callback with 'args' ... */
}
// Unsound - invokeLater "might" provide any number of arguments
invokeLater([1, 2], (x, y) => console.log(x + ', ' + y));
// Confusing (x and y are actually required) and undiscoverable
invokeLater([1, 2], (x?, y?) => console.log(x + ', ' + y));
}
// 函数重载
// 枚举
{
enum Status { Ready, Waiting };
enum Color { Red, Blue, Green };
let status = Status.Ready;
// status = Color.Green; // Error
status = 4;
}
// 类
// 类与对象字面量和接口差不多,但有一点不同:类有静态部分和实例部分的类型。 比较两个类类型的对象时,只有实例的成员会被比较。 静态成员和构造函数不在比较的范围内。
// 类的私有成员和受保护成员
// 类的私有成员和受保护成员会影响兼容性。 当检查类实例的兼容时,如果目标类型包含一个私有成员,那么源类型必须包含来自同一个类的这个私有成员。 同样地,这条规则也适用于包含受保护成员实例的类型检查。 这允许子类赋值给父类,但是不能赋值给其它有同样类型的类。
{
class Animal {
feet: number;
constructor(name: string, numFeet: number) { }
}
class Size {
feet: number;
constructor(numFeet: number) { }
}
let a: Animal;
let s: Size;
// a = s; // OK
// s = a; // OK
}
// 泛型
{
interface Empty<T> {
}
let x: Empty<number>;
let y: Empty<string>;
// x = y; // OK, because y matches structure of x
}
{
interface NotEmpty<T> {
data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;
// x = y; // Error, because x and y are not compatible
}
{
let identity = function<T>(x: T): T {
// ...
return x
}
let reverse = function<U>(y: U): U {
// ...
return y
}
identity = reverse; // OK, because (x: any) => any matches (y: any) => any
}
// 子类型与赋值
// 目前为止,我们使用了“兼容性”,它在语言规范里没有定义。 在TypeScript里,有两种兼容性:子类型和赋值。 它们的不同点在于,赋值扩展了子类型兼容性,增加了一些规则,允许和any来回赋值,以及enum和对应数字值之间的来回赋值。
// 语言里的不同地方分别使用了它们之中的机制。 实际上,类型兼容性是由赋值兼容性来控制的,即使在implements和extends语句也不例外。