API 设计和 jQuery

43

我经常听说jQuery在API方面做出了一些糟糕的决策。虽然jQuery不是我最喜欢的库,但它是我使用最频繁的库,我很难指出API设计中具体的错误或者怎样改进。

jQuery的API哪些部分可以做得更好,如何实现不同的API设计,以及为什么这种不同的实现会更好?

这个问题涵盖了API的低级细节高级细节。我们只谈论API中的缺陷,而不是库的高级设计/目的的缺陷。jQuery仍然是一个以选择器引擎为中心的DOM操作库。

由于流行库中API冻结的必要性,jQuery被困在它当前的状态中,开发者们正在做出出色的工作。正如最近看到的.attr.prop的变化,开发者们没有灵活性来改变他们的任何设计决策(这真是太遗憾了!)。

一个具体的例子是:

$.each(function(key, val) { })

$.grep(function(val, key) { })

这个例子非常令人困惑,以至于我不得不频繁地双倍检查参数。

请不要将jQuery 与诸如dojo和YUI等完整的框架进行比较,并抱怨缺乏功能。


4个回答

41
  • .load() 的行为会因传入的参数而完全不同。

  • .toggle() 的行为会因传入的参数而完全不同。

  • 或许 jQuery() 函数的过载太多了。

  • 你提到了 .attr()。我认为应该立即区分它与属性的区别。

  • .map(key, val) 但是 $.map(val, key),且 this 值不同。

  • 非标准选择器应该被排除在 Sizzle 之外。基于 JavaScript 的选择器引擎在未来几年内可能会过时,而沉迷于专有选择器的人将会更加困难地进行过渡。

  • 方法的命名不好,例如 .closest().live()。它们到底是做什么的?

  • 我最近发现,在创建新元素时,无法通过使用 props 参数设置标准的 widthheight 属性,而是要使用 jQuery 自己的 widthheight 方法。我认为规范属性应该优先考虑,特别是因为可以通过 css 设置 widthheight

$('<img/>', { 
    css:{width:100, height:100},
    width:100, // <-- calls method, why?
    height:100, // <-- calls method, why?
});
  • $.get().get()是完全不同的。

  • 当没有传递参数时,.get().toArray()是相同的。

  • toArray()$.makeArray()实际上执行相同的操作。为什么它们没有像.each()$.each()一样被赋予相同的名称?

  • 两种不同的事件委托方法。.delegate()是明智的选择,而.live()是神奇的“哇,它只是起作用了!”

  • .index()具有3种行为重载,但它们的区别可能会让人感到困惑。

 // v---get index   v---from collection (siblings is implied)
$('selector').index();
 // v---from collection   v---get index
$('selector').index(element);
 // v---get index      v---from collection
$('selector').index('selector');
第一个例子只对第一个元素操作,如果记住这一点就容易理解了。
第二个例子是最有意义的,因为jQuery方法通常会在整个集合上操作。
第三个例子非常令人困惑。该方法没有指示哪个选择器是集合,哪个选择器代表您想要从集合中获取索引的元素。
为什么不干脆取消第三个方法,让人们像这样使用第二个方法:
 // v---from collection      v---get index
$('selector').index( $('selector') );

这样做可以使其更加贴近于 jQuery 的其他部分,其中 .index() 作用于整个集合。

或者至少将选择器的含义反转,以更好地适应:

 // v---from collection   v---get index
$('selector').index('selector');

无论如何,这是另一个需要考虑的问题。

我对jQuery的事件处理/数据存储系统有一些担忧。人们赞扬它不会向on[event]属性添加可以关闭其他元素的函数,在IE中造成内存泄漏。相反,它放置了一个轻量级的扩展属性,它映射到jQuery.cache中的一个条目,该条目包含处理程序和其他数据。

我相信它随后附加了一个处理程序,然后调用了您分配的处理程序。或者类似于那样的东西。

无论系统是什么,都不重要。关键是元素与jQuery.cache之间的连接就是那个扩展的属性。

为什么这很重要?哲学上,jQuery不是一个框架,而是一个库。作为一个库,你应该能够使用或不使用jQuery函数,而不必担心负面影响。然而,如果您在删除DOM中的元素时走出jQuery,您将通过扩展程序孤立任何处理程序和其他与这些元素相关联的数据,从而创建一个漂亮且完全跨浏览器的内存泄漏问题。

因此,例如el.innerHTML = ''这样简单的东西可能非常危险。

