Fork me on GitHub

typescript2-基础知识(上)

基础类型

ts 里面大概有下面这些的基础类型(直接看代码即可):

数字:

1
2
3
//
// 这里可以使用不同的进制来做显示
let num: number = 20;

字符串:

1
2
3
// 字符串,一般用单引号同时支持模板字符串
let name1: string = "zoomdong";
let sentence: string = `Hello,My name is ${name1}`;

数组:

1
2
3
// 数组,这里数组里面的数组都必须和前面的类型相同
let list: number[] = [1, 2, 3];
let list1: Array<number> = [11, 2, 3];

元组:

1
2
3
4
5
6
// 元组可以解决数组里面存不同类型值的方案
let x: [string, number];
// 元组里面的数组类型也是需要一一对应的
x = ["hello", 12];
console.log(x[0].length);
// 元组现在是不能去访问越界信息了,x[5] ='xxx'就会报错了

枚举类型:

1
2
3
4
5
6
7
8
9
10
11
12
// 枚举类型,对js的标准类型的一个补充
// 枚举类型默认的编号是0,1,2,也可以自己赋值,例如下面
enum Color {
Red = 1,
Green = 2,
Blue = 4
}
// 枚举可以正向找值也可以反向找值
let c: Color = Color.Blue; // 4
let colorName: string = Color[2];
console.log(colorName); // Green

any类型:

1
2
3
// any类型,这玩意儿写法和js就没啥区别了,对js做重构会有用
let NotSure: any = 4;
NotSure = "maybe a string instead";

void类型:

1
2
3
4
5
6
7
// void类型,一般用于函数那边,其他的就没有啥意义了
function warnUser(): void {
console.log("this is my warnning message");
}
// 函数没有返回值就可以搞一波void
// void类型只能赋值null或者undefined
let qaq: void = null || undefined;

联合类型:

1
2
3
// 联合类型赋值,这样使用tsc xxx.ts --strictNullChecks去进行强校验还是没啥问题的
let num: number | null = 3;
num = null;

never类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
// nerver类型,用于函数没有返回值的场景,可以是任何类型的子类型,但是它不能是别人的子类型
// 函数抛错误或者永远无法结束就是用never类型
function initialloop(): never {
while (true) {}
}
function err(message: string): never {
throw new Error(message);
}
function fail() {
return err("something failed");
}

obejct类型:

1
2
3
4
// object类似于Object.create()相似
declare function create(o: object | null): void;
create({ prop: 0 });
create(null);

类型断言:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 类型断言,其实就是一种类型推断s
let value = "string";
// 那么这个时候value就被定义为了string类型
value = 5; // 这个时候就会报错
let value1: any = "this is a string";
// 这个时候使用string的方法就没有了
value1.length; // 会报错
// 使用类型断言对之进行一个强制的转换
// 一般多使用as jsx里面只能使用这种
let len1: number = (value1 as string).length;
let len2: number = (<string>value1).length;

变量申明

基本上就是些es6的操作吧,老生常谈而已

  • var
  • const
  • let

闭包的作用域链问题:

1
2
3
4
5
6
7
8
9
10
11
function f(){
var a = 10;
return function(){
var b = a + 1;
return b;
}
}
var g = f();
g();
// 最后的结果就是11,典型的闭包场景

var的变量提升:

1
2
3
4
5
6
7
8
9
10
function f(shouldInitialize){
if(shouldInitialize){
var x = 10;
}
return x;
}
f(true);
// 10
f(false);
// undefine

1
2
3
4
5
6
7
8
9
10
// 这里会出现内层循环的i覆盖外层循环i的问题,一般把内层的i换成j
function sumMatrix(matrix){
var sum = 0;
for(var i = 0;i<matrix.length;i++){
var current = matrix[i];
for(var i = 0 ;i<current.length;i++){
sum += current[i];
}
}
}

一个典型的闭包问题(这里会全部输出10):
因为我们去执行循环的时候,每个循环都会去生成一个定时器,在若干ms之后执行,但是js是个单线程之后,每次循环完成之后,i已经变成10了,因为这个是异步执行的,里面拿到的i每次都会成为10

