全局安装ts
npm install -g typescript
编译ts文件为js文件
tsc helloworld.ts
1.生成tsconfig.json文件
在项目根目录下运行下面命令:
tsc --init //运行命令后自动生成配置文件
2.在vscode上方的菜单栏中点击【终端】->【运行任务…】
3.点击【tsc:监视 - tsconfig.json】
vscode终端会自动出现如下图:就可以自动编译.ts文件
typescript中为了使编写的代码更规范,更有利于维护,增加了类型校验,在typescript中主要给我们提供了以下数据类型
布尔类型(boolean)
数字类型(number)
字符串类型(string)
数组类型(array)
元组类型(tuple)
枚举类型(enum)
任意类型(any)
未知的类型 (unknown)
null 和 undefined
void类型
never类型
var flag:boolean=true; // ts规范需要指定变量的类型
// flag=123; //错误
var num:number = 123;
num = 456; // 正确
num = 'str'; // 错误
var str:string='this is ts';
str='haha'; // 正确
str=true; // 错误
ts中定义数组有两种方式:
第一种:
var arr:number[]=[11,22,33,44];
第二种:
var arr:Array<number>=[11,22,33,55];
这两种方式均充分体现ts需要指定数据的类型,数组也不例外
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
例如定义一个具有两个值,且两个值分别为字符串和数字的数组:
let arr:[number,string] = [123,'this is ts'];
语法:
enum 枚举名{
标识符[=整型常数],
标识符[=整型常数],
...
标识符[=整型常数],
} ;
用法:
enum Flag {success=1,error=2};
let s:Flag=Flag.success;
let f:Flag=Flag.error;
enum Color {blue,red,'orange'};
var c:Color=Color.red;
console.log(c); //1 如果标识符没有赋值 它的值就是下标
enum Color {blue,red=3,'orange'};
var c:Color = Color.red;
console.log(c); //3
var c:Color = Color.orange;
console.log(c); //4
enum Err {'undefined' = -1,'null' = -2,'success' = 1};
var successCode:Err = Err.success;
console.log(successCode); //1
var num:any=123;
num='str';
num=true;
区别:any和unknown,任何类型都能分配给unknown,而unknown只能分配给unknown、any或者未声明类型,而any啥都能分配和被分配。
// unknown被分配
const foo1: unknown = false;
const foo2: unknown = {};
const foo3: unknown = [];
const foo4: unknown = () => { };
let foo: unknown = 1;
// unknown分配
const fooUnknown: unknown = foo;
const fooAny: any = foo;
const fooUndeclared = foo;
const fooString: string = foo; //Error
let baz: any = 1;
// any分配
const bazUndeclared = baz;
const bazAny = baz;
const bazString: string = baz;
const bazObject: object = baz;
ts中类型断言有两种方式:
第一种:(推荐使用)在 tsx 语法(React 的 jsx 语法的 ts 版)中必须使用这种
值 as 类型
第二种:
<类型>值
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function isFish(animal: Cat | Fish) {
if (typeof animal.swim === 'function') {
// xxx- error TS2339: Property 'swim' does not exist on type 'Cat | Fish'.
// Property 'swim' does not exist on type 'Cat'.
return true;
}
return false;
}
上面的例子中,获取 animal.swim
的时候会报错。
此时可以使用类型断言,将 animal
断言成 Fish
:
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function isFish(animal: Cat | Fish) {
if (typeof (animal as Fish).swim === 'function') {
return true;
}
return false;
}
null 和 undefined 为其他(包含never)类型的子类型
var num:number;
console.log(num) //编译时候会报错, 输出:undefined
var num:undefined;
console.log(num) //编译时候不会报错, 输出:undefined
var b:null;
b = 123 //编译时候会报错, 输出:undefined
b = null;
console.log(b) //编译时候不会报错, 输出:null
一个类型有可能是number类型也有可能是undefined类型,那我们就可以使用联合类型给其指定,通过管道(|)将变量设置多种类型,赋值时可以根据设置的类型来赋值。
let num:number | undefined | null
console.log(num); //undefined
num = null
console.log(num); //null
num = 123
console.log(num); //123
typescript中的void表示没有任何类型,一般用于定义函数的时候没有返回值。
用来表示函数无返回值的情况:
function run():void{
console.log('run')
}
run();
函数有返回值的情况,规范做法是写出返回值类型:
function fn():number{
return 123;
}
fn();
never 是其它类型(包括 null 和 undefined)的子类型,代表从不会出现的值。
这意味着声明never的变量只能被never类型所赋值。
var a:undefined;
a = undefined;
var b:null;
b = null;
let a:never
a = (()=>{
throw new Error("错了")
})()
console.log(a); // 抛出错误属于其他类型
第一种:函数声明法
function run():string{
return 'run';
}
第二种:匿名函数
var fun2 = function():number{
return 123;
}
带参函数:
带参函数的函数声明法
function getInfo(name:string, age:number):string{
return `${name} --- ${age}`;
}
带参函数的匿名函数法
var getInfo = function(name:string,age:number):string{
return `${name} --- ${age}`;
}
无返回值的方法:
function run():void{
console.log('run')
}
run();
function getInfo(name:string, age?:number):string{
if (age) {
return `${name} --- ${age}`;
} else {
return `${name} ---年龄保密`;
}
}
注意:可选参数必须配置到参数的最后面
es5里面没法设置默认参数,es6和ts中都可以设置默认参数
function getInfo(name:string, age:number=20):string{
if(age){
return `${name} --- ${age}`;
}else{
return `${name} ---年龄保密`;
}
}
有一种情况,我们不知道要向函数传入多少个参数,这时候我们就可以使用剩余参数来定义。
剩余参数语法允许我们将一个不确定数量的参数作为一个数组传入。
function sum(a:number, b:number, c:number, d:number):number{
return a+b+c+d;
}
sum(1,2,3,4);
// 三点运算符:接受实参传过来的值
function sum(a:number, b:number, ...restOfNum:number[]):number{
var sum = a+b;
// 在函数中可以直接使用这个数组
for(let i=0; i < restOfNum.length; i++){
sum += restOfNum[i];
}
return sum;
}
sum(1,2,3,4,5,6);
函数的最后一个命名参数 restOfNum以 … 为前缀,它将成为一个由剩余参数组成的数组,索引值从[ 0 ~ restOfNum.length)。
重载是方法名字相同,而参数不同,返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
typescript中的重载:通过为同一个函数提供多个函数类型定义来达到多种功能的目的。
/* 参数个数一样 */
function getInfo(name:string):string;
function getInfo(age:number):string;
function getInfo(str:any):any {
if(typeof str === 'string'){
return '我叫:'+ str;
}else{
return '我的年龄是'+ str;
}
}
console.log(getInfo('张三')); //正确
console.log(getInfo(20)); //正确
console.log(getInfo(true)); //错误写法
/* 参数个数不一样 */
function getInfo(name:string):string;
function getInfo(name:string, age:number):string;
function getInfo(name:any, age?:any):any {
if(age){
return '我叫:'+ name +'我的年龄是'+ age;
}else{
return '我叫:'+name;
}
}
console.log(getInfo('zhangsan')); //正确
console.log(getInfo(123)); //错误写法
console.log(getInfo('zhangsan',20)); //正确
和es6语法用法一样
setTimeout(() => {
console.log('run');
},1000);
function Person(){
this.name = '张三';
this.age = 20;
}
var p = new Person();
console.log(p.name);
2 .构造函数和原型链里面增加方法
function Person(){
this.name = '张三'; /*属性*/
this.age = 20;
this.run = function(){
console.log(this.name +'在健身');
}
}
// 原型链上面的属性会被多个实例共享,构造函数不会
Person.prototype.sex = "男";
Person.prototype.work = function(){
console.log(this.name +'在工作');
}
var p = new Person();
// console.log(p.name);
// p.run();
p.work();
3 .类里面的静态方法
function Person(){
this.name='张三'; /*属性*/
this.age=20;
this.run=function(){ /*实例方法*/
console.log(this.name +'在健身');
}
}
Person.getInfo=function(){ /*静态方法*/
console.log('我是静态方法');
}
// 原型链上面的属性会被多个实例共享,构造函数不会
Person.prototype.sex = "男";
Person.prototype.work = function(){
console.log(this.name +'在工作');
}
var p=new Person();
p.work();
//调用静态方法
Person.getInfo();
4 .es5里面的继承–上下文调用模式实现继承
function Person(name, age) {
this.name = name || '张三'; /*属性*/
this.age = age || 20;
this.run = function () {
/*实例方法*/
console.log(this.name + '在健身');
}
}
Person.prototype.sex = "男";
Person.prototype.work = function () {
console.log(this.name + '在工作');
}
//Web类 继承Person类 上下文调用模式实现继承
function Web() {
Person.call(this); /*把Person里的this换成 Web这个this*/
}
var w = new Web('王五',22);
w.run(); // run方法执行打印:'张三在健身',----上下文调用模式可以继承构造函数里面的属性和方法,没法给父类传参
/*-----------问题:没法继承原型链上面的属性和方法-------*/
w.work(); // '报错:Uncaught TypeError: w.work is not a function'
5 .es5里面的继承–原型链实现继承
function Person(name, age){
this.name = name || '张三'; /*属性*/
this.age = age || 20;
this.run=function(){ /*实例方法*/
console.log(this.name +'在健身');
}
}
Person.prototype.sex = "男";
Person.prototype.work = function(){
console.log(this.name + '在工作');
}
function Web(){
}
//Web类 继承Person类 原型链实现继承
Web.prototype = new Person();
var w = new Web();
w.run(); // run方法执行打印:'张三在健身' 原型链实现继承:可以继承构造函数里面的属性和方法 也可以继承原型链上面的属性和方法
w.work(); // run方法执行打印:'张三在工作'
/*-----------问题:实例化子类的时候没法给父类传参-------*/
// var w1 = new Web('王五',22);
// w1.run(); // run方法执行打印:'张三在健身'
6 .es5里面的继承–原型链+上下文调用模式组合实现继承
function Person(name, age) {
this.name = name || '张三'; /*属性*/
this.age = age || 20;
this.run = function () { /*实例方法*/
console.log(this.name + '在健身');
}
}
Person.prototype.sex = "男";
Person.prototype.work = function () {
console.log(this.name + '在工作');
}
// 原型链 +上下文调用模式继承,实例化子类可以给父类传参,也可以继承原型链上面的属性和方法
function Web(name, age) {
Person.call(this, name, age);
}
Web.prototype = new Person();
var w1=new Web('王五',22);
w1.run(); // run方法执行打印:'王五在健身'
w1.work(); // work方法执行打印:'王五在工作'
7 .原型链+上下文调用模式组合实现继承的另一种方式
function Person(name, age) {
this.name = name || '张三'; /*属性*/
this.age = age || 20;
this.run = function () {
/*实例方法*/
console.log(this.name + '在健身');
}
}
Person.prototype.sex = "男";
Person.prototype.work = function () {
console.log(this.name + '在工作');
}
function Web(name, age) {
Person.call(this, name, age); //上下文调用模式继承 可以继承构造函数里面的属性和方法、实例化子类可以给父类传参
}
Web.prototype = Person.prototype;
var w1=new Web('王五',22);
w1.run(); // run方法执行打印:'王五在健身'
w1.work(); // work方法执行打印:'王五在工作'
基本语法:
class 类名 {
属性名:属性类型;
constructor(){}
// 也可以包含方法
fn(){}
}
实体化类:
var obj = new Student();
class Person {
name:string; //属性 前面省略了public关键词
constructor(n:string){ //构造函数 实例化类时触发的方法
this.name = n;
}
run():void {
console.log(this.name);
}
}
var p = new Person('张三');
p.run()
class Person{
name:string;
constructor(name:string){ //构造函数 实例化类的时候触发的方法
this.name = name;
}
getName():string{
return this.name;
}
setName(name:string):void{
this.name=name;
}
}
var p = new Person('张三');
console.log(p.getName()); // 张三
p.setName('李四');
console.log(p.getName()); // 李四
ts中继承类会使用到extends和super关键字,被继承的类一般叫基类、父类、超类;继承的类一般被称作派生类、子类。
class Person{
name:string;
constructor(name:string){
this.name = name;
}
run():string{
return `${this.name}在健身`
}
}
// var p = new Person('王五');
// console.log(p.run()); // 王五在健身
class Web extends Person{
constructor(name:string){
super(name); /*初始化父类的构造函数*/
}
}
var w = new Web('李四');
console.log(w.run()); // 李四在健身
ts中父类的方法和子类的方法一致时,实例化谁就执行谁的方法
class Person{
name:string;
constructor(name:string){
this.name = name;
}
run():string{
return `${this.name}在健身--父类`
}
}
// var p = new Person('王五');
// console.log(p.run()) // 王五在健身--父类
class Web extends Person{
constructor(name:string){
// 如果继承的父类有构造函数,此时派生类必须调用 super(),它会执行父类的构造函数。 而且,在构造函数里访问this的属性之前,我们一定要调用super()。这个是ts强制执行的一条重要规则。
super(name); /*初始化父类的构造函数*/
}
run():string{
return `${this.name}在健身-子类`
}
work(){
console.log(`${this.name}在工作`)
}
}
var w = new Web('李四');
console.log(w.run()); // 李四在健身-子类
console.log(w.work()); // 李四在工作
typescript里面定义属性的时候给我们提供了 三种修饰符
public :公有 在当前类里面、 子类 、类外面都可以访问
protected:保护类型 在当前类里面、子类里面可以访问 ,在类外部没法访问
private :私有 在当前类里面可以访问,子类、类外部都没法访问
注意: 属性如果不加修饰符 默认就是 公有 (public)
class Person{
public name:string; /*公有属性*/
constructor(name:string){
this.name = name;
}
run():string{
return `${this.name}在健身` /*类里面访问公有属性*/
}
}
// var p = new Person('王五');
// console.log(p.run())
class Web extends Person{
constructor(name:string){
super(name); /*初始化父类的构造函数*/
}
run():string{
return `${this.name}在健身-子类` /*子类访问公有属性*/
}
work(){
console.log(`${this.name}在工作`)
}
}
var w = new Web('李四');
w.work();
类外部访问公有属性:
class Person{
public name:string; /*公有属性*/
constructor(name:string){
this.name=name;
}
run():string{
return `${this.name}在健身`
}
}
var p = new Person('哈哈哈');
console.log(p.name);
class Person{
protected name:string; /*公有属性*/
constructor(name:string){
this.name=name;
}
run():string{
return `${this.name}在健身`
}
}
var p=new Person('王五');
console.log(p.run());
class Web extends Person{
constructor(name:string){
super(name); /*初始化父类的构造函数*/
}
work(){
console.log(`${this.name}在工作`)
}
}
var w=new Web('李四11');
w.work();
console.log( w.run())
class Person{
private name:string; /*私有*/
constructor(name:string){
this.name = name;
}
run():string{
return `${this.name}在健身`
}
}
var p = new Person('哈哈哈').name // 报错,只能在Person类中访问;
静态属性与静态方法定义存在于类本身上面而不是类的实例上的属性和方法,不能直接被实例化对象直接访问,只能被类本身调用,使用 static定义:
class Per{
public name:string;
public age:number=20;
//静态属性
static sex="男";
constructor(name:string) {
this.name=name;
}
run(){ /*实例方法*/
console.log(`${this.name}在健身`)
}
work(){
console.log(`${this.name}在工作`)
}
static print(){ /*静态方法 不能直接调用类中的数据,但可以通过访问静态属性的方式来获取数据*/
// console.log('print方法'+ this.name); /* 错误,访问不到 */
console.log('print方法'+ Per.sex); /* 正确,可以访问静态方法*/
}
}
// var p=new Per('张三');
// p.run();
Per.print();
console.log(Per.sex);
父类定义一个方法不去实现,让继承它的子类去实现 每一个子类有不同的表现
/*父类 Animal*/
class Animal {
name:string;
constructor(name:string) {
this.name=name;
}
eat(){ //具体吃什么不知道,具体吃什么?继承它的子类去实现 ,每一个子类的表现不一样
console.log('吃的方法')
}
}
/*子类 Dog*/
class Dog extends Animal{
constructor(name:string){
super(name);
}
eat(){
return this.name +'吃肉骨头';
}
}
/*子类 Cat*/
class Cat extends Animal{
constructor(name:string){
super(name)
}
eat(){
return this.name +'吃鱼'
}
}
typescript中的抽象类特点:
标准:
abstract class Animal{
public name:string;
constructor(name:string){
this.name=name;
}
abstract eat():any; // 抽象方法 不包含具体实现并且必须在派生类中实现。
run(){
console.log('其他方法可以不实现')
}
}
// var a=new Animal() /*错误的写法--抽象类不能直接被实例化*/
抽象方法必须在派生类中实现:
/*子类 Dog*/
class Dog extends Animal{
//抽象类的子类必须实现抽象类里面的抽象方法
constructor(name:any){
super(name)
}
eat(){
console.log(this.name+'吃粮食')
}
}
var d=new Dog('小花花');
d.eat();
/*子类 Cat*/
class Cat extends Animal{
//抽象类的子类必须实现抽象类里面的抽象方法
constructor(name:any){
super(name)
}
run(){
}
eat(){
console.log(this.name+'吃老鼠')
}
}
var c=new Cat('小花猫');
c.eat();
typescript中的抽象类特点:
标准:
abstract class Animal{
public name:string;
constructor(name:string){
this.name=name;
}
abstract eat():any; // 抽象方法 不包含具体实现并且必须在派生类中实现。
run(){
console.log('其他方法可以不实现')
}
}
// var a=new Animal() /*错误的写法--抽象类不能直接被实例化*/
抽象方法必须在派生类中实现:
/*子类 Dog*/
class Dog extends Animal{
//抽象类的子类必须实现抽象类里面的抽象方法
constructor(name:any){
super(name)
}
eat(){
console.log(this.name+'吃粮食')
}
}
var d=new Dog('小花花');
d.eat();
/*子类 Cat*/
class Cat extends Animal{
//抽象类的子类必须实现抽象类里面的抽象方法
constructor(name:any){
super(name)
}
run(){
}
eat(){
console.log(this.name+'吃老鼠')
}
}
var c=new Cat('小花猫');
c.eat();
接口的作用:在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到一种限制和规范的作用。接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。 typescrip中的接口类似于java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类等。
定义接口,使用接口对传入参数进行约束:
// 定义属性接口
interface FullName{
firstName:string;
secondName:string;
}
// 使用接口对形参进行约束
function func(name:FullName):void{ //name是对象,接收的实际参数必须包含有firstName和secondName属性
console.log(`姓:${name.firstName},名:${name.secondName}`);
}
func({firstName:"张",secondName:"三"}) // 这种传递方式调用不能有多余的属性, 否则编译报错
let obj = {
firstName:"李",
secondName:"四",
age:19
}
func(obj) // 这种传递方式调用可以有多余的属性
接口规范可以被不同的方法使用,即对批量方法进行约束:
interface FullName{
firstName:string;
secondName:string;
}
// 使用接口对形参进行约束
function func(name:FullName):void{
console.log(`姓:${name.firstName},名:${name.secondName}`);
}
// 使用接口对形参进行约束
function func2(info:FullName):void{
console.log(`${info.firstName}----${info.secondName}`);
}
func({firstName:"张",secondName:"三"})
let obj = {
firstName:"李",
secondName:"四",
age:19
}
func(obj)
func2({firstName:"张",secondName:"三"})
func2(obj)
可选属性:
interface FullName{
firstName:string;
secondName:string;
age?:number;
}
// 使用接口对形参进行约束
function func(name:FullName):void{
console.log(`姓:${name.firstName},名:${name.secondName}`);
}
// 使用接口对形参进行约束
function func2(info:FullName):void{
if(info.age){
console.log(`${info.firstName}----${info.secondName}----${info.age}`);
}
else{
console.log(`${info.firstName}----${info.secondName}`);
}
}
func({firstName:"张", secondName:"三", age: 19})
let obj = {
firstName:"李",
secondName:"四",
age:19
}
func(obj)
func2({firstName:"张",secondName:"三"})
func2(obj)
使用举例:(了解)
// 定义接口
interface Config{
type:string;
url:string;
data?:string;
dataType:string;
}
//原生js封装的ajax
function ajax(config:Config){
const xhr = new XMLHttpRequest();
xhr.open(config.type, config.url, true);
xhr.send(config.data);
xhr.onreadystatechange = function():void{
if(xhr.readyState == 4 && xhr.status == 200){
console.log('success');
if(config.dataType=='json'){
console.log(JSON.parse(xhr.responseText));
}else{
console.log(xhr.responseText);
}
}
}
}
// 调用
ajax({
type:'get',
data:'name = zhangsan',
url:'http://**.com/api/productlist', //api
dataType:'json'
})
对方法传入的参数以及返回值进行约束,也是批量约束
//对实参和函数进行约束
interface SumOfNums{
(num1:number, num2:number):number; // :前是对实参的约束,:后是对返回值的约束
}
let sum:SumOfNums = function(num1,num2) {
return num1 + num2;
}
console.log(sum(10, 20));
对数组进行约束:
interface ArrItf{
[index:number]:number; // :前是对索引的约束,:后是每一个元素类型的约束
}
let arr1:ArrItf = [10, 20] //正确写法
// let arr1:ArrItf = [10, 20, "hello"] //编译报错
console.log(arr1);
对对象进行约束:
interface ObjItf{
[index:string]:any;
}
let obj:ObjItf = {name:'张三', age:17};
console.log(obj);
对类的约束和抽象类有点相似
//对类进行约束
interface DogItf{
name:string; //将来定义的类必须有name这个实例属性
eat(food:string):void; //将来定义的类必须有eat这个实例方法
}
class Animal{
drink(){
console.log("喝");
}
}
class Dog extends Animal implements DogItf{
name:string;
constructor(n:string){
super()
this.name = "小狗"
}
eat(food:string){
console.log(`${this.name}正在吃${food}`);
}
}
let dog1 = new Dog("小白");
dog1.eat("骨头"); // 小狗正在吃骨头
dog1.drink(); // 喝
implements与extends的区别
implements顾名思义,实现,一个新的类,从父类或者接口实现所有的属性和方法,同时可以重写
属性和方法,包含一些新的功能
extends顾名思义,继承,一个新的接口或者类,从父类或者接口继承所有的属性和方法,不可以重写属性,但可以重写方法
type作用就是给类型起一个新名字,支持基本类型、联合类型、元组及其它任何你需要的手写类型,常用于***联合类型***
type test = number; //基本类型
let num: test = 10;
type userObj = { name:string } // 对象
type getName = () => string // 函数
type data = [number,string] // 元组
type numOrFun = test | getName // 联合类型
相同点 :
1.都可以描述一个对象或者函数
// interface
interface User {
name: string
age: number
(name: string, age; number): void
}
// type
type User = {
name: string
age: number
}
type setProps = (name: string, age: number) => void
2.都可以进行拓展
// interface extends interface
interface Name {
name: string;
}
interface User extends Name {
age: number;
}
// type extends type
type Name = {
name: string
}
type User = Name & { age: number }
// interface extends type
interface User extends Name
// type extends interface
interface Name {
name: string
}
type user = Name & {
age: number
}
不同点
type 可以而 interface 不行
1.type 可以声明基本类型别名,联合类型,元组等类型
// 基本类型别名
type Name = string
interface Dog {
wong();
}
interface Cat {
miao();
}
// 联合类型
type Pet = Dog | Cat
// 元组
type PetList = [Dog, Pet]
2.type 语句中还可以使用 typeof 获取实例的 类型进行赋值
// 当你想获取一个变量的类型时,使用 typeof
let div = document.createElement('div');
type B = typeof div
interface 可以而 type 不行
interface 能够声明合并
interface User {
name: string
age: number
}
interface User {
sex: string
}
/*
User 接口为 {
name: string
age: number
sex: string
}
*/
泛型:软件工程中,我们不仅要创建一致的、定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
通俗理解:泛型就是解决方法、类、接口的复用性,以及对不特定数据类型的支持(类型校验)。
泛型:可以支持不特定的数据类型,T表示泛型,具体什么类型是调用这个方法的时候决定的:
/*只能返回string类型的数据*/
function getData(value:string):string{
return value;
}
/*同时返回 string类型 和 number类型 (代码冗余)*/
function getData1(value:string):string{
return value;
}
function getData2(value:number):number{
return value;
}
/*同时返回 string类型 和number类型,any可以解决这个问题*/
function getData(value:any):any{
return '哈哈哈';
}
getData(123);
getData('str');
any放弃了类型检查,传入什么 返回什么。比如:传入number 类型必须返回number类型 传入 string类型必须返回string类型。
上面代码,如果使用泛型:
function getData<T>(val :T): T { // 当中出现的T类型由调用的时候的<>中的类型决定
return val
}
// 第一种是,传入所有的参数,包含类型参数
console.log(getData<number>(30));
console.log(getData<string>("hello"));
console.log(getData<boolean>(true));
// 第二种方法更普遍。利用了类型推论 -- 即编译器会根据传入的参数自动地帮助我们确定T的类型:
console.log(getData(30));
console.log(getData("hello"));
console.log(getData(true));
class GetElem{
arr2:number[] = [];
add(val:number){
this.arr2.push(val)
}
getSecondElem():number{
return this.arr2[1]
}
}
上面代码,将类型规定为number之后,对字符串不适用,但如果使用泛型就可以解决:
class GetElem<T> { // 泛型类使用(<>)括起泛型类型,跟在类名后面。
arr2:T[] = [];
add(val:T){
this.arr2.push(val)
}
getSecondElem():T{
return this.arr2[1]
}
}
let obj1 = new GetElem<number>();
obj1.add(10);
obj1.add(20);
obj1.add(30);
console.log(obj1.getSecondElem());
let obj2 = new GetElem<string>();
obj2.add("hello");
obj2.add(" world");
obj2.add(" and ts");
console.log(obj2.getSecondElem());
泛型类接口一般有两种写法
// 第一种:
interface ConfigFn {
<T>(value:T):T;
}
const getData:ConfigFn = function<T>(value:T):T {
return value;
}
getData<string>('张三');
// getData(1243); //错误
// 第二种:
interface ConfigFn<T> {
(value:T) :T;
}
function getData<T>(value:T) :T {
return value;
}
const myGetData:ConfigFn<string> = getData;
myGetData('20'); /*正确*/
// myGetData(20) //错误
关于术语的一点说明: 请务必注意一点,TypeScript 1.5里术语名已经发生了变化。
“内部模块”现在称做“命名空间”。“外部模块”现在则简称为“模块”,模块在其自身的作用域里执行,而不是在全局作用域里;
这意味着定义在一个模块里的变量,函数,类等等在模块外部是不可见的,除非你明确地使用export形式之一导出它们。
相反,如果想使用其它模块导出的变量,函数,类,接口等的时候,你必须要导入它们,可以使用 import形式之一。
根据导出方式,可以有对应的导入格式:
md1.ts:
function func(){
console.log("这是md1中的func函数")
}
class Dog2{
eat(){
console.log("吃------")
}
}
// export {func, Dog2} // 对应导入方式:import {func, Dog2} from "./md1" 使用: a, func()
// export default {func, Dog2} // 对应导入方式:import md1 from "./md1" 使用:md1.a, md1.func()
命名空间:
在代码量较大的情况下,为了避免各种变量命名相冲突,可将相似功能的函数、类、接口等放置到命名空间内
同Java的包、.Net的命名空间一样,TypeScript的命名空间可以将代码包裹起来,只对外暴露需要在外部访问的对象。命名空间内的对象通过export关键字对外暴露。
命名空间和模块的区别:
命名空间:内部模块,主要用于组织代码,避免命名冲突。
模 块:ts的外部模块的简称,侧重代码的复用,一个模块里可能会有多个命名空间。
md2.js
export namespace A{
export let a = 10;
function func(){
console.log("这是md1中的func函数")
}
class Dog2{
eat(){
console.log("吃------")
}
}
}
console.log(A.a) //10
namespace B{
let a = 100;
function func(){
console.log("这是md1中的func函数")
}
class Dog2{
eat(){
console.log("吃------")
}
}
}
main.ts
import A from "./md2"
console.log(A.a)
装饰器: 是一种特殊类型的声明,它能够被附加到类声明,方法,属性上,可以为原本的代码添加额外的功能。
通俗的讲装饰器就是一个函数,可以注入到类、属性、方法参数上,来扩展类、属性、方法的功能。装饰器使用 @expression
这种形式,expression
求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。
常见的装饰器有:类装饰器、属性装饰器、方法装饰器
装饰器的写法:普通装饰器(无传参) 、 装饰器工厂(可传参)
Javascript里的装饰器目前处在 建议征集的第二阶段,但在TypeScript里已做为一项实验性特性予以支持。
若要启用实验性的装饰器特性,你必须在命令行或tsconfig.json
里启用experimentalDecorators
编译器选项:
命令行:
tsc --target ES5 --experimentalDecorators
// tsconfig.json:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
function logClass(params:any) {
// 这里的代码在开始装饰的时候就会执行了!
console.log(params); // Function:MyClass logClass() 接收的参数就是被装饰的类MyClass
params.prototype.run = function() { // 为`MyClass`动态扩展方法
console.log('run...');
};
}
@logClass
class MyClass{
}
const class1:any = new MyClass();
class1.run(); // run...
function logClass(params:any){
console.log(params) // hello params接收装饰器参数
// 这里的代码在开始装饰的时候就会执行了!
return function(target:any){
console.log(target) // target是类 MyClass
target.prototype.url = params; //扩展一个url属性
}
}
@logClass("hello") // 向装饰器传递参数
class MyClass{
}
const class1:any = new MyClass();
console.log(class1.url); //hello
属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:
// 实现一个装饰器,每次定义一个方法,都可以往数组arr中追加函数名作为数组的元素
function logProperty(p:any){
return function(target:any, attr:any){
console.log(target); // MyClass {}/[Function: MyClass] { gender: '男' }
console.log(attr); // name/gender 成员的名字
console.log(p); // aaa/bbb p接收装饰器参数
}
}
class MyClass{
@logProperty("aaa")
public name:string="zhangsan";
@logProperty("bbb")
static gender="男"
}
方法装饰器被应用到方法的属性描述符上,可以用来监视、修改、替换方法的定义,会在运行时传入3个参数:
function logMethod(p:any){
//这里的代码在开始装饰的时候就会执行了!
return function(target:any,methodName:any,desc:any){
// console.log(target) // { constructor:f, sum1:f }
// console.log(methodName); // sum1
// console.log(desc); // {value: ƒ, writable: true, enumerable: false, configurable: true} value就是方法体
// console.log(p) // p接收装饰器参数
// 1. 保存原方法体
let oMethod = desc.value;
// 2. 重新定义方法体
desc.value = function(...res:any){
// 3. 打印
console.log("log: 调用了sum1这个方法");
// 4. 执行原来的方法体
oMethod(...res)
}
}
}
class MyClass{
@logMethod("")
sum1(n1:number,n2:number) {
console.log(`和是:${n1+n2}`)
}
}
let obj = new MyClass()
obj.sum1(10,20) // 相当于每次调用这个函数,都会执行 desc.value中的
const class1:any = new MyClass();
console.log(class1.url); //hello