每天学习10个实用Javascript代码片段(七)

每天学习10个实用JavaScript代码片段,加深对 JavaScript 语法的理解,积累代码优化经验,新的内容来了,学习阅读代码是提高编码技能的最佳方式。

1. 时间格式化

JavaScript中时间相关的脚本库有很多,如 Moment 和 Luxon,提供比较丰富的时间相关的方法,这里介绍原生方法的实现。

const formatDate = (date, formatStr = "YYYY-MM-DD") => {
    date = new Date(date);
    const padStart = (str) => str.toString().padStart(2, "0");
    const month = date.getMonth() + 1;
    const year = date.getFullYear();
    const day = date.getDate();
    const hours = date.getHours();
    const minutes = date.getMinutes();
    const seconds = date.getSeconds();
    return formatStr
        .replace(/\bYYYY\b/g, year)
        .replace(/\bYY\b/g, `${year}`.substring(2))
        .replace(/\bMM\b/g, padStart(month))
        .replace(/\bM\b/g, month)
        .replace(/\bDD\b/g, padStart(day))
        .replace(/\bD\b/g, day)
        .replace(/\bHH\b/g, padStart(hours))
        .replace(/\bhh\b/g, hours)
        .replace(/\bmm\b/g, padStart(minutes))
        .replace(/\bss\b/g, padStart(seconds));
};
console.log(formatDate(Date.now())); // 2021-09-28
console.log(formatDate(Date.now(), "YYYY-MM-DD HH:mm:ss")); // 2021-09-28 18:49:29

2. 求数组差集

求两个数组的差集,可以通过数据结构 Set 来实现,具体实现请参阅《JavaScript中的Set数据操作:交集、差集、交集、对称差集》。这里介绍另一种方式,从另一个数组中减去一个数组并返回差值,并保留结果列表中其元素的顺序。

const difference = (a, b) => a.filter((item) => b.indexOf(item) < 0);

const arrA = [1, 2, 3, 4];
const arrB = [3, 4, 5, 6];
console.log(difference(arrA, arrB)); // // [ 1, 2 ]

3. 深度检索

代码片段利用了数组 reduce 方法,检索深度嵌套对象、数组和Map中的深度值,可以访问属于对象本身或其原型的值,例如访问字符串长度或获取Map的大小。

const getDeepValue = (dict, path) => {
    path = Array.isArray(path) ? path : path.split(".");
    const value = path.reduce(
        (obj, key) =>
            obj === undefined
                ? null
                : obj instanceof Map
                ? obj.get(key) ?? obj[key]
                : obj[key],
        dict
    );

    return value === undefined ? null : value;
};
const articlePage = {
    list: [
        {
            title: "js",
            keywords: ["js", "reduce"],
        },
    ],
};
// 下面展示相当于 articlePage.list[0].keywords[1]
console.log(getDeepValue(articlePage, "list.0.keywords.1")); // reduce

4. 对象数组排序

对于对象数组或者多维书序,可能需要对某个 key 或者索引进行排序,这在项目中是比较常见的需求。

const sortByKey = (obj, key) => {
    const values =
        obj instanceof Map ? Array.from(obj.values()) : Object.values(obj);
    return values.reduce(
        (keyedObj, value) => ({ ...keyedObj, [value[key]]: value }),
        {}
    );
};
const arrayObjs = [
    { id: 2, title: "二" },
    { id: 1, title: "一" },
    { id: 3, title: "三" },
];
console.log(sortByKey(arrayObjs, "id"));
/*
{
    '1': { id: 1, title: '一' },
    '2': { id: 2, title: '二' },
    '3': { id: 3, title: '三' }
}
*/
const arrays = [
    [1, 1, 4],
    [1, 1, 1],
];
console.log(sortByKey(arrays, "2")); // { '1': [ 1, 1, 1 ], '4': [ 1, 1, 4 ] }

5. 合并两个对象

代码片段实现合并两个对象,但不处理深嵌套,保持原来结构,通过使用递归的方法深度合并对象和数组,返回一个新的副本。

const mergeObjects = (a, b) => {
    if (a === null || typeof a !== "object") return b ?? {};
    if (b === null || typeof b !== "object") return b ?? {};

    const obj = Array.isArray(a) ? [...a] : a;

    for (const key in b) {
        if (b.hasOwnProperty(key)) {
            obj[key] = mergeObjects(obj[key], b[key]);
        }
    }

    return obj;
};