1
2
3
4
5
for(var i = 0; i < 10 ; i++){
setTimeout(function(){
console.log(i);
},100*i)
}

简单修改一波(用立即执行的函数表达式):

1
2
3
4
5
6
7
8
for(var i = 0; i < 10 ; i++){
// 这里就相当于把i当成一个参数传递进去
(function(i){
setTimeout(function(){
console.log(i);
},100*i)
})(i)
}

letvar主要的区别在与语义上面的区别。
这里有个块级作用域的规则(块级作用域不会出现变量提升的,变量定义之前都会出现一个暂时性死区的现象,即目前访问不到)。

1
2
3
4
5
6
7
8
9
function f(input:boolean){
let a = 1000;
if(input){
let b = a + 1;
return b;
}
// 这个b在外面是访问不到的
return b;
}

这里能够跑出正常的结果:

1
2
3
4
5
6
7
8
9
10
function sumMatrix(matrix: number[][]) {
let sum = 0;
for (let i = 0; i < matrix.length; i++) {
let current = matrix[i];
for (let j = 0; j < current.length; j++) {
sum += current[j];
}
}
return sum;
}

块级作用域变量的获取。(解决之前的作用域变量)

1
2
3
4
5
for (let i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 100 * i);
}

使用const去声明的变量:

1
2
3
4
5
6
7
8
9
10
11
// const 声明的变量是不能修改的
const numLivesForCat = 9;
const kitty = {
name:'Kitty',
numLives: numLivesForCat
}
// 引用没有变化,但是引用的值是可以改变的
kitty.name = 'Jerry';
kitty.numLives -- ;

解构

  • 数组解构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 声明为元组类型
    let input:[number,number] = [1,2];
    function f([first,second]:[number,number]){
    console.log(first);
    console.log(second);
    }
    f(input);
    // 拓展运算符的使用
    let [first,...rest] = [1,2,3,4,5];
    console.log(first); // 1
    console.log(res); // [2,3,4,5]
  • 对象解构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let o = {
    a:'foo',
    b: 12,
    c: 'bar'
    }
    let { a, b } = o;
    // a为foo,b为12
    let {a:newName1,b:newName2} = o;
    // b是个可选的参数
    function keepWholeObject(wholeObject: { a: string; b?: number }) {
    let { a, b = 101 } = wholeObject;
    }

切记解构表达式不要xjb用。

展开

展开分为对象的展开和数组的展开.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let first = [1, 2];
let second = [4, 5];
let bothPlus = [0,...first,...second,5];
let defaults = {
food:'spicy',
price:'$10',
ambiance:'noisy'
}
let search = {
...defaults,
// 这个地方会发生一个替换,覆盖的情况
food:'rich'
}
console.log(search);

接口

1
2
3
4
5
6
7
// 这里对label属性的值做一个string的校验
function printLabel(labelledObj: { label: string }) {
console.log(labelledObj.label);
}
let myObj = { size: 10, label: "Size 10 object" };
printLabel(myObj);

我们通过接口来对上面的代码进行一个重写:

