我的编程空间,编程开发者的网络收藏夹
学习永远不晚

如何写一个更好的Javascript DOM库

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

如何写一个更好的Javascript DOM库

如何写一个更好的Javascript DOM库,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。

目前,jQuery是事实上的操作文档对象模型(DOM)的库。它可以与流行的客户端MV*框架结合使用,并且拥有大量的插件与大型的社区。开发者 对于Javascript的兴趣与日俱增的同时,很多人开始好奇,原生的API是如何工作的,以及我们何时应该直接使用它们而不是引用一个额外的库。

最近,我开始发现越来越多的jQuery的问题,至少是在我的使用中是这样的。其中的绝大多数涉及到jQuery的核心,在不取消向后兼容的情况下无法解决——而向后兼容又非常重要。与很多人一样,我继续使用了它一段时间,每天浏览所有讨厌的浏览器怪异模式。

后来, Daniel Buchner创造了SelectorListener,于是有了“live扩展(live extensions)”的概念。我开始考虑创造一系列的函数,使得我们可以使用比迄今为止用过的方法都更好的方式来创建非干扰性的DOM组件。目标是回顾已有的API与解决方案,并创造一个更干净、可测试且轻量级的库。

向库添加有用的特性

是live扩展的想法鼓励我开发了better-dom项目,不过,还有一些其他的有趣的特性使得它成为一个独特的库。我们快速地看一下:

  • live扩展

  • 原生的动画

  • 内置的微模板

  • 国际化的支持

live扩展

jQuery有一个叫做“live事件(live events)”的概念。借助事件代理,它使得开发者可以处理现有的以及未来的元素。但是许多情况会要求更大的灵活度。比如为了初始化一个部件而需要对DOM进行转换,事件代理就会力不从心。故而,live扩展。

目标是,只需定义一次扩展并使得所有未来的元素快速略过初始化函数,而无论部件的复杂度。这个很重要,因为它使得我们可以声明式地开发web页面,从而在AJAX应用中表现优异。

Live扩展使得你无需调用初始化方法就可以操作未来的元素

我们来看一个简单的例子。假设我们的任务是实现一个完全自定义的提示框。:hover  伪类选择器并无帮助,因为提示框的位置随着鼠标移动而变化。事件代理也不是很合适;监听文档树中所有元素的mouseover 及mouseleave  事件代价很大。live扩展将拯救你!

