搜索
首页web前端js教程带有MORI的不变数据和功能性JavaScript

带有MORI的不变数据和功能性JavaScript

钥匙要点

  • > MORI利用Clojure的持久数据结构,为JavaScript开发人员不可变的数据选项提供了增强代码简单性和可靠性的不可能。
  • >
  • 使用MORI通过强制性不变性来促进JavaScript中的功能编程范式,从而防止意外副作用并确保整个应用程序生命周期中的数据一致性。库促进了处理数据的不同方法,其中函数分别在数据结构上运行,与JavaScript的典型对象方面的方法形成鲜明对比,从而允许使用更清洁,更可预测的代码。
  • MORI的结构共享技术通过在可能的情况下重复现有数据结构来使数据操纵有效,从而可以改善应用程序的性能。
  • >通过诸如具有撤消功能的像素编辑器之类的示例,Mori展示了不变数据结构的实际应用,从而为开发人员提供了构建既复杂且稳健的功能的工具。
  • >。
  • 本文由Craig Bilner和Adrian Sandu进行了同行评审。感谢SitePoint所有的同行评审员制作SitePoint内容的最佳状态!
>功能编程和不变数据是许多JavaScript开发人员的当前重点,因为他们试图找到使代码更简单,更易于推理的方法。

>尽管JavaScript一直支持某些功能性编程技术,但它们在过去几年中才真正流行,传统上也没有对不变数据的本地支持。 JavaScript仍在学习很多方面,最好的想法来自已经尝试并测试了这些技术的语言。 在编程世界的另一个角落,Clojure是一种功能性编程语言,致力于真正的简单性,尤其是在数据结构的情况下。 Mori是一个库,允许我们直接从JavaScript中使用Clojure的持久数据结构。 >本文将探讨这些数据结构设计背后的基本原理,并检查一些使用它们来改善我们的应用程序的模式。我们也可以将其视为对使用Clojure或Clojurescript进行编程的JavaScript开发人员的第一个垫脚石。

>

什么是持续数据?

>

> clojure在无法更改的

持续

的值之间进行区分,而 thristient

值在突变之间具有时间寿命。尝试修改持续数据结构的尝试避免通过返回使用更改的新结构来突变基础数据。>

>可能有助于查看这种区别在理论编程语言中的外观。

<span>// transient list
</span>a <span>= [1, 2, 3];
</span>b <span>= a.push(4);
</span><span>// a = [1, 2, 3, 4]
</span><span>// b = [1, 2, 3, 4]
</span>
<span>// persistent list
</span>c <span>= #[1, 2, 3]
</span>d <span>= c.push(4);
</span><span>// c = #[1, 2, 3]
</span><span>// d = #[1, 2, 3, 4]
</span>

>我们可以看到,当我们将一个值推向其上时,瞬态列表被突变了。 A和B都指向相同的可变值。相比之下,在持久列表上呼叫推送返回了一个新值,我们可以看到c和d指向不同的离散列表。

>

>这些持久的数据结构无法突变,这意味着一旦我们提到了一个值,我们也可以保证它永远不会被更改。这些保证通常可以帮助我们编写更安全,更简单的代码。例如,将持久数据结构作为参数的函数无法突变它们,因此,如果该函数想要传达有意义的更改,则必须来自返回值。这导致编写引用透明的纯函数,易于测试和优化。

更简单,不变的数据迫使我们编写更多功能代码。>

什么是mori?

> Mori使用clojurescript编译器来编译Clojure标准库中数据结构的实现,以汇编JavaScript。编译器发出了优化的代码,这意味着没有其他考虑,与JavaScript的Clojure进行交流并不容易。莫里是其他考虑因素的层。

>就像Clojure一样,Mori的功能与它们操作的数据结构分开,与JavaScript面向对象的趋势对比。我们会发现这种差异改变了我们编写代码的方向。

莫里还使用结构共享来通过尽可能多的原始结构共享对数据进行有效的更改。这使得持续数据结构几乎与常规瞬态效率一样有效。这些概念的实现在此视频中更详细地介绍了。

