我的typescript学习心得


用于静态类型检验,通俗讲就是定义的各种变量和数据类型都需要一个 类型去限定,包括但不限于使用:内置类型 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){} // 表明,s只能接受xyz中的任一个字符

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;// 编译后结果只有let x = 0;

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 数组和元组

数组

数组中的元素类型,必须是与定义类型一致!

  1. 数组声明
// 简单声明
let a:string[] = ['a','b']
let b:(string|number)[] = [1,'a']
// 复杂声明
let c:Array<string> = ['a','b']
let d:Array<string|number> = [1,'a']

// 特殊的any,不限制类型
let e:any[] = [1,'a',false]
  1. 初始化
    静态初始化:let a = [1,2,3],默认推断类型为number
    对象初始化:let a = new Array()
let a = new Array(); // 创建一个any类型空数组
let b = new Array(2); // 创建一个长度为2的any类型空数组
let c = new Array(1,2); // 创建一个[1,2]类型为number的数组
let d = new Array<number>(5); // 创建一个长度为5的number类型空数组

数组未初始化时,默认为undefined

  1. 删除元素
    使用delete a[0]删除,只会将该索引处元素删除,数组长度不变!

  2. 给数组添加属性

declare global{
  interface Array<T>{
    run():void;
  }
}
Array.prototype.run = ()=>console.log('run');
  1. 判断数组
let a = [1,2]
Array.isArray(a) // true 该方法是ES5的方法
a instanceof Array // true
Object.prototype.toString.call(a) // [object Array]

数组方法

  • 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]; // b为undefined
let [a,b=2] = [1];

let x=1,y=2;
[x,y]=[y,x]; // 解构交换

数组遍历

  1. for()循环遍历
  2. for(let i in a),遍历对象中的可枚举属性
  3. for(let v of a),使用对象的迭代器遍历
  4. forEach,无返回值,不可break
  5. map,返回等长数组,不可break

多维数组

  1. 定义
let a:string[][];
let b:Array<Array<string>>;
  1. 初始化
// 空值初始化
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(); // 每次调用返回一个值,且下次调用返回下一个值,直到内部done为true结束

生成器

生成器通过在函数名上加*来实现,使用yield来返回,并且下次从返回处恢复继续执行。调用生成器会返回一个迭代器。

// 定义
function* a () {}
function *a () {}
let a = function* () {}

// 示例
function *genIt(){
  yield 1;
  yield 2;
  yield 3;
}

let it = genIt(); // 获得一个迭代器
it.next() // {value:1,done:false}
it.next() // {value:2,done:false}
it.next() // {value:3,done:false}
it.next() // {value:undefined,done:true}

注意:不可以使用箭头函数创建生成器

让对象可迭代:

let obj = {
  *[Symbol.iterator](){
    ...
    yield ...;
    ...
  }
}

4 函数

先看函数的一个典型定义:

function func(a:string,b:string|number,c:boolean=true,d?:number):string{return '1'} // ?:表示可选参数,可传可不传,必须放在最后,类似的有剩余参数:...a
let func = (a:string,b:string|number,c:boolean=true,d?:number):void=>{}

函数名称:如果使用function定义,函数名就是定义时的,如果使用表达式定义,则函数名就是变量名,即使function后有函数名(该函数名只能内部使用)。
函数名一经定义不能被重新赋值
返回类型可以省略

function a(){} // 函数名为a
let b = function a(){} // 函数名为b
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中常见做法,如:

// js
let obj = {}
obj.name = "pp"
// ts中不支持直接添加属性,因为生成的obj模板是Object,不存在name属性
// ts中需要动态添加属性,则必须定义一个同名接口类型(在查询时先查接口?)
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();

// 1
// A
// 2
// log-log

方法装饰器

用于监听和修改方法,定义在类中方法上,接收

  • 对于静态成员为构造函数,对于实例成员为类原型对象
  • 成员名称
  • 成员属性描述符,具备一些属性,可以用于控制该属性的访问和其他

定义类时就会调用该注解返回的函数

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();

// 1
// {constructor: ƒ, greet: ƒ} greet {value: ƒ, writable: true, enumerable: false, configurable: true} 100
// 2 
// 3 
// 4

属性装饰器

声明在属性上,在解析到改行代码时就会调用装饰器函数,接收两个参数

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;
}
// 作用1:限制入参类型
function a(person:IPerson){person.age++}
// 作用2:类实现接口
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; // 此处不可省略readonly
  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

  1. 定义模块
    采用define函数来定义模块,接收一个或两个参数:
// a模块
// 一个参数:接收一个函数返回一个对象
define(function(){return{}})
// b模块
// 两个参数:接收一个依赖项数组,接收一个函数,参数为依赖项,返回一个对象
define(['a'],function(a){return{}})

其中:a和b表示当前模块以来的其余两个模块,依赖项和入参是一一对应的

  1. 使用
    使用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){}

  1. 定义模块
// a模块
define(function(require,exports,module){
  module.exports = {name:'pp'}
}
// b模块
define(function(require,exports,module){
  var a = require('a'); // 获取到a模块
  module.exports = {age:1}
}
// main.js
define(function(require,exports,module){
  var b = require('b'); // 获取到b模块
  // 此处使用了jq,由于没有require引入所以是全局变量,所以必须在index.html中手动引入
  $("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;

// 导入模块
// main.js
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';

查找规则:

  1. x为内置模块,直接返回该模块
  2. x以/./../开头,将x当作文件依次查找:x.js x.json x.node,将x当作目录依次查找:x/package.json中的main字段 x/index.js x/index.json x/indexnode
  3. x不带路径,查找当前路径下的node_module中,然后依次向上遍历每层的node_module
  4. not found

10 声明合并

在ts编译时会将同名的两个单独声明合并为一个声明,支持:namespace、interface、class

  1. 接口合并
    接口合并,后面同名接口的属性会出现在合并后的靠前位置
  2. 命名空间合并
    由于涉及导出和非导出的属性,对于非导出成员仅在原命名空间可见
  3. 命名空间和类合并
    必须将类定义放在命名空间之前!合并后既是命名空间也是类
  4. 命名空间和函数合并
    同上面相似,合并后可调用

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)
// 新增属性方法,直接操作prototype
C.prototype.run = ()=>console.log("run");
c.run();

// 普通函数
function b(){}
// 新增属性方法,直接操作prototype
b.prototype.age=1
new b().age // 1

// 类
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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值