用于静态类型检验,通俗讲就是定义的各种变量和数据类型都需要一个
类型 去限定,包括但不限于使用:内置类型
Array 和自定义类型
interface XX{} ,对于未定义类型,默认为
Any
1. ts基本语法
语句使用; 分割,建议显示加上。文件后缀为.ts 声明变量:let a:string = "pp" ,使用:类型 来限定
需要特别注意的是,形如string类型和String类类型是两个不同类型!!
ts类型分为:
- any:不写类型就是这个默认
- 原始类型:number boolean string symbol void null undefined enum(8类)
- 对象类型:
{} - 联合类型:
类型1 | 类型2 ,类型为其中一种:let x: t1 | t2 = <t1 | t2>{} ,x只能访问t1和t2中公有的属性方法,可以使用类型断言来使用独有方法。 - 交叉类型:
类型1 & 类型2 ,类型为两者的并集:let x: t1 & t2 = <t1 & t2>{} - 类型参数
类型别名:type 别名 = 类型or表达式
type a = ()=>void;
type b = string | number;
特殊的:| 如果可以用于表示值为其中之一
type a = 'x' | 'y' | 'z';
function m(s:a){}
null和 undefined是所有类型的子类 any是所有类型的父类
数值
二进制:0b1010 八进制:0o744 十六进制:0xf00d Number和number两种类型不相同!!
布尔
只有true和false,且不可用1和0来代替
let a:boolean = new Boolean(1)
let a:boolean = Boolean(1)
直接调用Boolean()方法会将入参转换为布尔值
枚举
一个对js类型的补充,默认从0开始编号,没显式编号的顺接前面的编号+1
enum Days {Sun,Mon,Tue,Wed,Thu,Fri,Sat};
let today: Days = Days.Mon;
let today: Days = 1;
枚举有常量枚举,这种会在编译期删除,直接替换成数字。非常量枚举会被编译成一个对象,属性为枚举名和枚举值的双向映射
const enum D{a,b,c};
let x:D = D.a;
enum B{a,b,c};
var B;
(function (B){
B[B['a']=0]='a';
B[B['b']=1]='b';
B[B['c']=2]='c';
}(B||B={})
void null undefined
void:用于声明函数的返回值 undefined:用于表示值没有初始化 null:表示初始化为null
Never
表示不会出现值,用于无限循环函数和异常出错函数中
function error(msg:string):never{
throw new Error(msg);
}
function ilop():never{
while(true){}
}
Symbol
Symbol函数接收一个字符串作为参数,只是作为一个描述,用于区分Symbol实例。相同参数返回的值不相等。 该函数不能使用new,因为返回值是一个值,不能添加属性。
let s = Symbol('name')
const
用于声明常量类型,声明的基础类型变量不可改变,声明的引用类型变量地址不可改变
特殊的,如果想让引用类型变量内容也不可变,可以使用Object.freeze()
Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。
2 数组和元组
数组
数组中的元素类型,必须是与定义类型一致!
- 数组声明
let a:string[] = ['a','b']
let b:(string|number)[] = [1,'a']
let c:Array<string> = ['a','b']
let d:Array<string|number> = [1,'a']
let e:any[] = [1,'a',false]
- 初始化
静态初始化:let a = [1,2,3] ,默认推断类型为number 对象初始化:let a = new Array()
let a = new Array();
let b = new Array(2);
let c = new Array(1,2);
let d = new Array<number>(5);
数组未初始化时,默认为undefined
-
删除元素 使用delete a[0] 删除,只会将该索引处元素删除,数组长度不变! -
给数组添加属性
declare global{
interface Array<T>{
run():void;
}
}
Array.prototype.run = ()=>console.log('run');
- 判断数组
let a = [1,2]
Array.isArray(a)
a instanceof Array
Object.prototype.toString.call(a)
数组方法
- concat:
数组1.concat(数组2) 返回连接结果,原数组不变 - every:检测每个元素是否都满足条件,全满足返回true,否则返回false
- filter:按条件过滤元素,返回过滤后数组,原数组不变
- forEach:遍历元素
- indexOf:从左到右匹配元素,返回第一个索引
- lastIndexOf:从右向左
- join:使用入参字符将数组连接成字符串
- map:遍历元素并返回等长数组,不可break
- pop:删除最后一个元素,长度-1
- push:增加一个新元素,长度+1
- reduce:累加器,
[1,2,3].reduce((x,y)=>x+y,1) 接收一个回调函数,还有一个初值。回调函数接收两个值:第一个为初值或上一次累加返回的值,第二个为当前遍历的值。 - reduceRight
- reverse:反转数组,改变原数组
- sort:排序,入参为排序器函数,该函数返回值>0则会交换位置,改变原数组
- shift:删除并返回第一个元素,长度-1
- unshift:从头增加n元素,长度+n
- slice:切片,返回新数组,
slice(start,end) ,start和end可为正负,截取方向取决于第二个参数,第二个参数为正则往右截取,原数组不变 - some:检测数组中是否有满足条件的元素,有就返回true
- find:检测数组是否存在满足条件元素,有就立刻返回该值并结束
- splice:增删数组,
splice(index,del_num[,a,b,c..n]) ,从何处起,删除多少个,新增元素序列,改变原数组 - toString:用逗号连接数组输出字符串
数组解构
let [a,b,c] = [1,2,3];
let [a,[b],c] = [1,[2],3];
let [a,b] = [1];
let [a,b=2] = [1];
let x=1,y=2;
[x,y]=[y,x];
数组遍历
for() 循环遍历for(let i in a) ,遍历对象中的可枚举属性for(let v of a) ,使用对象的迭代器遍历forEach ,无返回值,不可breakmap ,返回等长数组,不可break
多维数组
- 定义
let a:string[][];
let b:Array<Array<string>>;
- 初始化
let a:string[][] = [];
let b:string[][] = new Array<Array<string>>();
let a:string[][] = [['1'],['2']]
let b:string[][] = new Array<Array<string>>(['1'],['2']);
元组
特点:数据类型不要求统一,相当于固定了每个位置的类型 定义时[] 不可省略,且初始化个数和声明中类型个数一致,如果访问越界,编译报错
let a:[string,number,number] = ['1',1,1]
元组特点
元组定义后无法通过索引来新增元素,只能调用方法,且插入的元素类型需要和定义时兼容,且不能越界解构,其余和数组一样。
3 迭代器和生成器
迭代器
是一种特殊对象,包括一个next()方法,调用该方法可以返回一个对象,包括value和done两个属性。value表示当前迭代的值,done表示当前是否迭代完成。当一个对象包含Symbol.iterator属性,则表明该对象可迭代(该属性调用后要求返回一个迭代器对象)。
{
[Symbol.iterator]:function(){
return{
next(){
return {value:1,done:true}
}
}
}
}
function getIt(arr:number[]){
...
return {
next:function(){
...
return {done:,value:}
}
}
}
let it = getIt([1,2,3])
it.next();
生成器
生成器通过在函数名上加* 来实现,使用yield 来返回,并且下次从返回处恢复继续执行。调用生成器会返回一个迭代器。
function* a () {}
function *a () {}
let a = function* () {}
function *genIt(){
yield 1;
yield 2;
yield 3;
}
let it = genIt();
it.next()
it.next()
it.next()
it.next()
注意:不可以使用箭头函数创建生成器
让对象可迭代:
let obj = {
*[Symbol.iterator](){
...
yield ...;
...
}
}
4 函数
先看函数的一个典型定义:
function func(a:string,b:string|number,c:boolean=true,d?:number):string{return '1'}
let func = (a:string,b:string|number,c:boolean=true,d?:number):void=>{}
函数名称:如果使用function定义,函数名就是定义时的,如果使用表达式定义,则函数名就是变量名,即使function后有函数名(该函数名只能内部使用)。 函数名一经定义不能被重新赋值 返回类型可以省略
function a(){}
let b = function a(){}
b.name = 'c'
变量提升
使用var声明的变量会被提升到代码顶部,使用function定义的函数也会提升,但是函数表达式不会提升,即只作为变量被提升,编译器不知道其为函数,所以如果在定义前调用会报错。
箭头函数和普通函数的区别
普通函数:function a(){} 包含length,name,caller,arguments四个属性还有一个prototoye 属性,this动态绑定到调用者(如果函数独立调用,在严格模式下this为undefined,非严格模式下为window) 箭头函数:let a = ()=>{} 只包含lenght,name属性,this绑定为定义环境中的this
箭头函数和普通函数的__proto__ 都指向Function的原型
构造函数
构造函数常用来创建一个类对象,创建的该对象的构造函数===定义的构造函数,使用new来创建:new Func() 普通函数也可以被new,如果有返回值则被忽略。
new的过程即调用该构造函数的过程,如果其中包括this等,会在创建的类中进行属性赋值初始化等
函数参数化
即将函数作为参数进行传递,也很简单,看示例就懂:
function a(x:string,y:(msg:string)=>number){}
函数重载
js不支持函数重载,ts中通过不同声明来控制函数重载,实际调用的为同一个函数,只是内部做了判断
function a(x:number);
function a(x:number,y:number);
function a(x:number,y?:number){}
a(1)
a(1,2)
5 对象
生成一个对象,并给对象添加属性,这个是js中常见做法,如:
let obj = {}
obj.name = "pp"
let obj = {}
interface obj{
[key:string]:any;
}
obj.name = "pp"
duck-typing鸭子类型。即,如果某部分表现形式或结果相同,我们把他们看作同一种类型,相当于按某种特征归类
let type_a = []
function getA(x){
if(x.xxx ===xxx){type_a.push(x)}
}
类
和js相同,只不过在定义类属性时需要加上类型
class xxx {
a:string;
b:number;
constructor(a:string){this.a = a}
funca():string{return this.a}
funcb():void{console.log(this.b)}
}
let XXX = new xxx(1);
注意:构造函数只有一个!
继承
class xxx extends yyy{}
注意:在构造中访问this前一定要先调用super 子类继承父类后可以定义子类独有的方法,也可以重写父类的方法,也可以定义和父类相同的属性,不过属性类型必须相同或为any
多态
由于ts不能直接实现多态,所以,对于父类引用如果想指向子类对象,必须使用断言:as 或<>
let parent = child as Parent;
let parent = <Parent>child;
断言后的对象只能调用该类型包含的方法和属性,无法使用子类方法和属性
和Java不同,对于Java来说,父类引用指向子类对象时,实际调用的规则为:父类的成员变量和静态方法,子类的重写方法。口诀为:成员变量,静态方法看左边;非静态方法:编译看左边,运行看右边。
重载overload和重写override 重载:函数名相同,参数不同,与返回值无关(发生在同一个类中) 重写:函数名、参数和返回值都相同(发生在继承类中)
static
使用static定义的类属性为静态属性,无需实例化,直接使用类名调用。
访问修饰符
ts有三种访问修饰符:
- public:默认
- protected:只能自己和被子类访问,在类中访问,不能通过示例对象访问
- private:只能自己访问
abstract
抽象类可以包含签名也可以包含实现体,可以使用访问修饰符控制,不可以使用private,而且不可以缩小访问权限 抽象方法不能有实现体
abstract class A{
protected abstract x: number;
public abstract run():void;
a:string='1';
}
6 装饰器
类装饰器
在类前定义,在执行到该类时就调用注解对应函数,接收构造函数作为参数,返回值如果是函数会替换原来的构造函数,否则不替换。 不能在声明文件中使用,不能在外部上下文使用(declare的类)
@log
class A{
constructor(){
console.log("3")
}
}
function log(constructor:Function):any{
console.log(1)
console.log(constructor.name);
return function(){console.log("log-log");
}
}
console.log(2)
new A();
方法装饰器
用于监听和修改方法,定义在类中方法上,接收
- 对于静态成员为构造函数,对于实例成员为类原型对象
- 成员名称
- 成员属性描述符,具备一些属性,可以用于控制该属性的访问和其他
在定义类时就会调用该注解返回的函数
class A{
@m(100)
greet(){console.log("4")}
}
function m(n:number):any{
return function(target:any,propertyKey:string,descriptor:PropertyDescriptor){
console.log("1")
console.log(target,propertyKey,descriptor,n)
}
}
console.log("2")
let a = new A();
console.log("3");
a.greet();
属性装饰器
声明在属性上,在解析到改行代码时就会调用装饰器函数,接收两个参数
function n(target:any,key:string){
console.log(target,key)
}
@log
class A{
@n
age:number=1;
}
let a = new A();
console.log(a.age);
装饰器调用顺序
属性和方法装饰器按照声明顺序调用,最后调用类装饰器。都在解析类的时候就被调用,无需实例化!!
7 接口
接口作用是给出类型约束:interface xxx{} ,接口不能加初始值和实现体,不能加访问修饰符
interface IPerson{
name:string;
age:number;
}
function a(person:IPerson){person.age++}
class Person implements IPerson{
name='pp';
age=1;
}
interface SuperPerson extends Person{
fly:()=>void;
}
对于类来说,只能单继承类,但是可以多实现接口 对于接口来说,可以多继承接口,但是注意:如果多继承中包含同名属性,类型不同,则无法同时继承
接口继承类时,对于private和protected访问修饰符的属性也会继承。则该接口只能被该类的子类所实现!
接口中有一个特殊的关键字:readonly,限制一个属性只在构造中可修改,其余均只读
interface IPerson{
name:string;
readonly age:number;
}
class Person implements IPerson{
name='pp';
readonly age=0;
constructor(age:number){
this.age = age
}
}
ts具有ReadonlyArray<T> 类型,与Array<T> 相似,只不过数组创建后不能被修改 只读属性不能使用const只能使用readonly,限定变量使用const,限定属性使用readonly
8 namespace
命名空间类似于java的包,现在ts对于内部模块称作命名空间,外部模块称作模块。
namespace xxx.xxx{
export class A{}
export function B(){}
export namespace xxx{
export class C{}
}
}
new xxx.xxx.xxx.C();
如果需要在命名空间外部调用内部的属性,则需要export导出才行
命名空间引入:一个namespace分布在多个文件中,可以使用///<reference path="xxx.ts"/> 来引入
tsc --outFile n_all.js .\xxx.ts 可以将xxx.ts命名空间中引入的所有命名空间合并输出
命名空间别名:import x_x_x = xxx.xxx.xxx; 然后可以直接调用:new x_x_x()
外部模块
如果一个文件不带顶级import或者export声明,则该文件内容被视为全局可见 模块加载器包含多种规范
AMD
典型的浏览器实现的AMD规范的库:requireJS
- 定义模块
采用define 函数来定义模块,接收一个或两个参数:
define(function(){return{}})
define(['a'],function(a){return{}})
其中:a和b表示当前模块以来的其余两个模块,依赖项和入参是一一对应的
- 使用
使用require.config() 来接收一个配置项,配置各种库位置(如果模块名和文件名相同,则可以不配置,会自动寻找),然后使用require() 函数来接收依赖项和回调函数 main.js文件内容:
require.config({
path:{
jq:"http://xxx/jquery-1.11.1.min"
}
});
require(['jq','a','b'],function($,a,b){
})
注意:只有依赖项全部加载完毕才会执行脚本
需要通过引入require模块并提供data-main参数来引入入口js文件!! index.html文件内容:
<script type="text/javascript" src="require.js" data-main="main.js"></script>
CMD
国内发展而来,典型浏览器实现库:SeaJS,功能类似requireJS,定义和加载稍有不同 CMD认为一个js就是一个模块:define(id?,d?,factory) ,其中: id为模块id,省略默认为文件名, d为依赖,省略默认就近查找, factory为函数:function(require,exports,module){}
- 定义模块
define(function(require,exports,module){
module.exports = {name:'pp'}
}
define(function(require,exports,module){
var a = require('a');
module.exports = {age:1}
}
define(function(require,exports,module){
var b = require('b');
$("div").html(123+b.age);
}
index.html
<script type="text/javascript" src="http://xxx/jquery-1.11.1.min"></script>
<script type="text/javascript" src="sea.js"></script>
<script>seajs.use(['main.js'],function(main){};)</script>
总结就是:define定义模块,require导入模块,在首页需要自己导入远程依赖,引入sea库,调用seajs.use(依赖数组, 回调函数) 注意:exports就是module.exports的一个拷贝,不能用于给模块赋值
CommonJS
每个文件就是一个模块,加载模块相当于加载module.exports属性,使用require同步加载模块,通过module.exports来导出模块 nodejs按照该规范实现的,由于是同步加载模块所以不适合浏览器端!(可以借助工具转换即可)
var obj = {name:'pp'}
module.exports = obj;
var a = require('./A.js')
直接node运行即可
UMD
整合CommonJS和AMD,用一个工厂函数来统一不同模块定义规范。判断支持commonjs,否则再判断支持amd,否则就暴露到全局。
SystemJS
通用模块加载器,支持nodejs、浏览器、commonjs、amd、全局模块对象和es6模块。语法类似amd
ES6
使用import和export导入导出,比较简单就不赘述了
import a,{aa} from './a';
...
export {};
export default xxx;
可以使用babel-node 来对es6进行转码到commonjs
9 模块解析策略
在导入模块时,目录有相对路径:"./a" 或非相对路径"axios" ts有两种解析策略:Node和Classic,使用--moduleResolution 来指定,未指定则在使用--module AMD|System|ES2015 时为Classic,其他为Node
Classic
import a from './a';
/root/src/a.ts
/root/src/a.d.ts
import a from 'a';
/root/src/a.ts
/root/src/a.d.ts
/root/a.ts
/root/a.d.ts
/a.ts
/a.d.ts
Node
import x from 'x';
查找规则:
- x为内置模块,直接返回该模块
- x以
/ 、./ 或../ 开头,将x当作文件依次查找:x.js x.json x.node,将x当作目录依次查找:x/package.json中的main字段 x/index.js x/index.json x/indexnode - x不带路径,查找当前路径下的node_module中,然后依次向上遍历每层的node_module
- not found
10 声明合并
在ts编译时会将同名的两个单独声明合并为一个声明,支持:namespace、interface、class
- 接口合并
接口合并,后面同名接口的属性会出现在合并后的靠前位置 - 命名空间合并
由于涉及导出和非导出的属性,对于非导出成员仅在原命名空间可见 - 命名空间和类合并
必须将类定义放在命名空间之前!合并后既是命名空间也是类 - 命名空间和函数合并
同上面相似,合并后可调用
11 泛型
类型断言
<类型>x 或者x as 类型 :表明x的类型为这种,断言后可调用该类型的方法
function a(x:string|number){
if((<string>a).length){}
}
泛型定义
<T1,T2,T3> 中个数不限,符号可以多个,表示有多种类型 示例:
function func<T>(a:string,b:T):void{}
class C<T>{}
interface i<T> {}
对于泛型变量,如果要使用其属性或方法,需要进行断言。
<T,K extends keyof T> 其中K表示,T中公共属性名的集合中的一个元素,即K是T的一个属性
TS扩展语法
A 扩展全局对象
declare global {
interface Number{
run():void;
}
}
Number.prototype.run = ()=>console.log("run");
let a = 1;
a.run();
B 扩展外部模块
import {Person} from './Person';
let person = new Person("pp",1);
declare module './Person'{
interface Person{
run():void;
}
}
Person.prototype.run = ()=>console.log("run");
person.run()
C 扩展当前函数或类
不能对箭头函数进行扩展,因为没有prototype
function C(name:string){
this.name = name;
}
let c = new C("pp");
console.log(c.name)
C.prototype.run = ()=>console.log("run");
c.run();
function b(){}
b.prototype.age=1
new b().age
class D{}
let d = new D();
interface D{
run():void;
}
D.prototype.run = ()=>console.log("D");
d.run();
D 特别注意
ts中无法给空对象动态添加属性和方法,如果非要使用,可以:
let a = {}
a.name = "pp"
interface E{
[key:string]:any;
}
let obj:E = {}
obj.run = ()=>console.log("run")
原型
定义的函数和类等对象都包含一个prototype (只有定义出的对象拥有)和__proto__ (定义和实例对象拥有),称为当前对象的两个原型属性。 一般而言,对象中查找属性,是沿着__proto__ 一层一层往上查
prototype:是一个对象,包含
constructor:指向定义对象本身
__proto__指向Object.prototype==={}.__proto__,再往下遍历就指向null
__proto__:是一个对象指向当前定义对象所属实例的原型
对于一个对象的__proto__ 如果是函数就指向Function原型,如果是类指向父类原型,如果没有父类指向Function原型,Function原型的原型指向Object原型,Object原型指向null。
|