>
<span>// standard library
</span><span>Array(1, 2, 3).map(x => x * 2);
</span><span>// => [2, 4, 6]
</span>
<span>// mori
</span><span>map(x => x * 2, vector(1, 2, 3))
</span><span>// => [2, 4, 6]
</span>
为什么有用?

> 首先,让我们想象我们正在尝试在我们继承的JavaScript代码库中追踪一个错误。我们正在阅读代码,试图弄清楚为什么我们最终获得了奖学金的错误价值。

>

登录到控制台时奖学金的价值是多少?

>

>不运行代码或阅读deleteperson()的定义,就无法知道。它可能是一个空数组。它可能具有三个新属性。我们希望这是一个删除第二个元素的数组,但是由于我们通过可变的数据结构通过,因此无法保证。
<span>const fellowship = [
</span>  <span>{
</span>    <span>title: 'Mori',
</span>    <span>race: 'Hobbit'
</span>  <span>},
</span>  <span>{
</span>    <span>title: 'Poppin',
</span>    <span>race: 'Hobbit'
</span>  <span>}
</span><span>];
</span>
<span>deletePerson(fellowship, 1);
</span><span>console.log(fellowship);
</span>
更糟糕的是,该函数可以保持参考并在将来异步进行突变。从这里开始对奖学金的所有参考都将与不可预测的价值合作。>

将其与Mori的替代方案进行比较。

<span>// transient list
</span>a <span>= [1, 2, 3];
</span>b <span>= a.push(4);
</span><span>// a = [1, 2, 3, 4]
</span><span>// b = [1, 2, 3, 4]
</span>
<span>// persistent list
</span>c <span>= #[1, 2, 3]
</span>d <span>= c.push(4);
</span><span>// c = #[1, 2, 3]
</span><span>// d = #[1, 2, 3, 4]
</span>

>不管deleteperson()的实施如何,我们知道原始向量将被记录,仅仅是因为可以保证它不能突变。如果我们希望该函数有用,则应返回并删除指定项目的新向量。>

理解流过在不变数据上工作的函数的流很容易,因为我们知道它们的唯一效果是得出并返回独特的不变价值。

在可变数据上运行的带有MORI的不变数据和功能性JavaScript函数并不总是返回值,它们可以突变其输入,有时还留给程序员再次在另一侧拾取值。

>

带有MORI的不变数据和功能性JavaScript更简单,不变的数据可实现可预测性的文化。

在实践中

>我们将研究如何使用MORI来构建具有撤消功能的像素编辑器。以下代码可作为Codepen可用,您也可以在文章的脚下找到。

>

我们假设您要么跟随Codepen,要么在ES2015环境中使用MORI和以下HTML。 带有MORI的不变数据和功能性JavaScript

设置和实用程序

让我们开始通过破坏Mori名称空间所需的功能开始。

<span>// standard library
</span><span>Array(1, 2, 3).map(x => x * 2);
</span><span>// => [2, 4, 6]
</span>
<span>// mori
</span><span>map(x => x * 2, vector(1, 2, 3))
</span><span>// => [2, 4, 6]
</span>
这主要是一种风格偏好。您也可以通过直接在Mori对象上访问MORI(例如Mori.list())。

>我们要做的第一件事是设置用于查看持续数据结构的助手功能。莫里(Mori)的内部表示形式在控制台中没有多大意义,因此我们将使用tojs()函数将它们转换为可理解的格式。

>

>当我们需要检查Mori的数据结构时,我们可以将此功能用作cons.log()的替代方法。
<span>const fellowship = [
</span>  <span>{
</span>    <span>title: 'Mori',
</span>    <span>race: 'Hobbit'
</span>  <span>},
</span>  <span>{
</span>    <span>title: 'Poppin',
</span>    <span>race: 'Hobbit'
</span>  <span>}
</span><span>];
</span>
<span>deletePerson(fellowship, 1);
</span><span>console.log(fellowship);
</span>
接下来,我们将设置一些配置值和实用程序函数。

>希望您注意到我们的to2d()函数返回向量。向量有点像JavaScript数组,并且支持有效的随机访问。>

