写了一个 js 函数库,欢迎大家来使用

126 阅读4分钟

介绍

仓库地址: github.com/xiaotong-to…

文档地址: xiaotong-tong.github.io/xtt-utils/

仓库里面的函数都是我平时经常使用的的一些功能,可以用于日常开发,虽然目前包含的模块不多,但我会持续追加的,如果你对这个项目感兴趣,欢迎试用!(如果有想要的功能我这边也可以尽量满足,也欢迎提交 pr 和 issus)

使用方法

  • 安装
npm i xtt-utils

然后在项目中导入就行,支持按需导入

import { random } from "xtt-utils"; 
random();

项目中的所有方法在项目下 docs 目录下都有文档说明,具体说明可以查看文档,或者访问 github page

Methods

trimLineStart

格式化行前的缩进层级

实现这个方法主要是为了格式化 HTML 代码的层级,因为使用了 Prettier,然后获取元素的 innerHTML 时,自然也把前面的缩进也带上了,如果不进一步处理的话这些文字的所有行始终都有个缩进

trimLineStart("  123"); // -> 123
trimLineStart(`
            <xtt-code>
			<xtt-button>default</xtt-button>
			<style>
				.long-text {
					max-width: 200px;
				}
			</style>
			<xtt-button disabled>disabled</xtt-button>
            </xtt-code>
`)
/** ->
<xtt-code>
        <xtt-button>default</xtt-button>
        <style>
                .long-text {
                        max-width: 200px;
                }
        </style>
        <xtt-button disabled>disabled</xtt-button>
</xtt-code>
这里格式化可能不是那么准确
*/

"--------------------------"

主要思路是先查找所有行的行前缩进的数目,然后找到最小的共同缩进数,再把所有行前的这个缩进给删掉就行了

查找每行的行前空白,主要利用了正则中的 m 参数
const leadingSpacesAtLine = str.match(/^(?!$)[ \t]*/gm);

然后使用 reduce 遍历,获取最小空白值
const minSapceNum = leadingSpacesAtLine.reduce((min, line) => {
        return Math.min(
                typeof min === "number" ? min : min.length,
                line.length
        );
});
所有行都删除最小空白值的空白,就实现了格式化缩进
return str.replace(new RegExp(`^[ \\t]{${minSapceNum}}`, "gm"), "");

startsWith && endsWith

经典函数,原生 js 的字符串方法中也有这两个方法,不过我这里处理了一下参数,支持对正则表达式的判断

startsWith("abc", "a"); // -> true
startsWith("abc", /^a/); // -> true
endsWith("abc", "c"); // -> true
endsWith("abc", /c$/); // -> true

"--------------------------"

如果是字符串类型,则直接调用 string 的方法并返回
if (typeof prefix === "string") {
        return str.startsWith(prefix, startPosition);
}

如果是正则类型,实际就运行了一下 Regexp.test(string),不过内部对正则进行了一些加工
实际上 startsWith("abc", /a/) 等同于 startsWith("abc", /^a/), 两者实际运行的都是 /^a/.test("abc")
if (prefix instanceof RegExp) {
        const newStr = str.slice(startPosition);

        if (prefix.source.startsWith("^")) {
                return prefix.test(newStr);
        } else {
                return new RegExp("^" + prefix.source, prefix.flags).test(newStr);
        }
}

getTermLeft && getTermRight && getRangeByTerm

也是经典函数,就是获取字符串中指定位置的一串字符串,这里的参数也支持正则表达式进行判断

getTermLeft("abcdec", "c") // -> "ab"
getTermLeft("abcdec", "c", 2) // -> "abcde"
getTermLeft("abc1de2", /\d/) // -> "abc"
getTermLeft("abc1de2", /\d/, 2) // -> "abc1de"
...

"--------------------------"

使用正则表达式进行字符串切分
const grep = new RegExp(`.+?(?=${typeof searchTerm === "string" ? searchTerm : searchTerm.source})`, "g");
const result = str.match(grep);

对得到的结果根据第三个参数的值进行 slice 切分,切分后 join 转为字符串后返回。
return result.slice(0, beforeWhichTimes).join("");

"--------------------------"

getTermRight 也是同理,不过正则表达式要做些更改,代码中实例使用的是下面这个。
new RegExp(`(?<=(^|${searchGrep})).*?(${searchGrep}|$)`, "g")

"--------------------------"

getRangeByTerm("a1bcd2e", [/\d/, /\d/]) // -> "bcd"
getRangeByTerm 就不支持选择第几个参数了,仅支持设置一个左值和一个右值,然后返回中间值,实际使用的正则表达式如下
new RegExp(`${lGrep}((?:[\\s\\S](?<!${lGrep}))*?)${rGrep}`)

randomList

获取一个随机数列表

 randomList(1, 10) // -> [1 ~ 10, ...*9]
 randomList(1, 10, 5) // -> [1 ~ 10, ...*4]
 randomList(1, 10, { count: 5, unique: true }) // -> [1 ~ 10, ...*4] (unique)
 
 "--------------------------"
 
 如果没有 unique 参数,或者 unique 值为 false,那么直接 push 想要的个数的 random 就行