const obj1 = {
    title: "abc",
    remark: {
        text: "remark",
    },
};
const obj2 = {
    title: "bcd",
    desc: {
        info: "test",
    },
};
console.log(mergeObjects(obj1, obj2)); // { title: 'bcd', remark: { text: 'remark' }, desc: { info: 'test' } }

6. 类私有域

JavaScript 有自己的方法来创建类私有成员,但目前还处于ES2020试验草案中,并且语法比较奇怪,以 # 作为前缀。下面代码片段使用闭包、作用域来实现类的私有域。

const Helper = (() => {
    const defaultValue = (val, defVal) => {
        if (val && val !== "") {
            return val;
        } else {
            return defVal;
        }
    };
    const apiEndpoint = "/Auth";
    
    // 对外暴露的类
    class Utility {
        constructor() {
            this.loginPath = `${apiEndpoint}/login`;
        }

        getVal(key, defVal) {
            return defaultValue(key, defVal);
        }
    }
    return Utility;
})();
const testHelper = new Helper();
console.log(testHelper.getVal(undefined, 0)); // 0
console.log(testHelper.loginPath); // /Auth/login

7. 求最短长度

代码片段将找到给定字符串中单词的最短长度,可以从一些函数中获得灵感,例如 split()sort()pop()

const findShortLength = (str) =>
    str
        .split(" ")
        .sort((a, b) => b.length - a.length)
        .pop().length;

const testString = "A lazy person will find an easy way to do it.";
const testString2 = "Finding the best WebGL tool.";
console.log(findShortLength(testString)); // 1
console.log(findShortLength(testString2)); // 3

8. 数据分组

对于一组数据通过特定 key 的值来分组,使用 reduce 方法对数据进行分组,并按照分组进行归类。

const groupBy = (obj, key) => {
    const values =
        obj instanceof Map || obj instanceof Set
            ? Array.from(obj.values())
            : Object.values(obj);

    return values.reduce((acc, value) => {
        const groupKey = value[key];
        if (!Array.isArray(acc[groupKey])) {
            acc[groupKey] = [value];
        } else {
            acc[groupKey].push(value);
        }
        return acc;
    }, {});
};

const arrayBooks = [
    { title: "战国策", category: "历史" },
    { title: "秦汉史", category: "历史" },
    { title: "秦始皇", category: "历史" },
    { title: "刘伯温", category: "人物传记" },
    { title: "张居正", category: "人物传记" },
];
console.log(groupBy(arrayBooks, "category"));
/*
{
  '历史': [
    { title: '战国策', category: '历史' },
    { title: '秦汉史', category: '历史' },
    { title: '秦始皇', category: '历史' }
  ],
  '人物传记': [
    { title: '刘伯温', category: '人物传记' },
    { title: '张居正', category: '人物传记' }
  ]
}
*/

9. 一次性事件

代码片段实现监听DOM事件,并对绑定的事件只执行一次。

const onceTrigger = (callback) => {
    const handler = (e) => {
        e.currentTarget.removeEventListener(e.type, handler);
        callback(e);
    };
    return handler;
};

const elemSubmit = document.querySelector("button");
elemSubmit.addEventListener(
    "click",
    onceTrigger(() => {
        console.log("click");
    })
);

10. 条件事件

和上面的一次性事件类似,条件事件意味着当满足一定条件后事件将不再响应,如按钮点击三次后将不再出发 click 事件。

const conditionTrigger = (callback, checkFn) => {
    const handler = (e) => {
        if (checkFn(e)) {
            e.currentTarget.removeEventListener(e.type, handler);
        }
        callback(e);
    };
    return handler;
};

let clickCount = 0;
const elemSubmit = document.querySelector("button");
elemSubmit.addEventListener(
    "click",
    conditionTrigger(
        () => {
            console.log("click");
            clickCount = clickCount + 1;
        },
        () => clickCount > 3
    )
);

总结

JavaScript 的灵活性使得编程充满技巧,作为 JavaScript 开发人员,要感觉自己就是一个魔术师,合理灵活的利用其技巧。