构造数据

<span>import <span>{ vector, hashMap }</span> from 'mori';
</span>
<span>const fellowship = vector(
</span>  <span>hashMap(
</span>    <span>"name", "Mori",
</span>    <span>"race", "Hobbit"
</span>  <span>),
</span>  <span>hashMap(
</span>    <span>"name", "Poppin",
</span>    <span>"race", "Hobbit"
</span>  <span>)
</span><span>)
</span>
<span>const newFellowship = deletePerson(fellowship, 1);
</span><span>console.log(fellowship);
</span>
>我们将使用我们的to2d()函数来创建一系列坐标,该坐标将代表画布上的所有像素。

>我们使用range()函数来生成一个数字序列在0和高度 *宽度之间(在我们的情况100)之间,并且我们使用map()将其转换为使用to2d的2D坐标列表()辅助功能。

<span><span><span><div>>
  <span><span><span><h3 id="gt">></h3></span>Mori Painter<span><span></span>></span>
</span><span><span><span></span></span></span></span>
</div></span>></span>
</span><span><span><span><div> id<span>="container"</span>>
  <span><span><span><canvas> id<span>='canvas'</span>></canvas></span><span><span></span>></span>
</span><span><span><span></span></span></span></span>
</div></span>></span>
</span><span><span><span><div>>
  <span><span><span><button> id<span>='undo'</span>></button></span>↶<span><span></span>></span>
</span><span><span><span></span></span></span></span>
</div></span>></span>
</span>
>可能有助于可视化坐标的结构。

>

这是坐标向量的一维序列。

>在每个坐标的旁边,我们还需要存储一个颜色值。>
<span>// transient list
</span>a <span>= [1, 2, 3];
</span>b <span>= a.push(4);
</span><span>// a = [1, 2, 3, 4]
</span><span>// b = [1, 2, 3, 4]
</span>
<span>// persistent list
</span>c <span>= #[1, 2, 3]
</span>d <span>= c.push(4);
</span><span>// c = #[1, 2, 3]
</span><span>// d = #[1, 2, 3, 4]
</span>

>我们正在使用repot()函数来创建“ #FFF”字符串的无限序列。我们不必担心会填充内存并崩溃我们的浏览器,因为Mori序列支持懒惰评估。我们只在稍后要求时才计算序列中项目的值。

>最后,我们想以哈希地图的形式将坐标与我们的颜色相结合。

<span>// standard library
</span><span>Array(1, 2, 3).map(x => x * 2);
</span><span>// => [2, 4, 6]
</span>
<span>// mori
</span><span>map(x => x * 2, vector(1, 2, 3))
</span><span>// => [2, 4, 6]
</span>
>我们使用zipmap()函数来创建哈希地图,将坐标作为键,颜色作为值。同样,这可能有助于可视化数据的结构。

与JavaScript的对象不同,Mori的Hash Maps可以将任何类型的数据作为键。

绘制像素
<span>const fellowship = [
</span>  <span>{
</span>    <span>title: 'Mori',
</span>    <span>race: 'Hobbit'
</span>  <span>},
</span>  <span>{
</span>    <span>title: 'Poppin',
</span>    <span>race: 'Hobbit'
</span>  <span>}
</span><span>];
</span>
<span>deletePerson(fellowship, 1);
</span><span>console.log(fellowship);
</span>
为了更改像素的颜色,我们将把哈希地图中的一个坐标与新字符串相关联。让我们写一个纯净的函数,为单个像素着色。

>

>我们使用X和Y坐标来创建可以用作键的坐标向量,然后我们使用Assoc()将该密钥与新颜色相关联。请记住,由于数据结构是持续的,因此assoc.)函数将返回a

new

hash映射,而不是突变旧的。

绘画图片
<span>import <span>{ vector, hashMap }</span> from 'mori';
</span>
<span>const fellowship = vector(
</span>  <span>hashMap(
</span>    <span>"name", "Mori",
</span>    <span>"race", "Hobbit"
</span>  <span>),
</span>  <span>hashMap(
</span>    <span>"name", "Poppin",
</span>    <span>"race", "Hobbit"
</span>  <span>)
</span><span>)
</span>
<span>const newFellowship = deletePerson(fellowship, 1);
</span><span>console.log(fellowship);
</span>

