TypeScript 快速上⼿ (3:装饰器)
目录
一、简介
二、类装饰器
基本语法
应用举例
关于返回值
关于构造类型
替换被装饰的类
三、装饰器工厂
四、装饰器组合
五、属性装饰器
基本语法
关于属性遮蔽
应用举例
六、方法装饰器
基本语法
应用举例
七、访问器装饰器
基本语法
应用举例
八、参数装饰器
基本语法
应用举例
一、简介
- 装饰器本质是一种特殊的函数,它可以对:类、属性、方法、参数进行扩展,同时能让代码更简洁。
- 装饰器自
2015
年在ECMAScript-6
中被提出到现在,已将近10年。 - 截止目前,装饰器依然是实验性特性 ,需要开发者手动调整配置,来开启装饰器支持。
- 装饰器有 5 种:
1⃣类装饰器
2⃣属性装饰器
3⃣方法装饰器
4⃣访问器装饰器
5⃣参数装饰器
备注:虽然
TypeScript5.0
中可以直接使用**类装饰器**
,但为了确保其他装饰器可用,现阶段使用时,仍建议使用experimentalDecorators
配置来开启装饰器支持,而且不排除在来的版本中,官方会进一步调整装饰器的相关语法!
参考:《TypeScript 5.0发版公告》
二、类装饰器
基本语法
:::info
类装饰器是一个应用在类声明上的函数,可以为类添加额外的功能,或添加额外的逻辑。
:::
/* Demo函数会在Person类定义时执行参数说明:○ target参数是被装饰的类,即:Person
*/
function Demo(target: Function) {console.log(target)
}// 使用装饰器
@Demo
class Person { }
应用举例
:::tips
需求:定义一个装饰器,实现Person
实例调用toString
时返回JSON.stringify
的执行结果。
:::
// 使用装饰器重写toString方法 + 封闭其原型对象
function CustomString(target: Function) {// 向被装饰类的原型上添加自定义的 toString 方法target.prototype.toString = function () {return JSON.stringify(this)}// 封闭其原型对象,禁止随意操作其原型对象Object.seal(target.prototype)
}// 使用 CustomString 装饰器
@CustomString
class Person {constructor(public name: string, public age: number) { }speak() {console.log('你好呀!')}
}/* 测试代码如下 */
let p1 = new Person('张三', 18)
// 输出:{"name":"张三","age":18}
console.log(p1.toString())
// 禁止随意操作其原型对象
interface Person {a: any
}
// Person.prototype.a = 100 // 此行会报错:Cannot add property a, object is not extensible
// console.log(p1.a)
关于返回值
:::info
类装饰器有返回值:若类装饰器返回一个新的类,那这个新类将替换掉被装饰的类。
类装饰器无返回值:若类装饰器无返回值或返回undefined
,那被装饰的类不会被替换。
:::
关于构造类型
在 TypeScript 中,
Function
类型所表示的范围十分广泛,包括:普通函数、箭头函数、方法等等。但并非Function
类型的函数都可以被new
关键字实例化,例如箭头函数是不能被实例化的,那么 TypeScript 中概如何声明一个构造类型呢?有以下两种方式:
/*○ new 表示:该类型是可以用new操作符调用。○ ...args 表示:构造器可以接受【任意数量】的参数。○ any[] 表示:构造器可以接受【任意类型】的参数。○ {} 表示:返回类型是对象(非null、非undefined的对象)。
*/// 定义Constructor类型,其含义是构造类型
type Constructor = new (...args: any[]) => {};function test(fn:Constructor){}
class Person {}
test(Person)
// 定义一个构造类型,且包含一个静态属性 wife
type Constructor = {new(...args: any[]): {}; // 构造签名wife: string; // wife属性
};function test(fn:Constructor){}
class Person {static wife = 'asd'
}
test(Person)
替换被装饰的类
对于高级一些的装饰器,不仅仅是覆盖一个原型上的方法,还要有更多功能,例如添加新的方法和状态。
:::tips
需求:设计一个LogTime
装饰器,可以给实例添加一个属性,用于记录实例对象的创建时间,再添加一个方法用于读取创建时间。
:::
// User接口
interface User {getTime(): Datelog(): void
}// 自定义类型Class
type Constructor = new (...args: any[]) => {}// 创建一个装饰器,为类添加日志功能和创建时间
function LogTime<T extends Constructor>(target: T) {return class extends target {createdTime: Date;constructor(...args: any[]) {super(...args);this.createdTime = new Date(); // 记录对象创建时间}getTime() {return `该对象创建时间为:${this.createdTime}`;}};
}@LogTime
class User {constructor(public name: string,public age: number) { }speak() {console.log(`${this.name}说:你好啊!`)}
}const user1 = new User('张三', 13);
user1.speak()
console.log(user1.getTime())
三、装饰器工厂
装饰器工厂是一个返回装饰器函数的函数,可以为装饰器添加参数,可以更灵活地控制装饰器的行为。
:::tips
需求**:**定义一个LogInfo
类装饰器工厂,实现Person
实例可以调用到introduce
方法,且introduce
中输出内容的次数,由LogInfo
接收的参数决定。
:::
interface Person {introduce: () => void
}// 定义一个装饰器工厂 LogInfo,它接受一个参数 n,返回一个类装饰器
function LogInfo(n:number) {// 装饰器函数,target 是被装饰的类return function(target: Function){target.prototype.introduce = function () {for (let i = 0; i < n; i++) {console.log(`我的名字:${this.name},我的年龄:${this.age}`)}}}
}@LogInfo(5)
class Person {constructor(public name: string,public age: number) { }speak() {console.log('你好呀!')}
}let p1 = new Person('张三', 18)
// console.log(p1) // 打印的p1是:_classThis,转换的JS版本比较旧时,会出现,不必纠结
p1.speak()
p1.introduce()
四、装饰器组合
装饰器可以组合使用,执行顺序为:先【由上到下】的执行所有的装饰器工厂,依次获取到装饰器,然后再【由下到上】执行所有的装饰器。
//装饰器
function test1(target:Function) {console.log('test1')
}
//装饰器工厂
function test2() {console.log('test2工厂')return function (target:Function) { console.log('test2')}
}
//装饰器工厂
function test3() {console.log('test3工厂')return function (target:Function) { console.log('test3')}
}
//装饰器
function test4(target:Function) {console.log('test4')
}@test1
@test2()
@test3()
@test4
class Person { }/*控制台打印:test2工厂test3工厂test4test3test2test1
*/
// 自定义类型Class
type Constructor = new (...args: any[]) => {}interface Person {introduce():voidgetTime():void
}// 使用装饰器重写toString方法 + 封闭其原型对象
function customToString(target: Function) {// 向被装饰类的原型上添加自定义的 toString 方法target.prototype.toString = function () {return JSON.stringify(this)}// 封闭其原型对象,禁止随意操作其原型对象Object.seal(target.prototype)
}// 创建一个装饰器,为类添加日志功能和创建时间
function LogTime<T extends Constructor>(target: T) {return class extends target {createdTime: Date;constructor(...args: any[]) {super(...args);this.createdTime = new Date(); // 记录对象创建时间}getTime() {return `该对象创建时间为:${this.createdTime}`;}};
}// 定义一个装饰器工厂 LogInfo,它接受一个参数 n,返回一个类装饰器
function LogInfo(n:number) {// 装饰器函数,target 是被装饰的类return function(target: Function){target.prototype.introduce = function () {for (let i = 0; i < n; i++) {console.log(`我的名字:${this.name},我的年龄:${this.age}`)}}}
}@customToString
@LogInfo(3)
@LogTime
class Person {constructor(public name: string,public age: number) { }speak() {console.log('你好呀!')}
}const p1 = new Person('张三',18)
console.log(p1.toString())
p1.introduce()
console.log(p1.getTime())
五、属性装饰器
基本语法
/* 参数说明:○ target: 对于静态属性来说值是类,对于实例属性来说值是类的原型对象。○ propertyKey: 属性名。
*/
function Demo(target: object, propertyKey: string) {console.log(target,propertyKey)
}class Person {@Demo name: string@Demo age: number@Demo static school:stringconstructor(name: string, age: number) {this.name = namethis.age = age}
}const p1 = new Person('张三', 18)
关于属性遮蔽
如下代码中:当构造器中的
this.age = age
试图在实例上赋值时,实际上是调用了原型上age
属性的set
方法。
class Person {name: stringage: numberconstructor(name: string, age: number) {this.name = namethis.age = age}
}let value = 99
// 使用defineProperty给Person原型添加age属性,并配置对应的get与set
Object.defineProperty(Person.prototype, 'age', {get() {return value},set(val) {value = val}
})const p1 = new Person('张三', 18)
console.log(p1.age) //18
console.log(Person.prototype.age)//18
应用举例
:::tips
需求:定义一个State
属性装饰器,来监视属性的修改。
:::
// 声明一个装饰器函数 State,用于捕获数据的修改
function State(target: object, propertyKey: string) {// 存储属性的内部值let key = `__${propertyKey}`;// 使用 Object.defineProperty 替换类的原始属性// 重新定义属性,使其使用自定义的 getter 和 setterObject.defineProperty(target, propertyKey, {get () {return this[key]},set(newVal: string){console.log(`${propertyKey}的最新值为:${newVal}`);this[key] = newVal},enumerable: true, configurable: true,});
}class Person {name: string;//使用State装饰器@State age: number;school = 'atguigu';constructor(name: string, age: number) {this.name = name;this.age = age;}
}const p1 = new Person('张三', 18);
const p2 = new Person('李四', 30);p1.age = 80
p2.age = 90console.log('------------------')
console.log(p1.age) //80
console.log(p2.age) //90
六、方法装饰器
基本语法
/* 参数说明:○ target: 对于静态方法来说值是类,对于实例方法来说值是原型对象。○ propertyKey:方法的名称。○ descriptor: 方法的描述对象,其中value属性是被装饰的方法。
*/
function Demo(target: object, propertyKey: string, descriptor: PropertyDescriptor){console.log(target)console.log(propertyKey)console.log(descriptor)
}class Person {constructor(public name:string,public age:number,){}// Demo装饰实例方法@Demo speak(){console.log(`你好,我的名字:${this.name},我的年龄:${this.age}`)}// Demo装饰静态方法@Demo static isAdult(age:number) {return age >= 18;}
}const p1 = new Person('张三',18)
p1.speak()
应用举例
:::tips
需求:
- 定义一个
Logger
方法装饰器,用于在方法执行前和执行后,均追加一些额外逻辑。 - 定义一个
Validate
方法装饰器,用于验证数据。
:::
function Logger(target: object, propertyKey: string, descriptor: PropertyDescriptor){// 保存原始方法const original = descriptor.value;// 替换原始方法descriptor.value = function (...args:any[]) {console.log(`${propertyKey}开始执行......`)const result = original.call(this, ...args)console.log(`${propertyKey}执行完毕......`)return result;}
}function Validate(maxValue:number){return function (target: object, propertyKey: string, descriptor: PropertyDescriptor){// 保存原始方法const original = descriptor.value;// 替换原始方法descriptor.value = function (...args: any[]) {// 自定义的验证逻辑if (args[0] > maxValue) {throw new Error('年龄非法!')}// 如果所有参数都符合要求,则调用原始方法return original.apply(this, args);};}
}class Person {constructor(public name:string,public age:number,){}@Logger speak(){console.log(`你好,我的名字:${this.name},我的年龄:${this.age}`)}@Validate(120)static isAdult(age:number) {return age >= 18;}
}const p1 = new Person('张三',18)
p1.speak()
console.log(Person.isAdult(100))
七、访问器装饰器
基本语法
/* 参数说明:○ target: 1. 对于实例访问器来说值是【所属类的原型对象】。2. 对于静态访问器来说值是【所属类】。○ propertyKey:访问器的名称。○ descriptor: 描述对象。
*/
function Demo(target: object, propertyKey: string, descriptor: PropertyDescriptor) {console.log(target)console.log(propertyKey)console.log(descriptor)
}class Person {@Demoget address(){return '北京宏福科技园'}@Demostatic get country(){return '中国'}
}
应用举例
:::tips
需求:对Weather
类的temp
属性的set
访问器进行限制,设置的最低温度-50
,最高温度50
:::
function RangeValidate(min: number, max: number) {return function (target: object, propertyKey: string, descriptor: PropertyDescriptor) {// 保存原始的 setter 方法,以便在后续调用中使用const originalSetter = descriptor.set;// 重写 setter 方法,加入范围验证逻辑descriptor.set = function (value: number) {// 检查设置的值是否在指定的最小值和最大值之间if (value < min || value > max) {// 如果值不在范围内,抛出错误throw new Error(`${propertyKey}的值应该在 ${min} 到 ${max}之间!`);}// 如果值在范围内,且原始 setter 方法存在,则调用原始 setter 方法if (originalSetter) {originalSetter.call(this, value);}};};
}class Weather {private _temp: number;constructor(_temp: number) {this._temp = _temp;}// 设置温度范围在 -50 到 50 之间@RangeValidate(-50,50) set temp(value) {this._temp = value;}get temp() {return this._temp;}
}const w1 = new Weather(25);
console.log(w1)
w1.temp = 67
console.log(w1)
八、参数装饰器
基本语法
/* 参数说明:○ target:1.如果修饰的是【实例方法】的参数,target 是类的【原型对象】。2.如果修饰的是【静态方法】的参数,target 是【类】。○ propertyKey:参数所在的方法的名称。○ parameterIndex: 参数在函数参数列表中的索引,从 0 开始。
*/
function Demo(target: object, propertyKey: string, parameterIndex: number) {console.log(target)console.log(propertyKey)console.log(parameterIndex)
}// 类定义
class Person {constructor(public name: string) { }speak(@Demo message1: any, mesage2: any) {console.log(`${this.name}想对说:${message1},${mesage2}`);}
}
应用举例
:::tips
需求:定义方法装饰器Validate
,同时搭配参数装饰器NotNumber
,来对speak
方法的参数类型进行限制。
:::
function NotNumber(target: any, propertyKey: string, parameterIndex: number) {// 初始化或获取当前方法的参数索引列表let notNumberArr: number[] = target[`__notNumber_${propertyKey}`] || [];// 将当前参数索引添加到列表中notNumberArr.push(parameterIndex);// 将列表存储回目标对象target[`__notNumber_${propertyKey}`] = notNumberArr;
}// 方法装饰器定义
function Validate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {const method = descriptor.value;descriptor.value = function (...args: any[]) {// 获取被标记为不能为空的参数索引列表const notNumberArr: number[] = target[`__notNumber_${propertyKey}`] || [];// 检查参数是否为 null 或 undefinedfor (const index of notNumberArr) {if (typeof args[index] === 'number') {throw new Error(`方法 ${propertyKey} 中索引为 ${index} 的参数不能是数字!`)}}// 调用原始方法return method.apply(this, args);};return descriptor;
}// 类定义
class Student {name: string;constructor(name: string) {this.name = name;}@Validatespeak(@NotNumber message1: any, mesage2: any) {console.log(`${this.name}想对说:${message1},${mesage2}`);}
}// 使用
const s1 = new Student("张三");
s1.speak(100, 200);
相关文章:
TypeScript 快速上⼿ (3:装饰器)
目录 一、简介 二、类装饰器 基本语法 应用举例 关于返回值 关于构造类型 替换被装饰的类 三、装饰器工厂 四、装饰器组合 五、属性装饰器 基本语法 关于属性遮蔽 应用举例 六、方法装饰器 基本语法 应用举例 七、访问器装饰器 基本语法 应用举例 八、参数装…...

el-input设置后缀显示单位并阻止滚轮微调
项目中收集form表单信息时,有时会需要在el-input后面显示单位,效果如图: 当然,我们可以直接在输入框后面加上单位,但直接给输入框上加单位不管是视图上还是用户体验上看起来都要好一点 element-plus / element-ui给我…...

Redis Key的过期策略
Redis 的过期策略主要是指管理和删除那些设定了过期时间的键,以确保内存的有效使用和数据的及时清理。 具体来说,Redis 有三种主要的过期策略:定期删除(Scheduled Deletion)、惰性删除(Lazy Deletion&#…...

数据结构:时间复杂度与空间复杂度
目录 算法效率时间复杂度大O渐进表示法时间复杂度计算案例 空间复杂度空间复杂度案例 复杂度算法题 算法效率 算法在编写成可执行程序后,运⾏时需要耗费时间资源和空间(内存)资源 。因此衡量⼀个算法的好坏,⼀般是从时间和空间两个维度来衡量的…...

C语言实现贪吃蛇小游戏
✅博客主页:爆打维c-CSDN博客 🐾 🔹分享c语言知识及代码 🐾 目录 游戏展示视频 一、项目准备工作 二、功能实现分析 1.游戏开始 a.设置本地化、创建窗口、标题 b.隐藏光标,封装定位光标的函数 c.打印欢迎界面及提示信息 …...
深入解析包裹信息管理系统:关系型数据库逻辑数据模型设计、超类实体与派生属性探讨
目录 案例 【题目】 【问题 1】(14分) 【问题 2】(6分) 【问题 3】(5分) 【答案】 【问题 1】解析 【问题 2】解析 【问题 3】解析 案例 阅读下列说明,回答问题 1 至问题 3。 【题目】 某企业委托软件公司开发包裹信息管理系统,以便于对该企业…...

Cyber Weekly #24
赛博新闻 1、OpenAI发布最强模型o1 本周四(9月12日),OpenAI宣布推出OpenAIo1系列模型,标志着AI推理能力的新高度。o1系列包括性能强大的o1以及经济高效的o1-mini,适用于不同复杂度的推理任务。新模型在科学、编码、数…...

Java多线程面试精讲:源于技术书籍的深度解读
写在前面 ⭐️在无数次的复习巩固中,我逐渐意识到一个问题:面对同样的面试题目,不同的资料来源往往给出了五花八门的解释,这不仅增加了学习的难度,还容易导致概念上的混淆。特别是当这些信息来自不同博主的文章或是视…...

【Elasticsearch系列七】索引 crud
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

快速生成服务器响应json-server的安装和使用
json-server介绍地址:https://www.geeksforgeeks.org/json-server-setup-and-introduction/ 1.json-server是什么? 基于自定义的json文件,快速生成服务端响应,可用于前端调试接口 2.安装和卸载json-server 2.1 安装: 使用npm命令: npm install -g json-server 2.2 卸载 npm …...
增强LinkedList实现瑞士轮赛制编排
前言 LinkedList底层虽然是基于链表实现,但是由于其对底层节点进行了封装,导致无法操作底层Node对象。这也为使用上带来了很多不便,比如我之前遇到的一个需求:将n个队伍按照瑞士轮进行编排,组成n/2个队伍,…...

C++编译环境(IDE)推荐及安装
IDE是什么 嗨嗨嗨,我又来水博文了 今天来给大家推荐几款好用的IDE IDE是集成开发环境(Integrated Development Environment)的缩写,是一种软件应用程序,提供了用于软件开发的各种工具和功能,包括代码编辑…...

Android 12系统源码_窗口管理(八)WindowConfiguration的作用
前言 在Android系统中WindowConfiguration这个类用于管理与窗口相关的设置,该类存储了当前窗口的显示区域、屏幕的旋转方向、窗口模式等参数,应用程序通过该类提供的信息可以更好的适配不同的屏幕布局和窗口环境,以提高用户体验。 一、类定…...
已读论文创新点合集
系列文章目录 文章目录 系列文章目录一、《LAMM: Label Alignment for Multi-Modal Prompt Learning》二、《MaPLe: Multi-modal Prompt Learning》三、《Learning to Prompt for Vision-Language Models》CoOp 一、《LAMM: Label Alignment for Multi-Modal Prompt Learning》…...
12_持久化数据结构
菜鸟:老鸟,我在处理一个项目时遇到了问题。我需要频繁地修改和查询一个数据结构,但每次修改后我都得复制整个结构,性能实在是太低了。有没有什么办法可以高效地处理这种情况? 老鸟:你提到了一个很有意思的…...

【计算机网络】IP, 以太网, ARP, DNS
IP, 以太网, ARP, DNS IP协议回顾IP地址报文格式功能介绍地址管理IP地址数量问题初识 NAT 机制通信机制IP数量的解决方案网段划分特殊IP地址 路由选择 以太网协议报文格式源MAC/目的MACMAC地址是什么MAC地址格式MAC的作用 ARPDNS初识DNSDNS主要功能DNS的查询过程 IP协议 回顾I…...

OpenCore Legacy Patcher 2.0.0 发布,83 款不受支持的 Mac 机型将能运行最新的 macOS Sequoia
在不受支持的 Mac 上安装 macOS Sequoia (OpenCore Legacy Patcher v2.0.0) Install macOS on unsupported Macs 请访问原文链接:https://sysin.org/blog/install-macos-on-unsupported-mac/,查看最新版。原创作品,转载请保留出处。 作者主…...

爆改YOLOv8|使用MobileNetV4替换yolov8的Backbone
1,本文介绍 MobileNetV4 是最新的 MobileNet 系列模型,专为移动设备优化。它引入了通用反转瓶颈(UIB)和 Mobile MQA 注意力机制,提升了推理速度和效率。通过改进的神经网络架构搜索(NAS)和蒸馏…...

C语言 | Leetcode C语言题解之第406题根据身高重建队列
题目: 题解: int cmp(const void* _a, const void* _b) {int *a *(int**)_a, *b *(int**)_b;return a[0] b[0] ? a[1] - b[1] : b[0] - a[0]; }int** reconstructQueue(int** people, int peopleSize, int* peopleColSize, int* returnSize, int** …...

【Git】初识Git
本篇文章的环境是在 Ubuntu/Linux 环境下编写的 文章目录 版本控制器Git 基本操作安装 Git创建 Git 本地仓库配置 Git认识工作区、暂存区、版本库添加文件修改文件版本回退撤销修改删除文件 版本控制器 在日常工作和学习中,老板/老师要求我们修改文档,…...
零基础在实践中学习网络安全-皮卡丘靶场(第十四期-XXE模块)
本期内容涉及到很多前面的内容,因此复习后可以更好的了解本期内容 介绍 XXE -"xml external entity injection"即"xml外部实体注入漏洞"。 概括一下就是"攻击者通过向服务器注入指定的xml实体内容,从而让服务器按照指定的配置进行执行,导…...
atc abc409E
原题链接:E - Pair Annihilation 题目背景: n 个点 n - 1 条边的有权无向图,每个点都有一个值,两个连通的点的值可以互相抵消,既将u 的 -1 传给 v 时可以抵消掉 v 的 1 并花费边权值;求最小花费。 考察算…...
Hadolint:Dockerfile 语法检查与最佳实践验证的终极工具
在容器化应用开发的浪潮中,Dockerfile 作为构建 Docker 镜像的核心配置文件,其质量直接影响着应用的安全性、稳定性和可维护性。然而,随着项目复杂度的增加,手动检查 Dockerfile 不仅耗时,还容易遗漏潜在问题。今天,我要向大家介绍一款强大的工具——Hadolint,它将彻底改…...

FreeCAD:开源世界的三维建模利器
FreeCAD 开发模式 FreeCAD的开发采用多语言协作模式,其核心框架与高性能模块主要使用C构建,而用户界面与扩展功能则通过Python脚本实现灵活定制。具体来说: C核心层:作为基础架构,C负责实现与Open CASCADE Technology…...
链表题解——环形链表【LeetCode】
141. 环形链表 方法一 核心思想: 使用一个集合 seen 来记录已经访问过的节点。遍历链表,如果当前节点已经存在于集合中,说明链表存在环;否则,将当前节点添加到集合中,继续遍历。如果遍历结束(h…...
白银6月想法
一、市场回顾 2025年5月,SHFE白银主力合约总体呈现出震荡偏强的运行格局。从2025年5月1日至2025年5月30日,白银期货价格整体运行在7944元至8342元区间内,最高价出现在5月22日的8342.0元,最低价则为5月15日的7944元。最终在5月30日…...
Android实现点击Notification通知栏,跳转指定activity页面
效果 1、点击通知栏通知,假如app正在运行,则直接跳转到指定activity显示具体内容,在指定activity中按返回键返回其上一级页面。 2、点击通知栏通知,假如app已经退出,先从SplashActivity进入,显示app启动界…...

YOLO11解决方案之分析
概述 Ultralytics提供了一系列的解决方案,利用YOLO11解决现实世界的问题,包括物体计数、模糊处理、热力图、安防系统、速度估计、物体追踪等多个方面的应用。 Ultralytics提供了三种基本的数据可视化类型:折线图(面积图…...
机器学习复习3--模型的选择
选择合适的机器学习模型是机器学习项目成功的关键一步。这通常不是一个一蹴而就的过程,而是需要综合考虑多个因素,并进行实验和评估。 1. 理解问题本质 这是模型选择的首要步骤。需要清晰地定义试图解决的问题类型: 监督学习 : 数据集包含…...

Windows平台RTSP/RTMP播放器C#接入详解
大牛直播SDK在Windows平台下的RTSP、RTMP播放器模块,基于自研高性能内核,具备极高的稳定性与行业领先的超低延迟表现。相比传统基于FFmpeg或VLC的播放器实现,SmartPlayer不仅支持RTSP TCP/UDP自动切换、401鉴权、断网重连等网络复杂场景自适应…...