5

我正在为 C#制作一个 jquery 克隆。现在我已经把它设置好了,每个方法都是一个扩展方法,IEnumerable<HtmlNode>所以它可以很好地与已经使用的现有项目一起工作HtmlAgilityPack。我以为我可以在不保留状态的情况下逃脱……但是,然后我注意到 jQuery 有两种方法.andSelf.end它们将最近匹配的元素从内部堆栈中“弹出”。如果我更改我的类以便它始终在 SharpQuery 对象而不是枚举对象上运行,我可以模仿这个功能,但仍然存在问题。

使用 JavaScript,您会自动获得 Html 文档,但在使用 C# 时,您必须显式加载它,如果您愿意,可以使用多个文档。看来,当您调用时,$('xxx')您实际上是在创建一个新的 jQuery 对象并从一个空堆栈重新开始。在 C# 中,您不想这样做,因为您不想从 Web 重新加载/重新获取文档。因此,您只需将其加载到 SharpQuery 对象或 HtmlNode 列表中(您只需要 DocumentNode 即可开始)。

在 jQuery 文档中,他们给出了这个例子

$('ul.first').find('.foo')
  .css('background-color', 'red')
.end().find('.bar')
  .css('background-color', 'green')
.end();

我没有初始化方法,因为我不能重载()运算符,所以你只需从它开始sq.Find(),它在文档的根目录上运行,基本上做同样的事情。但是然后人们会尝试sq.Find()在一行上写,然后在sq.Find()某个地方,并且(正确地)期望它再次在文档的根目录上运行......但如果我保持状态,那么你已经刚刚在第一次调用后修改了上下文。

那么......我应该如何设计我的 API?我是否添加了另一种Init方法,所有查询都应该以重置堆栈开始(但是我如何强制它们开始呢?),或者添加一个Reset()他们必须在行尾调用的方法?我是否要重载[]而不是告诉他们从那开始?我会说“算了吧,反正没人使用那些状态保留的函数吗?”

基本上,您希望如何用 C# 编写 jQuery 示例?

  1. sq["ul.first"].Find(".foo") ...
    缺点:滥用[]财产。

  2. sq.Init("ul.first").Find(".foo") ...
    缺点:没有什么能真正迫使程序员从 Init 开始,除非我添加一些奇怪的“初始化”机制;用户可能会尝试开始,.Find但没有得到他期望的结果。而且,Init无论如何Find都几乎相同,除了前者也重置堆栈。

  3. sq.Find("ul.first").Find(".foo") ... .ClearStack()
    缺点:程序员可能会忘记清除堆栈。

  4. 做不到。
    end()未实现。

  5. 使用两个不同的对象。
    也许使用HtmlDocument作为所有查询应该开始的基础,然后每个方法都返回一个SharpQuery可以链接的对象。这样,HtmlDocument始终保持初始状态,但SharpQuery对象可能具有不同的状态。不幸的是,这意味着我必须两次实现一堆东西(一次用于 HtmlDocument,一次用于 SharpQuery 对象)。

  6. new SharpQuery(sq).Find("ul.first").Find(".foo") ...
    构造函数复制对文档的引用,但重置堆栈。

4

2 回答 2

4

我认为您在这里遇到的主要绊脚石是您试图摆脱SharpQuery每个文档只有一个对象的情况。这不是 jQuery 的工作方式。一般来说,jQuery 对象是不可变的。当您调用更改元素集的方法(如findorendadd)时,它不会更改现有对象,而是返回一个新对象:

var theBody = $('body');
// $('body')[0] is the <body>
theBody.find('div').text('This is a div');
// $('body')[0] is still the <body>

(有关更多信息,请参阅文档)end

SharpQuery 应该以同样的方式运行。使用文档创建 SharpQuery 对象后,方法调用应返回新SharpQuery对象,引用同一文档的不同元素集。例如:

var sq = SharpQuery.Load(new Uri("http://api.jquery.com/category/selectors/"));
var header = sq.Find("h1"); // doesn't change sq
var allTheLinks = sq.Find(".title-link") // all .title-link in the whole document; also doesn't change sq
var someOfTheLinks = header.Find(".title-link"); // just the .title-link in the <h1>; again, doesn't change sq or header

这种方法的好处是多方面的。因为sq, header,allTheLinks等都是同一个类,所以每个方法只有一个实现。然而,这些对象中的每一个都引用同一个文档,因此您没有每个节点的多个副本,并且对节点的更改反映在SharpQuery该文档的每个对象中(例如,之后allTheLinks.text("foo")someOfTheLinks.text() == "foo".)。

实现end和其他基于堆栈的操作也变得容易。当每个方法从另一个方法创建一个新的、过滤的SharpQuery对象时,它会保留对该父对象的引用(allTheLinksto headerheaderto sq)。然后end就像返回一个SharpQuery包含与父元素相同元素的新元素一样简单,例如:

public SharpQuery end()
{
    return new SharpQuery(this.parent.GetAllElements());
}

(或者无论如何你的语法会动摇。)

我认为这种方法会给你带来最类似 jQuery 的行为,而且实现起来相当简单。我一定会密切关注这个项目;这是个好主意。

于 2010-11-07T18:51:47.867 回答
0

我倾向于选项 2 的变体。在 jQuery 中 $() 是一个函数调用。C# 没有全局函数,静态函数调用是最接近的。我会使用一种方法来表明您正在创建一个包装器,例如..

SharpQuery.Create("ul.first").Find(".foo")

我不会担心将 SharpQuery 缩短为 sq,因为智能感知意味着用户不必输入整个内容(如果他们有更清晰的内容,他们只需要输入 SQ)。

于 2010-11-06T23:23:45.207 回答