如何使用方法创建 javascript 插件?

How to create a javascript plugin with methods?

来自How to create a jQuery plugin with methods?

如何在 javascript 没有 JQuery

的情况下做同样的事情

JQuery

    (function ( $ ) {
 
    $.fn.greenify = function() {
        this.css( "color", "green" );
        return this;
    };
 
}( jQuery ));

Js

?

如果您的意思是“如何将我自己的方法添加到 DOM 元素”,我的建议是:不要,因为您的方法可能会与后来添加到标准中的方法发生冲突。 (并且 绝对 不在库中,只在特定页面或应用程序的代码中这样做——如果你这样做的话。)相反,做 jQuery 所做的并且在元素周围放置包装器。

但是如果您无论如何都想这样做,您需要将函数添加到您希望它们出现的元素的原型中。基本 HTML 元素是 HTMLElement,因此要向所有 HTML 元素添加一个方法,您需要将其添加到那里。

// Ensure that your method isn't enumerable by using `Object.defineProperty`
Object.defineProperty(HTMLElement.prototype, "greenify", {
    configurable: true,
    writable: true,
    value() {
        this.style.color = "green";
    }
});

示例:

// Ensure that your method isn't enumerable by using `Object.defineProperty`
Object.defineProperty(HTMLElement.prototype, "greenify", {
    configurable: true,
    writable: true,
    value() {
        this.style.color = "green";
    }
});

// Use it
document.getElementById("a").greenify();
document.getElementById("b").greenify();

// Since it's on the prototype, it applies regardless of
// whether the element already existed:
const c = document.createElement("div");
c.greenify();
c.textContent = `This is "c"`;
document.body.appendChild(c);
<div id="a">This is "a"</div>
<div id="b">This is "b"</div>

目前,元素集合是 NodeList 个实例(querySelectorAll 和其他较新的集合)或 HTMLCollection 个实例(getElementsByXYZ),尽管这可能会进一步发展.因此,如果您希望能够在集合上调用 addClass,您可以将其添加到其中一个或两个原型中。让我们从 greenify 转到一个更有用的示例:将 class 添加到集合中的所有元素:

// Ensure that your method isn't enumerable by using `Object.defineProperty`
function addClass(...classes) {
    for (let n = 0; n < this.length; ++n) {
        this[n].classList.add(...classes);
    }
}
Object.defineProperty(NodeList.prototype, "addClass", {
    configurable: true,
    writable: true,
    value: addClass
});
Object.defineProperty(HTMLCollection.prototype, "addClass", {
    configurable: true,
    writable: true,
    value: addClass
});

// Use it
document.querySelectorAll("div").addClass("accent");
document.getElementsByClassName("x").addClass("emphasize");
.accent {
    color: green;
}
.emphasize {
    font-weight: bold;
}
<div class="x">This is "a"</div>
<div class="x">This is "b"</div>

普通旧函数

jQuery 只不过是 JavaScript 本身。您可以轻松地将 greenifyboldify 编写为普通函数。这是一个很好的方法,因为每个简单的函数都易于编写、测试和在需要此特定行为的程序的任何部分中重用 -

function greenify (elem) {
  elem.style.color = "green"
}

function boldify (elem) {
  elem.style.fontWeight = "bold"
}

greenify(document.querySelector("#foo"))
boldify(document.querySelector("#bar"))
<p id="foo">hello world</p>
<p id="bar">hello world</p>
<p id="qux">hello world</p>

html render: example 1

class注意事项和注意事项

jQuery 使用面向对象的风格,允许“链接”方法。这个接口是可取的,因为它使我们能够将程序编写为长链表达式,就像我们可以写句子一样。大多数人会使用 class 来完成此操作,它是定义函数并将“方法”添加到函数的 prototype -

的语法糖

class MyTools {
  constructor (elem) {
    this.elem = elem
  }
  greenify() {
    this.elem.style.color = "green"
    return this
  }
  boldify() {
    this.elem.style.fontWeight = "bold"
    return this
  }
}

function mytools (elem) {
  return new MyTools(elem) 
}

mytools(document.querySelector("#foo")).greenify()
mytools(document.querySelector("#bar")).boldify()
mytools(document.querySelector("#qux")).boldify().greenify()
<p id="foo">hello world</p>
<p id="bar">hello world</p>
<p id="qux">hello world</p>

html render: example 2

