首页 >web前端 >js教程 >构建您的第一个JavaScript库

构建您的第一个JavaScript库

尊渡假赌尊渡假赌尊渡假赌
尊渡假赌尊渡假赌尊渡假赌原创
2025-03-11 00:09:09624浏览

Build Your First JavaScript Library

你是否曾惊叹于React的魔力?是否曾好奇Dojo是如何运作的?是否曾对jQuery的巧妙操作感到好奇?在本教程中,我们将潜入幕后,尝试构建一个超简化的jQuery版本。

我们几乎每天都在使用JavaScript库。无论是实现算法、提供API抽象还是操作DOM,库在大多数现代网站中都执行许多功能。

在本教程中,我们将尝试从头开始构建一个这样的库(当然,这是一个简化的版本)。我们将创建一个用于DOM操作的库,类似于jQuery。是的,这很有趣,但在你兴奋之前,让我澄清几点:

  • 这不会是一个功能齐全的库。我们将编写一套可靠的方法,但这并非完整的jQuery。我们将做的足够多,让你对构建库时会遇到的问题类型有很好的了解。
  • 我们在这里不会追求跨所有浏览器的完全兼容性。我们今天编写的代码应该可以在Chrome、Firefox和Safari上运行,但在IE等旧版浏览器上可能无法运行。
  • 我们不会涵盖我们库的每一个可能的用途。例如,我们的prepend方法只在你传递给它们我们的库实例时才有效;它们不适用于原始DOM节点或节点列表。

  1. 创建库的框架

我们将从模块本身开始。我们将使用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];
};
  1. 使用文本和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;
        });
    }
}

就像我说的:几乎相同。


  1. 操作类

接下来,我们要能够添加和删除类,所以让我们编写addClassremoveClass方法。

我们的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方法。

  1. 使用属性

接下来,让我们添加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方法。

  1. 创建元素

我们应该能够创建新元素,任何好的库都可以做到这一点。当然,这作为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中,对吧?

  1. 附加和前置元素

接下来,我们将编写appendprepend方法。这些函数有点棘手,主要是因为有多种用例。以下是我们想要能够做的事情:

dome1.append(dome2);
dome1.prepend(dome2);

我们可能想要附加或前置:

  • 一个新元素到一个或多个现有元素
  • 多个新元素到一个或多个现有元素
  • 一个现有元素到一个或多个现有元素
  • 多个现有元素到一个或多个现有元素

我使用“新”来表示尚未在DOM中的元素;现有元素已在DOM中。让我们现在逐步讲解:

append(els) {

}

我们期望els是一个Dome对象。一个完整的DOM库会将其作为节点或节点列表接受,但我们不会这样做。我们必须遍历我们的每个元素,然后在其中,我们遍历我们想要附加的每个元素。

如果我们正在附加,则来自作为参数传入的外部Dome对象的i将只包含原始(未克隆的)节点。因此,如果我们只将单个元素附加到单个元素,则所有涉及的节点都将是它们各自的prepend方法的一部分。

  1. 删除元素

为了完整起见,让我们添加一个remove方法。这将非常简单,因为我们只需要使用removeChild方法。为了使事情更简单,我们将使用forEach循环反向遍历,我将使用removeChild方法反向遍历循环,每个元素的Dome对象仍然可以正常工作;我们可以使用任何我们想要的方法,包括将其附加或前置回DOM。不错,对吧?

  1. 使用事件

最后但并非最不重要的是,我们将编写一些事件处理程序函数。

查看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);
    });
}

  1. 使用库

要使用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中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn