TypeScript学习笔记

TS简介

Ts微软开发,包含ES6、包含ES5

编译 tsc xx.ts
每一次都得编译,可以自动编译

开发工具中配置typescirpt自动编译

vscode:

  1. 创建tsconfig.json文件 tsc --init 生成配置文件
  2. tsconfig.json配置文件中,修改outDir配置项,取消注释然后修改为.js
  3. vscode中,点击上方栏位run task,选择ts监听
  4. 完成

TS类型

  • 与es5中的区别
// es5:类型变化不报错
var flag = true;
flag = 234;

// ts:必须指定类型
typescript
var flag:boolean=true;
flag = 131;//报错

TS类型:

  • boolean
  • number
  • string
  • array数组:
    方式1:var arr:number[] = [1,2,3]//制定arr里面全是数字
    方式2:var arr:Array= [1,2,3]

元组类型(tuple)

  • 方式1:属于数组的一种,即数组中每一个元素指定类型
  • 方式2:var arr:[number, string]=[123,“this is ts”];

枚举类型(enum)

// 常用来标识状态码
enum Flag{
success=1,
error=2
}
let f:Flag=Flag.error;
console.log(f);// 2

// 如果 没有标识符没有赋值,那么打印的就是下标
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

// 常用来标识状态码
enum Err{
'undefined'=-1,
'null'=-2,
}
var c:Err=Err.null
console.log(c) // -2

任意类型any

  • 类似ES5不指定变量类型的var
var num:any=123;
num = true;// 不报错

null类型和undefined其他数据类型的子类型

  • 变量定义之后没有赋值,报undefined
// 一个元素可能是number类型,可能是null或者undefined
var num:number | undefined | null;

void和java一样 没有返回值类型

// 如果方法没有返回值
function run():void{
console.log('asdf')
}
// 如果方法有返回值:
function run():number{
return 1;
}

never类型,代表从来不会出现的值,是其他类型(包括null‘和undefined)的子类型,代表从不会出现的值

  • 自己理解为上述类型之外的数据类型
// 如下,接收Err类型的数据
var a:never;
a = undefined;//报错
a = (()=>{
new Throw Err("报错")
})()

函数的定义

  • ES5中:
// 函数声明法
function run(){
return ...
}
//匿名函数
var run2 = function(){
return ..
}
  • TS中:
//函数声明法
function run():number{
return 123;
}
// 匿名函数
var fun2=function():number{
return 123;
}

ts中定义方法传参

function getInfo(name:string, age:number):string{
return name + " " + age;
}
var getInfo= function(name:string, age:number):string{
return name+age;
}

方法可选参数

es5里方法实参和形参可以不一样,但是ts必须一致,如果不一样就需要配置可选参数

  • 参数后边加?可以设置参数可选传
  • 可选参数必须配置到参数的最后边
function getInfo(name:string, age?number):string{
return
}

默认参数

// 默认参数,直接在形参赋值
function getInfo(name:string, age:number=20):string{
return
}

剩余参数

function sum(a:number, b:number, c:number, d:number):number{
return a+b+c+d;
}

// 三点运算符:接收不固定参数的(剩余参数)的值
function sum(…rest:number[]):number{
var sum= 0 ;
for(var i=0; i<rest.length;i++){
sum+=rest[i];
}
return sum;
}

函数重载

  • 类似java,同名但是不同参数的多个函数方法
  • ts为了兼容es5,以及es6,和java有区别
  • es5中,出现同名方法时候,下面的方法会替换上面的方法
  • ts中的重载:
function getInfo(name:string):string;
function getInfo(age:number):string;
function getInfo(str:any):any{
if(typeof str ==="string"){
return "我叫:"+ str;
}else{
return "我的年龄是:" + str;
}
}

箭头函数

箭头函数里面的this指向上下文

1、ES5中定义类:

function Person(){
this.name='张三';
this.age = 20;
}
var p = new Person();
alert(p.name);

2、构造函数和原型链里面定义

// 声明的构造方法
function Person(){
this.name = "张三";
this.age=20;
this.run = function()){
alert(this.name+"在运动");
}
}
// 原型链的属性和方法
Person.prototype.sex="男";
Person.prototype.work=function(){
alert(xx)
}
var p = new Person();
p.work();

3、静态方法