但是如果我们的 mytools class 中有很多像 greenifyboldify 这样的方法呢?您可以看到 class 如何变得相当大,并且为每个方法重复 return this 有点负担。如果将来有人只想使用您工具库的 部分 怎么办?使用单体 class 使得无法使用 dead code elimination,这意味着即使需要 one 功能也必须包含整个模块。


高阶函数

我们能否构建一个没有这些缺点的直观界面?解决这些问题的一种方法是使用 higher-order functions。这样的函数可以将一个函数作为输入 and/or return 一个新函数作为输出。您可能使用过内置的高阶函数,例如 Array.prototype.mapArray.prototype.filterArray.prototype.reduce 或其他函数,但我们也可以轻松创建自己的 -

const effect = f =>
  x => { f(x); return x }

function identity (value) {
  return { value, map: f => identity(f(value)) }
}

const boldify =
  effect(elem => elem.style.fontWeight = "bold")

const greenify =
  effect(elem => elem.style.color = "green")

// prefix style; ordinary function calls
greenify(boldify(document.querySelector("#foo")))

// "chaining" style; by use of identity
identity(document.querySelector("#qux"))
  .map(boldify)
  .map(greenify)
<p id="foo">hello world</p>
<p id="bar">hello world</p>
<p id="qux">hello world</p>

html render: example 3

模块

即使是 jQuery 3.6.0 的“精简版”也超过了 72 KB,并且无法只导入其中的一部分。这是 jQuery 选择通过单个面向对象的接口实现所有内容的结果,jQuery 函数,别名为 $。然而,JavaScript 的未来在于其为 modules 设计。它们可重复使用、灵活并且可以即时加载,从而生成高度优化和简化的脚本,可以立即响应用户。

上面我们写了几个函数。让我们看看我们如何开始为我们自己的程序组织模块 -

// func.js

function identity (value) {
  return { value, map: f => identity(f(value)) }
}

const effect = f =>
  x => { f(x); return x }


export { effect, identity }
// css.js

import { effect } from "./func.js"

const boldify =
  effect(elem => elem.style.fontWeight = "bold")

const greenify =
  effect(elem => elem.style.color = "green")

export { boldify, greenify }

当我们使用模块时,我们 import 只使用我们需要的部分。这使编译器(转译器、“打包器”、“捆绑器”等)有机会删除死代码并显着减少已完成的生产就绪程序的大小 -

// main
import { greenify } from "./css.js"

greenify(document.querySelector("#some-elem"))

甚至 * 导入也可以优化,因为依赖项解析器可以确定仅使用来自 css 命名导入的特定函数 -

import { identity } from "./func.js"
import * as css from "./css.js"

identity(document.querySelector("#some-elem"))
  .map(css.boldify)
  .map(css.greenify)

增加你的模块

所以您想扩展您的模块以支持其他一些颜色,例如 bluifypurplify 等。你可以试试这样的 -

// css.js
// DON'T DO THIS!

const bluify =
  effect(elem => elem.style.color = "green")

const greenify =
  effect(elem => elem.style.color = "green")

const purplify =
  effect(elem => elem.style.color = "purplify")

// ...

export { bluify, greenify, purplify, ... }

相反,请注意每个函数中只有 elem.style.color = ...... 部分发生了变化。为了避免一遍又一遍地重复自己,我们创建函数 -

// css.js

import { effect } from "./func.js"

// any color, not just green!
const color = value =>
  effect(elem => elem.style.color = value)

// any font-weight, not just bold!
const fontWeight = value =>
  effect(elem => elem.style.fontWeight = value)
  
// other CSS properties...
const fontSize = value =>
  effect(elem => elem.style.fontSize = value)

// ...

export { color, fontWeight, fontSize, ... }

请注意 colorfontWeightfontSize 各有一个 value 参数。我们减少了代码重复并使我们的功能更有用 -

// main.js

import { identity } from "./func.js"
import * as css from "./css.js"

identity(document.querySelector("#bar"))
  .map(css.color("limegreen"))
  .map(css.fontSize("24pt"))
  
identity(document.querySelector("#qux"))
  .map(css.color("dodgerblue"))
  .map(css.fontWeight("900"))
html render: example 4

展开下面的代码段 4 以在您自己的浏览器中验证此结果 -

// func.js
const effect = f =>
  x => { f(x); return x }

function identity (value) {
  return { value, map: f => identity(f(value)) }
}

