TypeScript
本文引用枫枫知道不做商用,仅用于学习枫枫知道
可以购买枫枫知道的课程
建议下载长期维护版
安装之后把node加入环境变量
命令行输入 node -v
npm -v
npm
就是node里面安装第三方包的工具,相当于pip
tsc
它的作用就是将ts文件编译为js
文件
// .ts => .js npm i typescript -g tsc -v
tsc --init // 生成一个json文件 tsc // 会编译项目目录下所有的ts文件 tsc xxx.ts // 只编译xxx.ts文件 tsc -w xxx.ts // 监听xxx.ts 只要保存就编译 node hello_world.js // 执行
npm i ts-node -g ts-node xxx.ts // 直接运行xxx.ts
// hello_world.js const hw :string = "hello_world" console.log(hw) // cmd tsc --init // 生成一个json文件 tsc // 会编译项目目录下所有的ts文件 tsc xxx.ts // 只编译xxx.ts文件 tsc -w xxx.ts // 监听xxx.ts 只要保存就编译 node hello_world.js // 执行
可以是整数,小数,十六进制,八进制,二进制数
let n1 : number = 1 let n2 : number = 1.56 let n3 : number = 0xf let n4 : number = 0o7 let n5 : number = 0b01 let n6 : number = NaN
let s:string = "枫枫"
let b1 :boolean = true let b2 :boolean = false
null,undefined,void
null 指空值(empty value)
undefined 指没有值(missing value)
或者:
undefined 指从未赋值
null 指曾赋过值,但是目前没有值
null 是一个特殊关键字,不是标识符,我们不能将其当作变量来使用和赋值。然而
undefined 却是一个标识符,可以被当作变量来使用和赋值
void是没有返回值
严格模式下 >>> tsc --init
生成的json
文件中 strict:true
false是关闭严格模式
null,undefined它们两个不能相互赋值
let u1: null = null let u2: undefined = undefined
关闭严格模式,它们就可以相互赋值
let u3: null = undefined let u4: undefined = null function setName(name:string):void{ return } function setName(name:string):void{ return 1 // 错误 }
使用any类型,ts就会关闭类型检查,这样就和写js
一模一样了,尽量少用
let a1 : any = "123" a1 = 13 a1.name = "xxx" a1.push("123")
和any的区别
不能点属性
function getNum(num:string|number):number{ return 1 } getNum(123) getNum("354") let div:HTMLElement|null = document.getElementById("#div") function clearTimeout(id: number | undefined): void; // 定时器清除
就是认为将某一种不确定的类型断言为确定的类型
只是在编辑器层面进行的欺骗行为
let img = document.getElementById("#img") img.src = "http://xxx.xxx/img" // 报错 // --------------------------- let img = document.getElementById("#img") as HTMLImageElement img.src = "http://xxx.xxx/img" let img =document.getElementById("#img") img.src = "http://xxx.xxx/img"
两种写法
value as Typevalue document.getElementById("#img") as HTMLImageElement document.getElementById("#img")
function getAny():any{ return "" } let s = (getAny()as string).charAt() let s = (getAny()).charAt()
一般来说,数组里面的元素都应该是同类型的
let a1:string[] = ["e634"] let a2:boolean[] = [false, true] let a3:Array= [1,5,6,7]
let a4:any[] = [1, "s", [123, [13]]] let a5:(string|number)[] = [1,2,"345"]
let a7: [number, number] = [1, 2] console.log(a7[2]) // 防止越界 let a8:[1,string, true] = [1, "", true] let scope: [string, number, number, number] = ["小明", 12, 45, 23]
let arr1: number[][] = [[1, 2], [12]] let arr2: Array> = [[1], [2]]
interface UserInfoFace { name: string } let zhangsan :UserInfoFace = { name: "张三" } console.log(zhangsan.name)
interface UserInfoFace { name: string age?:number addr?:string like?:string[] } let zhangsan :UserInfoFace = { name: "张三", } zhangsan.age = 23 console.log(zhangsan.name)
interface UserInfoFace { readonly id: number name: string age?:number addr?:string like?:string[] } let zhangsan :UserInfoFace = { id: 1, name: "张三", } console.log(zhangsan.id) // zhangsan.id = 12 // 不能修改 zhangsan.age = 23 console.log(zhangsan.name)
interface UserInfoFace { readonly id: number name: string age?:number addr?:string like?:string[] getName: ()=>string setAge: (age:number)=>void } let zhangsan :UserInfoFace = { id: 1, name: "张三", getName: function ():string{ return this.name }, setAge:function (age:number){ this.age = age } } console.log(zhangsan.id) // zhangsan.id = 12 // 不能修改 // zhangsan.age = 23 zhangsan.setAge(21) console.log(zhangsan.age) console.log(zhangsan.getName()) console.log(zhangsan.name)
interface People { gender: number // 0 女 1 男 2 未知 } interface UserInfoFace extends People{ readonly id: number name: string age?:number addr?:string like?:string[] getName: ()=>string setAge: (age:number)=>void } let zhangsan :UserInfoFace = { id: 1, name: "张三", getName: function ():string{ return this.name }, setAge:function (age:number){ this.age = age }, gender: 1 } console.log(zhangsan.id) // zhangsan.id = 12 // 不能修改 // zhangsan.age = 23 zhangsan.setAge(21) console.log(zhangsan.age) console.log(zhangsan.getName()) console.log(zhangsan.name)
会将两个类型中的属性合并
interface People { gender: number // 0 女 1 男 2 未知 } interface UserInfoFace { readonly id: number name: string age?:number addr?:string like?:string[] getName: ()=>string setAge: (age:number)=>void } let zhangsan :UserInfoFace & People = { id: 1, name: "张三", getName: function ():string{ return this.name }, setAge:function (age:number){ this.age = age }, gender: 1 } console.log(zhangsan.id) // zhangsan.id = 12 // 不能修改 // zhangsan.age = 23 zhangsan.setAge(21) console.log(zhangsan.age) console.log(zhangsan.getName()) console.log(zhangsan.name)
type nameType = string | string[] let zhangName :nameType = "张三" let zhangName1 :nameType = ["张三"]
可以用来定义函数
type fn = (name: string, age: number) => string let fn1: fn = function (name: string, age: number): string { return "xxx" } let fn2: fn = (name: string, age: number): string => "xxx"
也可以用来定义对象
type infoType = { readonly name: string, age?: number } let zhangsan:infoType = { name: "张三", }
interface可以继承 type 只能通过 & 交叉类型合并
interface I1{ name: string } interface I2 extends I1{ age: string } type T1 = { name: string } type T2 = { age: number } let t:T1&T2 = { name:"张三", age: 12 }
type 可以定义 联合类型 和 可以使用一些操作符 interface不行
type nameType = string|string[]
interface 遇到重名的会合并 type 不行
interface A1 { name: string } // 遇到相同的会合并 interface A1{ age: number } let a1:A1={ name:"A1", age: 13 } type A1 = { name: string } // 会直接报错 type A1 ={ }
基本定义
function add1(n1:number, n2:number):number{ return n1 + n2 } let add2 = (n1:number, n2:number):number=>{ return n1+n2 }
在参数名的后面,加一个?,表示该参数可选,如果不传,那这个参数就是undefined
并且在实际使用中,要进行类型判断
function add(n1:number, n2?:number):number{ if (typeof n2 === "undefined"){ return n1 } return n1 + (n2 as number) } // 要么不传,要么传正确的类型 add(1) add(1, 2) add(1, "3") // 报错
function add(n1:number, n2:number = 10):number{ return n1 + n2 } console.log(add(1)) // 11 console.log(add(1, 2)) // 3
默认值和可选参数不能同时出现!
interface getUserListRequest { page?:number limit?:number key?:string name?:string } function getUserList(params:getUserListRequest){ console.log(params.limit) // 有类型提示 }
function addFn(n1:number, n2:number):number function addFn(n1:number):number function addFn(n1:number, n2:number = 10):number{ return n1 + n2 } console.log(addFn(1)) console.log(addFn(1, 20))
enum Color { Red, // 0 Blue, // 1 Green, // 2 } let c1:Color = Color.Blue let c2:Color = 5 // 编译会失败 let c3:Color = "234" // 直接失败
如果是纯数字,下面的会进行自动增长
自动从0开始
enum Color { Red=100, Blue, // 101 Green, // 102 Yellow, // 103 }
如果中断了,那么下面的就从中断的地方继续增长
enum Color { Red=100, Blue, // 101 Green=103, // 103 Yellow, // 104 }
它不能自动增长
enum Color { Red="red", Blue, // 报错 }
如果是字符串,那么定义的时候就要全部写上
enum Color { Red="red", Blue="blue", }
enum Color { Red, Blue, Yellow } interface ColorInter { color: Color, blue: Color.Blue } let c1: ColorInter = { color: Color.Red, // 这个color只能是Color里面的值 blue: Color.Blue, // blue就只能写 Color.Blue }
const
枚举主要作用就是节省因为枚举带来的性能消耗
enum Color { Red = 2, Blue, Yellow } interface ColorInter { color: Color, blue: Color.Blue } let c1: ColorInter = { color: Color.Red, // 这个color只能是Color里面的值 blue: Color.Blue, // blue就只能写 Color.Blue }
编译之后
var Color; (function (Color) { Color[Color["Red"] = 2] = "Red"; Color[Color["Blue"] = 3] = "Blue"; Color[Color["Yellow"] = 4] = "Yellow"; })(Color || (Color = {})); var c1 = { color: Color.Red, blue: Color.Blue, // blue就只能写 Color.Blue };
const enum Color { Red = 2, Blue, Yellow } interface ColorInter { color: Color, blue: Color.Blue } let c1: ColorInter = { color: Color.Red, // 这个color只能是Color里面的值 blue: Color.Blue, // blue就只能写 Color.Blue }
编译之后
var c1 = { color: 2 /* Color.Red */, blue: 3 /* Color.Blue */, // blue就只能写 Color.Blue };
泛型就是把类型当做参数
需求:编写一个函数,传入两个参数,返回这两个参数组成的数组。注意,两个参数的类型必须一致
// 例如 fun(1,2) // => [1, 2] fun("2", "2") // => ["2", "2"] // 不使用泛型 function numberArray(a1:number, a2:number):number[]{ return [a1, a2] } function stringArray(a1:string, a2:string):string[]{ return [a1, a2] }
使用泛型
function array(a:T, b:T):T[]{ return [a, b] } console.log(array(1,2)) console.log(array("1","5546")) console.log(array(true, false)) console.log(array(null, null)) console.log(array (1,2))
function array(a:T, b:K):(T|K)[]{ return [a, b] } console.log(array(1,"2")) console.log(array (1,"2"))
比如,我希望我传入的这个参数,能够 .length
那么它的类型就应该是 string或者是数组
function array(a:T, b:T):T[]{ console.log(a.length) return [a, b] } console.log(array("12334","2")) console.log(array(["1", "33"],["2"]))
接口函数
interface Fun { (name: string):string } let fun:Fun = function (name:string):string{ return "" }
接口函数的泛型
interface ArrType{ (a1: T,a2:T):T[] } let fun:ArrType = function (a1:string, a2:string):string[]{ return [a1, a2] }
属性泛型
interface Info{ like: T } let zhangsan: Info = { like: "羽毛球" } let lisi: Info = { like: ["羽毛球"] }
泛型约束
interface Info{ like: T } let zhangsan: Info = { like: "羽毛球" } let lisi: Info = { like: ["羽毛球"] }
和接口继承在一起的时候,不要搞混了
interface People { name?: string } interface Infoextends People{ like: T } let zhangsan: Info = { like: "羽毛球" } let lisi: Info = { like: ["羽毛球"] }
keyof
interface Info { name: string age: number } let zhangsan:Info = { name:"zhangsan", age: 21 } function getInfoValue(info:Info, key: string):void{ // 这个时候,这里会报错,原因是因为传入的这个key,有可能不是Info的属性 console.log(info[key]) } getInfoValue(zhangsan, "name")
使用keyof
interface Info { name: string age: number } let zhangsan:Info = { name:"zhangsan", age: 21 } function getInfoValue(info:Info, key: keyof Info):void{ console.log(info[key]) } getInfoValue(zhangsan, "name") getInfoValue(zhangsan, "age") getInfoValue(zhangsan, "age1") // 不满足keyof的校验,会报错
在getInfoValue
函数的基础上,实现将value返回
interface Info { name: string age: number } let zhangsan:Info = { name:"zhangsan", age: 21 } function getInfoValue(info:Info, key: T):Info[T]{ return info[key] } getInfoValue(zhangsan, "name") getInfoValue(zhangsan, "age")
ECMAScript
的内置对象如果是new 对象的,它的类型就是那个对象的名字
let date: Date = new Date() let regex: RegExp = /12/ let regex1: RegExp = new RegExp(/\s+/) let err: Error = new Error("123")
BOM
常见的DOM类型
都在HTMLElementTagNameMap
里面
"a": HTMLAnchorElement; "body": HTMLBodyElement; "button": HTMLButtonElement; "canvas": HTMLCanvasElement; "div": HTMLDivElement; "footer": HTMLElement; "form": HTMLFormElement; "h1": HTMLHeadingElement; "head": HTMLHeadElement; "header": HTMLElement; "html": HTMLHtmlElement; "title": HTMLTitleElement; "input": HTMLInputElement; "img": HTMLImageElement;
const body: HTMLElement = document.body; const divList: NodeList = document.querySelectorAll('div'); document.addEventListener('click', (e: MouseEvent) => { });
let l :Location = location let s:Storage = localStorage let s1:Storage = sessionStorage let c:string = document.cookie
使用fetch 请求一个json
文件,把响应的数据显示出来
json
文件
interface dataType { name: string age: number } interface responseType{ code: number data: T[] msg: string } fetch("/data.json").then((response: Response) => response.json()).then((res: responseType ) => { //返回一个Promise对象,要用.then() let app = document.getElementById("app") as HTMLDivElement res.data.forEach((item: dataType) => { let li = document.createElement("li") li.innerHTML = ` name: ${item.name} age:${item.age}` app.append(li) }) })
改成以下这种
interface dataType { name: string age: number } interface responseType{ code: number data: T[] msg: string } async function getList():Promise >{ let response = await fetch("/data.json") // 一个await相当于一个.then() return response.json() } async function getData(){ let res:responseType = await getList() let app = document.getElementById("app") as HTMLDivElement res.data.forEach((item:dataType)=>{ let li = document.createElement("li") li.innerHTML = ` name: ${item.name} age:${item.age}` app.append(li) }) } getData()
注意:tsc index.ts可能会报错,直接执行 tsc命令编译就行
错误原因是,tsc
编译单个文件会使用默认的tsconfig.json
的配置,而且编译单个文件无法指定配置文件
tsconfig.json
// 生成`tsconfig.json`文件 // tsc --init // tsc -init { "compilerOptions": { // "incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度 // "tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置 // "diagnostics": false, // 打印诊断信息 "target": "ES5", // 目标语言的版本 "module": "ES2015", // 生成代码的模板标准 // "outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD", "lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost, 如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array", // "allowJs": true, // 允许编译器编译JS,JSX文件 "checkJs": false, // 不允许在JS文件中报错,通常与allowJS一起使用 "outDir": "./dist", // 指定输出目录 "rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构 // "declaration": false, // 生成声明文件,开启后会自动生成声明文件 // "declarationDir": "./file", // 指定生成声明文件存放目录 // "emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件 // "sourceMap": true, // 生成目标文件的sourceMap文件 // "inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中 // "declarationMap": true, // 为声明文件生成sourceMap "typeRoots": [], // 声明文件目录,默认时node_modules/@types "types": [], // 加载的声明文件包 "removeComments":true, // 删除注释 // "noEmit": false, // 不输出文件,即编译后不会生成任何js文件 // "noEmitOnError": true, // 发送错误时不输出任何文件 // "noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用 // "importHelpers": true, // 通过tslib引入helper函数,文件必须是模块 // "downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现 "strict": true, // 开启所有严格的类型检查 "alwaysStrict": true, // 在代码中注入'use strict' "noImplicitAny": true, // 不允许隐式的any类型 "strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量 "strictFunctionTypes": true, // 不允许函数参数双向协变 "strictPropertyInitialization": true, // 类的实例属性必须初始化 "strictBindCallApply": true, // 严格的bind/call/apply检查 "noImplicitThis": true, // 不允许this有隐式的any类型 "noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错) "noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错) "noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行) "noImplicitReturns": true, //每个分支都会有返回值 "esModuleInterop": true, // 允许export=导出,由import from 导入 "allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块 "moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入 "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录 "paths": { // 路径映射,相对于baseUrl // 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置 "jquery": ["node_modules/jquery/dist/jquery.min.js"] }, "rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错 // "listEmittedFiles": true, // 打印输出文件 // "listFiles": true // 打印编译的文件(包括引用的声明文件) }, // 指定一个匹配列表(属于自动指定该路径下的所有ts相关文件) "include": [ ], // 指定一个排除列表(include的反向操作) "exclude": [ ], // 指定哪些文件使用该配置(属于手动一个个指定文件) "files": [ "index.ts" ] }
{ "compilerOptions": { "target": "ES5", // 目标语言的版本 "module": "AMD", // 生成代码的模板标准 "lib": ["DOM", "ES2015"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost, 如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array", "allowJs": true, // 允许编译器编译JS,JSX文件 "checkJs": true, // 不允许在JS文件中报错,通常与allowJS一起使用 "outDir": "./dist", // 指定输出目录 "outFile": "./dist/app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD", "removeComments":false, // 删除注释 "strict": true, // 开启所有严格的类型检查 "noImplicitAny": true, // 不允许隐式的any类型 "strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量 "noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错) "noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行) }, // 指定一个匹配列表(属于自动指定该路径下的所有ts相关文件) "include": [ "./*", ], // 指定一个排除列表(include的反向操作) "exclude": [ ], }
d.ts
声明文件在引入一些年久的js文件的时候,会存在编辑器不提示或报错的情况
主要原因是这些库是js编写的,开发者并没有编写为ts支持的声明文件
可以尝试输入
npm i @types/包名 -D
安装某个库的声明文件
这是一段express的一段代码,我们尝试在ts的环境下运行
// npm i -D express import express from "express" const app = express() const router = express.Router() app.use("/", router) router.get("/", (req, res) => { res.json({msg: "hi fengfeng",code: 200}) }) app.listen(9000, () => { console.log("server: http://127.0.0.1:9000") })
import axios from "axios"; import express from "express" const app = express() const router = express.Router() app.use("/", router) router.get("/", (req: any, res: any) => { res.json({code: 200}) }) app.listen(9000, () => { console.log("running: :9000") })
发现报错,且没有提示
在项目根目录下创建一个typings/
目录
在目录下创建express.d.ts
的声明文件
declare module "express" { interface Router { get(path: string, cb:(req:any, res:any)=>void):void } interface App { use(path: string, router: any): void listen(port: number, cb?:()=>void) } interface Express { (): App Router(): Router, } const express: Express export default express; }
构造函数
class People { name: string constructor(name: string) { // this表示当前对象 this.name = name } getName() { console.log(this.name) } } let p: People = new People("枫枫") console.log(p.name) // 访问属性 p.getName() // 调用方法
public:类的所有成员都可以被类的实例获取(默认)
private:类成员只能在当前类中被访问
protected:类成员在类以及子类中可以被访问
class Man { private mName: string protected mAge: number constructor(name: string, age: number) { this.mName = name this.mAge = age } getMName(){ console.log(this.mName) // private 只能在当前类中访问 } } class People extends Man { name: string private age: number constructor(name: string, age: number = 18) { super(name, age) // 调用父类的构造方法 this.name = name this.age = age } getName() { console.log(this.mAge) // protected 类的子类可以访问 console.log(this.name) } } let p: People = new People("枫枫") console.log(p.name) console.log(p.age) // 不能在外面访问
只能通过类去调用
class People { static ClassName:string = "类名" nickName: string constructor(nickName: string) { this.nickName = nickName } static getName() { console.log(People.ClassName) } } let p: People = new People("枫枫") People.getName()
注意
public和上面三个关键字可以连用
static作用于属性上,可以被称为静态属性,类属性
static作用于方法上,可以被称为静态方法,类方法
class People { private static ClassName:string = "类名" nickName: string constructor(nickName: string) { this.nickName = nickName } static getName() { console.log(People.ClassName) } } let p: People = new People("枫枫") People.getName() console.log(People)
注意,interface只能定义公共属性和方法
interface PersonType { name: string age: number getName():string } class Person implements PersonType{ name: string age: number constructor(name: string, age: number) { this.name = name this.age = age } getName(): string { return this.name } } let p1 = new Person("枫枫", 12)
装饰器是 ES7 提出的实验性功能
开启装饰器
"experimentalDecorators": true, "emitDecoratorMetadata": true,
ClassDecorator
装饰器作用,在不改变原有代码的前提下,实现在原有逻辑的前后增加功能
const dr: ClassDecorator = (fn) => { // fn就是Animal的构造函数 console.log(fn) // 可以给对象挂载属性和方法 fn.prototype.myName = "枫枫" fn.prototype.getMyName = function () { console.log(fn.prototype.myName) } } @dr class Animal { } let an = new Animal() console.log((an as any).myName); (an as any).getMyName()
原理
const dr: ClassDecorator = (fn) => { // fn就是Animal的构造函数 console.log(fn) // 可以给对象挂载属性和方法 fn.prototype.myName = "枫枫" fn.prototype.getMyName = function () { console.log(fn.prototype.myName) } } class Animal { } dr(Animal) let an = new Animal() console.log((an as any).myName); (an as any).getMyName()
使用函数柯里化
const big = (name: string):ClassDecorator=>{ return (fn)=>{ fn.prototype.myName = name fn.prototype.getMyName = function () { console.log(fn.prototype.myName) } } } @big("zhangsan") class Animal { } let an = new Animal() console.log((an as any).myName); (an as any).getMyName()
MethodDecorator
const get: MethodDecorator = (target, propertyKey, descriptor: PropertyDescriptor) => { descriptor.value("获取的数据") } class Animal { @get list(data: string) { console.log(data) } } let an = new Animal() import axios from "axios"; interface VideoInfo { id: number title: string userName:string userPic: string coverUrl: string playUrl: string duration: string } interface VideoResponse{ code: number message: string result: { total: number list: T[] } } const get = (url: string): MethodDecorator => { return (target, propertyKey, descriptor: PropertyDescriptor) => { axios.get(url).then(res=>{ descriptor.value(res.data) }) } } class Student { // @get("") create() { } @get("https://api.apiopen.top/api/getHaoKanVideo") list(data: VideoResponse ) { data.result.list.forEach((value, index, array)=>{ console.log(index, value.id, value.title, value.userName) }) } } let s1 = new Student()