记录学习ts的过程

319 阅读10分钟

这篇文章是记录我学习ts的过程,作记录与个人复习之用。我是根据csdn作者星河_赵梓宇的文章所学,因此内容也大相径庭,不过包含了一些我对于学习过程中自己的理解尝试与一些疑惑之处,并且略微多了一些内容 ,我个人的水平非常低,因此可能会有很多错误,欢迎各位指正。 csdn链接:(78条消息) TypeScript超详细入门教程(上)_星河_赵梓宇的博客-CSDN博客

一、类型

1.1 js中原有的基本类型

1.1.1 boolean类型

布尔类型的变量的值只能是true或false,但也可以是返回结果是true或false的计算表达式

let bool1: boolean = true;
let bool2: boolean = !1;
console.log(bool1); //true
console.log(bool2); //false

1.1.2 number类型

ts和js一样,只有number这一个数字类型,并且支持多种进制的数据。

let num1: number = 123;
let num2: number = 0b1010;
let num3: number = 0o127;
let num4: number = 0xa5;
console.log(num1); //123 十进制
console.log(num2); //10 二进制
console.log(num3); //87 八进制
console.log(num4); //165 十六进制

1.1.3 string类型

字符串类型,也可以使用模板字符串,这是es6的新语法

let str1: string = "coffee";
let str2: string = "给我一杯"
let str3: string;
str3 = `${str2}${str1}`;
console.log(str1); //coffee
console.log(str2); // 给我一杯
console.log(str3); // 给我一杯coffee

1.1.4 数组类型

数组类型,在ts中有两种定义数组的方式,第一种是推荐的写法,事实上我也认为第一种简洁多了。

let arr1: number[] = [1,2,3];
let arr2: Array<number> = [3,2,1];
let arr3: string[] = ['a','b','c'];
console.log(arr1); //[ 1, 2, 3 ]
console.log(arr2); //[ 3, 2, 1 ]
console.log(arr3); //[ 'a', 'b', 'c' ]

1.1.5 null与undefined

null和undefined既是实际的值,也是类型。我的理解是定义为null或undefined类型的数据,被赋予的值只能是他们本身。

let unde: undefined = undefined;
let nu: null = null;
let undef: undefined = 0;
console.log(unde); //undefined
console.log(nu); //null
console.log(undef); //不能将类型“0”分配给类型“undefined”。ts(2322)

在非严格模式(即tsconfig.json中的 "strict"为false)时,我们也可以将null和undefined作为值赋给任意类型

let num: number = undefined;
let str: string = null;
console.log(num); //undefined
console.log(str); //null

1.1.6 object

object是引用类型。在js中,基本数据类型存在栈中,引用类型存在堆中,因此我个人认为在ts中也是这样。
基本数据类型有固定的大小和值,存放在栈中,而引用类型不确定大小,但是其引用地址是固定的,因此,它的地址存在栈中,指向存在堆中的对象。
值得注意的是,在ts中,我们不能通过类似obj.a的方式访问某个对象的某个属性,我们需要使用接口来完成这个需求。

let obj1 = {a: 123}; //js
let obj2: object = {a: 321}; //tss
obj1.a = 99;
obj2.a = 11;
console.log(obj1); // 99
console.log(obj2); //类型“object”上不存在属性“a”。ts(2339)

因此,object的使用场景是:我们希望一个值只能是对象,而不是number或string。例如,定义了一个函数,但其参数只能是object

const myObj = (obj: object) =>{
    return Object.keys(obj);  
}
console.log(myObj({a: 'a', b: 'b'})); //[ 'a', 'b' ]

1.1.7 symbol

symbol类型是es6新增的一种基本数据类型,用来代表独一的值,通过Symbol函数生成。symbol前面不能加关键字new,我们直接调用就已经生成了一个独一的symbol类型的值

const s = Symbol();
console.log(typeof(s)); //symbol

可以在使用Symbol函数创建symbol值时传入字符串,但即使是传入相同的字符串,两个symbol值依然是不同的,这个字符串只起到标识作用。原文返回的false,但是实际上确是这个,但是我们只要懂得这一点就够了

const s1 = Symbol('coffee');
const s2 = Symbol('coffee');
console.log(s1 === s2); //此比较似乎是无意的,因为类型“typeof s1”和“typeof s2”没有重叠。ts(2367)

我们可以调用symbol值的toString()方法将其变成字符串,但是这个字符串不能参与运算,只能转为字符串和布尔类型的值。在看到关于这个定义的时候,我以为symbol.toString()也不能参与运算,但是后来发现只是symbol自身不行。

const s1 = Symbol('coffee');
console.log(s1); //Symbol(coffee)
console.log(s1.toString()); //Symbol(coffee)
console.log(Boolean(s1)); //true
console.log(!s1); //false
console.log(s1+'aa'); //“+”运算符不能应用于类型 "symbol"。ts(2469)
console.log(s1.toString()+'aa'); //Symbol(coffee)aa

那么在ts中使用symbol其实也很简单。

let s: symbol = Symbol('coffee');

而且在2.7版本对symbol类型做了补充,新增了unique symbol类型,它是symbol类型的子类型,只能通过Symbol()、Symbol.for()或通过指定类型来指定一个值是这种类型(说真的第三种说法根本没看懂),而unique symbol只能使用const创建。
在我个人的尝试中发现疑惑之处,我是这样尝试的: 首先我是这样写的

let s:symbol = Symbol('coffee')
let s1:symbol = Symbol.for(s); //类型“symbol”的参数不能赋给类型“string”的参数。ts(2345)

这说明这里需要传入一个字符串,然后是这样

let s1:symbol = Symbol.for('coffee');
const s2:symbol = Symbol.for('eeffoc')
console.log(s1); //Symbol(coffee)
console.log(s2); //Symbol(eeffoc)

这样不对啊,原文是说不能用let创建的,但是没报错而且有输出,所以我上网搜索了一下,然后发现原来是这样:
我们可以直接使用Symbol()创建unique symbol类型的意思是这样

const s1: unique symbol = Symbol('aaa');
let s2: unique symbol = Symbol('aaa'); //类型为 "unique symbol" 的变量必须为 "const"。ts(1332)

然后使用Symbol.for()的细节是这样的:用Symbol.for()创建的symbol会被放入一个全局symbol注册表,然后我们每用这个方法创建一个symbol对象,都会在这个表里寻找,如果有相同的,那么就不会新建一个symbol对象。换而言之,这两个symbol可以是相等的!

const s1: symbol = Symbol.for('coffee');
const s2: symbol = Symbol.for('coffee');
console.log(s1 === s2); //true

甚至于你还是可以用let

let s1: symbol = Symbol.for('coffee');
let s2: symbol = Symbol.for('coffee');
console.log(s1 === s2); //true

在这里我产生了深深的疑惑,这是ts中文文档的原文

image.png 这是怎么回事呢,我的疑惑在于,Symbol.for()创建的是symbol还是unique symbol呢:如果是symbol,那么不会有两个相等的symbol;如果是unique symbol,但它不能用let创建。本人才疏学浅,只是个小白,在网上搜索了一下也无法解决这个问题,也可能是我自己思考错了方向,如果有人能指点一二就太感谢了。
我们可以使用Symbol.keyFor()来获得Symbol.key()全局注册的键名

let s: symbol = Symbol.for('abc')
console.log(Symbol.keyFor(s)); //abc

关于symbol还有很多细节,这里也不再赘述了,如果想要深入了解,可以自己上网搜索一下。

1.2 ts中新增的六个类型

1.2.1 元组

元组是数组的拓展,是已知数量和对应类型的数组。对应位置的值和类型要一一对应

let tuple1: [string, number, string];
let tuple2: [string, number, string];
let tuple3: [string, number, string];
tuple1 = ['a', 1, 'b']; //[ 'a', 1, 'b' ]
tuple2 = [1, 1, 'b']; //不能将类型“number”分配给类型“string”。ts(2322)
tuple3 = ['a', 0]; //不能将类型“[string, number]”分配给类型“[string, number, string]”。源具有 2 个元素,但目标需要 3 个。ts(2322)

我们可以通过对应的索引值给元组赋值,但是必须是该索引值对应的数据类型

let tuple: [string, number, string];
tuple = ['a', 1, 'b']; 
tuple[1] = 2;
console.log(tuple); //[ 'a', 2, 'b' ]

我们还可以对某个索引值的元素进行操作,但是必须是该元素的数据类型所拥有的方法。例如charAt()是string类型的方法,number类型并没有charAt()这个方法,所以是会报错的

let tuple: [string, number, string];
tuple = ['abc', 123, 'b']; 
console.log(tuple[0].charAt(2)); //c
console.log(tuple[1].charAt(2)); //类型“number”上不存在属性“charAt”。ts(2339)

在ts2.6及之前的版本中,是允许元组中的数据超出定义的数据的,被称为越界元素,只要它是定义的所有类型中的一种就可以了。在ts2.6之后的版本,数据的而个数必须与定义的对应。

let tuple: [string, number, string];
tuple = ['abc', 123, 'b', 'a'];  //在ts2.6及之前的版本允许这样定义,因为多出来的元素tuple[3],也就是'a'符合string|number这一联合类型

1.2.2 枚举

枚举可以使我们给一组数组赋予名字,ts支持数字和字符串的枚举。ts会自动为每一个值分配编号,这个编号默认从0开始。

enum Rloes1{
    SUPER_ADMIN,
    ADMIN,
    USER
}
console.log(Rloes1[0]); //SUPER_ADMIN
console.log(Rloes1[1]); //ADMIN

但是我们也可以人为更改这个编号

    enum Rloes2{
    SUPER_ADMIN = 1,
    ADMIN,
    USER
    }
    
    console.log(Rloes2[0]); //undefined
    console.log(Rloes2[1]); //SUPER_ADMIN

    enum Rloes3{
    SUPER_ADMIN = 1,
    ADMIN =5,
    USER
    }
    console.log(Rloes3[1]); //SUPER_ADMIN
    console.log(Rloes3[5]); //ADMIN

如果我们为某一字段设置了一个值,那么该字段后面的内容的编号默认自动递增

enum Role{
  USER = 1,
  ADMIN,
  SUPER_ADMIN,
  BIG_ADMIN=9,
  MID_ADMIN,
}
console.log(Role.USER); //1
console.log(Role.ADMIN); //2
console.log(Role.SUPER_ADMIN); //3
console.log(Role.BIG_ADMIN); //9
console.log(Role.MID_ADMIN); //10

我们还可以用计算值和常量作为编号,但是如果这样做,该字段之后的字段需要手动重新设置初始值,因为它没有默认的递增了,即使函数返回的值是数字也是不行的。
例子中的枚举值中成员既有数字类型又有字符串类型,被称为异构枚举,并不建议使用。

const num = () =>{
  return 88;
}
enum Role{
  USER = 'user',
  ADMIN = 1 ,
  SUPER_ADMIN = num(),
  BIG_ADMIN = 9,
  MID_ADMIN,
}
console.log(Role.USER); //user
console.log(Role.ADMIN); //1
console.log(Role.SUPER_ADMIN); //88
console.log(Role.BIG_ADMIN); //9
console.log(Role.MID_ADMIN); //10

如此一来,这样我们就可以使用名字而不必记数字和名称的对应关系了。就像这样

enum Rloes1{
    SUPER_ADMIN,
    ADMIN,
    USER
}
const superAdmin1 = Rloes1[0];
const superAdmin2 = Rloes1.SUPER_ADMIN
console.log(superAdmin1); //SUPER_ADMIN
console.log(superAdmin2); //0

1.2.3 void

void表示没有任意类型,即什么类型也不是。在定义函数,但函数没有返回值时用到。

const noReturn = (text:string): void =>{
    console.log(text);
}
noReturn("123"); //123

void类型的变量只能被赋值为undefined或null

1.2.4 never

never是指永不存在的值的类型,是当变量被永不为真的类型保护所约束或是那些不会有返回值的函数表达式的返回值类型时,所使用的类型。简而言之,当不存在这个值的时候,使用never类型 这个函数errorFunc总是抛出异常,因此它的返回类型是never,表示这个函数的返回值不存在

const errorFunc = (msg: string): never =>{
  throw new Error(msg)
}

例如这是一个死循环,它不会有返回值,但他与void的不同之处在于:void类型是我们在定义函数是没有给它返回值,而never类型是指不存在返回值

const neverFun = (msg: string): never =>{
  while(true){}
}

never类型是任何类型的子类型,所以它可以复制给任何类型。反过来也一样,没有任何类型是never的子类型,所以它只能赋值给自身,any也不能赋值给never类型。

let neverFunc = (()=>{
  while(true){}
})();
neverFunc = 123;  //不能将类型“number”分配给类型“never”。ts(2322)
let never: never = never; //“never”仅表示类型,但在此处却作为值使用。ts(2693)

1.2.5 any

当我们不知道到底需要什么类型时,就可以使用any类型。当我们指定一个变量或数组为any时,就可以赋予任意类型的值。

let sth: any;
sth = 1;
sth = true;
sth = 'a';
let arr: any[];
arr = [1, 'a', 'true']

还可以这样

let sth:any;
sth = [1, "a", true];
console.log(sth); //[ 1, 'a', true ]

不要滥用any。但是如果滥用any的话,ts就失去了它的意义。

1.2.6 unknown

unknown是ts在3.0版本新增的内容,它表示位置的类型,可以看作是更为安全的any。因为我们将一个变量定义为any后,就可以对该变量使用任意方法了
例子中的所有方法都不会报错,因为any是任意类型,所以可以使用任意类型的方法

let value: any;
console.log(value.name);
console.log(value.length);
console.log(value.toFixed());

但是unknown类型的值不能随便操作,只有通过基于控制流的类型断言来缩小范围,才可以对其进行操作。
目前就是这些,原文后面还有非常多的内容去学习,本文也只作我的学习记录与复习之用,但要是能有人能指出我的错误或看了我的文章有所收获就太好了,谢谢!