首先,向我的偶像 Anders Hejlsberg 致敬!
本文是根据 慕课网 的 TypeScript入门 的视频教程整理的摘要,详情请访问原作视频。
完整信息参见官方文档 TypeScript Documentation
源码:TypeScript 在 GitHub 的仓库
【目的】
本文档目的是让熟悉 JavaScript 的同学快速了解 TypeScript 特色,准确信息请务必参考所使用版本 TypeScript 对应的官方文档。
[TOC]
一、开发环境
-
本地
- 编译环境安装:
npm install -g typescript
- TypeScript REPL 及执行环境安装:
npm install -g ts-node
二、变量声明及作用域
TypeScript可以使用兼容JavaScript的var
声明方式,也可以使用let
,let
解决了JavaScript臭名昭著的作用域问题。代码如下:
// TypeScript,使用 var 声明变量,不支持块级作用域
var n = 1;
{
var n = 200;
console.log('块级变量 n 值:' + n);
}
console.log('全局变量 n 值:' + n);
被编译成的 JavaScript,与上面的 TypeScript 一样:
// 使用 var 定义变量的 TypeScript 编译成的 JavaScript
var n = 1;
{
var n = 200;
console.log('块级变量 n 值:' + n);
}
console.log('全局变量 n 值:' + n);
期待的执行结果为:
块级变量 n 值:200
全局变量 n 值:1
实际执行结果为:
块级变量 n 值:200
全局变量 n 值:200
如果使用 TypeScript 的 let
声明变量:
// TypeScript,使用 let 声明变量,不支持块级作用域
let n = 1;
{
let n = 200;
console.log('块级变量 n 值:' + n);
}
console.log('全局变量 n 值:' + n);
被编译成的 JavaScript:
// 使用 let 定义变量的 TypeScript 编译成的 JavaScript
var n = 1;
{
var n_1 = 200;
console.log('块级变量 n 值:' + n_1);
}
console.log('全局变量 n 值:' + n);
发现编译器为块内变量重新命名,执行结果符合预期:
块级变量 n 值:200
全局变量 n 值:1
三、字符串新特性
- 多行字符串,使用反引号
\
进行引用,解决 JavaScript 中使用
\n和
+` 拼接的麻烦
var str = `line 1
line 2
...
line n`;
- 字符串模板,在
\
` 引用的字符串内,使用 ${变量/函数} 方式,直接引入执行结果
var content = 'abc';
console.log(`<div>
${content}
</div>`);
- 自动拆分字符串
调用时,自动将字符串中的表达式值作为参数传入函数
function myTest(template, param1, param2) {
console.log(template);
console.log(param1);
console.log(param2);
}
myTest`测试字符串分割,传入参数 ${111} and ${222} ,再看结果`
从执行结果看,template
是一个数组,这个数组是被字符串中的几个表达式给分割成段的结果。
四、函数
- 参数类型
function testParam(sUserName: string, nUserAge: number): string {
var ret = `User name: ${sUserName}
User age: ${nUserAge}`;
return ret;
}
var s: string = testParam('bahb', 2);
console.log(s);
- 参数默认值
带有默认值的参数,必须放在参数列表最后
function testArgumentDefaultValue(arg1, arg2, arg3 = 'arg 3 default value') {
console.log(arg1);
console.log(arg2);
console.log(arg3);
console.log('===============');
}
testArgumentDefaultValue('1', '2', '3');
testArgumentDefaultValue('5', '6');
- 任意参数个数
function func(...args) {
args.forEach(function (e) {
console.log(e);
});
console.log('--------------');
}
func(1, 2, 3);
func(4, 5, 6, 7, 8);
五、析构表达式
用于将对象、数组中的元素,拆分到变量中。
- 析构表达式(基本)
var {aa, bb} = {
aa: 11,
bb: 22
}
console.log(aa);
console.log(bb);
- 析构表达式(带别名)
var {aa: aa_alias, bb: bb_alias, cc:{v2}} = {
aa: 11,
bb: 22,
cc: {
v1: 333,
v2: 444
}
}
console.log(aa_alias);
console.log(bb_alias);
console.log(v2);
- 析构表达式(数组)
数组,通过直接写逗号 ,
来空过元素,通过 ...参数
的形式,读取剩余元素形成数组。
var [n1, n2, , ...others] = [1, 2, 3, 4, 5];
console.log(n1);
console.log(n2);
//注意通过逗号空过了第三个元素
console.log(others);
六、箭头表达式
箭头表达式 主要用于:
- 作为匿名函数使用
- 解决匿名函数中的
this
问题
- 一个参数
var myFunc = arg1 => console.log(arg1);
myFunc('aaa');
- 多个参数
var myFunc = (arg1, arg2) => arg1 + arg2;
console.log(myFunc(111, 222));
- 多行
var myFunc = (arg1, arg2) => {
var sum = arg1 + arg2;
console.log(sum);
return sum;
}
console.log(`exec result: ${myFunc(111, 222)}`);
- 解决
this
问题(重要)
原来 JavaScript 的书写方式:
var MyClass = function (name) {
this.name = name;
this.counter = 0;
setInterval(function () {
this.counter++;
console.log(this.name + this.counter);
}, 1000);
}
var obj = new MyClass('bahb_');
期待每间隔1秒,陆续输出bahb_1、bahb_2...,实际上没有取到值。
原因是由于通过 new
之后,this
的指代发生了变化,导致取不到值。
直接把 MyClass
作为函数调用,
MyClass('bahb_');
倒是可以得到正常的结果,原因是函数作为顶级对象,this
的指代不会变,但是这种形式不能满足需要创建对象的场景。
通过箭头表达式来解决问题:
var MyClass = function (name) {
this.name = name;
this.counter = 0;
setInterval(() => {
this.counter++;
console.log(this.name + this.counter);
}, 1000);
}
var obj = new MyClass('bahb_');
七、循环遍历集合
JavaScript 中循环遍历方法的问题:
- for in:不仅遍历数组中的元素,还会遍历到其他属性
- forEach:通过回调执行遍历方法,不能通过
break
停止遍历
// TypeScript 的 for of 循环
let arr = [1, 2, 3, 4];
arr.desc = 'abc'; // 使用 for of,属性不会被遍历到
for (let x of arr) {
console.log(x);
}
八、泛型(Generic)
这里只举例说明下基本概念,实际泛型的应用场景会广泛得多。
let myArr: Array<string> = [];
myArr.push('aa');
myArr.push('bb');
myArr.push(123); // 数据类型不一致,报错
console.log(myArr);
抄个官方的 Demo 吓吓人:
class Greeter<T> {
greeting: T;
constructor(message: T) {
this.greeting = message;
}
greet() {
return this.greeting;
}
}
let greeter = new Greeter<string>("Hello, world");
let button = document.createElement('button');
button.textContent = "Say Hello";
button.onclick = function() {
alert(greeter.greet());
}
document.body.appendChild(button);
九、面向对象
1、类定义、继承、访问权限控制符
class Animal {
// 构造函数,当构造函数传入的参数加上了“访问权限控制符”,则同时会声明同名类属性,并赋值
constructor(public name: string) { }
protected log(arg) {
console.log(arg);
}
move(distanceInMeters: number = 0) {
this.log(`${this.name} moved ${distanceInMeters}m.`);
this.log('==============');
}
}
class Snake extends Animal {
constructor(name: string) {
// 调用父类构造器
super(name);
}
move(distanceInMeters = 5) {
this.log("Slithering...");
// 通过 super 调用父类方法
super.move(distanceInMeters);
}
}
class Horse extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 45) {
this.log("Galloping...");
super.move(distanceInMeters);
}
}
let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");
sam.move();
tom.move(34);
2、接口
实现类必须实现接口中声明的每个方法
interface IPerson {
eat();
work();
}
class Person implements IPerson {
eat() { }; // 不实现该方法会报错
work() { }; // 不实现该方法会报错
}
十、模块(Module)
每个 .ts
文件就是一个模块,通过 export
来对外部模块暴露元素,通过 import
来引入模块。
举例:utility.ts
输出,main.ts
引用 utility.ts
中的输出
// Module Utility: utility.ts
let str: string = 'abc';
function add(arg1, arg2) {
return arg1 + arg2;
}
class MyClass {
doSth() { }
}
export {str};
export {add};
export {MyClass};
// Module Main: main.ts
import {add} from "./utility";
let ret = add(2, 3);
console.log(ret);
或者
// Module Main: main.ts
import * as myUtility from "./utility";
let ret = myUtility.add(2, 3);
console.log(ret);
十一、注解(Annotation)
给框架或IDE用的说明,比如:使用某个类时应该同时引入哪些页面等等,需要时具体看手册即可。
十二、类型定义文件(*.d.ts)
类型定义文件 用于描述一个库中所定义的类型
类型定义文件介绍
类型定义文件使用
类型定义文件从这里找:DefinitelyTyped
或者访问 DefinitelyTyped 在 GitHub 的仓库
类型定义文件查找工具