// css.js
const fontWeight = value =>
  effect(elem => elem.style.fontWeight = value)
  
const fontSize = value =>
  effect(elem => elem.style.fontSize = value)

const color = value =>
  effect(elem => elem.style.color = value)

// main.js
identity(document.querySelector("#bar"))
  .map(color("limegreen"))
  .map(fontSize("24pt"))
  
identity(document.querySelector("#qux"))
  .map(color("dodgerblue"))
  .map(fontWeight("900"))
<p id="foo">hello world</p>
<p id="bar">hello world</p>
<p id="qux">hello world</p>


jQuery精简版

也许您喜欢 jQuery,因为它接受 CSS 选择器并且不需要您使用 identitydocument.querySelector。 jQuery 做出了选择,你也可以。这是你的程序,你可以自由地发明你想要的任何便利 -

// myquery.js

import { identity } from "./func.js"

function $(selector) {
  const loop = t => plugin => loop(t.map(plugin))
  return loop(identity(document.querySelector(selector)))
}

export { $ }

我们可以开始看到我们自己的 $ 不断发展! -

// main.js

import { $ } from "./myquery.js"
import { color, fontSize, fontWeight } from "./css.js"

// example expressions
$("#bar")(color("gold"))(fontSize("8pt"))

$("#qux")(fontSize("14pt"))(color("hotpink"))(fontWeight(900))
html render: example 5

展开下面的代码段 5 以在您的浏览器中验证结果 -

// func.js
const effect = f =>
  x => { f(x); return x }

function identity (value) {
  return { value, map: f => identity(f(value)) }
}

// css.js
const fontWeight = value =>
  effect(elem => elem.style.fontWeight = value)
  
const fontSize = value =>
  effect(elem => elem.style.fontSize = value)

const color = value =>
  effect(elem => elem.style.color = value)
  
// myquery.js
function $(selector) {
  const loop = t => plugin => loop(t.map(plugin))
  return loop(identity(document.querySelector(selector)))
}

// main.js
$("#bar")(color("gold"))(fontSize("8pt"))
$("#qux")(fontSize("14pt"))(color("hotpink"))(fontWeight(900))
<p id="foo">hello world</p>
<p id="bar">hello world</p>
<p id="qux">hello world</p>

也许您想对多个元素进行操作,类似于jQuery?我们可以通过使用 querySelectorAll 并使用 Array.fromplugin 应用于可迭代对象中的所有元素来做到这一点。请注意,我们也可以绕过 identity 的需要,因为 loop 已经 closes overcontext -

// myquery.js (updated)

function $(selector) {
  const loop = context => plugin => loop(Array.from(context, plugin))
  return loop(document.querySelectorAll(selector))
}
// main.js

// target all <p> elements
$("p")(color("orchid"))

// target #foo element
$("#foo")(fontSize("18pt"))(fontWeight("bold"))

// target #bar and #qux elements
$("#bar,#qux")(fontSize("12pt"))
html render: example 6

展开下面的代码段 6 以在浏览器中验证结果 -

// func.js
const effect = f =>
  x => { f(x); return x }

function identity (value) {
  return { value, map: f => identity(f(value)) }
}

// css.js
const fontWeight = value =>
  effect(elem => elem.style.fontWeight = value)
  
const fontSize = value =>
  effect(elem => elem.style.fontSize = value)

const color = value =>
  effect(elem => elem.style.color = value)
  
// myquery.js
function $(selector) {
  const loop = t => plugin => loop(t.map(v => Array.from(v, plugin)))
  return loop(identity(document.querySelectorAll(selector)))
}

// main.js
$("p")(color("orchid"))
$("#foo")(fontSize("18pt"))(fontWeight("bold"))
$("#bar,#qux")(fontSize("12pt"))
<p id="foo">hello world</p>
<p id="bar">hello world</p>
<p id="qux">hello world</p>


鹰眼

您是否注意到 colorfontWeightfontSize 看起来几乎一模一样?我们可以继续使用抽象来去除额外的重复。通过捕获行为的最小可识别本质,我们获得了最大的灵活性,使我们能够轻松地从简单的行为构建复杂的行为 -

// css.js

import { effect } from "./func.js"

const style = property => value =>
  effect(elem => elem.style[property] = value)

const color = style("color")
const fontWeight = style("fontWeight")
const fontSize = style("fontSize")

// ...

export { color, fontWeight, fontSize, style, ... }