构建您的第一个JavaScript库
你是否曾惊叹于React的魔力?是否曾好奇Dojo是如何运作的?是否曾对jQuery的巧妙操作感到好奇?在本教程中,我们将潜入幕后,尝试构建一个超简化的jQuery版本。
我们几乎每天都在使用JavaScript库。无论是实现算法、提供API抽象还是操作DOM,库在大多数现代网站中都执行许多功能。
在本教程中,我们将尝试从头开始构建一个这样的库(当然,这是一个简化的版本)。我们将创建一个用于DOM操作的库,类似于jQuery。是的,这很有趣,但在你兴奋之前,让我澄清几点:
- 这不会是一个功能齐全的库。我们将编写一套可靠的方法,但这并非完整的jQuery。我们将做的足够多,让你对构建库时会遇到的问题类型有很好的了解。
- 我们在这里不会追求跨所有浏览器的完全兼容性。我们今天编写的代码应该可以在Chrome、Firefox和Safari上运行,但在IE等旧版浏览器上可能无法运行。
- 我们不会涵盖我们库的每一个可能的用途。例如,我们的
prepend
方法只在你传递给它们我们的库实例时才有效;它们不适用于原始DOM节点或节点列表。
-
创建库的框架
我们将从模块本身开始。我们将使用ECMAScript模块(ESM),这是一种在Web上导入和导出代码的现代方法。
export class Dome { constructor(selector) { } }
如你所见,我们导出一个名为Dome
的类,其构造函数将接受一个参数,但它可以是多种类型。如果它是一个字符串,我们将假设它是一个CSS选择器,但我们也可以接受单个DOM节点或document.querySelectorAll
的结果来简化元素查找。如果它具有length
属性,我们将知道我们拥有一个节点列表。我们将把这些元素存储在this.elements
中,Dome
对象可以包装多个DOM元素,我们几乎需要在每种方法中循环遍历每个元素,因此这些实用程序将非常方便。
让我们从一个map
函数开始,它接受一个参数,一个回调函数。我们将循环遍历数组中的项目,收集回调函数返回的内容,Dome
实例将接收两个参数:当前元素和索引号。
我们还需要一个forEach
方法,默认情况下,我们可以简单地将调用转发到mapOne
。很容易看出这个函数的作用,但真正的问题是,为什么我们需要它?这需要一点你可能称之为“库理念”的东西。
简短的理念探讨
如果构建库只是编写代码,那将不是一项太难的工作。但在从事这个项目时,我发现更难的部分是决定某些方法应该如何工作。
很快,我们将构建一个Dome
对象,它包装了多个DOM节点($("li").text()
),你将得到一个包含所有元素文本连接在一起的单个字符串。这有用吗?我认为没有,但我不知道更好的返回值是什么。
对于这个项目,我将把多个元素的文本作为数组返回,除非数组中只有一个项目;然后我们只返回文本字符串,而不是包含单个项目的数组。我认为你最常获取单个元素的文本,所以我们对此情况进行了优化。但是,如果你正在获取多个元素的文本,我们将返回你可以使用的内容。
返回编码
因此,mapOne
将首先调用map
,然后返回数组或数组中的单个项目。如果你仍然不确定这如何有用,请继续关注:你将看到!
mapOne(callback) { const m = this.map(callback); return m.length > 1 ? m : m[0]; };
-
使用文本和HTML
接下来,让我们添加text
方法来查看我们是在设置还是获取。请注意,这只是遍历元素并设置它们的文本。如果我们正在获取,我们将返回元素的mapOne
方法:如果我们正在处理多个元素,这将返回一个数组;否则,它将只是一个字符串。
html
方法与text
方法几乎相同,只是它将使用innerHTML
。
html(html) { if (typeof html !== "undefined") { this.forEach(function (el) { el.innerHTML = html; }); return this; } else { return this.mapOne(function (el) { return el.innerHTML; }); } }
就像我说的:几乎相同。
-
操作类
接下来,我们要能够添加和删除类,所以让我们编写addClass
和removeClass
方法。
我们的addClass
方法将在每个元素上使用classList.add
方法。当传递字符串时,只添加该类,当传递数组时,我们将遍历数组并添加其中包含的所有类。
addClass(classes) { return this.forEach(function (el) { if (typeof classes !== "string") { for (const elClass of classes) { el.classList.add(elClass); } } else { el.classList.add(classes); } }); }
很简单,对吧?
现在,删除类呢?为此,你几乎要做同样的事情,只是使用classList.remove
方法。
-
使用属性
接下来,让我们添加attr
函数。这将很容易,因为它与我们的html
方法几乎相同。像这些方法一样,我们将能够同时获取和设置属性:我们将接受一个属性名称和值来设置,只接受一个属性名称来获取。
attr(attr, val) { if (typeof val !== "undefined") { return this.forEach(function (el) { el.setAttribute(attr, val); }); } else { return this.mapOne(function (el) { return el.getAttribute(attr); }); } }
如果val
已定义,我们将使用setAttribute
方法。否则,我们将使用getAttribute
方法。
-
创建元素
我们应该能够创建新元素,任何好的库都可以做到这一点。当然,这作为Dome
类的方法是没有意义的。
export function create(tagName,attrs) { }
如你所见,我们将接受两个参数:元素的名称和属性对象。大多数属性将通过我们的attr
方法应用,文本内容将通过text
方法应用于Dome
对象。以下是所有这些的实际操作:
export function create(tagName, attrs) { let el = new Dome([document.createElement(tagName)]); if (attrs) { for (let key in attrs) { if (attrs.hasOwnProperty(key)) { el.attr(key, attrs[key]); } } } return el; }
如你所见,我们创建元素并将其直接发送到新的Dome
对象中。
但是现在我们正在创建新元素,我们将希望将它们插入到DOM中,对吧?
-
附加和前置元素
接下来,我们将编写append
和prepend
方法。这些函数有点棘手,主要是因为有多种用例。以下是我们想要能够做的事情:
dome1.append(dome2); dome1.prepend(dome2);
我们可能想要附加或前置:
- 一个新元素到一个或多个现有元素
- 多个新元素到一个或多个现有元素
- 一个现有元素到一个或多个现有元素
- 多个现有元素到一个或多个现有元素
我使用“新”来表示尚未在DOM中的元素;现有元素已在DOM中。让我们现在逐步讲解:
append(els) { }
我们期望els
是一个Dome
对象。一个完整的DOM库会将其作为节点或节点列表接受,但我们不会这样做。我们必须遍历我们的每个元素,然后在其中,我们遍历我们想要附加的每个元素。
如果我们正在附加,则来自作为参数传入的外部Dome
对象的i
将只包含原始(未克隆的)节点。因此,如果我们只将单个元素附加到单个元素,则所有涉及的节点都将是它们各自的prepend
方法的一部分。
-
删除元素
为了完整起见,让我们添加一个remove
方法。这将非常简单,因为我们只需要使用removeChild
方法。为了使事情更简单,我们将使用forEach
循环反向遍历,我将使用removeChild
方法反向遍历循环,每个元素的Dome
对象仍然可以正常工作;我们可以使用任何我们想要的方法,包括将其附加或前置回DOM。不错,对吧?
-
使用事件
最后但并非最不重要的是,我们将编写一些事件处理程序函数。
查看on
方法,然后我们将讨论它:
on(evt, fn) { return this.forEach(function (el) { el.addEventListener(evt, fn, false); }); }
这很简单。我们只需遍历元素并使用addEventListener
方法。off
函数(它取消挂钩事件处理程序)几乎相同:
off(evt, fn) { return this.forEach(function (el) { el.removeEventListener(evt, fn, false); }); }
-
使用库
要使用Dome
,只需将其放入脚本并导入它。
import {Dome, create} from "./dome.js"
从那里,你可以像这样使用它:
new Dome("li") ...
确保你导入它的脚本是ES模块。
就是这样!
我希望你能尝试一下我们的小型库,甚至可以扩展它一点。正如我前面提到的,我已经把它放在GitHub上了。随意分叉它,玩耍,并发送拉取请求。
让我再次澄清一下:本教程的目的并不是建议你应该总是编写自己的库。有专门的团队在共同努力,使大型的、成熟的库尽可能好。这里的目的是让你对库内部可能发生的事情有所了解;我希望你在这里学到了一些技巧。
我强烈建议你在你的一些最喜欢的库中四处挖掘。你会发现它们并不像你想象的那么神秘,而且你可能会学到很多东西。以下是一些不错的起点:
- 我从jQuery源代码中学到的11件事(Paul Irish)
- jQuery的幕后(James Padolsey)
- React 16:深入了解我们前端UI库的API兼容重写
这篇文章已更新,其中包含Jacob Jackson的贡献。Jacob是一位网络开发者、技术作家、自由职业者和开源贡献者。
以上是构建您的第一个JavaScript库的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