if (!unique) {
        const list = [];
        for (let i = 0; i < count; i++) {
                list.push(random(min, max));
        }
        return list;
} else {
如果需要 unique,这里有两个逻辑,如果 count 数量小,那就 push 到想要的个数
如果 count 数量过大,那就直接创建一个 [min, min + 1, ... , max - 1, max] 的数组,然后打乱再截取
        if (count > (max - min) / 2) {
                let randomArr = range(min, max);

                randomArr = shuffle(randomArr);
                return randomArr.slice(0, count);
        } else {
                const randomSet = new Set();
                while (randomSet.size < count) {
                        randomSet.add(random(min, max));
                }
                return Array.from(randomSet);
        }
}
 

fori

看名字就知道这是一个循环函数,原本想着原生的循环已经够用了,就不特意再写一个循环,结果本着方便还是简单实现了一下,因为有链式调用嘛(xd),为了方便,直接循环了 iterator, 所以可能并不严谨,

如果有 Symbol.iterator 属性或者有 Symbol.asyncIterator 属性代表可以循环
if (target[Symbol.iterator] || target[Symbol.asyncIterator]) {
        let i = 0;

        如果有 asyncIterator 参数,或者有 Symbol.asyncIterator 属性代表这是一个 异步循环,那么使用 for await of 进行循环,并返回一个 Promise
        if (asyncIterator || target[Symbol.asyncIterator]) {
                return new Promise((resolve, reject) => {
                        (async () => {
                                const resPromiseList = [];
                                for await (const iterator of target) {
                                        resPromiseList.push(
                                                new Promise((res) => {
                                                        res(iteratee.call(thisArg, iterator, i, target));
                                                })
                                        );
                                        i++;
                                }
                                Promise.all(resPromiseList)
                                        .then((v) => { resolve(v) })
                                        .catch((e) => { reject(e) });
                        })();
                });
        } else {
                如果是正常的实现了 Symbol.iterator 接口的对象,那么使用 for of 循环,然后返回一个数组
                const resList = [];
                for (const iterator of target) {
                        const res = iteratee.call(thisArg, iterator, i, target);
                        i++;
                        resList.push(res);
                }
                return resList;
        }
}

因为 Object 没有实现 Symbol.iterator 接口,所以默认无法循环,这里对 Object 进行特殊处理,循环了 Object.entries(collection) 方法处理后的结果。
if (collection instanceof Object) {
        results = loop(Object.entries(collection));
}

formatDate

普通的日期处理函数,当然如果对时间处理复杂一些还是推荐使用 day.js

formatDate("2023-01-01", "YYYY-MM-DD hh:mm:ss") // => '2023-01-01 00:00:00'
formatDate("2023-01-01")("[YYYYMD]YYYY-M-D h:m:s") // => 'YYYYMD2023-1-1 0:0:0'
formatDate("2023-01-01", "dddd") // => 'Sunday'
formatDate("2023-01-01", { format: "dddd", lang: "zh-CN" }) // => '星期日'

 "--------------------------"
 
 其它参数就是普通的匹配替换,这里简单说明一下对星期的处理
 switch (match) {
    case "d":
            如果是 d,那么就直接返回数字
            return week;
            
    如果是 dd,ddd,dddd,那么使用 Intl.DateTimeFormat 进行进一步的处理
    (Intl 为原生支持的一个用于国际化的 API, 它提供了精确的字符串对比、数字格式化,和日期时间格式化,具体可以去 MDN 上查看)
    因为其它地方都是数字替换,使用只有星期进行了特殊处理
    例如星期六,dd 会返回 六, ddd 会返回 周六,dddd会返回 星期六,分别对应了 weekday 的三种参数值。
    case "dd":

            return new Intl.DateTimeFormat(lang, {
                    calendar: lang.startsWith("zh") ? "chinese" : "iso8601",
                    weekday: "narrow"
            }).format(willFormatDate);
    case "ddd":
            return new Intl.DateTimeFormat(lang, {
                    calendar: lang.startsWith("zh") ? "chinese" : "iso8601",
                    weekday: "short"
            }).format(willFormatDate);
    case "dddd":
            return new Intl.DateTimeFormat(lang, {
                    calendar: lang.startsWith("zh") ? "chinese" : "iso8601",
                    weekday: "long"
            }).format(willFormatDate);
}
 

其它

当然还有其它一些方法,这里就不做具体介绍了,大家可以去 github 上查看,或者访问 github page 都有更加详细的介绍。

希望大佬们可以用用,如果觉得不错的话可以点个 star

碎碎念

项目一开始是打算使用 ts 写的,但是因为个人能力不足,写着写着发现写 type 的时间远超于写代码的时间。所以就没使用 ts,不过所有方法都有 jsdoc 注释,虽然可能因为能力不足导致类型不太符合,但是不影响正常使用。