4、es5中的继承-对象冒充继承

// 要实现Web类 继承 Person类  原型链+对象冒充的组合继承模式
function Person(){
this.name = "张三";
this.age=20;
this.run = function()){
alert(this.name+"在运动");
}
}
// 原型链的属性和方法
Person.prototype.sex="男";
Person.prototype.work=function(){
alert(xx)
}
// 要实现Web类 继承 Person类
function Web(){
Person.call(this); //对象冒充继承
}

var w = new Web();
w.run();//执行父类Person的run,对象冒充可以继承构造函数里面的属性和方法
w.work();// 对象冒充可以继承构造函数的属性和方法 但是没办法继承原型链的属性和方法(prototype)
  • 关于call:
function add(c, d) {
return this.a + this.b + c + d;
}

const obj = { a: 1, b: 2 };

console.error(add.call(obj, 3, 4)); // 10
大统上的说法就是,call改变了this的指向。然后,介绍this xxx什么一大堆名词,反正不管你懂不懂,成功绕晕你就已经ok了,但是实际发生的过程,可以看成下面的样子。

const o = {
a: 1,
b: 2,
add: function(c, d) {
return this.a + this.b + c + d
}
};

给o对象添加一个add属性,这个时候 this 就指向了 o,
o.add(5,7)得到的结果和add.call(o, 5, 6)相同。

5、原型链继承方法

function Person(){
this.name = "张三";
this.age=20;
this.run = function()){
alert(this.name+"在运动");
}
}
// 原型链的属性和方法
Person.prototype.sex="男";
Person.prototype.work=function(){
alert(xx)
}
// web原型链方式继承 person
function web(){

}
web.prototype= new person();// 原型链继承

web.work();// 可以工作,可以继承原型链属性方法

6、原型链实现继承的问题??无法给父类传参

function Person(){
this.name = "张三";
this.age=20;
this.run = function()){
alert(this.name+"在运动");
}
}
// 原型链的属性和方法
Person.prototype.sex="男";
Person.prototype.work=function(){
alert(xx)
}
var p = new person('李四', 20);
p.run(); // 没问题
// 继承,无法给父类传参
function Web(name,age){

}
Web.prototype= new Person();
var w = new Web('sss', 20);
w.run();// 父类会alert出来“undefiend在运动”
// 实例化子类时候没法给父类传参

7、原型链+构造函数的组合继承模式

function Person(){
this.name = "张三";
this.age=20;
this.run = function()){
alert(this.name+"在运动");
}
}
// 原型链的属性和方法
Person.prototype.sex="男";
Person.prototype.work=function(){
alert(xx)
}
var p = new person('李四', 20);
p.run(); // 没问题
// 继承,无法给父类传参
function Web(name,age){
Person.call(this,name,age); // 对象冒充继承 可以继承构造函数里面的属性和方法 实例化子类可以给父类传参
}
Web.prototype = new Person();// 实例化
var w = new Web('sss', 20);
w.run();// 父类会alert出来“undefiend在运动”
// 实例化子类时候没法给父类传参

8、原型链+对象冒充的另一种写法

function Person(){
this.name = "张三";
this.age=20;
this.run = function()){
alert(this.name+"在运动");
}
}
// 原型链的属性和方法
Person.prototype.sex="男";
Person.prototype.work=function(){
alert(xx)
}
var p = new person('李四', 20);
p.run(); // 没问题
// 继承,无法给父类传参
function Web(name,age){
Person.call(this,name,age); // 对象冒充继承 可以继承构造函数里面的属性和方法 实例化子类可以给父类传参
}
Web.prototype = Person.prototype; // 和方法7中不同的是这里!!!
var w = new Web('sss', 20);
w.run();// 父类会alert出来“undefiend在运动”
// 实例化子类时候没法给父类传参

类的定义

1、ts中定义类,类似java:

class Person(){
name:string; //属性 前面省略了private
construtor(n:string){
this.name = n;
}
run():void{
log(this.name);
}
}

2、继承

class Web extends Person{
constructor(name:string){
super(name);
}
}

var w = new Web("李四");
alert(w.run());

3、类里面的修饰符

ts提供了三种修饰符:
public(默认的): 公有 在类里面、子类类外边都可以访问
protected:在类里面、子类里面可以访问、在类外部无法访问
private:在类里面可以访问、子类、类外部没法访问

