慕课网@JoJozhai 老师 TypeScript入门课程分享
ES5,ES6,JS,TypeScript间的关系
ES5、ES6是两个版本的语言规则,他们规定了脚本语言如何去写,浏览器对应的去解析而JS脚本语言应用的就是ES5规则,TypeScript(以下简称ts)应用的是ES6规则,且由微软公司开发
在我个人理解,TypeScript是用更丰富的语法简便或优化了JavaScript里一些比较复杂、非人性化的代码编写过程,就如less、sass之于css
值得一提的是:新版本的angular2是由谷歌团队开发,基础代码应用TypeScript编辑的前端框架,因此可认为是由两大科技巨头主导的技术
TypeScript环境搭建
-
为何要搭建环境
目前的主流浏览器对TypeScript的支持还不够完善,因此需要编译工具将TypeScript代码转译成主流的JS代码才能得以使用
-
本地安装TypeScript环境compiler
在NodeJs条件下命令行输入以下代码全局安装TypeScript环境
npm install -g typescript
安装完成后输入
tsc -v
查看TypeScriptCompiler版本
-
tsc使用
进入到TypeScript文件所在的目录,在此目录的命令行中运行tsc命令将.ts文件转译为.js文件
tsc index.ts
(转译index.ts文件)
会在相同目录下生成一个index.js文件
-
IDE自动编译
上面的方法在实际开发过程中显得十分的笨拙,而主流的IDE都为ts文件准备了一套自动tsc的方法,例如在webstorm中,开启设置项languages中的TypeScript勾选Enable TypeScript compiler选项,即可自动在ts文件下生成对应的js文件
TypeScript语法
-
字符串
- 声明多行字符串
ts中允许声明字符串中换行,如下
var str = '123
456
789';
转译后在js文件中代码会自动添加换行符,如下
var str = '123\n456\n789';
- 字符串模板
ts中可以用`符号(大键盘1左边)对字符串模板进行引用,如下
var myName = 'programmer';
var getName = function () {return 'programmer'};
console.log(`I am ${myName}`); //此处小括号中不是引号
console.log(`I am ${getName()}`);
转译js代码如下
var myName = 'programmer';
var getName = function () {
return 'programmer';
};
console.log("I am " + myName);
console.log("I am " + getName());
- 自动拆分字符串
当将字符串模板应用到某一函数中时,将根据某种规则将模板进行拆分,拆分成函数相应个数的参数并调用,例如
function test(template,name,age) {
console.log(template);
console.log(name);
console.log(age)
}
var myname = 'programmer';
var getAge = function () {
return 18;
}
test`hello my name is ${myname}, I am ${getAge() }`
在其转译并运行后,我们会发现模板被拆分为三个参数打印出来
对应temlpate参数的是一个数组
Array[3]
0:"hello my name is "
1:", I am "
2:""
对应name参数的是"programmer",对应age参数的是 18
也就相当于模板被两段${}分割的每个部分构成了第一个参数,而两个${}是另外两个参数
-
参数、变量新特性
-
变量类型声明
ts中声明变量时可用冒号:
对其类型进行声明,或ts文件会对其自动声明。声明过类型的变量用其他类型的值进行赋值时会引发ts报错(注意:在被转译为js后并不会引发任何报错),如下图(波浪线即为报错),而:any
则代表声明为任意类型,可像js中随意赋值
-
函数参数类型定义
同样对函数参数的声明此方法也成立,另外可以在函数声明时对其是否返回值进行声明,:void
表示无返回值
声明类属性类型
在声明类和对已声明类进行调用时也可使用此规则,如
class Programmer {
skill: string;
age: number;
}
var me: Programmer = new Programmer();
me.age = 18;
me.skill = 'ts';
- 声明函数参数默认值
在声明函数时,可以用=
对参数声明默认值,这样在调用时若缺失此参数也不会报错,而是会按默认值进行调用(注意:带有默认值的参数一定要放在后面声明,否则将报错)
function test(a:string,b:string,c:string = 'ccc'){
console.log(a);
console.log(b);
console.log(c);
}
test('aaa','bbb');
//打印aaa,bbb,ccc
- 声明可选参数
在声明函数时,可以在参数后用?
表示此参数可选(注意:可选参数一定要放在必选参数之后,否则将报错)
function test(a:string,b?:string,c:string = 'ccc'){
console.log(a);
console.log(b);
console.log(c);
}
test('aaa');
//打印aaa,undefined,ccc
-
函数新特性
- rest and spread操作符
ts可以在声明函数时用...args
声明不定数量个参数
function test(...args) {
args.forEach(function (arg) {
console.log(arg)
})
};
test(1, 2, 3, 4);
//打印1,2,3,4
转译为js后如下
function test() {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
args.forEach(function (arg) {
console.log(arg);
});
}
;
test(1, 2, 3, 4);
也可以对声明了固定数量参数的函数进行参数数量不等的调用
function test(a,b,c) {
console.log(a);
console.log(b);
console.log(c);
};
var arr = [1, 2];
test(...arr);//打印1,2,undefined
var arr2 = [1, 2, 3, 4, 5, 6];
test(...arr2);//只识别前三个参数,打印1,2,3
- generator函数
通过function*
声明一个generator函数在函数中添加yield
和next()
可对其进行打断和分布执行操作,如下
function* test(){
console.log("start");
yield;
console.log("finish");
}
var test1 = test();
//须将函数声明为变量使用此方法
test1.next();
test1.next();
每个.next()
执行yield
分割的一段代码,第一个test1.next();
执行至yield
之前停止打印出"start",第二个test1.next()
执行之后的代码,打印出"finish"
- 析构表达式
- 针对对象
在ts中若想一对一地将含有多个属性的对象的函数返回值赋予多个变量时,可使用析构表达式,代码如下
- 针对对象
function programmer(){
return {
name:"Tom",
skill:{
skill1:"TypeScript",
skill2:"AngularJs"
},
age:18
}
}
var {name,age} = programmer();
//此简写方法须满足变量名与函数中的属性名对应相同
console.log(name,age);
//打印出Tom 18
var {name:name1,skill:{skill1:skills}} = programmer();
//此为变量名与函数中属性名不相同的写法
console.log(name1,skills);
//打印出Tom TypeScript
转译后的js代码如下
"use strict";
function programmer() {
return {
name: "Tom",
skill: {
skill1: "TypeScript",
skill2: "AngularJs"
},
age: 18
};
}
var _programmer = programmer();
var name = _programmer.name;
var age = _programmer.age;
console.log(name, age);
var _programmer2 = programmer();
var name1 = _programmer2.name;
var skills = _programmer2.skill.skill1;
console.log(name1, skills);
另外还可以在其中使用之前提过的Rest and Spread操作符拆分对象
var obj = {a:1,b:2,c:3,d:4};
var {a,b,...others}=obj;
//打印出1 2 {"c":3,"d":4} *将后两个属性打包进others中
转译后js代码
"use strict";
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
var obj = { a: 1, b: 2, c: 3, d: 4 };
var a = obj.a;
var b = obj.b;
var others = _objectWithoutProperties(obj, ["a", "b"]);
console.log(a, b, others);
- 针对数组
在数组中取值上面的方法一样成立
var arr = [1,2,3,4];
var [num1,num2]=arr;
//注意此处是中括号
console.log(num1,num2);
//打印1 2
var [,,num3,num4]=arr;
console.log(num3,num4);
//打印3 4 用逗号空出对应位置即可跳跃取值
var [num5,,num6]=arr;
console.log(num5,num6);
//打印1 3
var [num7,...others]=arr;
console.log(num7,others);
//打印1 [2,3,4] *此时将剩余的三个数打包放进others中
转译后的js代码如下
"use strict";
var arr = [1, 2, 3, 4];
var num1 = arr[0];
var num2 = arr[1];
console.log(num1, num2);
var num3 = arr[2];
var num4 = arr[3];
console.log(num3, num4);
var num5 = arr[0];
var num6 = arr[2];
console.log(num5, num6);
var num7 = arr[0];
var others = arr.slice(1);
console.log(num7, others);
-
函数表达式与循环
- 箭头表达式
在ts中我们可以运用箭头表达式=>
来声明一个匿名函数
var test = arg1=>arg1;
//单个参数可以不加小括号
var sum = (arg1,arg2) =>arg1+arg2;
//单行声明可以不加大括号
var sum2 = (arg1,arg2)=>{
return arg1+arg2
}
//多行写法
转译后js代码
"use strict";
var test = function test(arg1) {
return arg1;
};
var sum = function sum(arg1, arg2) {
return arg1 + arg2;
};
var sum2 = function sum2(arg1, arg2) {
return arg1 + arg2;
};
这样看其实箭头表达式只是比原来的声明方法简便些而已,其实不然。箭头表达式真正强大的是可以很轻松的化解之前一直困扰我们得this的作用域问题
下面一段代码在js中是打印不出东西的
function Person(x){
this.name=x;
setInterval(function(){
console.log(this.name)
},1000)
}
var man = new Person('zhangsan');
//setInterval中的this指向的是全局对象window而不是我们所创建的对象
而应用箭头表达式就可以轻松解决作用域的问题
function Person(x){
this.name=x;
setInterval(()=>{
console.log(this.name)
},1000)
}
var man = new Person('zhangsan');
//每一秒打印出一个zhangsan
转译后js代码中可以看出实际上是ts帮我们写了纠正this这段代码
function Person(x) {
var _this = this;
this.name = x;
setInterval(function () {
console.log(_this.name);
}, 1000);
}
var man = new Person('zhangsan');
- 新循环for of(与forEach、for in对比)
var arr = [1, 2, 3, 4];
arr.attr = 'number 5';
//这里在ts会报错 但其实可以运行
arr.forEach((value,key) => console.log(value,key));
//打印出1,0|2,1|3,2|4,3
for (var i in arr) {
console.log(i);
};
//打印出1,2,3,4,attr
for (var n of arr) {
console.log(n);
}
//打印出1,2,3,4
for (var n of arr) {
if(n>2)break
console.log(n);
}
//打印出1,2
for of与另两者最大的区别就在于for of会忽视掉循环中的非数字元素(for in不会),且可以打断(forEach不可打断)
-
面向对象特性
- 类的声明
在ts中有一种专门的用class
引导的类声明,例如下面的Programmer类声明
class Programmer{
name;
skill;
work(){
console.log("coding....")
}
};
var pro1 = new Programmer();
pro1.name = 'zhang san';
pro1.skill = 'TS';
pro1.work();
//转译后js代码
var Programmer = (function () {
function Programmer() {
}
Programmer.prototype.work = function () {
console.log("coding....");
};
;
return Programmer;
}());
var pro1 = new Programmer();
pro1.name = 'zhang san';
pro1.skill = 'TS';
pro1.work();
以上的代码中,声明了一个Programmer类,其中含有name,skill两个属性和work方法,而其调用方式与js中一样。我们可以看到转译后,work()实际上是定义在原型上的方法。
* 类的访问权限
在类的声明同时,可以用public
private
protected
对其中属性和方法的访问权限进行声明
图中分别用三个访问操作符声明了3个属性和一个方法,声明private和protected的属性和方法在外部,也就是class代码的外面调用是会报错的,而在内部work()方法可以任意调用属性。
总结来说
public
操作符声明的可以在任意地方使用,也是不声明时的默认操作符;
private
操作符会使其只可在class内部被调用;而
protected
操作符则在class内部和该类的后代继承元素上可以使用
public | protected | private | |
---|---|---|---|
外部 | √ | × | × |
后代 | √ | √ | × |
内部 | √ | √ | √ |
需要注意的是:这段代码只是在书写阶段会引发报错,而使用时不会有任何问题,因为转译成js后并不存在这些功能
-
- 类构造器
声明类时可用constructor
关键字在类内部声明一个函数,成为构造器函数,此函数只可在函数内部应用,当我们对类实例化的时候该函数就会运行一次
- 类构造器
class Programmer {
constructor() {
console.log('coding');
}
//构造器函数
};
var pro1 = new Programmer();
//打印一次coding
构造器函数的一个重要用途就是规定一个类里的某些属性必须在实例化时被传入值,如下
class Programmer {
name;
constructor(name:string) {
console.log('coding by '+name);
}
};
//也可以像下面这么写
class Programmer {
constructor(public name:string) {
console.log('coding by '+name);
}
};
var pro1 = new Programmer();
//这行代码会引发编辑器报错
var pro2 = new Programmer('zhangsan');
//打印 coding by zhangsan
- 类的继承
ts中也有继承类的关键词extends
,同js中一样,子类通过extends
会继承父类的所有方法和属性,并可定义只属于自身的方法和属性
class Programmer{
name;
skill;
work(){
console.log("coding with "+this.skill)
}
};
class WebProgrammer extends Programmer{
no;
learn() {
console.log(this.no+' is learning TS')
}
}
var Wp1 = new WebProgrammer();
Wp1.skill = 'js';
Wp1.no = 1;
Wp1.work();
//打印 coding with js
Wp1.learn();
//打印 1 is learning Ts
- 构造函数继承
ts语法规定在子类里声明构造函数时,必须要通过super
关键词在其构造函数内部调用父类的构造函数
class Programmer{
name;
skill;
constructor(name) {};
work(){
console.log("coding with "+this.skill)
}
};
class WebProgrammer extends Programmer{
no;
constructor(name:string,no:number) {
super(name);
this.no = no;
//在这里用super继承了父类构造函数的name属性,将name和no作为子类的两个实例化时必须赋值的属性
}
learn() {
console.log(this.no+' is learning TS')
}
}
var Wp1 = new WebProgrammer('zhangsan',1);
Wp1.skill = 'js';
Wp1.work();
Wp1.learn();
上面介绍的是用super
继承父类的属性,同样父类的方法也可以继承,下面是一个实例
class Programmer{
name;
skill;
constructor(name) { };
work(){
console.log("coding with "+this.skill)
}
};
class WebProgrammer extends Programmer{
no;
constructor(name:string,no:number) {
super(name);
this.no = no;
}
learn() {
super.work();
//继承父类的work方法
this.learnAfterWork;
};
private learnAfterWork() {
//将该方法声明为私有
console.log('learning sth')
};
}
var Wp1 = new WebProgrammer('zhangsan',1);
Wp1.skill = 'js';
Wp1.work();
//打印coding with js
Wp1.learn();
//打印coding with js,learning sth
Wp1.learnAfterWork();
//无法打印 因为方法learnAfterWork是私有的,外部访问到
- 泛型
在ts中,可以用尖括号<>
对集合中元素的类型加以限制,概括的云山雾绕,一看代码便知
class Programmer{
name;
skill;
work(){
console.log("coding with "+this.skill)
}
};
class WebProgrammer extends Programmer{
no;
learn() {
console.log(this.no+' is learning TS')
}
};
var Wp: Array = [];
//声明Wp数组里只能放入Programmer类的元素
Wp[0] = new Programmer();
//成功
Wp[1] = new WebProgrammer();
//成功 WebProgrammer也属于Programmer
Wp[2] = 1;
//在ts中会报错
- 接口
ts中独有的接口interface
用来形成一种约束,是的开发者在创建应用此接口的类或方法时必须要遵守接口中的代码规则,其有两种典型的使用方式,并且语法与声明类十分相似- 1:对类的属性进行约束
interface IProgrammer{
name: string;
age: number;
};
class Programmer{
constructor(public config: IProgrammer) {}
//构造函数中限制属性要应用IProgrammer接口约束
};
var p1 = new Programmer();
//报错
var p2 = new Programmer('zhangsan',18);
//报错
var p3 = new Programmer({
name: 'zhangsan',
age:18
});
//正确调用方法:传入一个带有规定属性的对象
- 2:对方法进行约束
对方法进行约束需要用到implements
关键词,它规定被约束的方法内必须实现接口中的函数
interface IProgrammer{
useTool();
};
class JsProgrammer implements IProgrammer{
useTool(){
console.log('use JS')
}
};
class TsProgrammer implements IProgrammer{
useTool(){
console.log('use TS')
}
}
例子中若在声明的方法中没有实现useTool方法则会引发报错
- 模块
在ts中,每个ts文件就相当于一个模块而在文件内部用export
,import
两个关键字进行导出、导入模块。只有在其他模块已经导出的元素才可以在其他模块中导入
例:在同一个目录下建立两个ts文件——export.ts和import.ts
export.ts代码
export class klass1 { };
class klass2 { };
export var x1;
var x2;
export function func1() { };
function func(){};
//没有加export的就是没有输出
import.ts代码
import {klass1,x1,func1} from "export.ts";
x1=1;
var k1=new klass1;
func1();
//这里是取不到export.ts中没有导出的klass2,x2,func2的
- 注解
ts注解是提供给指定的工具和框架使用的,为程序元素(类,方法,变量)加上更直观的的说明,而与程序业务逻辑无关
例如angular2里的@component
(待研究.....)
- 类型定义文件
"那么在ts文件怎么应用js中的那些库和框架呢?"
这时候就要用到类型定义文件xxx.d.ts
,如将jquery的类型定义文件index.d.ts放到ts同目录下,这时jquery的接口就已经全部导出了,可以直接在ts文件中调用
而寻找各种各样工具的类型定义文件就要用到