废话不多说 直接看用法
环境配置
本地环境
.ts
文件是不可以直接执行的,需要编译为.js
文件,才能够进行运行。那现在就开始配置 TypeScript 的环境吧。
tsc 的安装与使用
tsc
是 typescript compiler 的缩写,即 ts 的编译器。
# npm 全局安装 TypeScript
$ npm install typescript -g
# 检查是否存在 tsc 环境变量,配置正确时会回应tsc的版本号
$ tsc -v
# 尝试创建ts文件并写入一些内容
$ touch demo.ts && echo 'console.log("Hello ts!")' > demo.ts
# 将ts文件编译为同名js文件后运行
$ tsc demo.ts && node demo.js
# 将当前目录下所有文件编译为同名js文件
$ tsc *
上方的例子分为了两步(先转换在运行),可以通过 npm(ts-node),来进行合并操作:
$ npm install ts-node -g
# 直接进行运行
$ ts-node demo.ts
基础类型
基本类型
基础类型: boolean, number, string, symbol, null 和undefined
对象类型: {}
或者 object
, []
或者 Array<any>
, 还有function
, Class
类型
注意:
- number和Number的区别:TS中指定类型的时候要用number,这个是TypeScript的类型关键字。而Number为JavaScript的原生构造函数,用它来创建数值类型的值;
- TypeScript 和 JavaScript 一样,所有数字都是浮点数,所以只有一个number类型,而没有int或者float类型;
- TypeScript 还支持 ES6 中新增的二进制和八进制数字字面量,TypeScript 中共支持二、八、十和十六四种进制的数值;
- 默认情况下 undefined 和 null 可以赋值给任意类型的值;当你在 tsconfig.json 的"compilerOptions"里设置了"strictNullChecks": true时,那必须严格对待,undefined 和 null 将只能赋值给它们自身和 void 类;TS 对可选属性和对可选参数的处理一样,会被自动加上|undefined;
- object是引用类型
一些基础类型的示例:
// 布尔类型
let bool: boolean = false;
bool = true;
bool = 123; // error 不能将类型"123"分配给类型"boolean"
// 当然了,赋给 bool 的值也可以是一个计算之后结果是布尔值的表达式,比如:
let bool: boolean = !!0
console.log(bool) // false
// 数值类型
let num: number;
num = 123;
num = "123"; // error 不能将类型"123"分配给类型"number"
num = 0b1111011; // 二进制的123
num = 0o173; // 八进制的123
num = 0x7b; // 十六进制的123
// 字符串
let str: string = "toimc";
str = "imooc";
const first = "toimc";
const last = "imooc";
str = `${first} ${last}`;
console.log(str) // 打印结果为:toimc imooc
// 对象类型
class Person {}
const teacher: {
name: string
age: number
} = {
name: 'tikc',
age: 18
}
// 空对象
const obj: object = {}
// number类型的数组
const numbers: number[] = [1, 2, 3]
const arr: Array<any> = [1,2,3,4]
const zws: Person = new Person()
// 对象类型-函数的两种写法儿
// 第一种(可以忽略 number,类型推断会推断出返回值是number)
const func = (str: string): number => {
return parseInt(str, 10)
}
// 第二种(可以理解为:冒号后面跟的是函数的类型,等号后面是函数体。不能忽略number,不然语法错误)
const func1: (str: string) => number = str => {
return parseInt(str, 10)
}
// 如果开启了 strictNullChecks,可选参数会被自动加上|undefined,来看例子:
let str = "toimc";
str = null; // error 不能将类型“null”分配给类型“string”
let strNull: string | null = "lison"; // 这里你可以简单理解为,string | null即表示既可以是string类型也可以是null类型
strNull = null; // right
strNull = undefined; // error 不能将类型“undefined”分配给类型“string | null”
// 如果开启了 strictNullChecks,可选参数会被自动加上|undefined,来看例子:
const sum = (x: number, y?: number) => {
return x + (y || 0);
};
sum(1, 2); // 3
sum(1); // 1
sum(1, undefined); // 1
sum(1, null); // error Argument of type 'null' is not assignable to parameter of type 'number | undefined'```
关于null与undefined中,扩展知识:
有些情况下编译器是无法在我们声明一些变量前知道一个值是否是 null 的,需要通过!来告诉编译器该值不为null:
```typescript
function getNum(num: number | null): string {
// 这里在函数getSplicedStr里定义一个函数getRes,我们最后调用getSplicedStr返回的值实际是getRes运行后的返回值
function getLength(prefix: string) {
// 这里使用参数num,num的类型为number或null
// 在运行前编译器是无法知道在运行时num参数的实际类型的
// 这里如果没有 ! 会报错,因为num参数可能为null
return prefix + num!.toFixed().toString();
}
num = num || 0.1;
return getLength("toimc");
}
Symbol
symbol是 ES6 新增的一种基本数据类型,用来表示独一无二的值。
应用场景:Symbol可以作为属性名,比如:
let name = Symbol();
let obj = {
[name]: "toimc"
};
console.log(obj); // { Symbol(): 'toimc' }
Symbol 前面不能加new关键字,直接调用即可创建一个独一无二的 symbol 类型的值
const s1 = Symbol('imooc')
const s2 = Symbol('imooc')
// This condition will always return 'false' since the types 'typeof s1' and 'typeof s2' have no overlap.(2367)
s1 === s2 // false
Symbol 类型值作为属性名,这个属性不会被for…in遍历到,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()获取到;
可以使用Object.getOwnPropertySymbols方法获取对象的所有symbol类型的属性名;
const name1 = Symbol("name");
const obj = {
[name1]: "toimc",
age: 18
};
const SymbolPropNames = Object.getOwnPropertySymbols(obj);
console.log(SymbolPropNames);
// [ Symbol(name) ]
可以用 ES6 新提供的 Reflect 对象的静态方法Reflect.ownKeys,它可以返回所有类型的属性名:
console.log(Reflect.ownKeys(obj)); // ["age", Symbol(name)]
静态方法for 和 keyFor
使用 Symbol.for方法传入字符串,会先检查有没有使用该字符串调用 Symbol.for 方法创建的 symbol 值,如果有,返回该值,如果没有,则使用该字符串新创建一个
const s1 = Symbol("toimc");
const s2 = Symbol("toimc");
const s3 = Symbol.for("toimc");
const s4 = Symbol.for("toimc");
console.log(s3 === s4) // true
console.log(s1 === s3) // false
Symbol.keyFor该方法传入一个 symbol 值,返回该值在全局注册的键名:
const sym = Symbol.for("toimc");
console.log(Symbol.keyFor(sym)); // 'toimc'
内置的 Symbol 值
ES6 提供了 11 个内置的 Symbol 值:
Symbol.hasInstance
:当其他对象使用 instanceof 判断是否为这个对象的实例时,会调用你定义的这个方法
const obj = {
[Symbol.hasInstance](otherObj) {
console.log(otherObj);
}
};
console.log({ a: "a" } instanceof obj); // false
Symbol.isConcatSpreadable
: 当一个数组的 Symbol.isConcatSpreadable 设为 true 时,这个数组在数组的 concat 方法中不会被扁平化。
let arr = [1, 2];
console.log([].concat(arr, [3, 4])); // 打印结果为[1, 2, 3, 4],length为4
let arr1 = ["a", "b"];
console.log(arr1[Symbol.isConcatSpreadable]); // undefined
arr1[Symbol.isConcatSpreadable] = false;
console.log(arr1[Symbol.isConcatSpreadable]); // false
console.log([].concat(arr1, [3, 4])); // [ ["a", "b", Symbol(Symbol.isConcatSpreadable): false], 3, 4 ]
Symbol.species
:后续学习类后再进行介绍,可以看看如下示例:
class C extends Array {
getName() {
return "toimc";
}
}
const c = new C(1, 2, 3);
const a = c.map(item => item + 1);
console.log(a); // [2, 3, 4]
console.log(a instanceof C); // true
console.log(a instanceof Array); // true
console.log(a.getName()); // "toimc"
这个例子中,a 是由 c 通过 map 方法衍生出来的,我们也看到了,a 既是 C 的实例,也是 Array 的实例。但是如果我们想只让衍生的数组是 Array 的实例,就需要用 Symbol.species,我们来看下怎么使用:
class C extends Array {
// 关键代码:
static get [Symbol.species]() {
return Array;
}
getName() {
return "toimc";
}
}
const c = new C(1, 2, 3);
const a = c.map(item => item + 1);
console.log(a); // [2, 3, 4]
// 这里是false
console.log(a instanceof C); // false
console.log(a instanceof Array); // true
console.log(a.getName()); // error a.getName is not a function
就是给类 C 定义一个静态 get 存取器方法,方法名为 Symbol.species,然后在这个方法中返回要构造衍生数组的构造函数。所以最后我们看到,a instanceof C为 false,也就是 a 不再是 C 的实例,也无法调用继承自 C 的方法。
Symbol.match
:当在字符串 str 上调用 match 方法时,会调用这个方法
let obj = {
[Symbol.match](string) {
return string.length;
}
};
console.log("abcde".match(obj)); // 5
-
Symbol.replace
:当在字符串 str 上调用 replace 方法时,会调用这个方法,同上 -
Symbol.search
:当在字符串 str 上调用 search 方法时,会调用这个方法,同上 -
Symbol.split
:当在字符串 str 上调用 split方法时,会调用这个方法,同上 -
Symbol.iterator
:数组的 Symbol.iterator 属性指向该数组的默认遍历器方法:
const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator]();
console.log(iterator);
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
Symbol.toPrimitive
:对象的这个属性指向一个方法,当这个对象被转为原始类型值时会调用这个方法。
这个方法有一个参数,即是这个对象被转为的类型,我们来看下:
let obj = {
[Symbol.toPrimitive](type) {
console.log(type);
}
};
// const b = obj++ // number
const a = `abc${obj}`; // string
Symbol.toStringTag
:Symbol.toStringTag
和Symbol.toPrimitive
相似,对象的这个属性的值可以是一个字符串,也可以是一个存取器 get 方法,当在对象上调用 toString 方法时调用这个方法,返回值将作为"[object xxx]"中 xxx 这个值:
let obj = {
[Symbol.toStringTag]: "toimc"
};
obj.toString(); // "[object toimc]"
let obj2 = {
get [Symbol.toStringTag]() {
return "yoyo";
}
};
obj2.toString(); // "[object yoyo]"
Symbol.unscopables
:这个值和 with 命令有关,但在TS严格模式中,无法使用with
数组和对象
这部分,只是有一些基础的示例,并在后续的篇章展开具体的讨论:
数组
两种定义数组的方式:
let list1: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3];
第一种,形式通过number[]的形式来指定这个类型元素均为number类型的数组类型,这种写法是推荐的写法。
注意,这两种写法中的number指定的是数组元素的类型,你也可以在这里将数组的元素指定为任意类型。
如果你要指定一个数组里的元素既可以是数值也可以是字符串,那么你可以使用这种方式:number|string[]
对象
对象是引用类型
object
在 JS 中是引用类型。
它和 JS 中的其他基本类型不一样,像 number、string、boolean、undefined、null 这些都是基本类型,而 object 类型的变量存的是引用,看个简单的例子:
let originStr = "abc";
let strClone = originStr;
strClone = "efg";
console.log(originStr); // 'abc' 未改变原值
let objInit = { a: "aa" };
let objClone = objInit;
console.log(objClone) // { a: 'aa'}
objInit.a = "bb";
console.log(objClone); // { a: 'bb' } 改变原值
通过例子可以看出,我们修改 objInit 时,objClone 也被修改了,是因为 objClone 保存的是 objInit 的引用,实际上 objInit 和 objClone 是同一个对象。
当我们希望一个变量或者函数的参数的类型是一个对象的时候,使用这个类型,比如:
let obj: object
obj = { name: 'toimc' }
obj = 123 // error 不能将类型“123”分配给类型“object”
这里有一点要注意了,你可能会想到给 obj 指定类型为 object 对象类型,然后给它赋值一个对象,后面通过属性访问操作符访问这个对象的某个属性,实际操作一下你就会发现会报错:
let obj: object
obj = { name: 'toimc' }
console.log(obj.name) // error 类型“object”上不存在属性“name”
当一个值必须是对象而不是数值等类型时,比如我们定义一个函数,参数必须是对象,这个时候就用到object类型了:
function getKeys (obj: object) {
return Object.keys(obj) // 会以列表的形式返回obj中的值
}
getKeys({ a: 'a' }) // ['a']
getKeys(123) // error 类型“123”的参数不能赋给类型“object”的参数
这里涉及到的函数的相关知识,我们会在后面章节介绍的,你只要在这里明白object类型的使用就可以了。
Object vs {} vs object
var o: object;
o = { prop: 0 }; // OK
o = []; // OK
o = 42; // Error
o = "string"; // Error
o = false; // Error
o = null; // Error
o = undefined; // Error
var p: {}; // or Object
p = { prop: 0 }; // OK
p = []; // OK
p = 42; // OK
p = "string"; // OK
p = false; // OK
p = null; // Error
p = undefined; // Error
var q: { [key: string]: any };
q = { prop: 0 }; // OK
q = []; // OK
q = 42; // Error
q = "string"; // Error
q = false; // Error
q = null; // Error
q = undefined; // Error
var r: { [key: string]: string };
r = { prop: 'string' }; // OK
r = { prop: 0 }; // Error
r = []; // Error
r = 42; // Error
r = "string"; // Error
r = false; // Error
r = null; // Error
r = undefined; // Error
结论:
-
与Object类型相同的{}是最不具体的,可以将对象、数组和基元分配给它;
-
object是更具体的,类似于{ [key: string]: any };可以给它分配对象和数组,但不能分配原始类型的数据;
-
{ [key: string]: string }是最具体的,它不允许任何原始类型、数组或具有非字符串值的对象被分配到它
相关示例
// 数组可以是数字和字符串类型
const numberArr: (string | number)[] = [1, 2, 3]
// 即是number类型也可能是string数组
const numbers1: number[] | string[] = ['123', '333'] // 正确
const numbers2: number[] | string[] = [123, '333'] // 错误,不能两种类型混用
const numbers3: Array<number|string> = [123, '333'] // 正确
const arr: (string|number)[] = [123,'string'] // 正确
// undefined 数组
const undefinedArr: undefined[] = [undefined, undefined, undefined]
// 存储对象类型的内容
const objectArr: { name: string; age: number }[] = [
{
name: 'zws',
age: 18
}
]
// 使用类型别名(type alias)
type User = { name: string; age: number }
// 存储对象类型的内容
const objectArr: User[] = [
{
name: 'zws',
age: 18
}
]
// 关于 Class
class Teacher {
name: string
age: number
}
const objectArr: Teacher[] = [
new Teacher(),
{
name: 'zws',
age: 18
}
]