JavaScript是现代Web开发的基石,它的主要功能包括事件驱动编程、动态内容生成和异步编程。1)事件驱动编程允许网页根据用户操作动态变化。2)动态内容生成使得页面内容可以根据条件调整。3)异步编程确保用户界面不被阻塞。JavaScript广泛应用于网页交互、单页面应用和服务器端开发,极大地提升了用户体验和跨平台开发的灵活性。

Python和JavaScript开发者的薪资没有绝对的高低,具体取决于技能和行业需求。1.Python在数据科学和机器学习领域可能薪资更高。2.JavaScript在前端和全栈开发中需求大,薪资也可观。3.影响因素包括经验、地理位置、公司规模和特定技能。

如何在JavaScript中将具有相同ID的数组元素合并到一个对象中?在处理数据时,我们常常会遇到需要将具有相同ID�...

学习JavaScript不难,但有挑战。1)理解基础概念如变量、数据类型、函数等。2)掌握异步编程,通过事件循环实现。3)使用DOM操作和Promise处理异步请求。4)避免常见错误,使用调试技巧。5)优化性能,遵循最佳实践。

实现视差滚动和元素动画效果的探讨本文将探讨如何实现类似资生堂官网(https://www.shiseido.co.jp/sb/wonderland/)中�...

JavaScript的最新趋势包括TypeScript的崛起、现代框架和库的流行以及WebAssembly的应用。未来前景涵盖更强大的类型系统、服务器端JavaScript的发展、人工智能和机器学习的扩展以及物联网和边缘计算的潜力。

深入探讨console.log输出差异的根源本文将分析一段代码中console.log函数输出结果的差异,并解释其背后的原因。�...