再加上jQuery.noConflict()功能。这使开发人员可以在使用其他利用全局命名空间$的库时使用jQuery。好吧,如果其中一个库删除了一些元素怎么办?同样的问题。我有一种感觉,需要同时使用Prototypejs和jQuery的开发人员可能不了解足够的JavaScript知识来做出良好的设计决策,并且将受到我所描述的此类问题的影响。


就图书馆的预期哲学改进而言,据我所知,他们的哲学是“做更多,写更少”之类的。我认为他们做得非常好。您可以编写一些非常简洁但富有表现力的代码,这些代码将完成大量工作。

虽然这非常好,但从某种意义上说,我认为它具有一定的负面影响。你可以很容易地做很多事情,所以即使是初学者也很容易写出很糟糕的代码。我认为如果有一个“开发人员构建”,记录了库的误用警告,这将是有益的。

一个常见的例子是在循环中运行选择器。DOM选择非常容易做到,似乎您每次需要元素时都可以运行选择器,即使您刚刚运行了该选择器。我认为一种改进是让jQuery()函数记录对选择器的重复使用,并提供控制台备注,说明可以缓存选择器。

由于jQuery如此占主导地位,我认为他们不仅应该使成为JavaScript / DOM程序员易如反掌,而且还应帮助您成为更好的程序员。


你已经解决了问题,能否提供一些非平凡的API改进示例来扩展它。 - Raynos
1
关于在expando属性上过度干扰的一个好点子。当我们谈论jQuery与其他库的兼容性时,很少有人谈到这一点。+1 - Anurag
另外一个 .cache 内存泄漏问题和 jQuery.dev.js,我认为有一个 jQuery 代码检查工具可以解决。 - Raynos
@Raynos:我又添加了一些不满之处。无论如何,是的,有jQuery Lint。在我看来,这种东西应该是jQuery的一部分。现在jQuery已经非常庞大了,我认为他们有一些责任。他们在发布和引入错误方面有点马虎,我认为对正确使用模式的强制执行应该成为库的一个重要组成部分。 - user113716
@Raynos:StackOverflow 就破产啦!;o)感谢这个问题。它很有疗效。;o) - user113716
显示剩余27条评论

8

jQuery在处理集合与单个元素时的方式可能会让人感到困惑。

比如,如果我们要更新一组元素的某些css属性,可以这样写:

$('p').css('background-color', 'blue');

设置器将更新所有匹配元素的背景颜色。然而,获取器假定您只对检索第一个元素的值感兴趣。

$('p').css('background-color')

MooTools会返回一个包含每个匹配元素背景颜色的数组,这似乎更直观。

jQuery的命名约定促进简洁而不是清晰。我喜欢苹果在命名方面的策略:

清晰比简洁更重要。

下面是Objective-C中可变数组类(NSMutableArray)的一个方法名示例。

removeObjectAtIndex:(..)

这个方法并不试图聪明地删除什么或从哪里删除。所有你需要知道的信息都包含在方法名称中。与jQuery的大多数方法(如afterinsertAfter)相比,这一点非常不同。

如果有人可以直观地理解afterinsertAfter的作用而无需阅读文档或源代码,那么这个人是一个天才。不幸的是,我不是其中之一 - 直到今天,我仍然需要查看文档才能弄清楚使用这两种方法时到底放置了什么。


1
+1或者afterinsertAfter是什么意思。另外,appendappendTo也不是很简单。 - Raynos
1
afterinsertAfter(以及它们的before对应项)是另一个很好的例子。它们让我感到非常困扰! - user113716
1
这是一个荒谬的论点。保持代码大小并不意味着我会将我的变量命名为 abcd 等等。这是编译器的工作,或者如果你在谈论解释性语言,特别是 JavaScript 的话,那就是 缩小 的工作。 - Anurag
这不是关于程序员,而是关于库开发人员。他必须考虑库的使用方式。 - Jacek Kaniuk
1
一个库开发者也是一名程序员。对于库的用户来说,编写高质量的代码和对于库的开发者来说同样重要。事实上,选择良好的描述性变量名称非常重要,以至于软件构建中最流行的书籍之一(Code Complete)专门有一章讲述如何选择良好的变量名称。 - Anurag
显示剩余7条评论

5

Patrick dw在他(很棒的)回答中已经提到了大部分要点。这里再补充一些其他的例子。

API应该是一致的;jQuery在许多领域都做得很好(在返回jQuery对象/获取值方面非常一致,正如在许多情况下所期望的那样)。然而,在其他情况下,它并不那么出色。

方法名 正如patrick所指出的那样;closest()是一个糟糕的方法名。prev()next()看起来似乎可以完成prevAll()nextAll()实际上提供的工作。