静态属性 静态方法

function Person(){
this.run1=function(){// 实例方法

}
}

Person.run2=function(){} // 静态方法

调用实例方法:(实例化之后才能调用的)
var p = new Person();
p.run1();

调用静态方法:
Person.run2();

为什么会有静态方法和实例方法之分?
JQuery的实例方法css()方法和静态方法$.get()方法大概原码为:

// 生命一个节点/元素对象
function Base(element){
this.element = 获取dome节点的方法;
this.css = function(str, value){
this.element.style[str] = value;
}
}
// $方法去实例化这个BAse对象、实例方法
function $(element){
return new Base(element); // 实例化一个方法

}
// 静态方法get
$.get(){
。。。
}
// 那么css调用时候就可以这样写
实例方法:
$("#box").css("color", "red");

静态方法:
$.get('url', function(){

)
  • 另一种方式声明静态方法,利用static关键字声明:
class Person{
public name:string;
static sex = "男";
constructor(name:string){
this.name = name;
}
static print(){// 静态方法 里面没办法直接调用类里面的属性,
alert("静态方法:"+Person.sex);// 如果调用this.name就会报错!!!
}
}
// $.get(){// jq里面的get就是静态方法

}

多态

父类定义一个方法不去实现,让继承他的子类去实现,每一个子类都有不同的表现

抽象方法

  • 用来定义一个标准
  • ts中的抽象类,它是提供其他类继承的基类,不能直接被实例化
  • 用abstract关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类中实现
  • abstract 抽象方法只能放在抽象类中
  • 抽象类和抽象方法用来定义标准,基类要求他的子类必须包含某种方法
  • 抽象方法只能出现在抽象类中
  • 抽象类中必须包含至少一个抽象方法,不然没有意义
abstract class Animal{
// 省略构造方法
abstract eat():any;
}

// 抽象类无法直接实例化
var a = new Animal();// 这句话是错的

class Dog extends Animal{
// 省略构造方法
eat(){
console.log(this.name + '吃');
}
}

var d = new Dog("sss")
d.eat();// sss吃

接口

也是定义标准,定义了行为和动作的规范

1、属性接口

// 定义了这个方法的参数是jsonObjec,而且必须有
function printLabel(labelInfo:{label:string}):void{
console.log(labelInfo.label);
}
printLabel("ssss"); // 错误

printLabel({name:"asdf"}); // 错误
printLabel({label:"sss"}); // 正确,打印sss

2、接口,对批量方法进行约束

// 对批量方法传入参数进行约束
// 传入对象的约束

// 声明类型
interface FullName{
firstName:string; // 注意是;结束
secondName:string;
}
// 方法名中引用FullName类型
function printName(name:FullName){
log(name.firstName +" "+ name.secondName);
}

// 调用方式一(下方调用方式是会报错的,interface定义的属性object只能包含firstName和secondName)
printName({
age:20,
firstName: "张",
secondName: "三"
})

// 调用方式二,下方引入其他的object即可忽略多余参数
var obj = {
age:20,
firstName: "张",
secondName: "三"
};
printName(obj);// 这个不报错

3、接口、可选属性,加?问号表示可传可不传

interface FullName{
firstName:string;
secondName?:string;// secondName可传可不传
}

4、模拟封装一个ajax

interface Config{
type:string;
url:string;
data?:string;
dataType:string;
}

5、函数类型接口、对方法传入的参数、以及返回值进行约束、批量约束

  • 例子:定义加密的函数类型接口
interface encrypt{
// 定义了函数参数为string、value,返回string
(key:string,value:string):string;
}

var md5:encrypt=function(key:string, value:string):string{
// 实现encrypt类型的函数 return key+value;//模拟下
}

6、可索引接口:对数组、对象的约束

  • ts定义数组方法:
var arr:number[]=[123,234];
var arr1:Array<string> = ['123', '222'];
  • 对数组的约束,数组类型接口
interface UserArray{
// 表示数组中index必须是number,value必须是string
[index:numer]:string;
}
var arr:UserArray=['123', '22312'];
  • 对对象的约束,对象类型接口
interface UserObj{
[index:string]:string;
}
var obj:UserObj={name:"2342"};
  • 对类的约束,类类型接口,和抽象类有点相似