DOM.extend("[title]", {   constructor: function() {     var tooltip = DOM.create("span.custom-title");       // set the title's textContent and hide it initially     tooltip.set("textContent", this.get("title")).hide();       this       // remove legacy title       .set("title", null)       // store reference for quicker access       .data("tooltip", tooltip)       // register event handlers       .on("mouseenter", this.onMouseEnter, ["clientX", "clientY"])       .on("mouseleave", this.onMouseLeave)       // insert the title element into DOM       .append(tooltip);   },   onMouseEnter: function(x, y) {     this.data("tooltip").style({left: x, top: y}).show();   },   onMouseLeave: function() {     this.data("tooltip").hide();   } });

我们可以在CSS中定义 .custom-title 元素的样式:

.custom-title {   position: fixed;    border: 1px solid #faebcc;   background: #faf8f0; }

当你向页面中插入一个带title 属性的新元素时,最有趣的部分发生了。自定义的提示框无需调用任何初始化方法即可生效。

live扩展是独立的;这样它们并不需要为了使得未来的内容生效去调用一个初始化方法。因此它们可以与任何DOM库结合使用,将UI代码分割成许多小的独立的块,从而简化应用的逻辑。

***,同样很重要的,一些关于Web组件的内容。规范的一部分,“装饰器” ,意在解决类似的问题。目前,它使用了一种基于标记的实现,通过特殊的语法,将事件监听者绑定到子元素上。但它仍只是早期的草案:

“装饰器,与Web组件的其它部分不同的是,它还没有一个规范。”

原生动画

多亏了 Apple, CSS现在拥有了对动画的良好支持。过去动画通常使用Javascript的setInterval 及setTimeout实现。这曾经是很酷的特性——但是现在看来,它更像是坏的实践。原生的动画总是更平滑:常常更快,开销更小,并且在浏览器不支持的情况下可以很好地降级。

better-dom中,没有animate方法:只有show, hide 以及toggle。库使用基于标准的aria-hidden属性来在CSS中获取一个隐藏元素的状态。

为了说明它是如何工作的,我们来为先前介绍的提示框添加一个简单的动画效果:

.custom-title {   position: fixed;    border: 1px solid #faebcc;   background: #faf8f0;      opacity: 1;   -webkit-transition: opacity 0.5s;   transition: opacity 0.5s; }   .custom-title[aria-hidden=true] {   opacity: 0; }

show() 以及hide() 在内部将 aria-hidden 属性值设置为false或true。这使得CSS可以处理动画与转换。

你可以在这个demo中看到更多使用了better-dom的动画。

内置的微模板

HTML字符串冗长而繁琐。寻找替代的过程中我发现了超棒的Emmet。如今Emmet已经是一个非常流行的文本编辑器插件,它拥有漂亮而简洁的语法。比如这段HTML:

body.append("<ul><li class='list-item'></li><li class='list-item'></li><li class='list-item'></li></ul>");

与对应的微模板比较:

body.append("ul>li.list-item*3");

在better-dom中,任何接受HTML的方法同样接受Emmet表达式。缩写解析器很快,所以不用担心付出性能代价。如果需要,还有一个模板预编译函数可用。

国际化支持

开发一个UI组件经常会需要本地化&mdash;&mdash;这并不轻松。多年来,很多人使用不同的方法解决这个问题。在better-dom中,我相信改变CSS选择器的状态,就如同转换语言。

从概念上说,转换语言正是改变内容的“表现”。在CSS2中,有几个伪类选择器可用于描述这样一个模型::lang 以及:before。我们来看下边的代码:

[data-i18n="hello"]:before {   content: "Hello Maksim!"; }   [data-i18n="hello"]:lang(ru):before {   content: "Привет Максим!"; }

这是个很简单的把戏:html 元素的lang 属性控制当前语言,而content 属性值根据当前的语言变化。通过使用如data-i18n这样的属性,我们可以在HTML中维护文本内容。

[data-i18n]:before {   content: attr(data-i18n); }   [data-i18n="Hello Maksim!"]:lang(ru):before {   content: "Привет Максим!"; }

当然,这样的CSS并不吸引人,所以better-com提供了两个帮助方法:i18n 及DOM.importStrings。前者用于更新 data-i18n 属性为合适的值,后者为特定的语言本地化字符串。

label.i18n("Hello Maksim!"); // the label displays "Hello Maksim!" DOM.importStrings("ru",  "Hello Maksim!", "Привет Максим!"); // now if the page is set to ru language, // the label will display "Привет Максим!" label.set("lang", "ru"); // now the label will display "Привет Максим!" // despite the web page's language

还可以使用参数化的字符串。直接向关键字符串中添加${param} 变量:

label.i18n("Hello ${user}!", {user: "Maksim"}); // the label will display "Hello Maksim!"

让原生的APIs 更加优雅

通常我们都希望遵从标准。但是有时候标准对用户并不友好。DOM就是一团糟 ,为了将其变得好用,我们不得不把它包装到一个方便的API中。尽管开源的库已经作了很多改进,仍有一些部分可以做得更好:

  • getter 及setter

  • 事件处理

  • 功能性的方法支持

GETTER 及SETTER

原生的 DOM 元素有attributes 及properties的概念,但他们的行为并不完全一致。假设我们在一个web页面中有如下的标记:

<a href="/chemerisuk/better-dom" id="foo" data-test="test">better-dom</a>

为了解释为什么“DOM就是一团糟”,我们来看这:

var link = document.getElementById("foo");   link.href; // => "https://github.com/chemerisuk/better-dom" link.getAttribute("href"); // => "/chemerisuk/better-dom" link["data-test"]; // => undefined link.getAttribute("data-test"); // => "test"   link.href = "abc"; link.href; // => "https://github.com/abc" link.getAttribute("href"); // => "abc"

一个attribute与其在HTML中对应的字符串相等,但元素的同名property可能会有一些奇怪的行为,比如在上边列出来的,生成完全合格的URL。这些区别有时会导致混淆。

在实际使用中,很难想像一个这样的区别有用的场景。除此之外,开发者必须总是牢记哪些值(attribute 或property)被使用了,这会引入没必要的复杂度。

在better-dom中,事情要清楚得多。每个元素都只有智能的getter与setter。

var link = DOM.find("#foo");   link.get("href"); // => "https://github.com/chemerisuk/better-dom" link.set("href", "abc"); link.get("href"); // => "https://github.com/abc" link.get("data-attr"); // => "test"

首先,它做一次属性(property)查找,如果是已定义的,则返回供操作。不然,getter 及setter  作用于对应的元素属性(attribute)。对于boolean值(checked, selected, 这些), 可以直接使用 true 或  false 来更新值:改变元素的该属性(property)将触发对应的attibute(原生行为)的更新。

改良的事件处理

事件处理是DOM中很重要的一部分,然而,我发现一个根本性的问题:将event对象传入元素监听者的行为导致关心可测试性的开发者不得不伪造***个参数(事件对象),或是创建一个额外的函数来传入事件处理函数仅需的事件属性。

var button = document.getElementById("foo");   button.addEventListener("click", function(e) {   handleButtonClick(e.button); }, false);

这很烦人。不过如果我们将可变部分抽象为一个参数,我们就可以摆脱额外的函数:

var button = DOM.find("#foo");   button.on("click", handleButtonClick, ["button"]);

默认地,事件处理函数会被传入["target", "defaultPrevented"] 数组,所以不用为了获得这些属性添加***一个参数。

button.on("click", function(target, canceled) {     // handle button click here  });

延时绑定也得到了支持(我建议读一下Peter Michaux关于这个主题的回顾)。它是W3C的标准中常规事件绑定的更加灵活的替换物。它在你需要频繁进行启用和关闭方法调用时非常有用。

button._handleButtonClick = function() { alert("click!"); };   button.on("click", "_handleButtonClick"); button.fire("click"); // shows "clicked" message button._handleButtonClick = null; button.fire("click"); // shows nothing

***,同样很重要的,better-dom不提供任何对于遗留的或不同浏览器中表现不一致的API的调用,比如click(), focus() 和submit()。 调用他们的唯一方式是使用fire 方法,它在没有监听者返回false的情况下执行默认行为:

link.fire("click"); // clicks on the link link.on("click", function() { return false; }); link.fire("click"); // triggers the handler above but doesn't do a click

功能性方法的支持

ES5规范了一些的有用的数组方法,包括 map, filter 以及some。它们允许我们以符合标准的方式使用通用的集合操作。因此现在我们有了诸如Underscore 和Lo-Dash这样的项目,它们在老的浏览器上实现这些方法。

better-dom中的每个元素(或集合)都内置了如下的方法:

each (它与 forEach 的区别在于返回this 而不是 undefined)

some

every

map

filter

reduce[Right]

var urls, activeLi, linkText;   urls = menu.findAll("a").map(function(el) {   return el.get("href"); }); activeLi = menu.children().filter(function(el) {   return el.hasClass("active"); }); linkText = menu.children().reduce(function(memo, el) {   return memo || el.hasClass("active") && el.find("a").get() }, false);

避免jQuery的问题

在不放弃向后兼容的情况下,以下的绝大多数问题无法在jQuery中得到解决。这是为什么创造一个新的库看起来是合乎逻辑的解决途径。

  • “神奇的” $ 函数

  • [] 操作符的值

  • 关于 return false的问题

  • find 以及findAll

“神奇的” $ 函数

每个人都或多或少听说过$ (美元) 函数的神奇。一个单字符的名字并不具有描述性,所以它看起来像是一个内置的语言操作符。这也正是缺乏经验的开发者的代码中$的调用随处可见的原因。

在背后的实现中,$是个极其复杂的函数。经常地执行,尤其是 mousemove 、scroll这样的频繁事件中,会导致较差的UI性能。

尽管有很多文章建议将jQuery对象缓存下来,开发者依旧在将$大量嵌入在代码中,因为jQuery库的语法鼓励了这样的代码风格。

$函数的另一个问题是,它可以被用来做完全不同的两件事。人们已经喜欢了这样的语法,但通常来说,这是一个失败的函数设计:

$("a"); // => searches all elements that match “a” selector $("<a>"); // => creates a <a> element with jQuery wrapper

better-dom 使用了几个函数来承担jQuery中$函数的职责:find[All] 以及DOM.create。find[All]  被用来依据CSS选择器来获取元素。 DOM.create 在内存中创建一个新的节点树。它们的名字就可以清晰地表明它们的职责。

[]操作符的值

导致$函数被频繁调用的另一个原因正是方括号操作符。当一个新的jQuery对象被创建的时候,所有相关的节点都被存储在数值型属性中。但是请注意,这样一个数值属性的值包含了一个原生的元素实例(而非经jQuery包装过的对象):

var links = $("a");   links[0].on("click", function() { ... }); // throws an error $(links[0]).on("click", function() { ... }); // works fine

正因为这样的特性,jQuery或是其它库(比如Underscore)中的每一个功能方法都要求当前元素在回调函数中使用$()  包起来。因此,开发者必须时刻牢记他们正在操作的对象类型&mdash;&mdash;一个原生元素或是一个包装过的对象&mdash;&mdash;尽管事实上他们正在使用一个操作DOM的库。

在better-dom中,方括号操作符返回一个库对象,所以开发者可以忘记原生元素。只有一种可接受的方式来获取原生元素:使用一个特别的 legacy方法。

var foo = DOM.find("#foo");   foo.legacy(function(node) {   // use Hammer library to bind a swipe listener   Hammer(node).on("swipe", function(e) {     // handle swipe gesture here   }); });

事实上,只有非常少见的情况会需要这个方法,比如兼容一个原生的方法,或是另一个DOM库(比如上边例子中的Hammer)。

关于RETURN FALSE的问题

jQuery事件处理函数中返回false后的奇怪的拦截行为让我一直很纠结。依据W3C的标准,它应该在大多数情况下取消默认行为。在jQuery中,return false 还会阻止事件代理。

这样的捕获会导致问题:

1 自行调用stopPropagation() 可能导致兼容性问题,因为它阻止了其他任务相关的监听者的执行。

2 大部分开发者(即使是一些有经验的)并没有意识到这样的行为

尚不清楚为什么jQuery社区决定不遵循标准。但better-dom并不会重蹈覆辙。 所以,正如每个人所预期的,事件句柄中的return false 只会阻止浏览器默认行为,而不会干扰事件冒泡。

FIND 以及FINDALL

元素查找是在浏览器中代价***的操作之一。两个原生的方法可以用来实现它:querySelector以及querySelectorAll。区别在于前者在匹配到***个结果后即停止查找。

这个特性使得我们可以显著减少特定情形下的迭代次数。在我的测试中,速度提升到了二十倍!而且,可以预见,依据文档树的规模,提升可能达到更多。

jQuery提供了一个find 方法,使用querySelectorAll ,用于一般的情形。目前还没有函数使用querySelector 来只获取***个匹配的元素。

better-dom 库有两个单独的方法:find 及findAll。它们允许我们使用querySelector 优化。为了评估潜在的性能提升,我在我上一个商业项目的所有源代码中搜索了这些方法的使用:

find
在11个文件中匹配103次
findAll
在4个文件中匹配14次

很明显find 方法要受欢迎得多。这说明querySelector 优化在大多数情况下是有意义的,并能推动相当的性能提升。

结论

live扩展确实使得解决前端问题简单不少。将UI分割为许多小块可以带来更加独立、可维护的解决方案。不过正如我们所展示的,一个框架不仅仅是关于这些(尽管这是主要目标)。

我在开发过程中学习到的一件事是,如果你不喜欢某个标准,或者你对该如何做某件事情有自己不同看法,那么就去实现它,证明你的方法可行。

看完上述内容,你们掌握如何写一个更好的Javascript DOM库的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注编程网行业资讯频道,感谢各位的阅读!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

如何写一个更好的Javascript DOM库

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

如何用Python写一个NoSQL数据库

这篇文章主要介绍“如何用Python写一个NoSQL数据库”,在日常操作中,相信很多人在如何用Python写一个NoSQL数据库问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何用Python写一个NoSQL
2023-06-15

基于JavaScript如何编写一个翻卡游戏

这篇文章主要介绍“基于JavaScript如何编写一个翻卡游戏”,在日常操作中,相信很多人在基于JavaScript如何编写一个翻卡游戏问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”基于JavaScript如
2023-07-05

如何更好的进行编写Python程序

如何更好的进行编写Python程序,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。最中间的是关于 unittest 的一些设置。***条把 python 文件的编
2023-06-17

JavaScript如何将字符串中的第一个字母大写

这篇文章主要为大家展示了“JavaScript如何将字符串中的第一个字母大写”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“JavaScript如何将字符串中的第一个字母大写”这篇文章吧。如何将字
2023-06-27

详解如何用JavaScript编写一个单元测试

测试代码是确保代码稳定的第一步。能做到这一点的最佳方法之一就是使用单元测试。这篇文章主要介绍了如何用JavaScript编写你的第一个单元测试,感兴趣的可以了解一下
2022-11-13

如何注册一个好的gitee

随着互联网技术的发展和应用越来越广泛,开源软件的使用也逐渐成为业界的一种趋势。而Gitee作为国内领先的开源软件托管平台之一,被越来越多的开发者所认可和使用。如果你想成为一名Gitee的用户,在注册过程中需要注意哪些问题呢?本文将从以下几个
2023-10-22

如何编写一个 MySQL 存储函数来更新表中的值?

众所周知,当我们想要返回结果时,最好使用函数。因此,当我们创建存储函数来操作表(例如插入或更新值)时,它或多或少类似于存储过程。在下面的示例中,我们将创建一个名为“tbl_update”的存储函数,它将更新名为“student_marks”
2023-10-22

如何封装一个好用的axios

这篇文章主要介绍了如何封装一个好用的axios的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇如何封装一个好用的axios文章都会有所收获,下面我们一起来看看吧。通用能力列一下我想要这个通用请求能达到什么样的效果
2023-07-02

javascript如何捕获event.keyCode并将其更改为另一个key

要捕获event.keyCode并将其更改为另一个键,您可以使用事件监听器来捕获键盘事件,并在事件处理程序中更改keyCode的值。以下是一个示例代码:```javascriptdocument.addEventListener('keyd
2023-09-17

如何判断一个网站SEO做的好不好

本篇内容介绍了“如何判断一个网站SEO做的好不好”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!  1、家门口(头部代码优化)  网站源文件J
2023-06-10

编程热搜

目录