重点:
- 解构与指示类型有时很像
- 关于类的部分
- 有些时候会有语法错误,比如不能这样或那样调用。这其实都是ts的语法要求。我们发现,其实将所谓的错误代码编译成js后,是可以正常运行的。
数组类型:
let list: number[] = [1, 2, 3];
或
let list: Array<number> = [1, 2, 3];
元组 Tuple:数量、类型已知的 数组
let x: [string, number];
枚举类型:
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;
void、null、undefined类型
object表示非原始类型,也就是除number,string,boolean,symbol,null或undefined之外的类型。注意什么是原始类型、什么是非原始类型
类型断言,好比类型转换
使用尖括号 let strLength: number = (<string>someValue).length;
使用as let strLength: number = (someValue as string).length;
解构
https://www.tslang.cn/docs/handbook/variable-declarations.html
[first, second]: [number, number]
给属性以不同的名字 let { a: newName1, b: newName2 } = o;
令人困惑的是,这里的冒号不是指示类型的。 如果你想指定它的类型, 仍然需要在其后写上完整的模式。
let {a, b}: {a: string, b: number} = o;
对上面令人困惑的地方,我自己的理解及总结:
类似于let {a,b}=o的这种表达式,我们先从大的方面确定,这是一个解构表达式;
然后,对于解构的属性的类型,不能在其大括号内声明,只能在其大括号内设置别名;
若要进行类型声明,必须在其大括号外面。
默认值
let { a, b = 1001 } = wholeObject;
函数声明+解构+默认值
function f({ a, b = 0 } = { a: "" }): void {
// ...
}
表示函数接收一个参数,可以将其解构成{a,b},如果传入的参数没有b这个属性,则使用属性b的默认值0。如果没传参数,则使用参数的默认值{ a: "" }
展开,符号为... 注意:展开仅包含对象 自身的可枚举属性
let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5];
或
let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { ...defaults, food: "rich" };
接口的作用
为你代码中对象的结构类型命名
接口其实就是对一组规则的命名
不使用接口定义时的类型声明
function printLabel(labelledObj: { label: string }) {
console.log(labelledObj.label);
}
只读属性,关键字readonly
在接口中如何使用
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
是不是也可以这样用,不定义接口
let p1:{readonly x: number,readonly y: number} = { x: 10, y: 20 };
索引签名。TypeScript支持两种索引签名:字符串和数字。
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;// 这就是字符串索引签名
}
举一个数字索引签名的例子
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
函数类型,就是描述“某个方法应该长成什么样子”
定义
interface SearchFunc {
(source: string, subString: string): boolean;
}
使用
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
let result = source.search(subString);
return result > -1;
}
感想
可能只对声明一个函数变量然后赋值的这种情况有意义;对于ionic中的那种方法声明好像就没有意义了。
类类型,就是规定类应该长成什么样子
其实和java中的某个类实现某个接口没什么区别
看例子
interface ClockInterface {
currentTime: Date;
setTime(d: Date);
}
class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}
实例类型
let greeter: Greeter,意思是 Greeter类的实例的类型是 Greeter
备注:这里的Greeter是一个类
混合类型
interface Counter {
(start: number): string;// 表示这是一个方法
interval: number;// 表示该方法还有一个属性
reset(): void;// 表示该方法还有一个方法属性
}
使用
function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
接口继承类
这是一个类
class Control {
private state: any;
}
以下接口继承了上面这个类,并定义了一个方法
interface SelectableControl extends Control {
select(): void;
}
这个继承关系会将Control类的所有公有及私有属性和方法都继承过来,但不包括其具体实现(接口只可能有定义,不可能有实现)
因为接口可以继承类的私有成员,所以只有Control或Control的子类可以实现该接口。
错误实例
// 错误:“Image”类型缺少“state”属性。
class Image implements SelectableControl {
select() { }
}
类是具有两个类型的:静态部分的类型和实例的类型。
当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内。
关于类
- Private定义的属性或方法只能在本类中使用,protected定义的属性或方法可以在本类及子类中使用。它们统统都不能在类之外或者说在实例中使用
- 如果不想让一个类被直接调用其构造方法将其实例化,可以将其构造方法定义为protect的或者private的
- 在类中使用存取器,也就是使用getter和setter,其目的就是在对属性存取值的时候,可以加上逻辑控制,禁止直接访问属性,也就是把属性设置为private的, 将getter和setter设置成public的,通过getter和setter访问private的属性。
- 通过查看tsc编译ts文件后的代码可以知道:本质上,定义一个类,其实就是声明了一个方法(比如这个类叫Animal,那么编译后的这个方法也叫Animal)。在类中定义的方法(比如这个方法叫fun),其实是定义在了其(也就是Animal的)原型对象上。如果我们在类中定义的属性或方法是静态的,也就是static的,本质上就是将其绑定在了Animal这个方法上(Animal.fun)
- 把类当接口使用
- let greeterMaker: typeof Greeter = Greeter;如何解释?(备注:Greeter是定义的一个类)声明了一个变量,将Greeter这个类赋值给它,那么这个类是什么类型的呢?它是(typeof Greeter )类型的。其实,我们定义的某个类,它也是类类型(有点拗口,也不知道这么说对不对)的一个实例。就像我们平时 let str: string = ‘hello’; 它的意思是声明了一个变量str,它的类型是string的,‘hello’是string这个类型的一个实例。
泛型,本质就是在使用时,再决定类型,其实也相当于传参,只不过这个参数是个类型
定义
function identity<T>(arg: T): T {
return arg;
}
使用
let output = identity<string>("myString"); // type of output will be 'string'
如何定义一个泛型类?(泛型接口类似)
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; };
泛型约束,就是约束泛型只能是哪一类的
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
枚举
一组相关的变量应该考虑使用枚举来定义
常量枚举
const enum Enum {
A = 1,
B = A * 2
}
常量枚举只能使用常量枚举表达式,并且不同于常规的枚举,它们在编译阶段会被删除。 常量枚举成员在使用的地方会被内联进来。 之所以可以这么做是因为,常量枚举不允许包含计算成员。
类型推断,本质就是TypeScript类型检查器帮我们确定类型
就是在我们没有明确指定类型的情况下,TypeScript类型检查器帮我们推断出类型来,如果我们的调用有问题,就会给出我们提示。
类型兼容性,本质就是类型不一样但结构具有某种相似,就可以互相赋值
这是涉及到了名义类型
与结构子类型
。名义类型就是我们在java中用到的那种类型之间互相赋值的规则。结构子类型就是通过判断结构是否类似以决定是否可以赋值。搞不明白,typescript为何要这样搞?
高级类型
我不是很待见它,后面想起来的时候再来补充吧!
Symbols 原生类型!原生类型!原生类型!就像string等一样
它有点类似于字符串,但每个又是唯一且不相等的,它的作用也就是起了一个符号作用。这个单词本身的意思也是符号。它的用法和字符串有一点类似。
//Symbols是不可改变且唯一的。
let sym2 = Symbol("key");
let sym3 = Symbol("key");
sym2 === sym3; // false, symbols是唯一的
迭代器
当一个对象实现了Symbol.iterator
属性时,我们认为它是可迭代的
for..of vs. for..in 语句
for..of 迭代的是值,for..in 迭代的是键
模块
在一个模块里,我们可以有多个导出,就比如
export * from "./StringValidator"; // exports interface StringValidator
export * from "./LettersOnlyValidator"; // exports class LettersOnlyValidator
export * from "./ZipCodeValidator"; // exports class ZipCodeValidator
我们在导入时使用这样的语句
import { ZipCodeValidator } from "./ZipCodeValidator";
let myValidator = new ZipCodeValidator();
有没有想过,其实{ ZipCodeValidator }
是一种解构?
以下是一种自动执行的导入:
import "./my-module.js";
我们平时用的ts的导出,应该类似于nodejs中的export.x = ,我们在ts中的export default x应该类似于nodejs中的export =。
但是 export default 语法并不能兼容CommonJS和AMD的exports。
怎么办?
TypeScript提供了export =语法。若使用export =导出一个模块,则必须使用TypeScript的特定语法import module = require("module")来导入此模块。
例子:
//ZipCodeValidator.ts
let numberRegexp = /^[0-9]+$/;
class ZipCodeValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
export = ZipCodeValidator;
//Test.ts
import zip = require("./ZipCodeValidator");
// Some samples to try
let strings = ["Hello", "98052", "101"];
// Validators to use
let validator = new zip();
// Show whether each string passed each validator
strings.forEach(s => {
console.log(`"${ s }" - ${ validator.isAcceptable(s) ? "matches" : "does not match" }`);
});
所谓命名空间,其实就是为某些对象(whatever)的集合起了个名字,然后通过命名空间以点的形式访问这些对象。
模块里不要使用命名空间,为什么,嵌套太多了。模块本身已经具有命名空间的作用了。命名空间适用于为那些全局变量包裹一层,然后这些变量就变成命名空间下的一层了。些时,命名空间就起到了为变量归类和避免变量名称冲突的作用。
对于typescript来说,啥叫外部模块?就是不是用typescript语言编写的库,比如用javascript编写的jquery等。typescript是有类型检查的,jquery没有怎么办?我们这些开发人员给它定义类型呗。(后面的命名空间讲到,外部模块改叫模块了?内部模块,也就是用ts写的,改叫命名空间了?)
举个例子,比如有个库叫path,我们可以这样定义它的类型
declare module "path" { // 在命名空间讲到,不建议使用module了,而是使用namespace?
export function normalize(p: string): string;
export function join(...paths: any[]): string;
export let sep: string;
}
这里的module关键字和命名空间作用差不多。其实就是起了个包裹的作用。那怎么用呢?
/// <reference path="path.d.ts"/>
import * as PATH from "path";
这里为啥要用reference
呢?是不是declare module "path"
的话就不需要用到reference
了
命名空间
使用命名空间是为了提供逻辑分组和避免命名冲突。模块文件本身就是一个逻辑分组。
模块是要用import或require引入的,命名空间不能这样用。
declare是用来声明全局变量的?
如果没有将命名空间导出,引入它时就要用reference,类似于使用<script>引入?
网友评论