delay()让很多人感到困惑:在下面的例子中,你期望发生什么?(实际发生了什么?)

$('#foo').hide().delay(2000).slideDown().text('Hello!').delay(2000).hide();
方法参数 很多树遍历函数在接受的内容方面不一致;它们都接受选择器、jQuery对象和元素的混合,但是没有一个是一致的。考虑查看 closest()find()siblings()parents()parent() 并比较它们之间的差异!
在内部,jQuery的“核心”最初包含了许多相互交织的方法,开发团队努力将其分离(并且做得非常好),在过去的发布中进行了拆分。内部模块,例如css、attributes、manipulation和traversing,曾经都被捆绑在同一个大软件包中。

1
在那个例子中,<p> 标签从未被隐藏,你能解释一下为什么吗?同时,开发人员在可能被认为是一个糟糕的开始时做得非常好,给他们点赞。 - Raynos

4

jQuery中包含:

.post()
.get()
.getScript()
.getJSON()
.load()

不使用jQuery:

$.getXML();
$.headXML();
$.postXML();
$.putXML();
$.traceXML();
$.deleteXML();
$.connectXML();
$.getJSON();
$.headJSON();
$.postJSON();
$.putJSON();
$.traceJSON();
$.deleteJSON();
$.connectJSON();
$.headScript();
$.postScript();
$.putScript();
$.traceScript();
$.deleteScript();
$.connectScript();
$.getHTML();
$.headHTML();
$.postHTML();
$.putHTML();
$.traceHTML();
$.deleteHTML();
$.connectHTML();
$.getText();
$.headText();
$.postText();
$.putText();
$.traceText();
$.deleteText();
$.connectText();
$.head();
$.put();
$.trace();
$.delete();
$.connect();

为什么这让我烦恼呢?不是因为我们的库中没有上述方法,那只是愚蠢的事情,如果我们真的有了这些方法(而且大多数都无法与浏览器一起使用),我会很讨厌。但是我讨厌的是这些缩写方法到处都是:$.post();通过post方式发送ajax请求。

你知道什么可以涵盖此列表中的所有内容吗?唯一不是缩写的函数$.ajax,它具有完整的功能并覆盖了所有内容,您甚至可以配置它来存储默认值,并在本质上创建所有这些缩写。如果您希望调用该调用ajax的自己的方法(这就是这些方法所做的事情)。

这里真的非常令人烦恼。

有人使用缩写编写他们的所有代码:

$.getJSON(
  'ajax/test.html', 
   function(data) {
     $('.result').html(data);
   }
);

好的,等等,我们想要将其更改为从帖子获取的XML源,此外,在发布前后我们需要做一些事情。哦,让我们切换到非缩写方法。

$.ajax({
  type: 'POST',
  url: url,
  data: data,
  success: success
  dataType: dataType
});

等等,我不能只替换一个词,整个结构都有问题。

特别是对于像$.load()这样的东西,这涉及到为什么PHP有一个如此让人讨厌的API,他们试图做一些不该做的事情。

为了超越AJAX调用,看看动画部分。我们调用slideUp、slideDown、fadeIn、fadeOut、fadeToggle、show和hide。为什么?我的slide left、slide right、slide in、slide out、传送门和其他所有的效果在哪里?为什么不只使用$.animate(),如果我们想要这些效果,让别人编写插件呢?实际上,有人编写了一个插件,jQueryUI扩展了动画。这太疯狂了,导致人们不知道一些效果会在其div上创建CSS规则,最终弄乱他们以后想要做的事情。

保持纯粹,保持简单。


谁使用 TRACECONNECT。来吧 ;) - Raynos
说实话,大多数浏览器都禁止除 get 和 post 以外的任何东西,我列出这个列表只是为了让人们更清楚地看到当扩展时这种哲学是如何失败的。 - Incognito
@user257493 +1,因为jQueryUI扩展了animate,我想知道为什么没有jQueryUI,jQuery animate会出现问题。 - Raynos
我想提出一个观点,即jQuery是对常用方法的“速记”。更有可能有人使用$.getJSON而不是$.deleteJSON(或许多其他列出的不存在的函数 - 即使是XML函数(现在这些天))。同样,slideDownslideLeft更容易被使用。 - Ryan Kinal
这正是他们正在做的事情,并已得到确认,但我认为这是不良的实践。 - Incognito
1
然而,它遵循他们的“少做更多”的哲学。它们实际上在各个地方都有快捷方式。例如,.click.bind('click')的快捷方式。 - Ryan Kinal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接