interface Animal{
// 规定实现类必须要有name属性和eat方法
name:string;
eat(str:string):void;
}
class Dog implements Animal{
name:string;// 若没此属性,ts会编译报错
constructor(name:string){
this.name = name;
}
eat(){
log("eat")
}
}
  • 接口的扩展:接口可以继承接口
interface Animal{
eat():void;
}

interface Person extends Animal{
work():void;
}

class Web implements Person{
public name:string;
constructor(name:string){
this.name = name;
}

// eat必须定义
eat(){
log(this.name+"吃")
}

// work也必须定义
work(){
log(this.name+"工作")
}
}

interface Animal{
eat():void;
}

interface Person extends Animal{
work():void;
}

class Programmer{
//构造方法省略

coding(code:string){
log(this.name+ " "+code)
}
}
class Web extends Programmer implements Person{
public name:string;
constructor(name:string){
this.name = name;
}

// eat必须定义
eat(){
log(this.name+"吃")
}

// work也必须定义
work(){
log(this.name+"工作")
}
}

泛型

和any有什么区别?

  • any放弃了类型检查
  • 如果想做到传入什么类型就返回什么类型,例如传入number就返回number,这时候就可以使用泛型
function getData(value:any):any{
return ""//什么类型都可以
}

泛型:

  • 软件工程中,我们不仅要创建一致的定义好的API,同时也要考虑可重用性,组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能
  • 在像c#和java中,可以使用泛型来创建可重用的组件,一个组件可支持多种类型的数据,这样用户就可以以自己的数据类型来使用组件
  • 通俗理解:泛型就是解决 类 接口 方法的重用性、以及对不特定数据类型的支持
  • 可以支持不特定的数据类型
function getData<T>(value:T):T{
return value;//传入什么返回什么
}
// 这样调用
getData<number>(123123);
getData<string>("12131");

// 也可以写成:
function getData<T>(value:T):any{
return value;//传入什么返回什么
}
  • 泛型类,比如有个最小堆算法,需要同时支持返回数字和字符串两种类型,通过类的泛型来实现,示例:
// 定义泛型类
class MinClass{
public list:number[]=[];
add(num:number){
this.list.push(num);
}
min():number{
var minNum = this.list[0];
for(var i = 0;i<this.list.length;i++){
if(minNum>this.list[i]){
minNum=this.list[i];
}
}
return minNum;
}
}
// 调用
var m = new MinClass();
m.add(3);
m.add(2);
log(m.min());// 2
  • 但是上边的只能传入数字类型,是否可以用泛型解决?可以:
class MinClass<T>{
public list:T[]=[];
add(num:T):void{
this.list.push(num);
}
min():T{
var minNum = this.list[0];
for(var i = 0;i<this.list.length;i++){
if(minNum>this.list[i]){
minNum=this.list[i];
}
}
return minNum;
}
}
// 调用,实例化时候要先声明参数类型<bumber
var m1 = new MinClass<number>();
m1.add(2);
m1.add(4);
log(m.min());// 2
函数类型接口
  • 指定特殊类型的方法:
interface ConfigFn{
(value1:string, value2:string):string;
}

