几个月前把 ES6 的特性都过了一遍,收获颇丰。如今继续来看看 TypesScript(下文简称为 “TS”)。限于经验,本文一些总结若有不当,欢迎指正。前端
官网有这样一段描述:java
TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.es6
说的是 TS 是 JS 的超集,而且能够编译成普通的 JS。web
其中, 超集 的定义是:typescript
若是一个集合 S2 中的每个元素都在集合 S1 中,且集合 S1 中可能包含 S2 中没有的元素,则集合 S1 就是 S2 的一个超集,反过来,S2 是 S1 的子集。npm
而实际上,“超出” 的部分主要就是 “类型系统”。所以能够这样概括:编程
TS ≈ ES6 + 类型系统json
ES6 是 ES5 转向主流语规格的一个重要升级,顺着这个角度看,TS 让这门语言披上一层类型的外衣,直接演变成一种强类型的语言;从相反角度看,TS 将编程语言们一些主流的特性引入到了 JS 的世界。gulp
TypeScript 设计巧妙,兼具微软工业化的水准。首先,它仅靠一行命令,就融入到了广大前端人的世界:编程语言
npm install -g typescript
而后由你随便挑一个曾编写的 .js
脚本文件(不妨叫作hello.js
),不用对内容作任何修改,直接将文件后缀改为 .ts
。这样,你就已经完成了一份 TypeScript 脚本的编写!
而后编译它:
tsc hello.ts
OK,你已经平滑过渡到了 TS 的世界。就是这么简单!
固然这只是“一小步”,彷佛后边还有无数的坑要填。不用担忧,TS 已经填平了大部分的坑!
好比,时下最流行的 gulp,webpake 工具,只需作一些简单的配置,就能接引入TypeScript 进行编译;同时为了能与 React 完美融合,TS 引入了与 JSX 相似的 TSX 语法。固然,TS 在 Angular、Vue.js 以及 Node.js 中也是畅通的......
坑都填平了,你们过渡起来天然顺心顺手。
与 ES6 一脉相承的,同时也接轨大部分强类型语言,TS 的类型大概有这些:
1),Number
、Boolean
、String
、Null
、undefined
、Symbol
2), Array
、Function
、Object
3),Tuple
、enum
、Void
、 Never
、Any
TS 做为 JS 的一个超集,在 JS 的基础上扩展了一些很是有用的类型。第 3)中的类型就是从一些强类型语言引入的类型。
为了由简入繁,不妨将这些类型划分为:基本类型、复合类型。复合类型 通常由 基本类型 构成。如下将渐进式的对 TS 的这些类型进行了解。
强类型语言都有一套类型声明的语法规则,TS 也不例外。TS 采用类型注释的写法,像这样将一个带冒号的注释,置于声明变量名以后,就构成了 TS 类型声明的语法。
let str : string = 'hello typescript';
JAVA 的写法是相反的,但无实质差异:
String str = 'hello java';
这样的注释如同一种补充说明,后文将简称它为 “冒号注释”,熟悉书写规则,有利于快速进入到 TS 的代码世界。
实际上,ES6 有一种属性描述对象,是经过Object.getOwnPropertyDescriptor(obj, key)
获取的。
let obj = { set name(val) {} } Object.getOwnPropertyDescriptor(obj, 'name'); // { // configurable: true // enumerable: true // get: undefined // set: ƒ a(val) // }
若是将 setter
类型的 name
方法适当改写,咱们甚至能够实现 obj.name
赋值的类型检查功能,也很是有意思。
一样的,冒号注释 : string
也能够理解为对一个 str
变量的描述。凭借这个注释的描述,TS 的类型编译器就能进行类型检查了。
创建了类型的认知后,继续跑马圈地,巩固认知。其中,Function
、 Never
、Any
规则稍显复杂,但也没有什么特别的,留后细说。
// boolean 类型 let isBool: boolean = 1 < 5; // string 类型 let str: string = 'hello world'; // number 类型 let num: number = 123; // void 类型 let unusable: void = undefined; // undefined 类型 let u: undefined = undefined; // null 类型 let n: null = null; //Symbol 类型 // 类型 symbol 小写也能编译经过 let sym: Symbol = Symbol('hello');
// object 类型 let obj : object = {}; let arrObj : object = []; let funcObj : object = () => {}; // array 类型 let arrNum : number[] = [1, 2, 3] let arrStr : string[] = ['a', 'b', 'c'] let arrObj : object[] = [{}]; // 元组 类型 let tup : [number, string] = [1, 'hello']; // 枚举类型 enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
可谓尽收眼底,类型的语法就是冒号注释,仅凭这一条,60~70% 的状况你都无需担忧本身的类型书写有误。
但 JS 的动态类型太灵活了,null
和 undefined
的类似性, Array
、Function
和 Object
的纠缠不清的关系,仅凭一招恐怕还很难驾驭的住 JS 的 “多动症” 般的类型。好比:
// boolean 类型接收这样的赋值 let isBool_n: boolean = null; let isBool_u: boolean = undefined; // void 类型接收这样的赋值 let unusable: void = undefined; unusable = null; // Symbol 类型接收这样的赋值 let sym: Symbol = Symbol('hello'); sym = null; sym = undefined; // object 类型接收这样的赋值 let obj : object = {}; obj = null; obj = undefined;
它们都能编译经过。可是 null
不属于 boolean
类型,undefined
也并不属于object
类型,为何能经过类型检查?
事实上,undefined
和 null
是全部类型的子类型。也就是说,它们俩能够做为值赋给任何类型的变量。甚至,它们俩能够互相赋值给对方。
// undefined 类型 let u: undefined = null; // null 类型 let n: null = undefined;
有了这一条规则,就能解释一些 “复合类型” 中遇到的问题:
let arrNum: number[] = []; let arrStr: string[] = []; // undefined 也属于 number 类型 let arrNum: number[] = [undefined]; // undefined 也属于 object 类型 let obj : object = undefined;
有了这条规则,咱们能够大胆的写 TS 的类型声明了。
但太过放开的规则——本文姑且称之为 “混杂模式”,又彷佛一会儿让 TS 退回到了 JS 的动态类型的原始状态了,让习惯了强类型的同窗容易懵掉,也让从 JS 转 TS 的同窗体会不到强类型的好处。
好在,TS 设计了一套巧妙的类型系统,犹如给 JS 披上 了一层强大的盔甲。
TS 在 “混杂模式” 下,可能存在这样的风险,就是:编译正确,运行出错。好比:
// 无心得到一个 undefined 做为初始值 let init_name = undefined; let nameList: string[] = [init_name]; console.log(nameList[0].split('_')); // 运行报错
在非 “严格模式” 下,上述 TS 代码编译无误,可是真正拿到页面去运行编译结果时,出现错误。
那怎么办呢?要相信 TS 强大的类型系统,只需一项配置,就能将编译切换成 “严格模式”:
// 在配置文件 tsconfig.json 中增长一项 "compilerOptions": { // ... "strictNullChecks": true },
再次执行编译,就会出现错误提示信息:
error TS2322: Type 'undefined[]' is not assignable to type 'string[]'.
TypeScript 官方教程鼓励尽量地使用 --strictNullChecks
,所以这里也强烈建议配置该属性再进行编译,这样能很好的发挥 TS 类型检查的做用。
TS 编译经过指的是类型检查符合类型系统的规则,运行 OK 则是编译后的 JS 自己执行无误。编译经过,不等于运行OK,即便在 “严格模式” 下也是这样的,因此千万别觉得编译经过了就完事了。
以 Any
类型为例,在 --strictNullChecks
模式下:
// TS 代码 let anyThing: any = 'hello'; console.log(anyThing.myName); // 编译后的 ES6 let anyThing = 'hello'; console.log(anyThing.setName('world'));
很显然,编译后的 anyThing.setName('world')
会运行报错。
固然, Any
类型略有点特殊,由于它能够当作是 TS 平滑退化到 JS 的一个类型,官网教程也有这样解说:
在对现有代码进行改写的时候,any类型是十分有用的,它容许你在编译时可选择地包含或移除类型检查。
那问题又回来了,是否除了 Any
类型,其余编译OK,代码就运行无错呢?鉴于笔者正在入门,经验有限,不敢给这个结论。但不管如何,类型检查是能够排除大部分错误的。
最后,编译的时候,尽可能选择编译成 ES6 (前提是项目是用 ES6 写的)。配置是:
"compilerOptions": { "target": "es6" // "es5" }
TS “冒号注释” ——就这一条规则,贯穿始终。在函数的类型声明中,继续来巩固这条规则的写法。
类型声明只对变量负责,对于函数,需考察输入——函数参数(也是变量)、输出——函数返回值两个要素。
由于函数的特殊结构,全部 “冒号注释” 规则的写法要特别了解下:
// 声明函数 function add(x: number, y: number): number { return x + y; } // 函数直接量 let myAdd = function(x: number, y: number): number { return x + y; };
能够看到,参数的 “冒号注释” 和通常变量没有任何差异。却是函数输出类型注释有点特别——试想,: number
紧随函数名以后或者 function
关键字以后,是否是容易被误解为函数名的一部分?是否是对编译引擎不太友好?从这个角度看,注释置于)
以后最为合理。
对了,一个疑问一直从头保留到如今:要是一个变量是 function
类型,那类型注释怎么写,又不能拿 function
关键字去作类型注释?
let myAdd: (x: number, y: number) => number = function(x: number, y: number): number { return x + y; };
其中等号前边的 : (x: number, y: number) => number
就表明了函数类型。它仍然在结构上符合 “冒号注释” 的规则,只不过冒号后边是一串表达式。这样的结构有点像 Python 中的推导式的概念。
好了,补上这一块重要的缺漏,本文就完成了全部基本类型的类型声明的解释。凭借一条规则,但愿在 TS 学习上畅通无阻的敲代码~