>现在,我们有所有需要将简单图像绘制到画布上的一切。让我们创建一个函数,该函数将坐标图的哈希映射针对像素,并将它们绘制到renderingContext2d。 让我们花一点时间了解这里发生了什么。

>

>我们正在使用每个()在像素上迭代哈希地图上迭代。它将每个密钥和值(作为序列一起)作为p传递到回调函数中。然后,我们使用inarray()函数将其转换为可能破坏的数组,因此我们可以挑选出所需的值。

>

最后,我们使用帆布方法将彩色矩形绘制到上下文本身上。
<span><span><span><div>>
  <span><span><span><h3 id="gt">></h3></span>Mori Painter<span><span></span>></span>
</span><span><span><span></span></span></span></span>
</div></span>></span>
</span><span><span><span><div> id<span>="container"</span>>
  <span><span><span><canvas> id<span>='canvas'</span>></canvas></span><span><span></span>></span>
</span><span><span><span></span></span></span></span>
</div></span>></span>
</span><span><span><span><div>>
  <span><span><span><button> id<span>='undo'</span>></button></span>↶<span><span></span>></span>
</span><span><span><span></span></span></span></span>
</div></span>></span>
</span>

将其接线

现在,我们需要进行一些管道,以使所有这些部分将所有这些零件一起工作。

<span>const {
</span>  list<span>, vector, peek, pop, conj, map, assoc, zipmap,
</span>  range<span>, repeat, each, count, intoArray, toJs
</span><span>} = mori;
</span>
>我们将掌握画布,并使用它来创建上下文来呈现我们的图像。我们还将适当地调整它的大小以反映我们的尺寸。

>

>最后,我们将使用像素通过油漆方法绘制的像素来传递上下文。幸运的是,您的画布应该像白色像素一样渲染。不是最令人兴奋的揭露,但我们越来越接近。
<span>const log = (<span>...args</span>) => {
</span>  <span>console.log(...args.map(toJs))
</span><span>};
</span>
>

互动

>我们想收听点击事件,并使用它们以较早的draw()函数来更改特定像素的颜色。

>
<span>// the dimensions of the canvas
</span><span>const [height, width] = [20, 20];
</span>
<span>// the size of each canvas pixel
</span><span>const pixelSize = 10;
</span>
<span>// converts an integer to a 2d coordinate vector
</span><span>const to2D = (i) => vector(
</span>  i <span>% width,
</span>  <span>Math.floor(i / width)
</span><span>);
</span>

>我们将单击侦听器附加到我们的画布上,并使用事件坐标来确定要绘制哪个像素。我们使用此信息使用我们的draw()函数创建新的像素哈希地图。然后,我们将其绘制到我们的上下文中,并覆盖我们画的最后一帧。

在这一点上,我们可以将黑色像素绘制到画布中,每个帧将基于上一个,创建一个复合图像。

>跟踪帧

要实施撤消,我们将需要将每个历史性的修订存储到像素哈希地图上,因此我们将来可以再次检索它们。

我们正在使用列表来存储我们绘制的不同的“帧”。列表支持在头部的有效添加,O(1)查找第一项,这使其非常适合表示堆栈。
<span>// transient list
</span>a <span>= [1, 2, 3];
</span>b <span>= a.push(4);
</span><span>// a = [1, 2, 3, 4]
</span><span>// b = [1, 2, 3, 4]
</span>
<span>// persistent list
</span>c <span>= #[1, 2, 3]
</span>d <span>= c.push(4);
</span><span>// c = #[1, 2, 3]
</span><span>// d = #[1, 2, 3, 4]
</span>
我们需要修改单击侦听器以与帧堆栈一起使用。

我们正在使用PEEK()函数将框架放在堆栈顶部。然后,我们使用它来使用draw()函数创建一个新帧。最后,我们将conj()用于

