【go语言】reflect包与类型推断
reflect 包的核心概念
Go 中的反射涉及两个核心概念:
- Type:表示一个类型的结构体,
reflect.Type是类型的描述。 - Value:表示一个值的结构体,
reflect.Value是一个具体值的包装。
反射让我们能够动态地访问对象的类型和数据,并根据需要对其进行操作。
常用类型
reflect.Type
reflect.Type 是对 Go 类型的描述。可以通过它获取有关类型的信息,比如类型名、类型的种类、是否是指针、结构体的字段等。
常见方法:
t.Kind():获取reflect.Type的底层类型(如int、struct、slice等)。t.Name():获取类型的名称,仅对命名类型有效。t.NumField():获取结构体类型的字段数。t.Field(i):获取结构体的第i个字段。
reflect.Value
reflect.Value 代表一个变量的值,它包含了具体的值,可以通过它获取或修改数据。
常见方法:
v.Kind():获取reflect.Value的底层类型(如int、struct、slice等)。v.Interface():将reflect.Value转换为interface{}类型。v.Set():修改reflect.Value的值(需要是可修改的,即传入指针)。v.Type():获取reflect.Value的类型。v.String():获取reflect.Value的字符串表示。
常见的反射操作
获取类型和值
使用 reflect.TypeOf 获取类型,使用 reflect.ValueOf 获取值。
package mainimport ("fmt""reflect"
)func main() {var x int = 42// 获取类型t := reflect.TypeOf(x)// 获取值v := reflect.ValueOf(x)fmt.Println("Type:", t) // 输出:Type: intfmt.Println("Value:", v) // 输出:Value: 42
}
动态修改值
reflect 允许我们在运行时动态修改值。要修改值,必须传递指向变量的指针。
package mainimport ("fmt""reflect"
)func main() {var x int = 42p := reflect.ValueOf(&x) // 传入指针// 修改值p.Elem().SetInt(100)fmt.Println("Modified value:", x) // 输出:Modified value: 100
}
获取结构体字段
使用 reflect 获取结构体字段名和值。
package mainimport ("fmt""reflect"
)type Person struct {Name stringAge int
}func printStructFields(s interface{}) {val := reflect.ValueOf(s)if val.Kind() == reflect.Struct {for i := 0; i < val.NumField(); i++ {field := val.Field(i)fmt.Printf("%s: %v\n", val.Type().Field(i).Name, field)}}
}func main() {p := Person{"Alice", 30}printStructFields(p)
}
使用反射调用方法
反射不仅可以获取类型和值,还能动态调用方法。
package mainimport ("fmt""reflect"
)type Person struct {Name string
}func (p *Person) SayHello() {fmt.Println("Hello, my name is", p.Name)
}func main() {p := &Person{Name: "Alice"}// 获取反射对象v := reflect.ValueOf(p)// 获取方法并调用method := v.MethodByName("SayHello")method.Call(nil)
}
反射与类型断言的对比
类型断言与反射在用途上有很大区别:
- 类型断言:通常用于接口类型的断言,快速检查和转换接口类型为具体类型。
- reflect:允许动态地操作类型和值,可以用于获取更多类型信息或修改值。
示例:类型断言
package mainimport "fmt"func printType(i interface{}) {if str, ok := i.(string); ok {fmt.Println("String:", str)} else if num, ok := i.(int); ok {fmt.Println("Integer:", num)} else {fmt.Println("Unknown type")}
}func main() {printType("Hello")printType(42)printType(3.14)
}
示例:使用 reflect 获取类型和值
package mainimport ("fmt""reflect"
)func main() {var x interface{} = 42v := reflect.ValueOf(x)t := reflect.TypeOf(x)fmt.Println("Type:", t) // 输出:Type: intfmt.Println("Value:", v) // 输出:Value: 42
}
总结:类型断言与反射对比
| 特性 | 类型断言 | reflect 包 |
|---|---|---|
| 用途 | 用于接口类型的类型转换 | 用于动态类型检查、修改值、获取字段等 |
| 性能 | 高效,编译时确定类型 | 较慢,涉及运行时类型解析 |
| 语法简洁性 | 简单直观 | 语法较复杂 |
| 类型安全 | 类型安全,编译时检查 | 无类型安全,运行时可能出错 |
| 灵活性 | 灵活性较低,仅适用于接口类型断言 | 高度灵活,可动态修改、调用方法等 |
- 案例
package _caseimport ("fmt""reflect"
)type student struct {Name string `json:"name,omitempty" db:"name2"`Age int `json:"age,omitempty"` // omitempty Zero-Value不序列化
}type User struct {Id intName stringAge int
}// 匿名字段
type Boy struct {UserAddr string
}func (u User) Hello(name string) {fmt.Println("hello", name)
}func ReflectCase1() {//reflectTest1()//reflectType("cz")//reflectValue(55.6)//reflectTest2()//u := User{1, "chen", 18}//Poni(u)//m := Boy{User{1, "sa", 20}, "bj"}//reflectTest3(m)//fmt.Println(u)//setValue(&u)//fmt.Println(u)//userMethod(u)//var s student//getTag(&s)
}func getTag(o any) {v := reflect.ValueOf(o)// 返回reflect.TypeOf类型t := v.Type()// 获取字段for i := 0; i < t.Elem().NumField(); i++ {f := t.Elem().Field(i)fmt.Print(f.Tag.Get("json"), "\t")fmt.Println(f.Tag.Get("db"))}
}func userMethod(o any) {v := reflect.ValueOf(o)// 获取方法m := v.MethodByName("Hello")// 有参数的话需要传一个Value类型切片args := []reflect.Value{reflect.ValueOf("666")}// 没有参数只需要:var args []reflect.Value// m.Call()m.Call(args)
}func setValue(o any) {v := reflect.ValueOf(o)// 获取指针指向的元素v = v.Elem()// 取字段f := v.FieldByName("Name")if f.Kind() == reflect.String {f.SetString("zhen")}
}func reflectTest3(o any) {t := reflect.TypeOf(o)fmt.Println(t)// Anoymous:匿名fmt.Printf("%#v\n", t.Field(0))// 值信息fmt.Printf("%#v\n", reflect.ValueOf(o).Field(0))
}func Poni(o any) {t := reflect.TypeOf(o)fmt.Println("类型:", t)fmt.Println("字符串类型:", t.Name())// 获取值v := reflect.ValueOf(o)fmt.Println(v)// 获取所有属性for i := 0; i < t.NumField(); i++ {f := t.Field(i)fmt.Printf("%s : %v, ", f.Name, f.Type)// 获取字段值信息val := v.Field(i).Interface()fmt.Println("val:", val)}fmt.Println("==method==")for i := 0; i < t.NumMethod(); i++ {m := t.Method(i)fmt.Println(m.Name)fmt.Println(m.Type)}
}// 在处理处理少量已知类型时,使用类型断言+switch性能更好,reflect性能低
// 相较于使用interface{} + switch + 类型推断处理结构体时无法获取详细的字段或标签信息。
// reflect处理复杂结构体内的字段,具有优势可以获取结构体的字段、标签、方法等详细信息。
// reflect使用场景:处理大量动态、未知的复杂数据类型,且这些类型在编译时无法预知,使用 reflect 可以在运行时获取这些类型信息
// 实现通用代码
func reflectTest2() {stu := student{Name: "chenzhen",Age: 19,}v := reflect.ValueOf(stu)// 获取struct字段数量fmt.Println("NumFields:", v.NumField())// 获取字段Name值:// 1.v.Field(指定字段序号) -> 适用于不知道字段名(或者结合for遍历操作)// 2.v.FieldByName("指定字段名") -> 适用于知道字段名fmt.Println("Name value:", v.Field(0).String(), ", ", v.FieldByName("Name").String())// 字段类型fmt.Println("Name type:", v.Field(0).Type())t := reflect.TypeOf(stu)for i := 0; i < t.NumField(); i++ {// 获取字段名name := t.Field(i).Namefmt.Println("Field Name:", name)// 获取tagif fieldName, ok := t.FieldByName(name); ok {tag := fieldName.Tagfmt.Println("tag-", tag, ", ", "json:", tag.Get("json"), ", id", tag.Get("id"))}}
}func reflectTest1() {x := 1.2345fmt.Println("TypeOf==")// TypeOf()返回接口中保存值的类型t := reflect.TypeOf(x)fmt.Println("type:", t)fmt.Println("kind:", t.Kind())fmt.Println("ValueOf==")v := reflect.ValueOf(x)fmt.Println("value:", v)fmt.Println("type:", v.Type())fmt.Println("kind:", v.Kind())// Float传入一个Value类型值,返回一个float64类型fmt.Println("value:", v.Float())z := v.Interface() // Interface()返回一个any类型值fmt.Println(z)fmt.Printf("value is %g\n", z)x1 := []int{1, 2, 3}v1 := reflect.ValueOf(x1)fmt.Println("type:", v1.Type())fmt.Println("kind:", v1.Kind())x2 := map[string]string{"test1": "1", "test2": "2"}v2 := reflect.ValueOf(x2)fmt.Println("type:", v2.Type())fmt.Println("kind:", v2.Kind())fmt.Println("kind==")// Kind()返回类型种类,与Type()区别为:如下案例,Kind返回更底层type MyInt intm := MyInt(5)v3 := reflect.ValueOf(m)fmt.Println("type:", v3.Type())fmt.Println("kind:", v3.Kind())
}func reflectType(a any) {t := reflect.TypeOf(a)fmt.Println("类型是:", t)// kind()获取具体类型k := t.Kind()fmt.Println(k)switch k {case reflect.Float64:fmt.Println("a is float64")case reflect.String:fmt.Println("string")default:panic("unhandled default case")}
}func reflectValue(a any) {v := reflect.ValueOf(a)fmt.Println(v)fmt.Println(v.Type())switch k := v.Kind(); k {case reflect.Float64:fmt.Println("a is ", v.Float())default:panic("unhandled default case")}
}
package _caseimport ("errors""fmt""reflect"
)func ReflectCase2() {type user struct {ID int64Name stringHobby []string}type outUser struct {ID int64Name stringHobby []string}u := user{ID: 1, Name: "nick", Hobby: []string{"篮球", "羽毛球"}}out := outUser{}// 需求1:使用reflect动态copy structrs := copy(&out, u)fmt.Println(rs, out)// 需求2:sliceUser := []user{{ID: 1, Name: "nick", Hobby: []string{"篮球", "羽毛球"}},{ID: 2, Name: "nick1", Hobby: []string{"篮球1", "羽毛球1"}},{ID: 3, Name: "nick2", Hobby: []string{"篮球2", "羽毛球2"}},}slice := sliceColumn(sliceUser, "Hobby")fmt.Println(slice)
}// 从一个切片或结构体中提取指定字段(colu)的值,并返回一个包含这些值的切片
// 每次 t = t.Elem() 或 v = v.Elem() 都是为了处理某一层的指针解引用问题,以便获取实际的值或类型。
// 如果传入的切片类型涉及指针,例如 *[]*Struct,就需要多次解引用才能得到实际的元素类型和值。// 对于四次t = t.Elem()解释
// reflect.Elem(),顾名思义,是取得变量的元素部分
// 在Golang中,变量的元素部分指的是指针指向的变量本身。
// 第一个 t = t.Elem() 处理传入 slice 是指针的情况。
// 第二个 t = t.Elem() 获取切片元素的类型。
// 第三个 t = t.Elem() 处理切片元素是指针的情况,获取指针指向的实际类型。
// o.Elem() 处理遍历时元素是指针的情况,解引用以访问字段。// 我的理解:对于
//
// if t.Kind() == reflect.Ptr {
// t = t.Elem()
// v = v.Elem()
// }
// 第一个t = t.Elem()这是为了处理传入时传入的是切片地址的情况,如果传入的 slice 不是指针,比如 []Struct,这一段代码不会执行,因此不会影响后面的逻辑。
// 而如果传入的是切片,则会在第二个t = t.Elem()生效,这是因为切片打印出来是指向其第一个元素的地址,我们要的是其值,
// 所以要t = t.Elem()而接下来的
// if t.Kind() == reflect.Ptr {
// t = t.Elem()
// }则是为了应对其在切片内部还有一个切片指针的情况,需要获取其值而最后的:
// if o.Kind() == reflect.Ptr {
// v1 := o.Elem()
// val := v1.FieldByName(colu)
// s = reflect.Append(s, val)
// }则是处理切片中的切片中的field中指针的情况。
func sliceColumn(slice any, colu string) any {t := reflect.TypeOf(slice)v := reflect.ValueOf(slice)// 因为这里传入一个切片,切片值为指向其第一个元素的地址,所以要elemif t.Kind() == reflect.Ptr {t = t.Elem()v = v.Elem()}// 如果直接传入的slice是一个结构体,那么直接返回要找的colu对应值if v.Kind() == reflect.Struct {val := v.FieldByName(colu)return val.Interface()}// 处理切片情况if v.Kind() != reflect.Slice {return nil}t = t.Elem()// 如果还是一个指针,要找value,我们期望他是一个structif t.Kind() == reflect.Ptr {t = t.Elem()}f, _ := t.FieldByName(colu)// 获取要找字段的类型sliceT := reflect.SliceOf(f.Type)// 根据类型创建切片s := reflect.MakeSlice(sliceT, 0, 0)for i := 0; i < v.Len(); i++ {// index(i)返回v持有值的第i个元素。如果v的Kind不是Array、Chan、Slice、String,或者i出界,会panico := v.Index(i)if o.Kind() == reflect.Struct {val := o.FieldByName(colu)s = reflect.Append(s, val)}if o.Kind() == reflect.Ptr {v1 := o.Elem()val := v1.FieldByName(colu)s = reflect.Append(s, val)}}return s.Interface()
}func copy(dest any, source any) error {// 对sorece的reflect处理sT := reflect.TypeOf(source)sV := reflect.ValueOf(source)// 但是如果source传入的是指针,那么还要多操作一次,获取它的值if sT.Kind() == reflect.Ptr {sT = sT.Elem()sV = sV.Elem()}// 对于dest的reflect处理dT := reflect.TypeOf(dest)dV := reflect.ValueOf(dest)// 因为dest要被修改,所以传入的一定是指针if dT.Kind() != reflect.Ptr {return errors.New("target对象必须为指针类型")}dT = dT.Elem()dV = dV.Elem()// source必须为struct或者struct指针if sV.Kind() != reflect.Struct {return errors.New("sorce必须为struct或者struct指针")}// dest必须为struct指针if dV.Kind() != reflect.Struct {return errors.New("dest对象必须为struct指针")}// New()返回一个Value类型值,该值持有一个指向类型为传入类型的新申请的零值的指针,返回值的Type为PtrTo(typ)// 这里destObj是待复制对象,所以new出zero-valuedestObj := reflect.New(dT)for i := 0; i < dT.NumField(); i++ {// 每字段dField := dT.Field(i)if sField, ok := sT.FieldByName(dField.Name); ok {if dField.Type != sField.Type {continue}// 取sV中与dField.Name同名的Value赋给valuevalue := sV.FieldByName(dField.Name)// 设置destObj(指针)对应dField.Name的字段的值为valuedestObj.Elem().FieldByName(dField.Name).Set(value)}}dV.Set(destObj.Elem())// error nilreturn nil
}相关文章:
【go语言】reflect包与类型推断
reflect 包的核心概念 Go 中的反射涉及两个核心概念: Type:表示一个类型的结构体,reflect.Type 是类型的描述。Value:表示一个值的结构体,reflect.Value 是一个具体值的包装。 反射让我们能够动态地访问对象的类型和…...
3.python运算符
Python 提供了多种运算符,用于执行算术、比较、逻辑等各种操作。以下是 Python 中常见的运算符类型及其用法: 文章目录 1. 算术运算符2. 比较运算符3. 逻辑运算符4. 赋值运算符5. 位运算符6. 成员运算符7. 身份运算符8. 运算符优先级 1. 算术运算符 算…...
【竞技宝】CS2-上海major:spirit力克MOUZ niko梦碎
北京时间2024年12月15日,CS2上海major正在如火如荼的进行中,昨日迎来两场半决赛MOUZ对阵spirit以及FAZE对阵G2。Spirit和MOUZ和各自赢下了自己的选图之后,spirit双子星在图三抗住压力帮助队伍杀入决赛。而G2和FAZE的比赛中,FAZE依然延续上一场的火热手感完全压制了G2,G2的明星选…...
【Leetcode 每日一题】3266. K 次乘运算后的最终数组 II
问题背景 给你一个整数数组 n u m s nums nums,一个整数 k k k 和一个整数 m u l t i p l i e r multiplier multiplier。 你需要对 n u m s nums nums 执行 k k k 次操作,每次操作中: 找到 n u m s nums nums 中的 最小 值 x x x&a…...
etcd集群常见日志
1、节点失去领导者 {"level":"info","ts":"2024-05-07T01:54:04.948Z","logger":"raft","caller":"etcdserver/zap_raft.go:77","msg":"raft.node: 9afce9447872453 lost le…...
【漫话机器学习系列】005.神经网络的结构(architecture on the neural network)
神经网络(Neural Network)是一种模拟人脑神经系统的计算模型,由大量相互连接的神经元(节点)组成,广泛应用于深度学习和机器学习领域。以下是神经网络的基本结构及关键组成部分。 1. 神经网络的基本组成 一…...
基于 Couchbase 数据仓库元数据管理的可行性方案
在大数据体系中,元数据管理是数据治理的关键一环。以下是一套元数据管理的可行性方案,适合你的当前架构设计(基于 Couchbase 数据仓库)并支持高效管理数据的分层与结构。 1. 元数据管理的目标 统一数据管理:清晰描述 …...
SpringBoot:快速构建微服务应用
一、SpringBoot简介 什么是SpringBoot 是由Pivotal团队提供的快速开发框架。它基于Spring框架,可以用于快速构建微服务应用程序。SpringBoot提供了一种快速、便捷的方式来启动和配置一个基于Spring的应用程序,它封装了很多常用的配置,简化了开…...
汽车嵌入式软件构建高效技术团队的全面思考
在汽车嵌入式软件开发领域,构建一支高效的通用技术团队至关重要。这类团队负责为各种项目提供可复用、标准化的技术基石,从而提高开发效率、降低成本并确保产品质量。构建这样的团队需要从技术能力、角色分工、标准化与复用、流程管理与质量保证、工具和…...
【跨库查询、多库查询】.NET开源 ORM 框架 SqlSugar 系列
文章目录 一、跨库方式1:跨库导航二、手动跨库查询三、同服务器:自动查询跨库查询3.1 Mysql和SqlServer自动3.2 自动: PgSql跨Scheme查询3.3 其他库同服务器 四、跨服务器:自动跨库查询4.1 配置SqlServer dblink4.2 配置 Oracle dblink4.3 配…...
智能人体安全防护:3D 视觉技术原理、系统架构与代码实现剖析
随着工业化程度的提高,生产安全已成为企业关注的重点。尤其是在一些存在禁区的工业厂区和车间,人员误入或违规进入将带来严重的安全隐患。为了解决这一问题,迈尔微视推出了智能人体安全检测解决方案,为企业提供全方位的人员安全监…...
第24周:文献阅读
目录 摘要 Abstract 一、现有问题 二、提出方法 三、创新点 模型结构创新 强化学习与GAN结合 属性特征与通顺性优化 四、方法论 生成对抗网络(GAN) 强化学习(RL) 模型组件 五、实验研究 数据集 数据预处理 评价指…...
yolov8 转华为昇腾om脚本
目录 yolov8 转华为昇腾 om脚本 测试ok 推理demo: yolov8 转华为昇腾 om脚本 测试ok import sys import osos.chdir(os.path.dirname(os.path.abspath(__file__)))import torchcurrent_dir = os.path.dirname(os.path.abspath(__file__))paths = [os.path.abspath(__file__)…...
分布式事物XA、BASE、TCC、SAGA、AT
分布式事务——Seata 一、Seata的架构: 1、什么是Seata: 它是一款分布式事务解决方案。官网查看:Seata 2.执行过程 在分布式事务中,会有一个入口方法去调用各个微服务,每一个微服务都有一个分支事务,因…...
域名信息收集(小迪网络安全笔记~
附:完整笔记目录~ ps:本人小白,笔记均在个人理解基础上整理,若有错误欢迎指正! 2.1 域名信息收集 引子:上一章介绍了服务器的信息收集。本篇则介绍在面对存在Web资产企业时,其域名信息该如何收…...
力扣-图论-13【算法学习day.63】
前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向和记录学习过程(例如想要掌握基础用法,该刷哪些题?)我的解析也不会做的非常详细,只会提供思路和一些关键点,力扣上的大佬们的题解质量是非…...
【设计模式】如何用C++实现观察者模式【发布订阅机制】
【设计模式】如何用C实现观察者模式【发布订阅机制】 一、问题背景 代码质量影响生活质量。最近工作中频繁接触各种设计模式,深刻体会到优秀的设计模式不仅能显著降低后续维护的压力,还能提升开发效率。观察者模式作为一种降低耦合度、提高扩展性的利器…...
【LC】2717. 半有序排列
题目描述: 给你一个下标从 0 开始、长度为 n 的整数排列 nums 。 如果排列的第一个数字等于 1 且最后一个数字等于 n ,则称其为 半有序排列 。你可以执行多次下述操作,直到将 nums 变成一个 半有序排列 : 选择 nums 中相邻的两…...
AI智算-k8s部署大语言模型管理工具Ollama
文章目录 简介k8s部署OllamaOpen WebUI访问Open-WebUI 简介 Github:https://github.com/ollama/ollama 官网:https://ollama.com/ API:https://github.com/ollama/ollama/blob/main/docs/api.md Ollama 是一个基于 Go 语言开发的可以本地运…...
CloudberryDB(二) 演化路线图
CloudberryDB 制定了演化路线图(https://github.com/orgs/cloudberrydb/discussions/369)并在逐步改进,这是 Cloudberry Database 发挥独特价值之处。 计划、正在进行或已完成的一些工作。 支持轻松升级 PostgreSQL 内核版本。 原有 Greenp…...
深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...
免费数学几何作图web平台
光锐软件免费数学工具,maths,数学制图,数学作图,几何作图,几何,AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...
