JavaScript 与 TypeScript 的区别
TypeScript 扩展了 JavaScript 的语法,因此现有的 JavaScript 代码可与 TypeScript 一起工作无需任何修改,TypeScript 通过类型注解提供编译时的静态类型检查。
TypeScript 可处理已有的 JavaScript 代码,并只对其中的 TypeScript 代码进行编译。
typescript的好处
TypeScript 增加了代码的可读性和可维护性
typescript是javascript的超集,能够使得js像java等语言一样有静态类型定义特点,它可以在编译阶段就发现大部分错误,并且还增强了编辑器和 IDE 的功能,包括代码补全、接口提示、跳转到定义、重构等
TypeScript 拥有活跃的社区
除此之外,ts还支持大部分第三方库,比如react,Google 开发的 Angular2 就是使用 TypeScript 编写的。
原始数据类型
在js中,我们知道数据类型一共有七种,分别是symbol、null、undefined、boolean、number、string、object。
这一小结我将介绍TS语法来显式定义五种简单数据类型
布尔值
let bool:boolean=true //正确编译
let boo2:boolean=2 //报错,类型错误
number
let num:number=1 //正确编译
let num2:number=true //报错,类型错误
string
let str:string='字符串' //正确编译
let str2:string=2 //报错,类型错误
undefined
let u:undefined=undefined //正确编译
let u2:undefined=null //正确编译
let u3:undefined='string' //编译错误
上面的代码都事先定义变量名的类型,如果数据本身是undefined,那么可以赋值给任何定义为其他类型的变量
let number:number=undefined //正确编译
let string:string=undefined //正确编译
let bool:boolean=undefined //正确编译
let n:null=undefined //正确编译
let u:undefined=undefined //正确编译
null
null跟undefined也是一样的
let n:null=null //正确编译
null和undefined都属于其他类型的子集,所以可以赋值给任何定义为其他类型的变量
let number:number=null //正确编译
let string:string=null //正确编译
let bool:boolean=null //正确编译
let n:null=null //正确编译
let u:undefined=null //正确编译
void
这里需要注意一点,在JS中是没有void这个概念的,但是在TS中,可以使用void来定义一个没有返回值的函数
function fn(): void {
console.log(111);
}
const fn2 = (): void => {
return 123; .//报错,不能有返回值
};
任意值
上面介绍了显式定义原始数据类型,TS还支持任意值any,也就是说使用它,你可以用来定义任意数据类型
const n: any = 123; //正确编译
const b: any = true; //正确编译
const str: any = "string"; //正确编译
const u: any = undefined; //正确编译
const nu: any = null; //正确编译
未声明类型的变量
如果一个变量被声明但是没有定义类型没有被赋值,那么就默认它是一个任意值
let anyThing;//等价于 ==> let anyThing:any
anyThing = 7;
anyThing = true;
anyThing = "string";
类型推论
上面说到如果声明了但是没有定义类型没有被赋值,默认是任意值。如果赋值了,就默认其为其数据值相应数据类型
let style = 7; // 等价于 ==> let style:number = 7
style = "string"; //报错
联合类型
联合类型就是同时定义多种类型,使用|来进行联合类型定义
let a: string | number;
a = "string"; //正确
a = 8; //正确
a = true; //报错
访问联合类型的属性或方法
当TS不知道我们定义的联合属性到底取哪一个时,我们只能访问他们共有属性,否则会报错
let a: string | number;
a.length // 报错,因为length不属于共有属性
a.toString // 正确,因为它们都有原型链中的toString方法
如果TS知道取哪一个,那么会启动类型推论,定义类型
let a: string | number;
a = "string"; //正确编译
a.length; //正确编译
a = 1; //正确编译
a.length; //报错
对象类型
我们使用接口(Interfaces)来定义对象的类型,以下示例对于对象的形状(Shape)进行描述
interface Person {
name: string;
age: number;
}
let obj: Person = {
name: "qiuyanxi",
age: 28
};
以上示例中定义了一个Person接口(接口首字母大写),接着定义一个对象obj,它的类型定义为Person,这样obj的形状就必须与接口Person一致。
比接口多一个属性是不被允许的
let obj: Person = {
name: "qiuyanxi",
age: 10,
height:1 //报错
};
比接口少一个属性也是不被允许的
let obj: Person = { //报错
name: "qiuyanxi",
};
赋值的时候,变量的形状(Shape)必须与接口的形状保持一致
可选属性
但是有时候我们又不一定想要完全匹配形状,可以使用可选属性,使用一个?来描述可选属性
interface Person {
name: string;
age?: number;
}
let obj: Person = {
name: "qiuyanxi",
};
可选属性的意思是这个属性可以不存在,但是不能新增属性,这样是不被允许的。
interface Person {
name: string;
age?: number;
}
let obj: Person = {
name: "qiuyanxi",
age:28,
height:1 //报错
};
任意属性
有时候我们又需要有任意属性,我并不想定义内部太多的属性名,那么就可以用到任意属性
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let obj: Person = {
name: "yanxi",
age: 28,
firstName: "qiu"
};
上面示例中,使用 [propName: string]: any来定义任意类型,那么在下面就可以使用string类型的属性和任意类型的值
但是需要注意的一点,确定了任意属性后,那么确定的属性和可选的属性都必须是它的子集
interface Person {
name: string;
age?: number; //编译报错,number跟string冲突
age2?:string;//正确编译
[propName: string]: string;
}
const obj: Person = {
name: "qiu",
height: "123",
};
上面例子中,要求确定的属性name和可选属性age都属于它的任意属性的子集,由于number不是string的子集,所以报错了。
一个接口只能有一个任意属性,如果要同时有多个类型,那么可以用联合属性
interface Person {
name: string;
age?: number;
[propName: string]: number | string;
}
let tom: Person = {
name: "qiu",
height: 2,
};
只读属性
如果我们希望一个属性允许只读,不允许编辑,那么可以使用readonly来定义只读属性。
interface Person {
readonly name: string;
age?: number;
[propName: string]: number | string;
}
let tom: Person = {
name: "qiu",
height: 2,
};
tom.name = "tom";//编译错误 无法分配到 "name" ,因为它是只读属性
可以看到如果想要对此属性重新赋值就会报错
数组类型
在TS中,如果要对数组进行定义,可以使用“类型+[]”的形式来表示。
let array: number[] = [1, 2, 3];
上面定义了一个数组,数组里都要求为number类型
let array: number[] = ["1", 2, "3"];
//报错 不能将类型“number”分配给类型“string”。ts(2322)
如果事先定义好了类型,当使用数组的一些方法时,也会编译报错
let array: string[] = ["1", "2", "3"];
array.push(1);//类型“number”的参数不能赋给类型“string”的参数。
上面的示例要求我们push一个string类型
伪数组
伪数组的定义方式不跟数组一样
function sum() {
let args: {
[index: number]: number;
length: number;
callee: Function;
} = arguments;
}
在这个例子中,我们除了约束当索引的类型是数字时,值的类型必须是数字之外,也约束了它还有 length 和 callee 两个属性
事实上常用的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等:
function sum() {
let args: IArguments = arguments;
}
其中 IArguments 是 TypeScript 中定义好了的类型,它实际上就是:
interface IArguments {
[index: number]: any;
length: number;
callee: Function;
}
数组中的any
let arr: any[] = [1, 2, 3, 4, { age: 1 }];
使用any来表示数组中可以存在任意的类型
函数类型
在js中,有函数表达式和函数声明两种定义方式
function fn(){} //函数声明
const fn=()=>{} //表达式
函数声明的定义类型方式
在函数声明时,我们可以对函数的输入和输出进行声明
function fn(x: number, y: string): string {
return x + y;
}
fn(1, "2");
不允许多参数或者少参数
function fn(x: number, y: string): string {
return x + y;
}
fn(1, "2", 1);//报错 应有 2 个参数,但获得 3 个。ts(2554)
对返回的值也有要求
function fn(x: number, y: string): number { //报错
return x + y;
}
fn(1, "2");
由于x+y的结果为string,所以不能定义number类型
表达式的定义类型方式
const fn = (x: number, y: number): string => {
return (x + y).toString();
};
上面这种定义方式没有问题不会报错,不过实际上只是对匿名函数进行了定义,对于fn只是通过ts推论出来的,可以手动进行设置。
const fn: (x: number, y: number) => string = (
x: number,
y: number
): string => {
return (x + y).toString();
};
太长了~
可选参数
我们也可以使用?来定义一个可选参数,不过需要注意的是,可选参数一定要放到必须参数的后面
function fn(x: number, y?: number): number {
return x + y;
}
默认参数
function fn(x: number, y: number = 8): number {
return x + y;
}
fn(1);
剩余参数
在es6中,我们可以使用...rest来获取剩余参数
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
});
}
let a: any[] = [];
push(a, 1, 2, 3);
事实上,items 是一个数组。所以我们可以用数组的类型来定义它:
function push(array: any[], ...items: any[]) {
items.forEach(function(item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
重载
重载就是根据参数的不同(数量、类型)来做不同的处理
function fn(x: number | string, y: number): number | string {
if (typeof x === "number") {
return x + y;
} else {
return y.toString() + x;
}
}
上面代码就是根据参数类型不同所做的重载+联合类型,但是这样并不够精确。精确的写法应该是这样的
function fn(x: number, y: number): number;
function fn(x: string, y: number): string;
function fn(x: string | number, y: number): number | string {
if (typeof x === "number") {
return x + y;
} else {
return y.toString() + x;
}
}
上例中,我们重复定义了多次函数 fn,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。
注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
内置对象
JavaScript 中有很多内置对象,它们可以直接在 TypeScript 中当做定义好了的类型。
内置对象是指根据标准在全局作用域(Global)上存在的对象。这里的标准是指 ECMAScript 和其他环境(比如 DOM)的标准。
ECMAScript 的内置对象
ECMAScript 标准提供的内置对象有:
Boolean、Error、Date、RegExp 等。
我们可以在 TypeScript 中将变量定义为这些类型:
let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;
而他们的定义文件,则在 TypeScript 核心库的定义文件中。
DOM 和 BOM 的内置对象
DOM 和 BOM 提供的内置对象有:
Document、HTMLElement、Event、NodeList 等。
TypeScript 中会经常用到这些类型:
let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
// Do something
});
type
类型别名
类型别名就是给类型取个名字,这种方式很方便帮助我们来解决联合类型的代码重复问题
type a = number | string;
type obj = { name: string; age: number };
let b: a = 123;
let c: a = "123";
let obj1: obj = {
name: "qiu",
age: 18,
};
上面的方式就是定义了联合类型以及定义了对象的类型,这种方式使得不需要写重复的代码
字符串字面量类型
字符串字面量类型用来约束取值只能是某几个字符串中的一个
type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
// do something
}
handleEvent(document.getElementById('hello'), 'scroll'); // 没问题
handleEvent(document.getElementById('world'), 'dblclick'); // 报错
上面报错是因为dblclick不属于EventNames字符串字面量类型中的一种