conjoin

框架顶部的新框架。
<span>// standard library
</span><span>Array(1, 2, 3).map(x => x * 2);
</span><span>// => [2, 4, 6]
</span>
<span>// mori
</span><span>map(x => x * 2, vector(1, 2, 3))
</span><span>// => [2, 4, 6]
</span>

尽管我们正在更改本地状态(frame = conj(框架,newFrame)),但我们实际上并未突变任何数据。 撤消更改

>最后,我们需要实现一个撤消按钮,以从堆栈中弹出顶框。>

>单击“撤消按钮”时,我们检查当前是否有任何帧要撤消,然后使用pop()函数用不再包含顶帧的新列表替换帧。>最后,我们将新堆栈上的顶帧传递给油漆()函数以反映更改。在这一点上,您应该能够绘制并撤消对画布的更改。> demo

这是我们最终得到的:
<span>const fellowship = [
</span>  <span>{
</span>    <span>title: 'Mori',
</span>    <span>race: 'Hobbit'
</span>  <span>},
</span>  <span>{
</span>    <span>title: 'Poppin',
</span>    <span>race: 'Hobbit'
</span>  <span>}
</span><span>];
</span>
<span>deletePerson(fellowship, 1);
</span><span>console.log(fellowship);
</span>

请参阅codepen上的sitepoint(@sitepoint)的笔莫里像素。

扩展

这是您可以改善此应用程序的方式的想法列表:>

添加一个调色板,允许用户在绘制

之前选择颜色

>使用本地存储来保存会话之间的帧

使CTRL Z键盘快捷键撤消更改

允许用户在拖动鼠标时绘制

    通过移动索引指针来实现重做,而不是从堆栈中删除帧
  • >仔细阅读相同程序的clojurescript源
  • 结论
  • >我们已经研究了向量,列表,范围和哈希地图,但是Mori还附带了集合,排序的集合和队列,这些数据结构中的每一个都带有用于与之合作的多态性功能的补充。
  • >我们几乎没有刮过可能的表面,但是希望您能看到足够的时间来重视将持久数据与一组功能强大的简单功能配对的重要性。

    经常询问有关不变数据和功能性JavaScript的问题

    JavaScript中不变性的概念是什么?这意味着一旦分配了一个值,就无法更改它。这个概念对于功能编程至关重要,因为它有助于避免副作用,并使您的代码更容易预测和易于理解。它还可以通过允许有效的数据检索和内存使用量来提高应用程序的性能。

    >

    > MORI库如何帮助处理JavaScript中的不变数据?

    持续的数据结构中的JavaScript。这些数据结构是不可变的,这意味着它们一旦创建就无法更改。这有助于保持数据的完整性并避免意外修改。 MORI还提供了一套丰富的功能性编程实用程序,使操纵这些数据结构变得更容易。

    >

    >使用MORI而不是本机JavaScript方法处理不变数据有什么好处?确实提供了处理不变数据的方法,Mori提供了一种更有效,更健壮的方法。与本机JavaScript方法相比,MORI的持续数据结构更快,并且消耗的内存更少。此外,莫里(Mori 。由于创建一旦创建就无法更改不变的对象,因此可以在多个函数调用中安全地重复使用,而不会被修改。这会导致有效的内存使用和更快的数据检索,从而提高了应用程序的整体性能。

    >可突变和不可变的数据结构之间有什么区别?

    > MORI如何处理数据操作?

    MORI提供了丰富的功能编程实用程序来操纵数据。这些实用程序允许您在数据结构上执行诸如地图,减少,过滤等的各种操作,而无需更改原始数据。

    Mori中的持续数据结构是什么?在Mori中,是不可变的数据结构,在修改后可以保留先前版本的数据。这意味着每次您对持久数据结构执行操作时,都会创建一个新版本,并保留旧版本。

    MORI如何确保数据完整性?

    MORI通过提供不可变的数据结构来确保数据完整性。由于创建后无法修改这些数据结构,因此消除了意外数据修改的风险。这有助于维持数据的完整性。

    >

    在JavaScript中使用MORI?

    在JavaScript中使用MORI的功能编程具有什么优势。通过避免副作用,它使您的代码更容易预测,并且更容易理解。它还通过允许有效的数据检索和内存使用量来增强您的应用程序的性能。

    >

    >我如何在我的JavaScript项目中开始使用Mori?

    在您的JavaScript项目中开始使用Mori,您需要在您的项目中包括Mori库。您可以通过通过NPM安装它或将其直接包含在HTML文件中来执行此操作。包含库后,您可以在代码中开始使用MORI的功能和数据结构。

    >

以上是带有MORI的不变数据和功能性JavaScript的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
从网站到应用程序:JavaScript的不同应用从网站到应用程序:JavaScript的不同应用Apr 22, 2025 am 12:02 AM

JavaScript在网站、移动应用、桌面应用和服务器端编程中均有广泛应用。1)在网站开发中,JavaScript与HTML、CSS一起操作DOM,实现动态效果,并支持如jQuery、React等框架。2)通过ReactNative和Ionic,JavaScript用于开发跨平台移动应用。3)Electron框架使JavaScript能构建桌面应用。4)Node.js让JavaScript在服务器端运行,支持高并发请求。

