ORM是是非非
发布日期:2021-09-29 20:09:22 浏览次数:9 分类:技术文章

本文共 12142 字,大约阅读时间需要 40 分钟。

简单讲,开发的时候方便了(敏捷开发?),运行的时候慢了,而且,不能深入细节

一些参考吧

回答 用的

 

某人 写道
Advantages:
Speeds-up Development - eliminates the need for repetitive SQL code.
Reduces Development Time.
Reduces Development Costs.
Overcomes vendor specific SQL differences - the ORM knows how to write vendor specific SQL so you don't have to.
Disadvantages:
Loss in developer productivity whilst they learn to program with ORM.
Developers loose understanding of what the code is actually doing - the developer is more in control using SQL.
ORM has a tendency to be slow.
ORM fail to compete against SQL queries for complex queries.

 

 

写道
面向对象有它的本质要求。如果你的编程仅仅是“基于对象”而不是“面向对象”的,虽然你也能收到一些“代码封装成类型”的好处,但是你其实很难用到真正深入的面向对象软件工程设计技术。
面向对象ORM非常重要,堪称面向对象软件工程技术的核心。但是,有很多ORM只是“基于对象”的,不是“面向对象”的。面向对象的ORM应该与面向对象的软件工程技术一样,首要问题是:解决基于对象的结构化技术(如今所有顽固坚持结构化技术的人都使用OOPL语言来说明自己的理论,因此OOPL不等代表OO的水平)与基于多态的面向对象技术的“阻抗不匹配”现象。
套用面向对象技术的本质特种,面向对象ORM应该有以下本质特征(下面是用了熟悉的c#的术语):
1. 自动维护类型定义。我们把一个对象“丢给”面向对象ORM,它就应该自动分析对象的Field、Property。如果在使用一段时间之后我们的对象类型定义改变了,原来的数据应该仍然能够回复为新的类型的对象,只不过有些新增字段成为默认值。例如:
User u=null;
//todo: 产生u实例
using(var db=Domain(数据库连接))
{
db.Save(u);
db.Commit();
}
db事先并不知道“User”这个类型,它应该“临时”反射User类型并记录下来。那些要求我们依据关系数据库的数据字典来写class代码的想法可谓“居心叵测”。
2. 标识唯一性。至少在同一个进程中(也就是在同一个ORM环境中)两个不同的查询,如果逻辑上应该有相同的对象返回,那么ORM就应该体现这个规则。例如有一个过程是“保存工单记录”,工单中有字段记录负责评估工单的产品经理,另外有一个过程是“查询某个项目组的产品经理”。看上去这两个过程没有紧密的逻辑联系,但是他们有数据联系。如果一个地方建立个一个产品经理对象并调用了第一个方法,另一个地方随后调用了第二个方法,那么对于同一个产品经理,第二个方法不应重新建立新的内存对象,而应该返回第一个方法所使用的那个对象(除非调用第二个方法时第一个方法使用的那个对象已经被GC释放了)。
显然,你的ORM应该有自己的缓存机制来维护对各个对象的弱引用。缓存并不是仅用来提高查询速度的,还有逻辑意义上的用处。
3. 继承性。如果上述例子中的变量u被实例化为一个从User类继承的“系统管理员”类,数据库当然应该可以保存,并且在随后“查询系统管理员”操作中可以正确返回系统管理员的全部字段。显然,子类和父类在数据库中肯定要分开成不同的类型(或者叫做“表”)。
例如假设Domain数据库类型的Query <T>返回所有T类型对象的一个QueryProvider(Linq定义的):
User u=null;
u=new 系统管理员(){姓名="大宝"}; //已经设置(例如使用一个Attribute在class中声明)“姓名”在数据库中是唯一的。
using(var db=Domain(数据库连接))
{
db.Save(u);
db.Commit();
var result=(from u in db.Query <系统管理员> where u.姓名=="大宝" select u).FirstOrDefault();
//todo: result.Print();
}
4. 多态性。
User u=null;
u=new 系统管理员(){姓名="大宝"}; //已经设置(例如使用一个Attribute在class中声明)“姓名”在数据库中是唯一的。
using(var db=Domain(数据库连接))
{
db.Save(u);
db.Commit();
var result=(from u in db.Query <User> where u.姓名=="大宝" select u).FirstOrDefault();
//todo: result.Print();
}
注意result的定义类型是User而不是“系统管理员”。多态让我们写出“引擎式”的代码,这个引擎拖动的车子是虚构的、通用的。ORM应该支持OO的这个本质要求。
5. 网状关联。ORM应该隐藏面向对象数据库与关系数据库在处理关联方面的差别。
假设User中有一个“配偶”字段,并且有一个“情人”集合,查询“系统管理员的配偶和情人”方法可能这样写:
User u=null;
u=new 系统管理员(){姓名="大宝"}; //已经设置(例如使用一个Attribute在class中声明)“姓名”在数据库中是唯一的。
//todo: u.配偶=......
using(var db=Domain(数据库连接))
{
db.Save(u);
db.Commit()
var result1=from u in db.Query <系统管理员> select u.配偶;
//todo: result1.ForEach(p=>{p.Print();});
var result2=(from u in db.Query <系统管理员> select u).SelectMany(u=>u.情人);
//todo: result2.ForEach(q=>{q.Print();});
}
如果你使用面向对象数据库,那么从User到它的配偶之间以及每一位情人之间的存储关联是“一步到位”的,而不是像关系数据库那样需要使用inner join。因此,面向对象昂数据库(理论上)应该比关系数据库速度快至少10倍。
不应该强迫对象类型之间关系模仿关系数据库的“外键”来建立,应该使用上面的自然、OOPL原始的形态。这样,如果你的ORM的底层是关系数据库,例如是SQL Server,那么你的QueryProvider(Linq)实现就不得不将关系翻译为关联操作。
6. 延迟加载。显然,当我们查询一堆User出来之后,它的“七姑八姨”等关系对象不需要加载到内存里(否则这个加载操作就太可怕了),但是当我们访问关联对象时应该自动从数据库中加载到内存。对于引用单个对象和引用集合对象都是应该这样的。
7. 级联更新。如果我们实例化一个User类型对象,并且给它的“配偶”赋值,那么在保存这个User类型对象的时候就应该自动保存(新增或者更新都叫是保存操作)它的配偶,而不需要我们在程序中去写保存配偶的代码。如果我们查询出一个User,他有很多情人,我们用程序向他的“情人集合”里插入一个情人或者删除一个情人,然后保存他,那么ORM应该自动也去保存这个情人或者删除它与这个情人的数据库内连接,但是不需要重复保存那些没有动过的情人对象。
<完>
http://topic.csdn.net/u/20080227/12/aeeec383-2def-48b9-8bab-336926f1d33b.html
对我有用[0]
丢个板砖[0]
引用
举报
管理
TOP
zzxap用户头像
zzxap
(风语者)
等 级:
2
#2楼 得分:0回复于:2009-11-04 12:07:20
例如我们需要开发一个“注册用户”工程,这个工程显然要给将来各个应用系统共享,也就是说将来的各个系统依赖于它。这个系统中定义了用户类,保存了用户对象。那么当用户被删除,或者其某些属性修改了,我们要通知其它子系统,怎么办呢?调用其它子系统的功能吗?有设计知识的架构师不会这样轻易回答,因为前面已经说了各个系统依赖于它,而不是它依赖于在它之后才开发的子系统。这就需要各个子系统能够注册事件处理程序到用户对象类中。但是这看起来有点麻烦,就是以往在使用事件时我们通常都是将事件处理程序注册到一个对象上,而这里我们是开发ORM,我们希望处理程序针对“一类”对象均自动注册,这两者的编程方法有区别。
我们的ORM是一个完整的面向对象风格的数据库工具,它不可能依赖于底层关系数据库来实现一个纯粹工作在应用程序中的触发器,必须依靠自身力量解决。
不要告诉我“这就是AOP”就完了(当您并不能直接拿出一个成熟的AOP代码给我直接使用在此系统上时),因为我接下来要描述处理这个过程的设计规格,所以我其实并不管我的做法是不是AOP,我在此只是这样实现这个系统的:
public static class RegisterCallbacks
{
public static void RegisterCallbackAfterCreate(Type type, CallbackHandler handler);
public static void RegisterCallbackAfterDelete(Type type, CallbackHandler handler);
public static void RegisterCallbackAfterUpdate(Type type, CallbackHandler handler);
public static void Created(IDomainClient sender, object obj);
public static void Deleted(IDomainClient sender, object obj);
public static void Updated(IDomainClient sender, object obj);
}
这个类是一个static类,它是ORM的一个独立的信息管理类,它记录了各个类型的数据当在数据库中更新(Create、Update、Delete)时应该触发哪些方法(handlers)。显然,同一类型同一更新方法可以注册多个handler。而handler的类型定义如下:
public delegate void CallbackHandler(IDomainClient sender, CallbackArgs args);
public class CallbackArgs : EventArgs
{
public object Obj;
}
IDomainClient 是我定义的数据库打开之后的操作接口(提供了事务功能),其设计规格解释可以参考我开头提到的那篇帖子。
假设一个“发送欢迎短信任务安排”的类需要被“用户”这个类所触发,也就是说一旦一个用户被新增入系统数据库就要触发给他发送一个欢迎短信的任务记录,那么我们(通常在发送欢迎短信任务安排这个类中,当然也可以写在其它代码文档中)应该可以见到这样的代码:
[RegisterCallbacks]
public static void Register()
{
RegisterCallbacks.RegisterCallbackAfterCreate(typeof(用户), 准备发送欢迎短信);
}
private static void 准备发送欢迎短信(IDomainClient db, CallbackArgs args)
{
var obj = args.Obj as 用户;
db.Save(new 发送欢迎短信任务安排{ 用户=obj, 开始时间=DateTime.Now, 过期时间=DateTime.Now.AddHours(1) });
}
这个代码告诉 RegisterCallbacks 这个管理类,当“用户”类型的对象被数据库创建时要触发“准备发送欢迎短信”这个方法。
在 RegisterCallbacks 中并不关联具体的数据库。一旦通过某一个数据库对象实例新增了一个对象,这个数据库对象就会去调用静态方法
RegisterCallbacks.Created(thisDb,theObject)
这样 RegisterCallbacks 就会帮助数据库调用 theObject 的所有类型(包括各层父类、接口)在 Create 操作上所需要的触发方法。这样,各个在ORM之后开发的领域对象工程,以及各种不同的数据库实现,可以在ORM帮助下实现触发器功能。
对于Update、Delete操作,其机制完全与Create操作一致。
我们使用关系数据库的经验可以告诉我们,触发器所修改的数据是可以回滚的。因此,这个“准备发送欢迎短信”中无需写
    
    db.Commit()
因为如果
Save(用户);
触发它,紧接下来会被 Rollback 命令回滚,那么触发器中的更新当然也应该回滚。同样,如果调用方 Commit,这时候这个新增发送欢迎短信安排记录才算真正保存到数据库文件中。在这个触发器程序执行过程中,可以从 db 查询到所有调用方的代码已经更新到数据库但是还没有 Commit 的数据,尽管这些数据可能随后被 Rollback。说白了,触发器是在一个事务中对象被更新到数据库但是还没有进行 Commit 或者 Rollback 时触发的。
但是,我们什么时候去调用 Register 方法呢?是在领域类型的.cctor中吗?显然,这时候有点晚,因为.cctor只有在类型第一次被真正用来实例化对象时才执行。如果我们启动一个应用系统,新增了一个用户,此时还没有使用过发送欢迎短信任务安排对象,我们就无法让这个触发器被注册到 RegisterCallbacks 中。
为了解决这个问题,我首先定义了一个标签
[AttributeUsage(AttributeTargets.Method, AllowMultiple=false, Inherited=false)]
public class RegisterCallbacksAttribute : Attribute
{
}
这个标签用于注明类型中上述Register静态方法。当数据库对象第一次实例化时,它应该去遍历当前应用程序域中所有的引用了这个ORM工程的assembly中的每一个类型,从类型中找出具有 RegisterCallbacksAttribute 这个标签的静态方法(为了方便,我设计为public或者private均可,这实际上由各个实现了 IDomainClient 接口的数据库对象类来决定),并且反射执行它。当然,仅仅执行这个静态方法一次就足够了。
我最后总结一下主要的设计初衷:虽然我们使用OOP工具进行数据持久化方面的编程,但是往往在许多人那里与面向实际应用领域的OOAD的结果并不一致。例如对于一些没有经验的软件工程师,他可能认为成百上千类领域对象的各个实例的更新是调用那些理应依赖于这个对象影响的对象的方法来进行级联更新的。运行时当然是这样的,但这是排好队干活的“机器”所做的事,而不是设计师所关心的大事。设计师负责研究把机器放进工位使其依照一定关系行事的策略,即那个“you don't call me, we’ll call you”的好莱坞原则所表现的灵活性。然而,我们在不少ORM实现中看不到正常的触发器框架,甚至看到根本不是由客户去注册反而要求服务端去注册触发器这种可笑的现象,这不能不说是一个硬伤。
 

 

 

面向对象有它的本质要求。如果你的编程仅仅是“基于对象”而不是“面向对象”的,虽然你也能收到一些“代码封装成类型”的好处,但是你其实很难用到真正深入的面向对象软件工程设计技术。 

面 向对象ORM非常重要,堪称面向对象软件工程技术的核心。但是,有很多ORM只是“基于对象”的,不是“面向对象”的。面向对象的ORM应该与面向对象的 软件工程技术一样,首要问题是:解决基于对象的结构化技术(如今所有顽固坚持结构化技术的人都使用OOPL语言来说明自己的理论,因此OOPL不等代表 OO的水平)与基于多态的面向对象技术的“阻抗不匹配”现象。 
套用面向对象技术的本质特种,面向对象ORM应该有以下本质特征(下面是用了熟悉的c#的术语): 
1. 自动维护类型定义。我们把一个对象“丢给”面向对象ORM,它就应该自动分析对象的Field、Property。如果在使用一段时间之后我们的对象类型定义改变了,原来的数据应该仍然能够回复为新的类型的对象,只不过有些新增字段成为默认值。例如: 
  User u=null; 
  //todo: 产生u实例 
  using(var db=Domain(数据库连接)) 
  { 
  db.Save(u); 
  db.Commit(); 
  } 
db事先并不知道“User”这个类型,它应该“临时”反射User类型并记录下来。那些要求我们依据关系数据库的数据字典来写class代码的想法可谓“居心叵测”。 
2. 标识唯一性。至少在同一个进程中(也就是在同一个ORM环境中)两个不同的查询,如果逻辑上应该有相同的对象返回,那么ORM就应该体现这个规则。例如有 一个过程是“保存工单记录”,工单中有字段记录负责评估工单的产品经理,另外有一个过程是“查询某个项目组的产品经理”。看上去这两个过程没有紧密的逻辑 联系,但是他们有数据联系。如果一个地方建立个一个产品经理对象并调用了第一个方法,另一个地方随后调用了第二个方法,那么对于同一个产品经理,第二个方 法不应重新建立新的内存对象,而应该返回第一个方法所使用的那个对象(除非调用第二个方法时第一个方法使用的那个对象已经被GC释放了)。 
显然,你的ORM应该有自己的缓存机制来维护对各个对象的弱引用。缓存并不是仅用来提高查询速度的,还有逻辑意义上的用处。 
3. 继承性。如果上述例子中的变量u被实例化为一个从User类继承的“系统管理员”类,数据库当然应该可以保存,并且在随后“查询系统管理员”操作中可以正确返回系统管理员的全部字段。显然,子类和父类在数据库中肯定要分开成不同的类型(或者叫做“表”)。 
例如假设Domain数据库类型的Query <T>返回所有T类型对象的一个QueryProvider(Linq定义的): 
  User u=null; 
  u=new 系统管理员(){姓名="大宝"}; //已经设置(例如使用一个Attribute在class中声明)“姓名”在数据库中是唯一的。 
  using(var db=Domain(数据库连接)) 
  { 
  db.Save(u); 
  db.Commit(); 
  var result=(from u in db.Query <系统管理员> where u.姓名=="大宝" select u).FirstOrDefault();
  //todo: result.Print(); 
  } 
4. 多态性。 
  User u=null; 
  u=new 系统管理员(){姓名="大宝"}; //已经设置(例如使用一个Attribute在class中声明)“姓名”在数据库中是唯一的。 
  using(var db=Domain(数据库连接)) 
  { 
  db.Save(u); 
  db.Commit(); 
  var result=(from u in db.Query <User> where u.姓名=="大宝" select u).FirstOrDefault(); 
  //todo: result.Print(); 
  } 
注意result的定义类型是User而不是“系统管理员”。多态让我们写出“引擎式”的代码,这个引擎拖动的车子是虚构的、通用的。ORM应该支持OO的这个本质要求。 
5. 网状关联。ORM应该隐藏面向对象数据库与关系数据库在处理关联方面的差别。 
  假设User中有一个“配偶”字段,并且有一个“情人”集合,查询“系统管理员的配偶和情人”方法可能这样写: 
  User u=null; 
  u=new 系统管理员(){姓名="大宝"}; //已经设置(例如使用一个Attribute在class中声明)“姓名”在数据库中是唯一的。 
  //todo: u.配偶=...... 
  using(var db=Domain(数据库连接)) 
  { 
  db.Save(u); 
  db.Commit() 
  var result1=from u in db.Query <系统管理员> select u.配偶; 
  //todo: result1.ForEach(p=>{p.Print();}); 
  var result2=(from u in db.Query <系统管理员> select u).SelectMany(u=>u.情人); 
  //todo: result2.ForEach(q=>{q.Print();}); 
  } 
如果你使用面向对象数据库,那么从User到它的配偶之间以及每一位情人之间的存储关联是“一步到位”的,而不是像关系数据库那样需要使用inner join。因此,面向对象昂数据库(理论上)应该比关系数据库速度快至少10倍。 
不应该强迫对象类型之间关系模仿关系数据库的“外键”来建立,应该使用上面的自然、OOPL原始的形态。这样,如果你的ORM的底层是关系数据库,例如是SQL Server,那么你的QueryProvider(Linq)实现就不得不将关系翻译为关联操作。 
6. 延迟加载。显然,当我们查询一堆User出来之后,它的“七姑八姨”等关系对象不需要加载到内存里(否则这个加载操作就太可怕了),但是当我们访问关联对象时应该自动从数据库中加载到内存。对于引用单个对象和引用集合对象都是应该这样的。 
7. 级联更新。如果我们实例化一个User类型对象,并且给它的“配偶”赋值,那么在保存这个User类型对象的时候就应该自动保存(新增或者更新都叫是保存 操作)它的配偶,而不需要我们在程序中去写保存配偶的代码。如果我们查询出一个User,他有很多情人,我们用程序向他的“情人集合”里插入一个情人或者 删除一个情人,然后保存他,那么ORM应该自动也去保存这个情人或者删除它与这个情人的数据库内连接,但是不需要重复保存那些没有动过的情人对象。 

 

 

ORM解决的主要问题是对象关系的映射。域模型和关系模型分别是建立在概念模型的基础上的。域模型是面向对象的,而关系模型是面向关系的。一般情况下,一个持久化类和一个表对应,类的每个实例对应表中的一条记录,类的每个属性对应表的每个字段

ORM的缺点是会牺牲程序的执行效率和会固定思维模式。 
  从系统结构上来看,采用ORM的系统一般都是多层系统,系统的层次多了,效率就会降低。ORM是一种完全的面向对象的做法,而面向对象的做法也会对性能产生一定的影响。 
  开发系统时,一般都有性能问题。性能问题主要产生在算法不正确和与数据库不正确的使用上。ORM所生成的代码一般不太可能写出很高效的算法,在数据库应用 上更有可能会被误用,主要体现在对持久对象的提取和和数据的加工处理上,如果用上了ORM,程序员很有可能将全部的数据提取到内存对象中,然后再进行过滤 和加工处理,这样就容易产生性能问题。 
  在对对象做持久化时,ORM一般会持久化所有的属性,有时,这是不希望的。 
  但ORM是一种工具,工具确实能解决一些重复,简单的劳动。这是不可否认的。但不能指望工具能一劳永逸的解决所有问题,有些问题还是需要特殊处理的,但需要特殊处理的部分对绝大多数的系统,应该是很少的

 

 

ORM优势和缺点:

优势:ORM自其概念被提出,就得到了无数的响应,花样繁多的应用框架更是应接不暇。可见,他是有其独到的优势的。那么他的优势有哪些那:

首先,ORM最大的优势。
        隐藏了数据访问细节,“封闭”的通用数据库交互,ORM的核心。他使得我们的通用数据库交互变得简单易行,并且完全不用考虑该死的SQL语句。快速开发,由此而来。
第二:ORM使我们构造固化数据结构变得简单易行。
         在ORM年表的史前时代,我们需要将我们的对象模型转化为一条一条的SQL语句,通过直连或是DB helper在关系数据库构造我们的数据库体系。而现在,基本上所有的ORM框架都提供了通过对象模型构造关系数据库结构的功能。这,相当不错。
缺点:
第一:
        无可避免的,自动化意味着映射和关联管理,代价是牺牲性能(早期,这是所有不喜欢ORM人的共同点)。现在的各种ORM框架都在尝试使用各种方法来减轻这块(LazyLoad,Cache),效果还是很显著的。
第二:
         面向对象的查询语言(X-QL)作为一种数据库与对象之间的过渡,虽然隐藏了数据层面的业务抽象,但并不能完全的屏蔽掉数据库层的设计,并且无疑将增加学习成本.
第三:
         对于复杂查询,ORM仍然力不从心。虽然可以实现,但是不值的。视图可以解决大部分calculated column,case ,group,having,order by, exists,但是查询条件(a and b and not c and (d or d))。。。。。。
        世上没有驴是不吃草的(又想好又想巧,买个老驴不吃草),任何优势的背后都隐藏着缺点,这是不可避免的。问题在于,我们是否能容忍缺点。ADA代码虽然 易用性很差,但是US.DoD(the department of defense)欣赏他的运算速度;.net平台很不错,但是他是MS的。^_^

 ORM为何而生

          在数月以前,我有幸参加了一个公司内部的组件发布会。令我深感意外的事,一向无人关心的组件发布会这次变得人山人海,在漫长的新版本介绍之后。每个开发组 长都跳出来抱怨上一个版本的问题,并且宣布与刚发布的新版本也是无法满足他们的需要的。一切都是如此的混乱,以至领导层不得不采用镇压的方式来平息怒火冲 天的人们。

       在会后的那个晚上,我仔细回想了这次冲突。因为据我了解,这一系列的组件非常完美的完成了他所被期待的功能。可是为什么还是会被抱怨如此那。
         我觉得,可能,他(组件)是没有被正确使用了。
不知道还有谁记得James Elliott 的那句话,

As good object-oriented developers got tired of this repetitive work, their typical tendency towards enlightened laziness started to manifest itself in the creation of tools to help automate the process. When working with relational databases, the culmination of such efforts were object/relational mapping tools.

    ORM构架只能是一个helper,他定位与此,而不是完整的数据持久层。他的设计者从来就没把他定位于取代一切的超级美女。ORM致力为长久以来的程序员与”重复劳动”的战争而助拳。与任何一个helper一样,他有自己的不足,他有优点也有缺点。

       无数的开发人员试图将使用ORM的框架构架自己项目的数据持久层,很多人感受到了ORM的优势,他们欢心鼓舞。但是很不幸,也有很多人失败或是深受蹉责。
         还有许多人,无奈的编写着很多ORM不适合作的事情。其实想一想,被自己舍弃了的以前的helper工具,难道真的一无是处了?
ORM与DB Helper Library:
      很多人可能都接触过这类的helper,每个公司都有自己的helper。许多Helper提供了很多的强大的功能,封闭交互底层,实体类支持,提供 SQL翻译功能。ORM比之这些Helper只是多提供了一层,他尝试封闭的自动化的(或是映射文件)来实现关联。以前,这都是我们手打的。(灵活替换数 据库也算ORM优点,
        问题就在与有些人发现封闭的自动化关联满足他们需要了,所以ORM对他而言是成功的。而有些人发现封闭的自动化关联不适合他们的项目,所以ORM被诟病。
          写到这里,我想不用多言了。该结束了。
           我的观点是ORM试图取代helper,为此提供了更多的功能。他为了应付更加严格和复杂的企业需求而不断发展,在很多情况下,这些工具开始具有自身的复 杂性,使得开发人员必须学习使用它们的详细规则,并修改组成应用程序的类以满足映射系统的需要,使用它们所面临的复杂性反而盖过了所能获得的好处。在我们 的大部分项目中Helper依然是我们构建数据持久层的主力,ORM或许在有些项目(模块)中可以独揽一切,但是ORM(就目前而言)无法面对一切考验。

转载地址:https://blog.csdn.net/horace_lee/article/details/84085743 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Module方法调用好不好
下一篇:jquery的选择器

发表评论

最新留言

感谢大佬
[***.8.128.20]2024年04月23日 08时51分05秒