1
2
3
4
5
6
7
8
9
10
interface LabelledValue{
label:string,
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
// 上面声明之后,那么myObj这里就必须要有这个属性
let myObj = { size: 10, label: "Size 10 object" };
printLabel(myObj);

接口的可选属性:
设置可选属性,在接口里面的属性名称后面加个?也挺好的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
interface Square {
color: string;
area: number;
}
// 加个问号表示属性是可选的
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): Square {
let newSquare = { color: "white", area: 1000 };
// 这里判断一下可选属性
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
let mySquare = createSquare({})
console.log(createSquare({}));
console.log(createSquare({color:'blue',width:100}));
console.log(createSquare({color:'yello'}));

接口的只读属性设置:

1
2
3
4
5
6
7
8
9
interface Point{
readonly x: number,
readonly y: number,
}
let p1:Point = { x:10,y:20 }
// 这里会报错
p1.x = 5

ts里面还有一种只读的数组泛型:

1
2
3
4
5
6
7
8
let a: number[] = [1,2,3,4]
let ro: ReadonlyArray<number> = a;
// 只读的数组泛型,下面这些操作都会犯罪
ro[0] = 1;
ro.push(5);
// 正确操作回去可以使用类型断言
a = ro as number[]

只读属性和const的区别在于是变量还是属性。

额外的属性检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
interface Square {
color: string;
area: number;
}
// 加个问号表示属性是可选的
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}
function createSquare(config: SquareConfig): Square {
let newSquare = { color: "white", area: 1000 };
// 这里判断一下可选属性
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
console.log(createSquare({}));
// 如果我们这里不小心把属性名称写错了,但是由于上面有额外的属性检测,这里就不会报错
console.log(createSquare({ colour: "blue", width: 100 }));
console.log(createSquare({ color: "yello" }));

额外的属性检查比我们直接使用一个值来去绕过属性检查是要舒服很多的。

函数类型的接口:

1
2
3
4
5
6
7
8
9
// 本质上就是定义一个函数式接口
interface SearchFunc{
(source: string,subString:string):boolean
}
let mySearch: SearchFunc
mySearch = function(src,sub){
let result = src.search(sub);
return result > -1;
}

可索引类型的接口:

1
2
3
4
5
6
7
8
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animal {
name: string;
}
class Dog extends Animal {
breed: string;
}
interface NotOkay {
[x: number]: Dog;
[x: string]: Animal;
// 这个地方按照下面写会报错,因为数字签名并不是字符串签名的子类型
// [x: string]: Dog;
// [x: number]: Animal;
}

设置一个只读类型的索引签名:

1
2
3
4
5
6
7
interface ReadOnlyStringArray{
readonly [index:number]: string
}
let myArray1: ReadOnlyStringArray = ['Alice','Bob']
// 这个时候去赋这个值就会报error
myArray1[2] = 'wd'

类类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 构造器接口
interface ClockInterface {
// 这两个类型是实例类型
currentTime: Date;
setTime(d: Date);
}
// 实例接口
interface ClockContructor {
new (hour: number, minute: number);
}
// 如果这个地方去继承ClockContructor的话会报error
class Clock implements ClockInterface {
currentTime: Date;
constructor(h: number, m: number) {}
setTime(d: Date) {
this.currentTime = d;
}
}

可是如果我们想去使用一个实例类型的话,代码是可以这样写的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 实例接口类型
interface ClockInter {
tick();
}
interface ClockCon {
new (hour: number, minute: number): ClockInter;
}
function createClock(ctor: ClockCon, hour: number, minute: number):ClockInter {
return new ctor(hour,minute);
}
class DClock implements ClockInter{
constructor(h:number,m:number){
}
tick(){
console.log('beep beep');
}
}
class AClock implements ClockInter{
constructor(h:number,m:number){
}
tick(){
console.log('tick tick');
}
}
// 创造类的实例
let digtial = createClock(DClock,12,17);
let a = createClock(AClock,15,38);

继承接口(和类一样接口也是可以集成的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 继承接口
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
// 先这样继承下来,后续实例对多个接口变量进行访问
let square = {} as Square;
square.color = "blue";
square.sideLength = 12;
square.penWidth = 15;

混合类型(接口既可以当成对象使用,也可以当函数用):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface Counter {
// 函数类型,有个start参数,返回一个string类型
(start: number): string;
// 一些额外的属性
interval: number;
reset():void;
}
// 定义一个函数,返回一个Counter类型的接口
function getCounter():Counter{
let counter = (function(star:number){}) as Counter;
counter.interval = 123;
counter.reset = function(){
}
return counter;
}
let c = getCounter();
// 因为这就是个这样的函数
c(10);
// 它也可以当成对象使用
c.reset();
c.interval = 5.0;

接口继承类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Control {
private state: any;
}
// 接口的类型只能被其类或者子类所实现
// 继承Control类并且定义一个方法
interface SelectableControl extends Control {
select();
}
class Button extends Control implements SelectableControl {
select() {}
}
class Textbox extends Control {
select() {}
}
// 这种方法会报错
class ImageC implements SelectableControl {
// 这个类没有继承Control就去直接实现select这个方法是会报错的
select() {}
}

-------------本文结束感谢您的阅读-------------