var setData:ConfigFn=function(value1:string, value2:string):string{
return value1 + value2;
}
setData("name", "张三);
  • 泛型接口写法1:
interface Config{
<T>(value:T):T;
}

var getData:ConfigFn=function<T>(value:T):T{
return value;
}

getData<string>("张三");
  • 泛型接口写法2:
   interface Config<T>{
(value:T):T;
}

function getData<T>(value:T):T{
return value;
}

var myGetData:ConfigFn<string>=getData;

myGetData("张三");
  • 把类作为参数来约束数据传入的类型
class User{
username:string | undefined;
password:string | undefined;
}
class MySqlDb{
add(user:User):boolean{
console.log(user);
retrun true;
}
}

// 调用
var u = new User();
u.username="张三";
u.password="123456";

var Db = new MySqlDb();
Db.add(u);// console.log(u)
  • 上述方法可以改为泛型类
// 操作数据库的泛型类,这样可以规范插入数据库数据的类规范
class MySqlDb<T>{
add(info:T):boolean{
log(info);
return true;
}
}

// 想给User表增加数据
// 1、定义一个User类 和数据库进行映射
class User{
username:string | undefined;
password:string | undefined;
}
var u = new User();
u.username= '张三';
u.password="2312"
var Db = new MySqlDb<User>();// 这一步很关键,要定义User类型
Db.add(u);

// 2、文章类,数据库映射
class Article{
title:string | undefined;
desc:string | undefined;
status:number | undefined;
constructor(params:{
title:string | undefined;
desc:string | undefined;
status?number | undefined;// status可选参数
}){
this.title=params.title;
this.desc=params.desc;
this.status=params.status;
}
}

// 调用
var a = new Article({
title:"分类",
desc:"111",
status:1
})

//类当前参数的泛型类
var Db = MySqlDB<Article>();// 指定类型
Db.add(a);// log a

实战:要实现TS封装统一操作Mysql Mongodb Mssql的底层库

// 先定义一个接口,用于提供各类型数据库规范
interface DBI<T>{
add(info:T):boolean;
update(info:T, id:number):boolean;
delete(id:number):boolean;
get(id:number):any[];
}

// 定义一个操作mysql的类,注意 要实现泛型接口 这个类也应该一定是个泛型类
class MysqlDb<T> implements DBI<T>{
add(info:T): boolean{
log(info);
}
update...
delete...
get...
}

// 调用 操作数据表,定义一个User类和数据库进行映射,并进行MySql数据的插入操作
class User{
username:string | undefined;
password:string | undefined;
}

var u = new User();
u.username = "张三";
u.password="213";

var oMysql = new MysqlDb<User>();// 声明User类型参数
oMysql.add(u);// 插入

模块

概念:

  • 把一些公共的功能单独抽离成一个文件作为一个模块
  • 模块里面的变量 函数 类等默认都是私有的,如果我们要在外部访问模块内的数据(函数、变量、类)
  • 我们就需要通过export暴露模块里面的数据
  • 然后其他地方通过import引入模块就可以使用模块内的数据

模块暴露export:

//  方式一
export function a(){
...
}
// 方式二
function a(){
...
}
export { a }

模块导入import:

import { a, a as alias } from "xxx";
a();
alias();

模块默认导出default,一个模块只能用一次
暴露:

export default a(){

}

引入(不用花括号):

import a from "aaa";
a();

DB库用模块化封装// 省略了,代码比较简单

TS命名空间

内部模块,主要用于组织代码,避免命名冲突,
个人理解:模块之中再分模块
定义模块、并导出不同命名空间:

export namespace A{
interface Animal{
name: string;
eat(): void;
}
export Class Dog implements Animal{
name: string;
constructor(name:string){
this.name = name;
}
eat:void(){
log(this.name +"在空间A中吃狗粮")
}
}
}
export namespace B{
interface Animal{
name: string;
eat(): void;
}
export Class Dog implements Animal{
name: string;
constructor(name:string){
this.name = name;
}
eat:void(){
log(this.name +"在空间A中吃狗粮")
}
}
}

  • 调用:
import {A, B} from "xxx";
var d = new A.Dog("小黑");
d.eat();// 小黑在空间A中吃狗粮

var dog = new B.Dog("小花");
dog.eat(); // 小花在空间B中吃狗粮

装饰器

定义:

  • 装饰器是一种特殊类型的声明,他能够被附加到类声明,方法,属性或者参数上,可以修改类的行为。
  • 通俗的将装饰器就是一个方法,可以注入到类、方法、属性参数上来扩展类、属性、方法、参数的功能。
  • 常见的装饰器有:类装饰器、属性装饰器、方法装饰器、参数装饰器
  • 装饰器的写法:普通装饰器(无法传参)、装饰器工厂(可传参)
  • 装饰器是过去几年中js最大的成就之一,已经是ES7的标准特性之一

类装饰器:普通装饰器

function logClass(params:any){
console.log(params);
// params就是当前类
params.prototype.apiUrl = "动态扩展的属性";
params.prototype.run=function(){
console.log("我是一个run方法");
}
}

@logClass // 类装饰器,普通装饰器,无法传参,默认吧class传入
class HttpClient{
constructor(){

}
getData(){

}
}

类装饰器:装饰器工厂

作用:

  1. 修改构造函数
  2. 扩展类属性和方法
    定义:
   function logClass(params:string){// params是下方传过来的参数
return function(target:any){// target相当于是默认传过来的
log(target);
log(params);
target.prototype.apiUrl = params;
}
}

@logClass("https://baidu.com")// 可以传参
class HttpClient{
constructor(){

}
getData(){

}
}

var http:any = new HttpClient();
console.log(http.apiUrl);// https://baidu.com

可以修改构造函数的写法
function logClass(target:any){
log(target);
return class extends target{
apiUrl:any = "我是修改后的新数据";

getData(){
this.apiUrl = this.apiUrl + "----";
log(this.apiUrl);
}
}
}

@logClass
class HttpClient{
public apiUrl:string | undefined;
constructor(){
this.apiUrl = "我是构造函数里面的apiUrl"
}
getData(){
log(this.apiUrl)

}

var http= new HttpClient();
http.getData();

属性装饰器

作用:

  1. 可以给属性赋值
// 类装饰器
function logClass(params:string){// params是下方传过来的参数
return function(target:any){// target相当于是默认传过来的
log(target);
log(params);
target.prototype.apiUrl = params;
}
}

// 属性装饰器
function logProperty(params:any){
// 固定写法,参数中,target为类对象,attr为参数名称
return function(target:any, attr:any){
log(target);
log(attr);
target[attr] = params;
}
}

@logClass("https://baidu.com")// 可以传参
class HttpClient{

// 这个属性修饰器的作用就是给url赋值初始值
@logProperty("http://baidu.com")
public url:any | undefined;
constructor(){

}
getData(){

}
}

var http:any = new HttpClient();
console.log(http.apiUrl);// https://baidu.com

方法装饰器

用的是最多的

function get(params:any){
return function(target:any, methodName:any, desc:any){
log(target); // 类属性
log(methodName); // 方法名字 getData
log(desc); // 方法的描述,desc.value是方法描述
target.apiUrl = "xxx"; // 修改雷属性
target.run=function(){
log("run");
}
}
}
class HttpClient{
public url:any | undefined;
constructor(){

}
@get("https://www.baidu.com")
getData(){
log(this.url);
}
}

var http:any = new HttpClient();
log(http.apiUrl); // https://www.baidu.com‘
http.run(); // log run

  • 修改当前的方法(主要作用是装饰方法,并把方法的参数给变换类型):
// 这个方法装饰其主要作用就是把参数都给格式化成string类型
function get(params:any){
return function(target:any, methodName:any, desc:any){
log(target); // 类属性
log(methodName); // 方法名字 getData
log(desc.value); // 方法

// 想修改下方法,装饰一下,让他们的所有参数变成string类型,并且打印出来
var oMethod = desc.value;
desc.value = function(...args:any[]){
args = args.map((value) => {
return String(value);
})

// 利用apply进行对象冒充,对getdata进行修改,如果没有apply就相当于是把getData方法给替换掉了

oMethod.apply(this, args);// this就是指function(...args:any[])这个函数
}
}
}
class HttpClient{
public url:any | undefined;
constructor(){

}
@get("https://www.baidu.com")
getData(...args:any[]){
log(args);
log("我是getData方法");
}
}

var http:any = new HttpClient();
http.get(123,"xxx");

// 就会先打印["123", "xxx"]后打印 我是getData方法

方法参数装饰器

用的比较少,类装饰器也可以实现这个功能

  • 运行时候当做函数被调用,可以使用参数张诗琪为累的原型增加一些元素数据,传入下列三个参数:
  • 1对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  • 2方法的名字
  • 3参数在函数参数列表中的索引
function logParams(params:any){
return function(target:any, methodName:any, paramsIndex:any){
log(params);// xxxx
log(target); // 原型对象
log(methodName);// getData
log(paramsIndex); // 0
}
}
class HttpClient{
public url:any | undefined;
constructor(){

}

getData(@logParams("xxxx") uuid:any){
log(uuid); // iii
}
}

var a = new HttpClient();
a.getData("iii");

先后输出:
1. xxxx
2. 原型对象
3. getData
4. 0
5. iii


装饰器执行顺序

当存在 多个装饰器时候:

  • 执行优先级:属性装饰器>方法装饰器>方法参数装饰器>类装饰器

  • 如果有多个同样的装饰器,它会先从后边执行

掘金:前端LeBron

知乎:前端LeBron

持续分享技术博文,关注微信公众号👇🏻

img