1.快速入门
1.1 简介
TypeScript 是 JavaScript 的一个超集,支持 ECMAScript 6 标准。
TypeScript 由微软开发的自由和开源的编程语言。
TypeScript 设计目标是开发大型应用,它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在任何浏览器上。
TypeScript 是一种给 JavaScript 添加特性的语言扩展,新增的功能包括:
- 类型批注和编译时类型检查
- 类型推断
- 类型擦除
- 接口
- 枚举
- Mixin
- 泛型编程
- 名字空间
- 元组
- Await
从 ECMA 2015 反向移植而来的功能:
- 类
- 模块
- lambda 函数的箭头语法
- 可选参数以及默认参数
JavaScript与TypeScript的区别:
TypeScript 是 JavaScript 的超集,扩展了 JavaScript 的语法,因此现有的 JavaScript 代码可与 TypeScript 一起工作无需任何修改,TypeScript 通过类型注解提供编译时的静态类型检查。
TypeScript 可处理已有的 JavaScript 代码,并只对其中的 TypeScript 代码进行编译。
TypeScript官网:https://www.typescriptlang.org/
TypeScript中文官网:TypeScript中文网 · TypeScript——JavaScript的超集
1.2 TypeScript安装
我们需要使用到 npm 工具安装,npm是随同NodeJS一起安装的包管理工具,如果没有安装npm,直接安装nodejs就可以了,nodejs安装包下载 https://nodejs.org/en/download
cmd 执行npm -v 命令查看版本号
安装国内镜像
npm config set registry https://registry.npmmirror.com
安装TypeScript
npm install -g typescript
tsc -v 查看安装的版本号
开发工具使用 Visual Studio Code,下载地址Visual Studio Code - Code Editing. Redefined
1.3 基础语法
TypeScript组成:
- 模块
- 函数
- 变量
- 语句和表达式
- 注释
第一个TypeScript程序
创建myTS.ts文件,在vsCode中写入代码
const hello:string ="Hello MyWorld!"
console.log(hello);
打开命令终端,cd命令进入ts文件目录,执行以下命令
1)tsc myTS.ts 命令将ts文件编译成js文件
2)node myTS.js 运行js文件
1.3.1 tsc常用编译参数
编译参数 | 说明 |
--help | 显示帮助信息 |
--module | 载入扩展模块 |
--target | 设置 ECMA 版本 |
--declaration | 额外生成一个 .d.ts 扩展名的文件。 tsc ts-hw.ts --declaration 以上命令会生成 ts-hw.d.ts、ts-hw.js 两个文件。 |
--removeComments | 删除文件的注释 |
--out | 编译多个文件并合并到一个输出的文件 |
--sourcemap | 生成一个 sourcemap (.map) 文件。sourcemap 是一个存储源代码与编译代码对应位置映射的信息文件。 |
--module noImplicitAny | 在表达式和声明上有隐含的 any 类型时报错 |
--watch | 在监视模式下运行编译器。会监视输出文件,在它们改变时重新编译。 |
1.3.2 保留关键字
break | as | catch | switch |
case | if | throw | else |
var | number | string | get |
module | type | instanceof | typeof |
public | private | enum | export |
finally | for | while | void |
null | super | this | new |
in | return | true | false |
any | extends | static | let |
package | implements | interface | function |
do | try | yield | const |
continue |
1.3.3 特别说明
- 空白和换行:TypeScript 会忽略程序中出现的空格、制表符和换行符。空格、制表符通常用来缩进代码,使代码易于阅读和理解。
- TypeScript 区分大小写
- 分号是可选的:每行指令都是一段语句,你可以使用分号或不使用, 分号在 TypeScript 中是可选的,建议使用。
以下代码都是合法的:
console.log("Runoob")
console.log("Google");
如果语句写在同一行则一定需要使用分号来分隔,否则会报错,如:
console.log("MyTS");console.log("Google");//不报错
console.log("MyTS") console.log("Google");//报错
2.基础类型
数据类型速览表
数据类型 | 关键字 | 描述 |
任意类型 | any | 声明为 any 的变量可以赋予任意类型的值。 |
数字类型 | number | 双精度 64 位浮点值。它可以用来表示整数和分数。 |
字符串类型 | string | 一个字符系列,使用单引号(')或双引号(")来表示字符串类型。反引号(`)来定义多行文本和内嵌表达式。 |
布尔类型 | boolean | 表示逻辑值:true 和 false。 |
数组类型 | 无 | 声明变量为数组。 |
元组 | 无 | 元组类型用来表示已知元素数量和类型的数组,各元素的类型不必相同,对应位置的类型需要相同。 |
枚举 | enum | 枚举类型用于定义数值集合。 |
void | void | 用于标识方法返回值的类型,表示该方法没有返回值。只能给他赋予undefined和null: let unusable: void = undefined; |
null | null | 表示对象值缺失。 |
undefined | undefined | 用于初始化变量为一个未定义的值 |
never | never | never 是其它类型(包括 null 和 undefined)的子类型,代表从不会出现的值。 |
2.1 任意类型Any
任意值是 TypeScript 针对编程时类型不明确的变量使用的一种数据类型,它常用于以下三种情况:
- 变量的值会动态改变时,比如来自用户的输入,任意值类型可以让这些变量跳过编译阶段的类型检查
- 改写现有代码时,任意值允许在编译时可选择地包含或移除类型检查
- 定义存储各种类型数据的数组时
/*any 变量的值会动态改变时,比如来自用户的输入,任意值类型可以让这些变量跳过编译阶段的类型检查 */
let x:any=1;
console.log(x);//输出结果 1
x="hello any";
console.log(x);//输出结果 hello any
x=false;
console.log(x);//输出结果 false
/**any 改写现有代码时,任意值允许在编译时可选择地包含或移除类型检查 */
let y: any = 4;
y.ifItExists(); // 正确,ifItExists方法在运行时可能存在,但这里并不会检查
y.toFixed(); // 正确
let prettySure:Object=4;
prettySure.toFixed(); // 错误 Property 'toFixed' does not exist on type 'Object'.
/**any 定义存储各种类型数据的数组时 */
let arrayList: any[] = [1, false, 'fine'];
arrayList[1] = 100;
console.log(arrayList); //输出结果 [ 1, 100, 'fine' ]
2.2 数字类型number
2.2.1 number类型
和JavaScript一样,TypeScript里的所有数字都是浮点数。 这些浮点数的类型是 number
。 除了支持十进制和十六进制字面量,Typescript还支持ECMAScript 2015中引入的二进制和八进制字面量。
let decLiteral: number = 6;//十进制
console.log(decLiteral);//输出结果 6
let hexLiteral: number = 0xf00d;//十六进制
console.log(decLiteral);//输出结果 6
let binaryLiteral: number = 0b1010;//二进制
console.log(decLiteral);//输出结果 6
let octalLiteral: number = 0o744;//八进制
console.log(decLiteral);//输出结果 6
2.2.2 Number对象
Number 对象是原始数值的包装对象
语法:var num=new Number(value);
注意: 如果一个参数值不能转换为一个数字将返回 NaN (非数字值)
Number对象属性
属性 | 描述 |
MAX_VALUE | 可表示的最大的数,MAX_VALUE 属性值接近于 1.79E+308。大于 MAX_VALUE 的值代表 "Infinity"。 |
MIN_VALUE | 可表示的最小的数,即最接近 0 的正数 (实际上不会变成 0)。最大的负数是 -MIN_VALUE,MIN_VALUE 的值约为 5e-324。小于 MIN_VALUE ("underflow values") 的值将会转换为 0。 |
NaN | 非数字值(Not-A-Number)。 |
NEGATIVE_INFINITY | 负无穷大,溢出时返回该值。该值小于 MIN_VALUE。 |
POSITIVE_INFINITY | 正无穷大,溢出时返回该值。该值大于 MAX_VALUE。 |
prototype | Number 对象的静态属性。使您有能力向对象添加属性和方法。 |
constructor | 返回对创建此对象的 Number 函数的引用。 |
var num=new Number(20);
console.log(num);// 输出结果 [Number: 20]
var strToNum=new Number("30");
console.log(strToNum);//输出结果 [Number: 30]
var enToNum=new Number("abcs");
console.log(enToNum);//输出结果 [Number: NaN]
//prototype 属性
function employee(id:number,name:string) {
this.id = id;
this.name = name;
}
var emp = new employee(123,"admin");
employee.prototype.email = "admin@runoob.com";
console.log("员工号: "+emp.id);
console.log("员工姓名: "+emp.name);
console.log("员工邮箱: "+emp.email);
Number对象方法
方法 | 描述 |
toExponential() | 把对象的值转换为指数计数法。 |
toFixed() | 把数字转换为字符串,并对小数点指定位数。 |
toLocaleString() | 把数字转换为字符串,使用本地数字格式顺序。 |
toPrecision() | 把数字格式化为指定的长度。 |
toString() | 把数字转换为字符串,使用指定的基数。数字的基数是 2 ~ 36 之间的整数。若省略该参数,则使用基数 10。 |
valueOf() | 返回一个 Number 对象的原始数字值。 |
var num=new Number(20.1314);
console.log(`toExponential转为指数计数法:${num.toExponential()}`);// 输出结果 2.01314e+1
console.log(`toFixed不保留小数:${num.toFixed()}`);//输出结果 20
console.log(`toFixed保留2位小数:${num.toFixed(2)}`);//输出结果20.3
console.log(`toFixed保留五位小数:${num.toFixed(5)}`);//输出结果 20.13140
console.log(`toLocaleString转成字符串:${num.toLocaleString()}`);//输出结果 20.1314
console.log(`toPrecision数字格式化指定长度:${num.toPrecision()}`);// 输出结果 20.1314
console.log(`toPrecision数字格式化指定1位长度:${num.toPrecision(1)}`);//输出结果 2e+1
console.log(`toPrecision数字格式化指定2位长度:${num.toPrecision(2)}`);//输出结果 20
console.log(`toPrecision数字格式化指定3位长度:${num.toPrecision(3)}`);//输出结果 20.1
console.log(`toString转换为字符换:${num.toString()}`);// 输出结果 20.1314
console.log(`toString转换为二进制字符换:${num.toString(2)}`);// 输出结果 10100.0010000110100011011011100010111010110001110001
console.log(`toString转换为八进制字符换:${num.toString(8)}`);// 输出结果 24.1032155613530704
console.log(`toString转换为十六进制字符换:${num.toString(16)}`);// 输出结果 14.21a36e2eb1c4
console.log(`valueOf原始数字值:${num.valueOf()}`);//输出结果 20.1314
2.3 字符串string
2.3.1 string类型
let myname: string = "TypeScript";//双引号
let mySimpleName:string='TS';//单引号
let years: number = 5;
let words: string = `您好,今年是${ myname }(简称${mySimpleName})发布${ years + 1}周年`;//模板字符串
console.log(words);//输出结果 您好,今年是TypeScript(简称TS)发布6周年
let fullTxt:string='您好,今年是'+myname+'(简称'+mySimpleName+')发布'+(years+1)+'周年';//拼接字符串
console.log(fullTxt);//输出结果 您好,今年是TypeScript(简称TS)发布6周年
2.3.2 String对象
String 对象用于处理文本(字符串)
语法:var txt=new String("string");
String对象属性
属性 | 描述 |
constructor | 对创建该对象的函数的引用 |
length | 返回字符串的长度 |
prototype | 允许您向对象添加属性和方法 |
var str = new String( "This is string" ); // 等同于 var str="This is string";
console.log(`str.constructor is:${str.constructor}`);
console.log(`字符串长度:${str.length}`);
//prototype
function employee(id:number,name:string) {
this.id = id;
this.name = name;
}
var emp = new employee(123,"admin");
employee.prototype.email="admin@runoob.com"; // 添加属性 email
console.log("员工号: "+emp.id);
console.log("员工姓名: "+emp.name);
console.log("员工邮箱: "+emp.email);
String对象方法
方法 | 描述 |
charAt() | 返回在指定位置的字符。 |
charCodeAt() | 返回在指定的位置的字符的 Unicode 编码。 |
concat() | 连接两个或更多字符串,并返回新的字符串。 |
indexOf() | 返回某个指定的字符串值在字符串中首次出现的位置。 |
lastIndexOf() | 从后向前搜索字符串,并从起始位置(0)开始计算返回字符串最后出现的位置。 |
localeCompare() | 用本地特定的顺序来比较两个字符串。 |
match() | 查找找到一个或多个正则表达式的匹配。 |
replace() | 替换与正则表达式匹配的子串 |
search() | 检索与正则表达式相匹配的值 |
split() | 把字符串分割为子字符串数组。 |
substring() | 提取字符串中两个指定的索引号之间的字符。 |
toLocaleLowerCase() | 根据主机的语言环境把字符串转换为小写,只有几种语言(如土耳其语)具有地方特有的大小写映射。 |
toLocaleUpperCase() | 据主机的语言环境把字符串转换为大写,只有几种语言(如土耳其语)具有地方特有的大小写映射。 |
toLowerCase() | 把字符串转换为小写。 |
toString() | 返回字符串。 |
toUpperCase() | 把字符串转换为大写。 |
valueOf() | 返回指定字符串对象的原始值。 |
var strTxt=new String("MyTypeScript");
var concatStr=" hello world!";
//concat 连接两个或更多字符串,并返回新的字符串
let fullTxt=strTxt.concat(concatStr);
console.log(`concat后的字符串:${fullTxt}`);//输出结果 MyTypeScript hello world!
//charAt 返回在指定位置的字符
console.log(`charAt范围0处的字符串:${strTxt.charAt(0)}`);// 输出结果 M
console.log(`charAt范围12处的字符串:${strTxt.charAt(12)}`);// 输出结果
//charCodeAt 返回在指定的位置的字符的 Unicode 编码
console.log(`charCodeAt 0处的字符串:${strTxt.charCodeAt(0)}`);// 输出结果 77
console.log(`charCodeAt 12处的字符串:${strTxt.charCodeAt(12)}`);// 输出结果 NAN
//indexOf 返回某个指定的字符串值在字符串中首次出现的位置
console.log(`indexOf:${strTxt.indexOf("y")}`);//输出结果 1
console.log(`indexOf:${strTxt.indexOf("G")}`);//输出结果 -1
//lastIndexOf 从后向前搜索字符串,并从起始位置(0)开始计算返回字符串最后出现的位置
console.log(`lastIndexOf:${strTxt.lastIndexOf("y")}`);//输出结果 3
console.log(`lastIndexOf:${strTxt.lastIndexOf("G")}`);//输出结果 -1
//localeCompare 用本地特定的顺序来比较两个字符串
console.log(`localeCompare:${strTxt.localeCompare("MyTypeScript")}`);//输出结果 0
console.log(`localeCompare:${strTxt.localeCompare(concatStr)}`);//输出结果 1
//match 查找找到一个或多个正则表达式的匹配
console.log(`match:${strTxt.match(/p/g)}`);//输出结果 p,p
console.log(`match:${strTxt.match(/G/g)}`);//输出结果 null
//search 检索与正则表达式相匹配的值
console.log(`match:${strTxt.search(/p/g)}`);//输出结果 4
console.log(`match:${strTxt.search(/G/g)}`);//输出结果 -1
//replace 替换与正则表达式匹配的子串
console.log(`replace:${concatStr.replace("!","")}`);//输出结果 hello world
//split 把字符串分割为子字符串数组
var txtSplit="my,your,her,his";
console.log(txtSplit.split(","));//输出结果 [ 'my', 'your', 'her', 'his' ]
console.log(txtSplit.split(",",2));//输出结果 [ 'my', 'your' ]
//substring 提取字符串中两个指定的索引号之间的字符
console.log(`substring:${strTxt.substring(2)}`);//输出结果 TypeScript
console.log(`substring:${strTxt.substring(2,4)}`);//输出结果 Ty
//toUpperCase 字符转大写
console.log(`toUpperCase:${strTxt.toUpperCase()}`);//输出结果 MYTYPESCRIPT
//toLowerCase 字符转小写
console.log(`toLowerCase:${strTxt.toLowerCase()}`);//输出结果 mytypescript
2.4 数组
定义数组方式:
- 在元素类型后面接上[]
- 使用数组泛型Arrary<元素类型>
- 使用数组对象Arrary
// 在元素类型后面加上[]
let arr1: number[] = [1, 2];
//使用数组泛型
let arr: Array<number> = [1, 2];
console.log(arr);
//使用Arrary对象
//空数组
let arrNums:number[]=new Array();
console.log(arrNums);
arrNums.push(50);//数组新增元素
console.log(arrNums);
//指定长度的数组
let arrNums1:number[]=new Array(4);//长度为4的空数组
console.log(arrNums1);//输出结果 [ <4 empty items> ]
arrNums1.push(20);//数组新增元素
arrNums1.push(30);//数组新增元素
arrNums1.push(40);//数组新增元素
arrNums1.push(50);//数组新增元素
arrNums1.push(60);//数组新增元素
console.log(arrNums1);//输出结果 [ <4 empty items>, 20, 30, 40, 50, 60 ]
//直接初始化数组元素
var sites:string[] = new Array("Google","RunTS","Taobao","Facebook")
console.log(sites);//输出结果 [ 'Google', 'RunTS', 'Taobao', 'Facebook' ]
2.4.1数组解构
//数组元素赋值给变量
var arr:number[] = [12,13];
var[x,y] = arr; // 将数组的两个元素赋值给变量 x 和 y 等价于 var x=arr[0];var y=arr[1];
console.log(x);// 输出结果 12
console.log(y);//输出结果 13
2.4.2 多维数组
一个数组的元素可以是另外一个数组,这样就构成了多维数组,最简单的多维数组就是二维数组,定时方式:var arr_name:datatype[][]=[ [val1,val2,val3],[v1,v2,v3] ]
var multi:number[][] = [[1,2,3],[23,24,25]];
console.log(multi[0][0]);
console.log(multi[0][1]);
console.log(multi[0][2]);
console.log(multi[1][0]);
console.log(multi[1][1]);
console.log(multi[1][2]);
2.4.4 数组方法
常用数组方法
方法 | 描述 |
concat() | 连接两个或更多的数组,并返回结果 |
every() | 检测数值元素的每个元素是否都符合条件 |
filter() | 检测数值元素,并返回符合条件所有元素的数组 |
forEach() | 数组每个元素都执行一次回调函数 |
indexOf() | 搜索数组中的元素,并返回它所在的位置,如果搜索不到,返回值 -1,代表没有此项 |
join() | 把数组的所有元素放入一个字符串。 |
lastIndexOf() | 返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索 |
map() | 通过指定函数处理数组的每个元素,并返回处理后的数组 |
pop() | 删除数组的最后一个元素并返回删除的元素 |
push() | 向数组的末尾添加一个或更多元素,并返回新的长度 |
reduce() | 将数组元素计算为一个值(从左到右) |
reduceRight() | 将数组元素计算为一个值(从右到左) |
reverse() | 反转数组的元素顺序 |
shift() | 删除数组的第一个元素并返回删除的第一个元素 |
slice() | 选取数组的的一部分,并返回一个新数组 |
some() | 检测数组元素中是否有元素符合指定条件 |
sort() | 对数组的元素进行排序 |
splice() | 从数组中添加或删除元素 |
toString() | 把数组转换为字符串,并返回结果 |
unshift() | 向数组的开头添加一个或更多元素,并返回新的长度 |
var alpha = ["a", "b", "c"];
// concat方法,只能同种类型的数组连接
var concatArr = ["1", "2", "3"];
let fullConcat = alpha.concat(concatArr);
console.log("fullConcat : " + fullConcat );//输出结果 a,b,c,1,2,3
//push 向数组的末尾添加一个或更多元素,并返回新的长度
let pushArr=alpha.push("d");
console.log(`push后的新数组 : ${alpha} 新的数组长度:${pushArr}`);//输出结果 push后的新数组 : a,b,c,d 新的数组长度:4
//pop 删除数组的最后一个元素并返回删除的元素
let popArr=alpha.pop();
console.log(`pop后的新数组 : ${alpha} 删除的元素:${popArr}` );// 输出结果 pop后的新数组 : a,b,c 删除的元素:d
//shift 删除数组的第一个元素并返回删除的第一个元素
let shiftArr=concatArr.shift();
console.log(`shift后的新数组:${concatArr};删除的第一个元素${shiftArr}`);//输出结果 shift后的新数组:2,3;删除的第一个元素1
//unshift 向数组的开头添加一个或更多元素,并返回新的长度
let unshiftArr=concatArr.unshift("1");
console.log(`unshift后的新数组:${concatArr};新的数组长度${unshiftArr}`);//输出结果 unshift后的新数组:1,2,3;新的数组长度3
//splice 从数组中添加或删除元素
let addItem=concatArr.splice(2,0,"4"); //参数 第一个表示开始位置的索引编码,第二个表示删除的字符数,第三个参数表示新增的元素
console.log(`splice添加元素后的数组:${concatArr};新增的元素${addItem}`); //输出结果 splice添加元素后的数组:1,2,4,3;新增的元素
let delItem=concatArr.splice(2,1);
console.log(`splice删除元素后的数组:${concatArr};删除的元素${delItem}`); //输出结果 splice删除元素后的数组:1,2,3;删除的元素4
//indexOf 搜索数组中的元素,并返回它所在的位置,如果搜索不到,返回值 -1,代表没有此项。
let cindex=alpha.indexOf('c');
console.log(`查找字母c的索引编号:${cindex}`);//输出结果 2
let dindex=alpha.indexOf('d');
console.log(`查找字母d的索引编号:${dindex}`);//输出结果 -1
//lastIndexOf 返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索。
let cLastIndex=alpha.lastIndexOf("c");
console.log(`查找字母c的最后出现的索引编号:${cLastIndex}`);//输出结果 2
//join 把数组的所有元素放入一个字符串
let arrToStr=alpha.join();
console.log(`join后的结果:${arrToStr}`);//输出结果 a,b,c
//toString 把数组转换为字符串,并返回结果
let strArr=alpha.toString();
console.log(`toString后的结果:${strArr}`);//输出结果 a,b,c
//forEach() 数组每个元素都执行一次回调函数
alpha.forEach(item=>{ console.log(`forEach遍历 当前值:${item}`);});
//every方法 检测数值元素的每个元素是否都符合条件
var everyArrary=new Array(1,2,3,4,5,6);
let eResult=everyArrary.every((val,indexVal,arrary)=>{
return val%2==0;
});
console.log(`every遍历检测每个元素除以2余数都为0结果:${eResult}`);//输出结果 false
//some 检测数组元素中是否有元素符合指定条件
let someResult=everyArrary.some((val,indexVal,arrary)=>{ return val%8==0;});
console.log(`some遍历检测存在8的倍数结果:${someResult}`);//输出结果 false
let someResult1=everyArrary.some((val,indexVal,arrary)=>{ return val%2==0;});
console.log(`some 遍历检测存在2的倍数结果:${someResult1}`);//输出结果 true
//filter方法,检测数值元素,并返回符合条件所有元素的数组
var filterArrary=new Array(10,20,30,40,50,60);
let rArrary=filterArrary.filter((val,indexVal,arrary)=>{return (val>=20&&val<=40)});
console.log(`filter过滤20-40之间的整数${rArrary}`);//输出结果 20,30,40
//slice 选取数组的的一部分,并返回一个新数组
var sliceArr=filterArrary.slice(1,3);// 第一个参数表示开始索引编码 第二个参数表示结束索引编码
console.log(`slice选取部分数据:${sliceArr}`);//输出结果 20,30
//sort 对数组的元素进行排序
var sortNumArr=[1,3,4,6,5,8,10,9];
var sortTxtArr=["a","d","f","c"];
console.log(`sortNum数组排序前的数据:${sortNumArr}`);//输出结果 1,3,4,6,5,8,10,9
sortNumArr.sort();
console.log(`sortNum数组排序后的数据:${sortNumArr}`);//输出结果 1,10,3,4,5,6,8,9
console.log(`sortTxt数组排序前的数据:${sortTxtArr}`);//输出结果 a,d,f,c
sortTxtArr.sort();
console.log(`sortTxt数组排序后的数据:${sortTxtArr}`);//输出结果 a,c,d,f
//reverse 反转数组的元素顺序
var reverseNumArr=[1,3,4,6,5,8,10,9];
var reverseTxtArr=["a","d","f","c"];
console.log(`reverseNum数组排序前的数据:${reverseNumArr}`);//输出结果 1,3,4,6,5,8,10,9
reverseNumArr.reverse();
console.log(`reverseNum数组排序后的数据:${reverseNumArr}`);//输出结果 9,10,8,5,6,4,3,1
reverseNumArr.reverse();
console.log(`reverseNum数组二次排序后的数据:${reverseNumArr}`);//输出结果 1,3,4,6,5,8,10,9
console.log(`reverseTxt数组排序前的数据:${reverseTxtArr}`);//输出结果 a,d,f,c
reverseTxtArr.reverse();
console.log(`reverseTxt数组排序后的数据:${reverseTxtArr}`);//输出结果 c,f,d,a
//reduce 将数组元素计算为一个值(从左到右)
var reduceArrary=[20,40,7,8];
let reduceNum=reduceArrary.reduce(function(preVal,curVal,curIndex,arrary)
{
console.log(`reduce:preVal ${preVal},curVal ${curVal},curIndex ${curIndex}, arrary ${arrary}`);
return preVal+curVal;
});
console.log(`reduce计算结果:${reduceNum}`);//输出结果 75
//reduceRight 将数组元素计算为一个值(从右到左)
let reduceRightNum=reduceArrary.reduceRight(function(preVal,curVal,curIndex,arrary)
{
console.log(`reduceRight:preVal ${preVal},curVal ${curVal},curIndex ${curIndex}, arrary ${arrary}`);
return preVal+curVal;
});
console.log(`reduceRight计算结果:${reduceNum}`);//输出结果 75
//map 通过指定函数处理数组的每个元素,并返回处理后的数组
var mapNumArrary=[1,2,3,4];
let newNumArr=new Array();
//void函数
let mapArr1=mapNumArrary.map(function(val,index,arrary){
newNumArr.push(val);
});
console.log(newNumArr);//输出结果 [ 1, 2, 3, 4 ]
console.log(mapArr1);//输出结果 [ undefined, undefined, undefined, undefined ]
//有返回值的函数
let mapArr2=mapNumArrary.map(function(val,index,arrary){
return val*2;
});
console.log(mapArr2);//输出结果 [ 2, 4, 6, 8 ]
2.5 元组
元组类型用来表示已知元素数量和类型的数组,各元素的类型不必相同,对应位置的类型需要相同
let x: [string, number];
x = ['TypeScript', 1]; // 运行正常
x = [1, 'TypeScript']; // 报错 Type 'number' is not assignable to type 'string'. Type 'string' is not assignable to type 'number'.
console.log(x[0]); // 输出 TypeScript
2.5.1 元组运算
- push() 向元组添加元素,添加在最后面。
- pop() 从元组中移除元素(最后一个),并返回移除的元素。
var mytuple = [10,"Hello","World","typeScript"];
mytuple.push(12);// 添加到元组中
console.log(`添加后元组数据:${mytuple}`);//10,Hello,World,typeScript,12
mytuple.pop();//删除元组最后一个元素
console.log(`删除后元组数据:${mytuple}`);//10,Hello,World,typeScript
2.5.2 更新元组
var mytuple = [10, "RunTs", "Taobao", "Google"]; // 创建一个元组
console.log("元组的第一个元素为:" + mytuple[0]);
// 更新元组元素
mytuple[0] = 121;
console.log("元组中的第一个元素更新为:"+ mytuple[0]);
2.5.3 解构元组
var a =[10,"RunTs"];
var [b,c] = a;
console.log(b);//输出结果 10
console.log(c);//输出结果 RunTs
2.6 枚举enum
enum类型是对JavaScript标准数据类型的一个补充,默认情况下,从0开始为元素编号。 你也可以手动的指定成员的数值,或者全部都采用手动赋值。枚举类型提供的一个便利是你可以由枚举的值得到它的名字
//默认0元素开始编号
enum Color {Red, Green, Blue};
let c: Color = Color.Green;
let colorName: string = Color[2];//获取枚举值名称
console.log(colorName);//输出结果 Blue
//从1开始编号
enum Color1 {Red = 1, Green, Blue};
let c1: Color1 = Color1.Green;
let colorName1: string = Color1[2];
console.log(colorName1);//输出结果 Green
//全部手动赋值
enum Color2 {Red = 2, Green = 3, Blue = 4};
let c2: Color2 = Color2.Green;
let colorName2: string = Color2[2];
console.log(colorName2);//输出结果 Red
let colorName3: string =Color2[Color2.Red];
console.log(colorName3);//输出结果 Red
2.7 Null和Undefined
null
在 JavaScript 中 null 表示 "什么都没有"。null是一个只有一个值的特殊类型,表示一个空对象引用。用 typeof 检测 null 返回是 object。
undefined
在 JavaScript 中, undefined 是一个没有设置值的变量。
typeof 一个没有值的变量会返回 undefined。
Null 和 Undefined 是其他任何类型(包括 void)的子类型,可以赋值给其它类型,如数字类型,此时,赋值后的类型会变成 null 或 undefined。而在TypeScript中启用严格的空校验(--strictNullChecks)特性,就可以使得null 和 undefined 只能被赋值给 void 或本身对应的类型
// 启用 --strictNullChecks
let x: number;
x = 1; // 正确
x = undefined; // 错误 Type 'undefined' is not assignable to type 'number'.
x = null; // 错误 Type 'null' is not assignable to type 'number'.
// 启用 --strictNullChecks
let y: number | null | undefined;
y= 1; // 正确
y = undefined; // 正确
y = null; // 正确
2.8 never类型
never 是其它类型(包括 null 和 undefined)的子类型,代表从不会出现的值。这意味着声明为 never 类型的变量只能被 never 类型所赋值,在函数中它通常表现为抛出异常或无法执行到终止点(例如无限循环)
let x: never;
let y: number;
// 错误,Type 'number' is not assignable to type 'never'.
x = 123;
// 运行正确,never 类型可以赋值给 never类型
x = (()=>{ throw new Error('exception')})();
// 运行正确,never 类型可以赋值给 数字类型
y = (()=>{ throw new Error('exception')})();
// 返回值为 never 的函数可以是抛出异常的情况
function error(message: string): never {
throw new Error(message);
}
// 返回值为 never 的函数可以是无法被执行到的终止点的情况
function loop(): never {
while (true) {}
}
2.9 类型断言
有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。
类型断言有两种形式。 其一是“尖括号”语法:
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
console.log(strLength);//输出结果16
另一个为as语法:
let someValue: any = "this is a string";
let strLength: number = (someValue as string ).length;
console.log(strLength);//输出结果16
2.10 联合类型
联合类型(Union Types)可以通过管道(|)将变量设置多种类型,赋值时可以根据设置的类型来赋值。
注意:只能赋值指定的类型,如果赋值其它类型就会报错。
创建联合类型的语法格式:Type1|Type2|Type3
var val:string|number;
val = 12;
console.log("数字为 "+ val);
val = "RunTs";
console.log("字符串为 " + val);
//赋值其他类型的数据就会报错
val=true;//编译报错 Type 'boolean' is not assignable to type 'string | number'.
//联合类型作为函数参数使用
function f(personName:string|string[]){
console.log(`人员姓名 ${personName}`);
}
f("TypeScript");
f(["TS","JS"]);
//数组声明为联合类型
var arr:string[]|number[];
arr=["TS","JS"];
console.log(arr);
arr=[1,2,3,4,5];
console.log(arr);
3.变量声明
变量是一种使用方便的占位符,用于引用计算机内存地址。我们可以把变量看做存储数据的容器。
TypeScript 变量的命名规则:
- 变量名称可以包含数字和字母。
- 除了下划线 _ 和美元 $ 符号外,不能包含其他特殊字符,包括空格。
- 变量名不能以数字开头。
3.1 var
3.1.1 声明方式
1)声明变量的类型及初始值
var [变量名] : [类型] = 值;
2)声明变量的类型,但没有初始值,变量值会设置为 undefined
var [变量名] : [类型]
3)声明变量并初始值,但不设置类型,该变量可以是任意类型
var [变量名] = 值;
4)声明变量没有设置类型和初始值,类型可以是任意类型,默认初始值为 undefined
var [变量名];
//声明变量的类型及初始值
var myName:string="TypeScript";
console.log(myName);
//声明变量的类型,但没有初始值,变量值会设置为 undefined
var myAge:number;
console.log(myAge);//编译报错 Variable 'myAge' is used before being assigned.
//声明变量并初始值,但不设置类型,该变量可以是任意类型
var myHight=165;
console.log(typeof(myHight));//输出结果 number
//声明变量没有设置类型和初始值,类型可以是任意类型,默认初始值为 undefined
var myWeight;
console.log(myWeight);// 输出结果 undefined
3.1.2 作用域
var的作用域:
- 全局作用域 − 全局变量定义在程序结构的外部,它可以在你代码的任何位置使用
- 类作用域 − 这个变量也可以称为字段。类变量声明在一个类里头,但在类的方法外面。 该变量可以通过类的对象来访问。类变量也可以是静态的,静态的变量可以通过类名直接访问。
- 局部作用域 − 局部变量,局部变量只能在声明它的一个代码块(如:方法)中使用。
var global_num = 12; //全局变量
//Nmbers 类
class Numbers {
num_val = 13; // 实例变量
static sval = 10;// 静态变量
storeNum():void {
var local_num = 14; // 局部变量
console.log(local_num);
}
}
console.log("全局变量为: "+global_num);// 输出结果 全局变量为: 12
console.log(Numbers.sval);// Numbers 静态变量 输出结果 10
var obj = new Numbers();
console.log("Numbers实例变量: "+obj.num_val);//输出结果 Numbers实例变量: 13
obj.storeNum();//输出结果 14
变量获取特别之处
for (var i = 0; i < 10; i++) {
setTimeout(function() { console.log(i); }, 100 * i);
}
此代码的返回结果
我们期待的返回结果应该是
原因分析: setTimeout在若干毫秒后执行一个函数,并且是在for循环结束后。 for循环结束后,i的值为10。 所以当函数被调用的时候,它会打印出 10!
一个通常的解决方法是使用立即执行的函数表达式(IIFE)来捕获每次迭代时i的值
for (var i = 0; i < 10; i++) {
// capture the current state of 'i'
// by invoking a function with its current value
(function(i) {
setTimeout(function() { console.log(i); }, 100 * i);
})(i);
}
3.2 let
let用来声明变量,有以下几个特点
- 在代码块内有效
- 不能重复声明
- 不影响作用域链
let与var声明变量比较
特点 | let | var | 备注 |
作用域 | 代码块内 | 全局范围内 | for循环计数器很适合用let |
重复声明 | 不可以 | 可以 |
//代码块内有效
{
let a = 0;
var b = 1;
}
console.log(a); // 编译报错 Cannot find name 'a'.
console.log(b); // 1
//不允许重复声明
let c = 1; //编译报错 Cannot redeclare block-scoped variable 'c'.
let c = 2;//编译报错 Cannot redeclare block-scoped variable 'c'.
var d = 3;
var d = 4;
console.log(c);
console.log(d); //输出结果 4
//拥有块级作用域的变量的另一个特点是,它们不能在被声明之前读或写。 虽然这些变量始终“存在”于它们的作用域里,但在直到声明它的代码之前的区域都属于 时间死区。 它只是用来说明我们不能在 let语句之前访问它们
console.log(e); //编译报错 Variable 'e' is used before being assigned.
let e = "apple";
3.3 const
const关键字用来声明常量,const声明有以下特点:
- 不能重复声明
- 声明必须赋初始值,否则报错
- 声明之后值不允许修改
- 在代码块内有效
{
var myName="TypeScript";
const myAge=30;
//不可修改值
myAge=40; //编译报错 Cannot assign to 'myAge' because it is a constant.
//声明必须赋初始值
const cityName; //编译报错 'const' declarations must be initialized.
}
console.log(myName);
console.log(myAge);// 编译报错 Cannot find name 'myAge'.
4.运算符
4.1 算术运算符
运算符 | 描述 |
+ | 加法 |
- | 减法 |
* | 乘法 |
/ | 除法 |
% | 取模(余数) |
++ | 自增 |
-- | 自减 |
4.2 关系运算符
运算符 | 描述 |
== | 等于 |
!= | 不等于 |
> | 大于 |
< | 小于 |
>= | 大于或等于 |
<= | 小于或等于 |
4.3 逻辑运算符
运算符 | 描述 |
&& | and |
|| | or |
! | not |
4.4 位运算符
运算符 | 描述 | 例子 | 类似于 | 结果 | 十进制 |
& | AND,按位与处理两个长度相同的二进制数,两个相应的二进位都为 1,该位的结果值才为 1,否则为 0。 | x = 5 & 1 | 0101 & 0001 | 0001 | 1 |
| | OR,按位或处理两个长度相同的二进制数,两个相应的二进位中只要有一个为 1,该位的结果值为 1。 | x = 5 | 1 | 0101 | 0001 | 0101 | 5 |
~ | 取反,取反是一元运算符,对一个二进制数的每一位执行逻辑反操作。使数字 1 成为 0,0 成为 1。 | x = ~ 5 | ~0101 | 1010 | -6 |
^ | 异或,按位异或运算,对等长二进制模式按位或二进制数的每一位执行逻辑异按位或操作。操作的结果是如果某位不同则该位为 1,否则该位为 0。 | x = 5 ^ 1 | 0101 ^ 0001 | 0100 | 4 |
<< | 左移,把 << 左边的运算数的各二进位全部左移若干位,由 << 右边的数指定移动的位数,高位丢弃,低位补 0。 | x = 5 << 1 | 0101 << 1 | 1010 | 10 |
>> | 右移,把 >> 左边的运算数的各二进位全部右移若干位,>> 右边的数指定移动的位数。 | x = 5 >> 1 | 0101 >> 1 | 0010 | 2 |
>>> | 无符号右移,与有符号右移位类似,除了左边一律使用0 补位。 | x = 2 >>> 1 | 0010 >>> 1 | 0001 | 1 |
4.5 赋值运算符
运算符 | 描述 |
= | 赋值 |
+= | 先进行加运算后赋值 |
-= | 先进行减运算后赋值 |
*= | 先进行乘运算后赋值 |
/= | 先进行除运算后赋值 |
4.6 三元运算符
三元运算有 3 个操作数,并且需要判断布尔表达式的值。该运算符的主要是决定哪个值应该赋值给变量。
语法:Test ? expr1 : expr2
- Test − 指定的条件语句
- expr1 − 如果条件语句 Test 返回 true 则返回该值
- expr2 − 如果条件语句 Test 返回 false 则返回该值
let myName:string="TypeScript";
let isTS=myName=="TypeScript"?true:false; //等价于 if(myName==""){isTS=true;} else { isTS=true;}
4.7 类型运算符
- typeof运算符:typeof 是一元运算符,返回操作数的数据类型
- instanceof运算符:用于判断对象是否为指定的类型
let myName:string="TypeScript";
console.log(typeof(myName));//输出结果 string
let myage=30;
console.log(typeof(myage));//输出结果 number
5. 条件语句
条件语句分为:
- if 语句 - 只有当指定条件为 true 时,使用该语句来执行代码
- if...else 语句 - 当条件为 true 时执行代码,当条件为 false 时执行其他代码
- if...else if....else 语句- 使用该语句来选择多个代码块之一来执行
- switch 语句 - 使用该语句来选择多个代码块之一来执行
if....else if....else语句注意事项
- 一个 if 判断语句可以有 0 或 1 个 else 语句,这些语句必需在 else..if 语句后面。
- 一个 if 判断语句可以有 0 或多个 else if,这些语句必需在 else 之前。
- 一旦执行了 else..if 内的代码,后面的 else..if 或 else 将不再执行。
switch语句需要遵循的规则
- switch 语句中的 expression 是一个要被比较的表达式,可以是任何类型,包括基本数据类型(如 number、string、boolean)、对象类型(如 object、Array、Map)以及自定义类型(如 class、interface、enum)等。
- 在一个 switch 中可以有任意数量的 case 语句。每个 case 后跟一个要比较的值和一个冒号。
- case 的 constant-expression 必须与 switch 中的变量 expression 具有相同或兼容的数据类型。
- 当被测试的变量等于 case 中的常量时,case 后跟的语句将被执行,直到遇到 break 语句为止。
- 当遇到 break 语句时,switch 终止,控制流将跳转到 switch 语句后的下一行。
- 不是每一个 case 都需要包含 break。如果 case 语句不包含 break,控制流将会 继续 后续的 case,直到遇到 break 为止。
- 一个 switch 语句可以有一个可选的 default case,出现在 switch 的结尾。default 关键字则表示当表达式的值与所有 case 值都不匹配时执行的代码块。default case 中的 break 语句不是必需的。
switch(expression){
case constant-expression :
statement(s);
break; /* 可选的 */
case constant-expression :
statement(s);
break; /* 可选的 */
/* 您可以有任意数量的 case 语句 */
default : /* 可选的 */
statement(s);
}
6.循环
6.1 for循环
语法
for ( init; condition; increment ){
statement(s);
}
示例
let count:number=10;
for(let i=0;i<count;i++){
console.log(i);
}
6.2 for..in循环
for...in 语句用于一组值的集合或列表进行迭代输出
语法
for (var val in list) {
//语句
}
示例
let arraryInput=[1,2,3,4,5];
for(let j in arraryInput){
console.log(j);
}
6.3 for..of、forEach、every和some循环
for...of 语句创建一个循环来迭代可迭代的对象。在 ES6 中引入的 for...of 循环,以替代 for...in 和 forEach() ,并支持新的迭代协议。for...of 允许你遍历 Arrays(数组), Strings(字符串), Maps(映射), Sets(集合)等可迭代的数据结构等。
因为 forEach 在 iteration 中是无法返回的,所以可以使用 every 和 some 来取代 forEach。
let someArray = [1, "string", false];
//for...of循环
for (let entry of someArray) {
console.log(entry); // 1, "string", false
}
//forEach循环
someArray.forEach((val, indexVal, array) => {
console.log(`当前值 ${val}`);
console.log(`当前索引 index${indexVal}`);
console.log(`当前array ${array}`);
});
//every循环 有返回值
//默认只遍历循环第一个元素,返回值为false
let returnEvery=someArray.every((val, indexVal, array) => {
console.log(`当前值 ${val}`);
console.log(`当前索引index ${indexVal}`);
console.log(`当前array ${array}`);
});
console.log(returnEvery);//false
//会循环遍历所有元素,返回值为true
returnEvery=someArray.every((val, indexVal, array) => {
console.log(`当前值 ${val}`);
console.log(`当前索引index ${indexVal}`);
console.log(`当前array ${array}`);
return true;//continue
});
console.log(returnEvery);//true
//只会循环遍历第一个元素,返回值为false
returnEvery=someArray.every((val, indexVal, array) => {
console.log(`当前值 ${val}`);
console.log(`当前索引index ${indexVal}`);
console.log(`当前array ${array}`);
return false;//quit
});
console.log(returnEvery);//false
//会循环遍历所有元素,返回值为true
returnEvery=someArray.every((val, indexVal, array) => {
console.log(`当前值 ${val}`);
console.log(`当前索引index ${indexVal}`);
console.log(`当前array ${array}`);
return "ok";//quit
});
console.log(returnEvery);//true
//some循环
//默认遍历所有元素 返回值 false
let someReturn=someArray.some((val, indexVal, array) => {
console.log(`当前值 ${val}`);
console.log(`当前索引index ${indexVal}`);
console.log(`当前array ${array}`);
});
console.log(someReturn);//false
//只会遍历第一个元素 返回值 true
someReturn=someArray.some((val, indexVal, array) => {
console.log(`当前值 ${val}`);
console.log(`当前索引index ${indexVal}`);
console.log(`当前array ${array}`);
return true;
});
console.log(someReturn);//true
//会遍历所有元素 返回值false
someReturn=someArray.some((val, indexVal, array) => {
console.log(`当前值 ${val}`);
console.log(`当前索引index ${indexVal}`);
console.log(`当前array ${array}`);
return false;
});
console.log(someReturn);//false
//只会遍历第一个元素 返回值true
someReturn=someArray.some((val, indexVal, array) => {
console.log(`当前值 ${val}`);
console.log(`当前索引index ${indexVal}`);
console.log(`当前array ${array}`);
return 'ok';
});
console.log(someReturn);//true
6.4 while循环
语法
while(condition)
{
statement(s);
}
示例
var num:number = 5;
var factorial:number = 1;
while(num >=1) {
factorial = factorial * num;
num--;
}
console.log("5 的阶乘为:"+factorial);
6.5 do...while循环
语法
do
{
statement(s);
}while( condition );
条件表达式出现在循环的尾部,所以循环中的 statement(s) 会在条件被测试之前至少执行一次
示例
var n:number = 10;
do {
console.log(n);
n--;
} while(n>=0);
6.6 break语句
break 语句有以下两种用法:
- 当 break 语句出现在一个循环内时,循环会立即终止,且程序流将继续执行紧接着循环的下一条语句。
- 它可用于终止 switch 语句中的一个 case。
如果您使用的是嵌套循环(即一个循环内嵌套另一个循环),break 语句会停止执行最内层的循环,然后开始执行该块之后的下一行代码。
var i:number = 1;
while(i<=10) {
if (i % 5 == 0) {
console.log ("在 1~10 之间第一个被 5 整除的数为 : "+i);
break; // 找到一个后退出循环
}
i++;
} // 输出 5 然后程序执行结束
//循环内嵌套循环
let testArrary=[1,2,3,4,5];
let testArrary2=[3,4,5,6,10];
for(let i of testArrary)
{
for(let j of testArrary2)
{
if((i*2)==j)
{
console.log(`i:${i} j:${j}`);
break;
}
}
}
6.7 continue语句
continue 语句有点像 break 语句。但它不是强制终止,continue 会跳过当前循环中的代码,强迫开始下一次循环。
对于 for 循环,continue 语句执行后自增语句仍然会执行。对于 while 和 do...while 循环,continue 语句重新执行条件判断语句。
var num:number = 0;
var count:number = 0;
for(num=0;num<=20;num++) {
if (num % 2==0) {
continue;
}
count++;
}
console.log ("0 ~20 之间的奇数个数为: "+count) //输出10个偶数
6.8 无限循环
无限循环就是一直在运行不会停止的循环。 for 和 while 循环都可以创建无限循环。
for 创建无限循环语法格式:
for(;;) {
// 语句
}
示例
for(;;) {
console.log("这段代码会不停的执行")
}
while 创建无限循环语法格式:
while(true) {
// 语句
}
示例
while(true) {
console.log("这段代码会不停的执行")
}
7.对象
7.1 标准对象
对象是包含一组键值对的实例。 值可以是标量、函数、数组、对象等
var houseData=
{
door:"white door",
windows:"",
bedNum:5,
TVBrand:"XiaoMi",
bedRoomNum:5,
cars:["Red Car","Green Car","Black Car"],
keyOfRoomNum:function(){},//函数模型
houseDesc:function(){ return "MyHouse";}
};
console.log(houseData.door);//输出结果 white door
houseData.keyOfRoomNum=function(){
console.log("房间钥匙总共15把");
};
houseData.keyOfRoomNum();// 输出结果 房间钥匙总共15把
console.log(houseData.houseDesc());// 输出结果 MyHouse
console.log(houseData.cars);//输出结果 [ 'Red Car', 'Green Car', 'Black Car' ]
鸭子类型(Duck Typing)
鸭子类型(英语:duck typing)是动态类型的一种风格,是多态(polymorphism)的一种形式。
在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。
在鸭子类型中,关注点在于对象的行为能做什么,而不是关注对象所属的类型。
interface IPoint{
xPoint:number,
yPoint:number
}
function addPoints(p1:IPoint,p2:IPoint):IPoint {
let xPointSum = p1.xPoint + p2.xPoint;
let yPointSum = p1.yPoint + p2.yPoint;
return {xPoint:xPointSum,yPoint:yPointSum};
}
var newPoint = addPoints({xPoint:3,yPoint:4},{xPoint:5,yPoint:1});
console.log(newPoint);//输出结果 { xPoint: 8, yPoint: 5 }
//错误 Argument of type '{ xPoint: number; }' is not assignable to parameter of type 'IPoint'. Property 'yPoint' is missing in type '{ xPoint: number; }' but required in type 'IPoint'.
var newPoint2 = addPoints({xPoint:1},{xPoint:4,yPoint:3});
7.2 Map对象
Map 对象保存键值对,并且能够记住键的原始插入顺序,任何值(对象或者原始值) 都可以作为一个键或一个值
Map函数与属性
- map.clear() – 移除 Map 对象的所有键/值对 。
- map.set() – 设置键值对,返回该 Map 对象。
- map.get() – 返回键对应的值,如果不存在,则返回 undefined。
- map.has() – 返回一个布尔值,用于判断 Map 中是否包含键对应的值。
- map.delete() – 删除 Map 中的元素,删除成功返回 true,失败返回 false。
- map.size – 返回 Map 对象键/值对的数量。
- map.keys() - 返回一个 Iterator 对象, 包含了 Map 对象中每个元素的键 。
- map.values() – 返回一个新的Iterator对象,包含了Map对象中每个元素的值 。
let namesMap=new Map();
//设置map
namesMap.set("1","TypeScript");
namesMap.set("2","JavaScript");
namesMap.set("3","ES6");
//数量
console.log(namesMap.size);// 输出结果 3
//循环遍历 Map中的key
for (let key of namesMap.keys()) {
console.log(key);
}
//循环遍历 Map中的value
for (let value of namesMap.values()) {
console.log(value);
}
//循环遍历 Map中的key => value
for (let entry of namesMap.entries()) {
console.log(entry[0], entry[1]);
}
//循环遍历——使用对象解析
for (let [key, value] of namesMap) {
console.log(key, value);
}
//获取键值对应的value值
console.log(namesMap.get("1"));//输出结果 TypeScript
//判断是否包含key
console.log(namesMap.has("1"));// 输出结果 true
console.log(namesMap.has("7"));// 输出结果 false
//删除键值对
console.log(namesMap.delete("3"));// 输出结果 true
console.log(namesMap);// 输出结果 Map(2) { '1' => 'TypeScript', '2' => 'JavaScript' }
console.log(namesMap.delete("7"));// 输出结果 false
//清除
namesMap.clear();
console.log(namesMap.size);// 输出结果 0
8.函数
函数是一组一起执行一个任务的语句,可以把代码划分到不同的函数中,如何划分代码到不同的函数中是由您来决定的,但在逻辑上,划分通常是根据每个函数执行一个特定的任务来进行的。函数声明告诉编译器函数的名称、返回类型和参数。函数定义提供了函数的实际主体。
8.1 标准函数
function MyTs(){
console.log("欢迎来到TypeScript的世界");
}
MyTs();
8.2 有返回值函数
语法
function function_name():return_type {
// 语句
return value;
}
示例
function MyTWithVal():string{
return "欢迎来到TypeScript的世界";
}
console.log(MyTWithVal());
8.3 带参函数
基础带参函数
function fWithPara(studentName:string,age:number){
console.log(`姓名为 ${studentName}的学生,年龄是${age}岁`);
}
fWithPara("王明",18);
可选带参函数
可选参数使用问号标识 ?
function fSelPara(companyName:string,simpleName?:string){
if(simpleName){
console.log(`公司名称为 ${companyName}的简称是${simpleName}`);
}
else {
console.log(`公司名称为 ${companyName}`);
}
}
fSelPara("阿里巴巴","淘宝");//输出结果 公司名称为 阿里巴巴的简称是淘宝
fSelPara("阿里巴巴");//输出结果 公司名称为 阿里巴巴
默认带参函数
语法
function function_name(param1[:type],param2[:type] = default_value) {
}
示例
function fDefault(studentName:string,age:number=0){
console.log(`姓名为 ${studentName}的学生,年龄是${age}岁`);
}
fDefault("王明",18);//输出结果 姓名为 王明的学生,年龄是18岁
fDefault("王明");//输出结果 姓名为 王明的学生,年龄是0岁
注意:参数不能同时设置为可选和默认
剩余参数函数
我们不知道要向函数传入多少个参数,这时候我们就可以使用剩余参数来定义
function fRePara(mainGroupName:string,...otherGroupName:string[]){
if(otherGroupName.length>0){
console.log(`主队为 ${mainGroupName},其他队${otherGroupName}`);
}
else{
console.log(`主队为 ${mainGroupName}`);
}
}
fRePara("第一梯队","第二梯队","第三梯队");//输出结果 主队为 第一梯队,其他队第二梯队,第三梯队
fRePara("第一梯队");//输出结果 主队为 第一梯队
8.4 匿名函数
匿名函数是一个没有函数名的函数,匿名函数在程序运行时动态声明,除了没有函数名外,其他的与标准函数一样。我们可以将匿名函数赋值给一个变量,这种表达式就成为函数表达式。
语法
var res = function( [arguments] ) { ... }
示例
//匿名函数
var showTime=function(){
var curDate=new Date();
console.log(`当前日期:${curDate.getFullYear()}年${curDate.getMonth()+1}月${curDate.getDate()}日`);
}
showTime();
//匿名函数自调用
(function(){
console.log("hello world");
}())
8.5 构造函数
TypeScript 也支持使用 JavaScript 内置的构造函数 Function() 来定义函数
语法
var res = new Function ([arg1[, arg2[, ...argN]],] functionBody)
参数说明
- arg1, arg2, ... argN:参数列表。
- functionBody:一个含有包括函数定义的 JavaScript 语句的字符串。
示例
var myFunction = new Function("a", "b", "return a * b");
var x = myFunction(4, 3);
console.log(x);//输出结果 12
8.6 箭头函数
语法
( [param1, param2,…param n] )=>statement;
示例
//带参箭头函数
var func=(x:number,y:number)=>{
let sum=x+y;
console.log(sum);
}
func(20,30);
//单个带参箭头函数
//标准写法
var funcSS=(x:number)=>{
console.log(x);
}
funcSS(10);
//可以省略()
var funcS=x=>{
console.log(x);
}
funcS(50);
//无参数箭头函数
var testLambda=()=>{
console.log("这是没有参数的箭头函数");
}
testLambda();
8.7 函数重载
重载是方法名字相同,而参数不同,返回类型可以相同也可以不同。每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
示例
function disp(s1:string):void;
function disp(n1:number,s1:string):void;
function disp(x:any,y?:any):void {
console.log(x);
console.log(y);
}
disp("abc");//输出结果 abc undefined
disp(1,"xyz");//输出结果 1 xyz
9.接口
接口是一系列抽象方法的声明,是一些方法特征的集合,这些方法都应该是抽象的,需要由具体的类去实现,然后第三方就可以通过这组抽象方法调用,让具体的类执行具体的方法,定义接口的关键字为interface
9.1 标准接口
//标准接口
interface IPerson{
PersonName:string,
Height:number,
weight:number,
Gender:string
}
//定义一个类型为IPerson的变量,变量中的字段必须和接口中的一致(字段名称和字段数量及字段类型),否则会编译报错
let commonPerson:IPerson=
{
PersonName:"接口类型的变量",
Height:167,
weight:116,
Gender:"女",
}
console.log(commonPerson.PersonName);
commonPerson.PersonName="接口类型的变量测试";
console.log(commonPerson.PersonName);
//定义一个类,继承IPerson,类中必须包含IPerson中所有的字段,否则编译报错;类中的字段可以比接口中的字段多
class PersonInfo implements IPerson
{
PersonName: string;
Height: number;
weight: number;
Gender: string;
TelNum:string;
DetailAddress:string;
}
let personTest=new PersonInfo();
personTest.PersonName="类继承接口";
personTest.TelNum="13775175856";
console.log(personTest.PersonName);
//带有函数的接口定义
interface IFunc
{
/**新增用户
*userName:用户姓名
*/
AddUser(userName:string):void,
GetUserName(userId:number):string,
DeleteUser(userId:number):boolean,
GetUserList():void
}
//定义一个类,继承IFunc接口
class MyFunc implements IFunc
{
AddUser(userName:string):void
{
console.log("新增用户:"+userName);
}
GetUserName(userId: number): string
{
console.log("获取用户姓名:"+userId);
return "获取用户名称";
}
DeleteUser(userId: number): boolean
{
console.log("删除用户:"+userId);
return true;
}
GetUserList(): void
{
console.log("获取用户列表");
}
/**获取用户分页列表
* pageIndex:页码
* pageSize:每页数量
*/
GetUserPage(pageIndex:number,pageSize:number):void {
}
}
let myfunc=new MyFunc();
myfunc.AddUser("张三");
let delResult=myfunc.DeleteUser(1);
console.log(delResult);
9.2 接口继承
和类一样,接口也可以相互扩展。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里
9.2.1 接口继承接口
一个接口可以继承多个接口,创建出多个接口的合成接口
interface IPerson{
PersonName:string,
Height:number,
weight:number,
Gender:string
}
//单个接口继承
interface IPersonDetail extends IPerson
{
Address:string,
TelNum:string,
Email:string
}
//多个接口继承
interface IUser extends IPerson,IPersonDetail
{
UserName:string
}
9.2.2 接口继承类
当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的private和protected成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。
这是很有用的,当你有一个很深层次的继承,但是只想你的代码只是针对拥有特定属性的子类起作用的时候。子类除了继承自基类外与基类没有任何联系。
//定义类
class PersonDetail
{
Address:string;
TelNum:string;
Email:string;
private personCompany:string="";
protected companyName:string="";
}
//接口继承类
interface IPerson extends PersonDetail
{
PersonName:string,
Height:number,
weight:number,
Gender:string
}
//定义类,继承 IPerson
// 对于 PersonDetail类中的private及protected成员 编译报错 Type 'PersonInfo' is missing the following properties from type 'IPerson': personCompany, companyName
class PersonInfo implements IPerson
{
PersonName:string;
Height:number;
weight:number;
Gender:string;
Address:string;
TelNum:string;
Email:string;
}
//类继承类 再继承接口 ,必须包含接口中的字段,否则编译报错
class Control extends PersonDetail implements IPerson
{
private state: any;
PersonName:string;
Height:number;
weight:number;
Gender:string
}
9.2.3 混合类型的接口继承
interface ICounter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): ICounter {
let counter = <ICounter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
10.类
类描述了所创建的对象共同的属性和方法
定义类的关键字为 class,后面紧跟类名,类可以包含以下几个模块(类的数据成员):
- 字段 − 字段是类里面声明的变量。字段表示对象的有关数据。
- 构造函数 − 类实例化时调用,可以为类的对象分配内存。
- 方法 − 方法为对象要执行的操作。
10.1 简单类的实现
//只有字段的类
class ClassInfo{
classNo:string;//班级名称字段
studentNum:number;//学生数字段
}
var classInfo=new ClassInfo();
classInfo.classNo="高三一班";
classInfo.studentNum=50;
console.log(`${classInfo.classNo}共有学生${classInfo.studentNum}名`);
// 字段、构造函数、方法同事包含的类
class Car {
// 字段
engine:string;
// 构造函数
constructor(engine:string) {
this.engine = engine;
}
// 方法
disp():void {
console.log("函数中显示发动机型号:"+this.engine)
}
}
// 创建一个对象
var obj = new Car("XXSY1");
// 访问字段
console.log("读取发动机型号 : "+obj.engine);
// 访问方法
obj.disp();
10.2 类的继承
TypeScript 支持继承类,即我们可以在创建类的时候继承一个已存在的类,这个已存在的类称为父类,继承它的类称为子类。类继承使用关键字 extends,子类除了不能继承父类的私有成员(方法和属性)和构造函数,其他的都可以继承。TypeScript 一次只能继承一个类,不支持继承多个类,但 TypeScript 支持多重继承(A 继承 B,B 继承 C)。
//类的继承
class AreaInfo{
Area:number;//面积
Unit:string;//面积单位
}
class Circle extends AreaInfo{
Diam:number;//直径
}
var mycircle=new Circle();
mycircle.Diam=10;
mycircle.Area=500;
mycircle.Unit="平方米";
console.log(mycircle);
//重写继承类的方法
class PrinterClass {
doPrint():void {
console.log("父类的 doPrint() 方法。")
}
}
class StringPrinter extends PrinterClass {
doPrint():void {
super.doPrint(); // 调用父类的函数
console.log("子类的 doPrint()方法。")
}
}
注意:子类只能继承一个父类,TypeScript 不支持继承多个类,但支持多重继承
10.3 类的静态属性及instanceof运算符
static关键字
static 关键字用于定义类的数据成员(属性和方法)为静态的,静态成员可以直接通过类名调用
class ClassInfo{
static classNo:string;
static showInfo(){
console.log("我是班级"+this.classNo+"中的一员");
}
}
ClassInfo.classNo="高三一班";
ClassInfo.showInfo();
instanceof运算符
instanceof 运算符用于判断对象是否是指定的类型,如果是返回 true,否则返回 false
class Person{ }
var obj = new Person()
var isPerson = obj instanceof Person;
console.log("obj 对象是 Person 类实例化来的吗? " + isPerson);
10.4 访问控制修饰符
10.4.1 public、protected及 private修饰符
TypeScript 中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。TypeScript 支持 3 种不同的访问权限:
- public(默认) : 公有,可以在任何地方被访问。
- protected : 受保护,可以被其自身以及其子类访问。
- private : 私有,只能被其定义所在的类访问。
class Person{
public PersonName:string;//人员姓名
protected LinkTel:string;//联系电话
private MoneyNum:number;
}
var perInfo=new Person();
perInfo.PersonName="马云";
perInfo.LinkTel="";//编译报错 Property 'LinkTel' is protected and only accessible within class 'Person' and its subclasses.
perInfo.MoneyNum=50000000000;//编译报错 Property 'MoneyNum' is private and only accessible within class 'Person'.
10.4.2 readonly修饰符
readonly关键字将属性设置为只读的,只读属性必须在声明时或构造函数里被初始化
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor (theName: string) {
this.name = theName;
}
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // Cannot assign to 'name' because it is a read-only propert
10.4.3 存取器
TypeScript支持通过getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问
let passcode = "secret passcode";
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
if (passcode && passcode == "secret passcode") {
this._fullName = newName;
}
else {
console.log("Error: Unauthorized update of employee!");
}
}
}
let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
console.log(employee.fullName);
}
对于存取器有下面几点需要注意的:
首先,存取器要求你将编译器设置为输出ECMAScript 5或更高。 不支持降级到ECMAScript 3。 其次,只带有 get不带有set的存取器自动被推断为readonly。 这在从代码生成 .d.ts文件时是有帮助的,因为利用这个属性的用户会看到不允许够改变它的值。
10.5 抽象类
抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法。
抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。 抽象方法的语法与接口方法相似。 两者都是定义方法签名但不包含方法体。 然而,抽象方法必须包含 abstract
关键字并且可以包含访问修饰符。
abstract class Department {
constructor(public name: string) {
}
printName(): void {
console.log('Department name: ' + this.name);
}
abstract printMeeting(): void; // 必须在派生类中实现
}
class AccountingDepartment extends Department {
constructor() {
super('Accounting and Auditing'); // constructors in derived classes must call super()
}
printMeeting(): void {
console.log('The Accounting Department meets each Monday at 10am.');
}
generateReports(): void {
console.log('Generating accounting reports...');
}
}
let department: Department; // ok to create a reference to an abstract type
department = new Department(); // error: cannot create an instance of an abstract class
department = new AccountingDepartment(); // ok to create and assign a non-abstract subclass
department.printName();
department.printMeeting();
department.generateReports(); // error: Property 'generateReports' does not exist on type 'Department'.
10.6 类与接口
类可以实现接口,使用关键字 implements,并将 interest 字段作为类的属性使用
interface ILoan {
interest:number;
}
class AgriLoan implements ILoan {
interest:number;
rebate:number;
constructor(interest:number,rebate:number) {
this.interest = interest;
this.rebate = rebate;
}
}
var obj = new AgriLoan(10,1);
console.log("利润为 : "+obj.interest+",抽成为 : "+obj.rebate);
11.模块
TypeScript 模块的设计理念是可以更换的组织代码。
模块是在其自身的作用域里执行,并不是在全局作用域,这意味着定义在模块里面的变量、函数和类等在模块外部是不可见的,除非明确地使用 export 导出它们。类似地,我们必须通过 import 导入其他模块导出的变量、函数、类等。
两个模块之间的关系是通过在文件级别上使用 import 和 export 建立的。
模块使用模块加载器去导入其它的模块。 在运行时,模块加载器的作用是在执行此模块代码前去查找并执行这个模块的所有依赖。 大家最熟知的JavaScript模块加载器是服务于 Node.js 的 CommonJS 和服务于 Web 应用的 Require.js,此外还有有 SystemJs 和 Webpack。
11.1 导出
任何声明(比如变量,函数,类,类型别名或接口)都能够通过添加export
关键字来导出
export interface StringValidator
{
IsAcceptable(s: string): boolean;
}
导出语句
导出语句很便利,因为我们可能需要对导出的部分重命名
class UserRepository
{
GetUserName(userId:number):string
{
return "查询用户:"+userId;
}
}
export {UserRepository};//导出语句
export{ UserRepository as UserRep};//导出语句并重命名
重新导出
我们经常会去扩展其它模块,并且只导出那个模块的部分内容。 重新导出功能并不会在当前模块导入那个模块或定义一个新的局部变量
Validation.ts
export interface StringValidator
{
IsAcceptable(s: string): boolean;
}
ZipCodeValidator.ts
import { StringValidator } from './Validation';
const numberRegexp = /^[0-9]+$/;//数字验证正则表达式
/**邮政编码验证 */
export class ZipCodeValidator implements StringValidator {
IsAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
myTS.ts
export class ParseIntBasedZipCodeValidator
{
isAcceptable(s: string)
{
return s.length === 5 && parseInt(s).toString() === s;
}
}
// 导出原先的验证器但做了重命名
export {ZipCodeValidator as RegExpBasedZipCodeValidator} from "./ZipCodeValidator";
或者一个模块可以包裹多个模块,并把他们导出的内容联合在一起通过语法:export * from "module"
AllValidators.ts
export * from "./StringValidator"; // exports interface StringValidator
export * from "./ZipCodeValidator"; // exports class ZipCodeValidator
默认导出
每个模块都可以有一个default
导出。 默认导出使用 default
关键字标记;并且一个模块只能够有一个default
导出, 需要使用一种特殊的导入形式来导入 default
导出。
default
导出十分便利。 比如,像JQuery这样的类库可能有一个默认导出 jQuery
或$
,并且我们基本上也会使用同样的名字jQuery
或$
导出JQuery。
JQuery.d.ts
declare let $: JQuery;
export default $;
App.ts
import $ from "JQuery";
$("button.continue").html( "Next Step..." );
类和函数声明可以直接被标记为默认导出, 标记为默认导出的类和函数的名字是可以省略的。
11.2 导入
模块的导入操作与导出一样简单。 可以使用以下 import
形式之一来导入其它模块中的导出内容
导入一个模块中的某个导出内容
import { ZipCodeValidator } from "./ZipCodeValidator";
let myValidator = new ZipCodeValidator();
对导入内容重命名
import { ZipCodeValidator as ZCV} from "./ZipCodeValidator";
let letValid=new ZCV();
将整个模块导入到一个变量,并通过它来访问模块的导出部分
import * as validator from "./ZipCodeValidator";
let myZipCodeValid = new validator.ZipCodeValidator();
具有副作用的导入模块
尽管不推荐这么做,一些模块会设置一些全局状态供其它模块使用。 这些模块可能没有任何的导出或用户根本就不关注它的导出。 使用下面的方法来导入这类模块:
import "./my-module.js";
export =
和 import = require()
CommonJS和AMD都有一个exports
对象的概念,它包含了一个模块的所有导出内容。
它们也支持把exports
替换为一个自定义对象。 默认导出就好比这样一个功能;然而,它们却并不相互兼容。 TypeScript模块支持 export =
语法以支持传统的CommonJS和AMD的工作流模型。
export =
语法定义一个模块的导出对象。 它可以是类,接口,命名空间,函数或枚举。
若要导入一个使用了export =
的模块时,必须使用TypeScript提供的特定语法import let = 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" }`);
});
12.命名空间
命名空间一个最明确的目的就是解决重名问题。命名空间定义了标识符的可见范围,一个标识符可在多个名字空间中定义,它在不同名字空间中的含义是互不相干的。这样,在一个新的名字空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其他名字空间中。TypeScript 中命名空间使用namespace关键字定义,语法如下:
namespace SomeNameSpaceName {
export interface ISomeInterfaceName { }
export class SomeClassName { }
}
以上定义了一个命名空间 SomeNameSpaceName,如果我们需要在外部可以调用 SomeNameSpaceName 中的类和接口,则需要在类和接口添加 export 关键字。
要在另外一个命名空间调用语法格式为:
SomeNameSpaceName.SomeClassName;
如果一个命名空间在一个单独的 TypeScript 文件中,则应使用三斜杠 /// 引用它,语法格式如下:
/// <reference path = "SomeFileName.ts" />
命名空间不在单独的ts文件中的使用
/**验证器 */
namespace Validation
{
const lettersRegexp = /^[A-Za-z]+$/;//字母验证表达式
const numberRegexp = /^[0-9]+$/;//数字验证正则表达式
export interface StringValidator
{
IsAcceptable(s: string): boolean;
}
/**字母验证 */
export class LettersOnlyValidator implements StringValidator
{
IsAcceptable(s: string)
{
return lettersRegexp.test(s);
}
}
/**邮政编码验证 */
export class ZipCodeValidator implements StringValidator
{
IsAcceptable(s: string)
{
return s.length === 5 && numberRegexp.test(s);
}
}
}
let strings = ["Hello", "98052", "101"];
// Validators to use
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// Show whether each string passed each validator
strings.forEach(s => {
for (let name in validators)
{
console.log(`"${ s }" - ${ validators[name].IsAcceptable(s) ? "matches" : "does not match" } ${ name }`);
}
});
命名空间在单独文件的使用
我们把上面的Validation拆分成四个ts文件
Validation.ts
namespace Validation
{
export interface StringValidator
{
IsAcceptable(s: string): boolean;
}
}
LettersValidator.ts
/// <reference path="Validation.ts" />
namespace Validation
{
const lettersRegexp = /^[A-Za-z]+$/;//字母验证表达式
/**字母验证 */
export class LettersOnlyValidator implements StringValidator
{
IsAcceptable(s: string)
{
return lettersRegexp.test(s);
}
}
}
ZipCodeValidator.ts
/// <reference path="Validation.ts" />
namespace Validation
{
const numberRegexp = /^[0-9]+$/;//数字验证正则表达式
/**邮政编码验证 */
export class ZipCodeValidator implements StringValidator
{
IsAcceptable(s: string)
{
return s.length === 5 && numberRegexp.test(s);
}
}
}
myTS.ts
/// <reference path="Validation.ts" />
/// <reference path="LettersValidator.ts" />
/// <reference path="ZipCodeValidator.ts" />
let strings = ["Hello", "98052", "101"];
// Validators to use
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// Show whether each string passed each validator
strings.forEach(s => {
for (let name in validators)
{
console.log(`"${ s }" - ${ validators[name].IsAcceptable(s) ? "matches" : "does not match" } ${ name }`);
}
});
当涉及到多文件时,我们必须确保所有编译后的代码都被加载了,我们有两种方式。
第一种方式,把所有的输入文件编译为一个输出文件,编译器会根据源码里的引用标签自动地对输出进行排序,需要使用--outFile
标记:
tsc --outFile sample.js myTS.ts
第二种方式,编译每一个文件(默认方式),那么每个源文件都会对应生成一个JavaScript文件,页面上通过<script>标签引入
别名
另一种简化命名空间操作的方法是使用import q = x.y.z
给常用的对象起一个短的名字。 不要与用来加载模块的 import x = require('name')
语法弄混了,这里的语法是为指定的符号创建一个别名。 你可以用这种方法为任意标识符创建别名,也包括导入的模块中的对象。
namespace Shapes {
//嵌套的命名空间
export namespace Polygons {
export class Triangle { }
export class Square { }
}
}
import polygons = Shapes.Polygons;
let sq = new polygons.Square(); // Same as "new Shapes.Polygons.Square()"
注意,我们并没有使用require
关键字,而是直接使用导入符号的限定名赋值。 这与使用 var
相似,但它还适用于类型和导入的具有命名空间含义的符号。 重要的是,对于值来讲, import
会生成与原始符号不同的引用,所以改变别名的var
值并不会影响原始变量的值。
使用其它的JavaScript库
为了描述不是用TypeScript编写的类库的类型,我们需要声明类库导出的API。 由于大部分程序库只提供少数的顶级对象,命名空间是用来表示它们的一个好办法。
我们称其为声明是因为它不是外部程序的具体实现。 我们通常在 .d.ts
里写这些声明。
比如 外部命名空间的示例:
流行的程序库D3在全局对象d3
里定义它的功能。 因为这个库通过一个 <script>
标签加载(不是通过模块加载器),它的声明文件使用内部模块来定义它的类型。 为了让TypeScript编译器识别它的类型,我们使用外部命名空间声明。 比如,我们可以像下面这样写:
D3.d.ts (部分摘录)
declare namespace D3 {
export interface Selectors {
select: {
(selector: string): Selection;
(element: EventTarget): Selection;
};
}
export interface Event {
x: number;
y: number;
}
export interface Base extends Selectors {
event: Event;
}
}
declare let d3: D3.Base;
13.命名空间和模块
关于术语的一点说明: 请务必注意一点,TypeScript 1.5里术语名已经发生了变化。 “内部模块”现在称做“命名空间”。 “外部模块”现在则简称为“模块”,这是为了与 ECMAScript 2015里的术语保持一致,(也就是说module X {
相当于现在推荐的写法 namespace X {
)。
13.1 使用命名空间
命名空间是位于全局命名空间下的一个普通的带有名字的JavaScript对象。 这令命名空间十分容易使用。 它们可以在多文件中同时使用,并通过 --outFile
结合在一起。 命名空间是帮你组织Web应用不错的方式,你可以把所有依赖都放在HTML页面的 <script>
标签里。
但就像其它的全局命名空间污染一样,它很难去识别组件之间的依赖关系,尤其是在大型的应用中。
13.2 使用模块
像命名空间一样,模块可以包含代码和声明。 不同的是模块可以 声明它的依赖。
模块会把依赖添加到模块加载器上(例如CommonJs / Require.js)。 对于小型的JS应用来说可能没必要,但是对于大型应用,这一点点的花费会带来长久的模块化和可维护性上的便利。 模块也提供了更好的代码重用,更强的封闭性以及更好的使用工具进行优化。
对于Node.js应用来说,模块是默认并推荐的组织代码的方式。
从ECMAScript 2015开始,模块成为了语言内置的部分,应该会被所有正常的解释引擎所支持。 因此,对于新项目来说推荐使用模块做为组织代码的方式。
13.3 命名空间和模块的陷阱
这部分我们会描述常见的命名空间和模块的使用陷阱和如何去避免它们。
对模块使用/// <reference>
一个常见的错误是使用/// <reference>
引用模块文件,应该使用import
。 要理解这之间的区别,我们首先应该弄清编译器是如何根据 import
路径(例如,import x from "...";
或import x = require("...")
里面的...
,等等)来定位模块的类型信息的。
编译器首先尝试去查找相应路径下的.ts
,.tsx
再或者.d.ts
。 如果这些文件都找不到,编译器会查找 外部模块声明。 回想一下,它们是在 .d.ts
文件里声明的。
myModules.d.ts
// In a .d.ts file or .ts file that is not a module:
declare module "SomeModule" {
export function fn(): string;
}
myOtherModule.ts
/// <reference path="myModules.d.ts" />
import * as m from "SomeModule";
这里的引用标签指定了外来模块的位置。 这就是一些Typescript例子中引用 node.d.ts
的方法。
不必要的命名空间
如果你想把命名空间转换为模块,它可能会像下面这个文件一件:
shapes.ts
export namespace Shapes {
export class Triangle { /* ... */ }
export class Square { /* ... */ }
}
顶层的模块Shapes
包裹了Triangle
和Square
。 对于使用它的人来说这是令人迷惑和讨厌的:
shapeConsumer.ts
import * as shapes from "./shapes";
let t = new shapes.Shapes.Triangle(); // shapes.Shapes?
TypeScript里模块的一个特点是不同的模块永远也不会在相同的作用域内使用相同的名字。 因为使用模块的人会为它们命名,所以完全没有必要把导出的符号包裹在一个命名空间里。
再次重申,不应该对模块使用命名空间,使用命名空间是为了提供逻辑分组和避免命名冲突。 模块文件本身已经是一个逻辑分组,并且它的名字是由导入这个模块的代码指定,所以没有必要为导出的对象增加额外的模块层。
下面是改进的例子:
shapes.ts
export class Triangle { /* ... */ }
export class Square { /* ... */ }
shapeConsumer.ts
import * as shapes from "./shapes";
let t = new shapes.Triangle();
模块的取舍
就像每个JS文件对应一个模块一样,TypeScript里模块文件与生成的JS文件也是一一对应的。 这会产生一种影响,根据你指定的目标模块系统的不同,你可能无法连接多个模块源文件。 例如当目标模块系统为 commonjs
或umd
时,无法使用outFile
选项,但是在TypeScript 1.8以上的版本能够使用outFile
当目标为amd
或system
。
14.声明文件
TypeScript 作为 JavaScript 的超集,在开发过程中不可避免要引用其他第三方的 JavaScript 的库。虽然通过直接引用可以调用库的类和方法,但是却无法使用TypeScript 诸如类型检查等特性功能。为了解决这个问题,需要将这些库里的函数和方法体去掉后只保留导出类型声明,而产生了一个描述 JavaScript 库和模块信息的声明文件。通过引用这个声明文件,就可以借用 TypeScript 的各种特性来使用库文件了。
我们需要使用 declare 关键字来定义它的类型,声明文件以.d.ts为后缀
声明文件或模块的语法格式如下:
declare module Module_Name {
}
TypeScript 引入声明文件语法格式:
/// <reference path = " runoob.d.ts" />
14.1 声明文件结构
一般来讲,你组织声明文件的方式取决于库是如何被使用的。 在JavaScript里提供了很多库的使用方法,这就需要你书写声明文件去匹配它们。
14.1.1 全局库
全局库是指能在全局命名空间下访问的(例如:不需要使用任何形式的import
)。 许多库都是简单的暴露出一个或多个全局变量。 比如,如果你使用过 jQuery,$
变量可以被够简单的引用:
$(() => { console.log('hello!'); } );
你经常会在全局库的指南文档上看到如何在HTML里用脚本标签引用库:
<script src="http://a.great.cdn.for/someLib.js" rel="external nofollow" ></script>
目前,大多数流行的全局访问型库实际上都以UMD库的形式进行书写(见后文)。 UMD库的文档很难与全局库文档两者之间难以区分。 在书写全局声明文件前,一定要确认一下库是否真的不是UMD。
从代码上识别全局库
全局库的代码通常都十分简单。 一个全局的“Hello, world”库可能是这样的:
function createGreeting(s) {
return "Hello, " + s;
}
或这样:
window.createGreeting = function(s) {
return "Hello, " + s;
}
当你查看全局库的源代码时,你通常会看到:
- 顶级的
var
语句或function
声明 - 一个或多个赋值语句到
window.someName
- 假设DOM原始值像
document
或window
是存在的
你不会看到:
- 检查是否使用或如何使用模块加载器,比如
require
或define
- CommonJS/Node.js风格的导入如
var fs = require("fs");
define(...)
调用- 文档里说明了如果
require
或导入这个库
14.1.2 模块化库
一些库只能工作在模块加载器的环境下。 比如,像 express
只能在Node.js里工作所以必须使用CommonJS的require
函数加载。
ECMAScript 2015(也就是ES2015,ECMAScript 6或ES6),CommonJS和RequireJS具有相似的导入一个模块的表示方法。 例如,对于JavaScript CommonJS (Node.js),有下面的代码
var fs = require("fs");
对于TypeScript或ES6,import
关键字也具有相同的作用:
import fs = require("fs");
你通常会在模块化库的文档里看到如下说明:
var someLib = require('someLib');
或
define(..., ['someLib'], function(someLib) {
});
与全局模块一样,你也可能会在UMD模块的文档里看到这些例子,因此要仔细查看源码和文档。
从代码上识别模块化库
模块库至少会包含下列具有代表性的条目之一:
- 无条件的调用
require
或define
- 像
import * as a from 'b';
orexport c;
这样的声明 - 赋值给
exports
或module.exports
它们极少包含:
- 对
window
或global
的赋值
14.1.3 UMD
UMD模块是指那些既可以作为模块使用(通过导入)又可以作为全局(在没有模块加载器的环境里)使用的模块。 许多流行的库,比如 Moment.js,就是这样的形式。 比如,在Node.js或RequireJS里,你可以这样写:
import moment = require("moment");
console.log(moment.format());
然而在纯净的浏览器环境里你也可以这样写:
console.log(moment.format());
识别UMD库
UMD模块会检查是否存在模块加载器环境。 这是非常形容观察到的模块,它们会像下面这样:
(function (root, factory) {
if (typeof define === "function" && define.amd) {
define(["libName"], factory);
} else if (typeof module === "object" && module.exports) {
module.exports = factory(require("libName"));
} else {
root.returnExports = factory(root.libName);
}
}(this, function (b) {
如果你在库的源码里看到了typeof define
,typeof window
,或typeof module
这样的测试,尤其是在文件的顶端,那么它几乎就是一个UMD库。
UMD库的文档里经常会包含通过require
“在Node.js里使用”例子, 和“在浏览器里使用”的例子,展示如何使用<script>
标签去加载脚本。
14.1.4 模块插件或UMD插件
一个模块插件可以改变一个模块的结构(UMD或模块)。 例如,在Moment.js里, moment-range
添加了新的range
方法到monent
对象。
对于声明文件的目标,我们会写相同的代码不论被改变的模块是一个纯粹的模块还是UMD模块。
module-plugin.d.ts模板:
// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]
// Project: [~THE PROJECT NAME~]
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>
/*~ This is the module plugin template file. You should rename it to index.d.ts
*~ and place it in a folder with the same name as the module.
*~ For example, if you were writing a file for "super-greeter", this
*~ file should be 'super-greeter/index.d.ts'
*/
/*~ On this line, import the module which this module adds to */
import * as m from 'someModule';
/*~ You can also import other modules if needed */
import * as other from 'anotherModule';
/*~ Here, declare the same module as the one you imported above */
declare module 'someModule' {
/*~ Inside, add new function, classes, or variables. You can use
*~ unexported types from the original module if needed. */
export function theNewMethod(x: m.foo): other.bar;
/*~ You can also add new properties to existing interfaces from
*~ the original module by writing interface augmentations */
export interface SomeModuleOptions {
someModuleSetting?: string;
}
/*~ New types can also be declared and will appear as if they
*~ are in the original module */
export interface MyModulePluginOptions {
size: number;
}
}
14.4.5 全局插件
一个全局插件是全局代码,它们会改变全局对象的结构。 对于 全局修改的模块,在运行时存在冲突的可能。
比如,一些库往Array.prototype
或String.prototype
里添加新的方法。
识别全局插件
全局通常很容易地从它们的文档识别出来。
你会看到像下面这样的例子:
var x = "hello, world";
// Creates new methods on built-in types
console.log(x.startsWithHello());
var y = [1, 2, 3];
// Creates new methods on built-in types
console.log(y.reverseAndSort());
14.4.6 全局修改的模块
当一个全局修改的模块被导入的时候,它们会改变全局作用域里的值。 比如,存在一些库它们添加新的成员到String.prototype
当导入它们的时候。 这种模式很危险,因为可能造成运行时的冲突, 但是我们仍然可以为它们书写声明文件。
识别全局修改的模块
全局修改的模块通常可以很容易地从它们的文档识别出来。 通常来讲,它们与全局插件相似,但是需要 require
调用来激活它们的效果。
你可能会看到像下面这样的文档:
// 'require' call that doesn't use its return value
var unused = require("magic-string-time");
/* or */
require("magic-string-time");
var x = "hello, world";
// Creates new methods on built-in types
console.log(x.startsWithHello());
var y = [1, 2, 3];
// Creates new methods on built-in types
console.log(y.reverseAndSort());
14.4.7 使用依赖
可能会有以下几种依赖
1.依赖全局库
如果你的库依赖于某个全局库,使用/// <reference types="..." />
指令:
/// <reference types="someLib" />
function getThing(): someLib.thing;
2.依赖模块
如果你的库依赖于模块,使用import
语句:
import * as moment from "moment";
function getThing(): moment;
3.依赖UMD库
1)从全局库
如果你的全局库依赖于某个UMD模块,使用/// <reference types
指令:
/// <reference types="moment" />
function getThing(): moment;
2)从一个模块或UMD库
如果你的模块或UMD库依赖于一个UMD库,使用import
语句:
import * as someLib from 'someLib';
不要使用/// <reference
指令去声明UMD库的依赖!
14.4.8 补充说明
防止命名冲突
注意,在书写全局声明文件时,允许在全局作用域里定义很多类型。 我们十分不建义这样做,当一个工程里有许多声明文件时,它会导致无法处理的命名冲突。
一个简单的规则是使用库定义的全局变量名来声明命名空间类型。 比如,库定义了一个全局的值 cats
,你可以这样写
declare namespace cats {
interface KittySettings { }
}
不要
// at top-level
interface CatsKittySettings { }
这样也保证了库在转换成UMD的时候没有任何的破坏式改变,对于声明文件用户来说。
ES6模块插件的影响
一些插件添加或修改已存在的顶层模块的导出部分。 当然这在CommonJS和其它加载器里是允许的,ES模块被当作是不可改变的因此这种模式就不可行了。 因为TypeScript是能不预知加载器类型的,所以没在编译时保证,但是开发者如果要转到ES6模块加载器上应该注意这一点。
ES6模块调用签名的影响
很多流行库,比如Express,暴露出自己作为可以调用的函数。 比如,典型的Express使用方法如下:
import exp = require("express");
var app = exp();
在ES6模块加载器里,顶层的对象(这里以exp
导入)只能具有属性; 顶层的模块对象 永远不能被调用。 十分常见的解决方法是定义一个 default
导出到一个可调用的/可构造的对象; 一会模块加载器助手工具能够自己探测到这种情况并且使用 default
导出来替换顶层对象。
14.2 声明文件举例
声明 | 描述 | 示例 |
declare var | 声明变量 | declare var foo:number; |
declare const | 声明只读变量 | declare const foo:number; |
declare let | 声明拥有块级作用域使用的变量 | declare let foo:number; |
declare function | 声明函数 | declare function greet(greeting: string): void; |
declare namespace | 描述用点表示法访问的类型或值 | // 示例 全局变量myLib包含一个makeGreeting函数, 还有一个属性 numberOfGreetings指示目前为止欢迎数量 declare namespace myLib { function makeGreeting(s: string): string; let numberOfGreetings: number; } |
declare calss | 描述一个类或像类一样的对象。 类可以有属性和方法,就和构造函数一样 | declare class Greeter { constructor(greeting: string); greeting: string; showGreeting(): void; } |