Python vs. JavaScript:比较用例和应用程序Python vs. JavaScript:比较用例和应用程序Apr 21, 2025 am 12:01 AM

Python更适合数据科学和自动化,JavaScript更适合前端和全栈开发。1.Python在数据科学和机器学习中表现出色,使用NumPy、Pandas等库进行数据处理和建模。2.Python在自动化和脚本编写方面简洁高效。3.JavaScript在前端开发中不可或缺,用于构建动态网页和单页面应用。4.JavaScript通过Node.js在后端开发中发挥作用,支持全栈开发。

C/C在JavaScript口译员和编译器中的作用C/C在JavaScript口译员和编译器中的作用Apr 20, 2025 am 12:01 AM

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。 1)C 用于解析JavaScript源码并生成抽象语法树。 2)C 负责生成和执行字节码。 3)C 实现JIT编译器,在运行时优化和编译热点代码,显着提高JavaScript的执行效率。

JavaScript在行动中:现实世界中的示例和项目JavaScript在行动中:现实世界中的示例和项目Apr 19, 2025 am 12:13 AM

JavaScript在现实世界中的应用包括前端和后端开发。1)通过构建TODO列表应用展示前端应用,涉及DOM操作和事件处理。2)通过Node.js和Express构建RESTfulAPI展示后端应用。

JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

JavaScript在Web开发中的主要用途包括客户端交互、表单验证和异步通信。1)通过DOM操作实现动态内容更新和用户交互;2)在用户提交数据前进行客户端验证,提高用户体验;3)通过AJAX技术实现与服务器的无刷新通信。

了解JavaScript引擎:实施详细信息了解JavaScript引擎:实施详细信息Apr 17, 2025 am 12:05 AM

理解JavaScript引擎内部工作原理对开发者重要,因为它能帮助编写更高效的代码并理解性能瓶颈和优化策略。1)引擎的工作流程包括解析、编译和执行三个阶段;2)执行过程中,引擎会进行动态优化,如内联缓存和隐藏类;3)最佳实践包括避免全局变量、优化循环、使用const和let,以及避免过度使用闭包。

Python vs. JavaScript:学习曲线和易用性Python vs. JavaScript:学习曲线和易用性Apr 16, 2025 am 12:12 AM

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

Python vs. JavaScript:社区,图书馆和资源Python vs. JavaScript:社区,图书馆和资源Apr 15, 2025 am 12:16 AM

Python和JavaScript在社区、库和资源方面的对比各有优劣。1)Python社区友好,适合初学者,但前端开发资源不如JavaScript丰富。2)Python在数据科学和机器学习库方面强大,JavaScript则在前端开发库和框架上更胜一筹。3)两者的学习资源都丰富,但Python适合从官方文档开始,JavaScript则以MDNWebDocs为佳。选择应基于项目需求和个人兴趣。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

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

热工具

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。