专栏首页多云转晴十个常用的工具函数​

十个常用的工具函数​

10个常用的工具函数

几个比较常用的JavaScript工具函数汇总。

1. 数组扁平化

一次扁平化

function flat(arr){
    return Array.prototype.concat.apply([],arr);
}

上面函数只能扁平化二维的数组,比如:

console.log(flat([1,2,[3,4]]));     // [1,2,3,4]

// 更深层次的数组不能被扁平化
console.log(flat([1,[2,3,[4,5]]]));  // [1,2,3,[4,5]]

深层次扁平化

利用闭包和递归来实现。

function flat(arr){
    // 判断是不是数组的函数
    if(!Array.isArray(arr)){
        return arr;
    }
    function isArr(arr){
        return Object.prototype.toString.call(arr) === '[object Array]' ? true : false;
    }

    // 利用闭包存储结果
    var result = [];

    function getResult(array){
        for(let i = 0,len = array.length; i < len;i ++){
            if(isArr(array[i])){
                // 如果里面的项还是数组就递归
                getResult(array[i]);
            }else{
                result.push(array[i]);
            }
        }
        // 返回最终的结果,递归的出口
        return result;
    }
    return getResult(arr);
}

或者:

function flat(arr){
    if(!Array.isArray(arr)){
        return arr;
    }
    var result = [];
    (function fn(array){
        array.forEach(item => {
            if(Array.isArray(item)) fn(item);
            else result.push(item);
        })
    })(arr);

    return result;
}

ES6 中的数组扁平化

ES6 的数组中提供了 flat 函数。这个函数是 Array.prototype 上的一个函数。而且可以指定要提取嵌套数组的结构深度,默认值为 1。

var arr = [
    1,
    [
        2,
        [3,4]
    ]
]

console.log(arr.flat(1));   // [1,2,[3,4]]
console.log(arr.flat(2));   // [1,2,3,4]

使用 Infinity 作为深度,展开任意深度的嵌套数组。应当注意的是,这个 API 很新,浏览器兼容性会比较差。

console.log(arr.flat(Infinity));   // [1,2,3,4]

2. 深度拷贝

利用 JSON 序列化进行深度拷贝

function deepClone(data){
    return JSON.parse(JSON.stringify(data));
}

这种方式有些缺点,不支持函数和 undefined(undefined还会报错)。可以利用递归来实现深度拷贝。

function deepClone(data){

    function judge(val){
        var toString = Object.prototype.toString;
        switch(toString.call(val)){
            case '[object Object]':
                return 'object';
            case '[object Array]':
                return 'array';
        }
    }

    var result;

    switch(judge(data)){
        case 'array':
            result = [];
            break;
        case 'object':
            result = {};
            break;
            // deepClone 的出口
            // 返回对象和数组之外的类型
        default: return data;
    }

    for(let k in data){
        var value = data[k];
        if(judge(value) === 'object'){
            // 是对象或者数组时递归
            result[k] = deepClone(value);
        }else if(judge(value) === 'array'){
            result[k] = deepClone(value);
        }else{
            result[k] = value;
        }
    }
    return result;
}

解决循环引用问题

// 引入 WeakMap 是为了解决循环引用的问题
function deepClone(object, wMap = new WeakMap()){
    var res = null;
    // 不是对象时,直接返回 object
    // 因为 typeof null === 'object' 成立
    if(object === null || !typeof object === 'object'){
        return object;
    }
    // 是数组时 res 等于一个数组
    if(Array.isArray(object)){
        res = [];
    }else if(Object.prototype.toString.call(object) === "[object Object]"){
        // 是对象时,res 等于一个对象
        res = {};
    }
    const obj = wMap.get(object);
    if(obj){
        // 如果 WeakMap 实例中有了要遍历的对象
        // 直接引用即可,这样就避免了循环引用
        return obj;
    }
    // 没有的话,就设置上
    wMap.set(object,res);
    for(let p in object){
        if(typeof object[p] === "object"){
            // 是对象时,递归遍历对象
            res[p] = deepClone(object[p],wMap);
        }else{
            // 不是对象时直接赋值
            res[p] = object[p];
        }
    }
    // 返回每一个递归生成的 res 对象
    return res;
}

上面代码中只考虑了要克隆的对象是一个“纯对象”或者“纯数组”。对于正则表达式、函数等特殊的对象并没有考虑。 需要注意的是,wMap.set(object,res); 的值就应该是 res,理由如下:

var b = {};
var a = {a1: b,a2: b};
console.log(a.a1 === a.a2);     // true

var clone = deepClone(a);
// 如果 wMap 的值设置的是 res,则下面等式成立
console.log(clone.a1 === clone.a2);     // true

上面代码中,要克隆对象 a 中的 a1 和 a2 属性值都指向对象 b 的地址,因此这俩属性值相等。在克隆 a 后,克隆的对象中的 a1、a2 的属性值也应该相等。

3. 数组乱序

给定一个数组,将数组中的元素重新随机分配到不同的下标上。 使用交换算法实现:

/**
 * 数组乱序(洗牌算法)
 * @param {Array} array
 */
function shuffle(array){
    // 浅克隆
    var result = [...array],
        len = array.length;

    for(let i = 0;i < len;i ++){
        // 获取随机的下标
        var randomIdx = Math.floor(Math.random() * len);
        // 交换
        var temp = result[randomIdx];
        result[randomIdx] = result[i];
        result[i] = temp;
    }

    return result;
}

4. 数组去重

ES5 中利用对象去重

var unique = function(array){
    var obj = {},
        result = [];
    for(var v of array){
        if(!obj[v]){
            // 对象中没有这个属性时就添加
            result.push(v);
            obj[v] = v;
        }
    }
    return result;
}

ES6 中利用 Set 去重:

var unique = function(array){
    return [...new Set(array)];
}

5. 寻找数组中的最大项

ES5 中使用 Math.minMath.max 来实现。

var getMaxItem = function(array){
    return Math.max.apply(null,array);
}
var getMinItem = function(array){
    return Math.min.apply(null,array);
}

ES6 的实现

var getMaxItem = function(array){
    return Math.max(...array);
}
var getMinItem = function(array){
    return Math.min(...array);
}

6. 防抖

防抖是为了让在指定时间内一个事件只触发一次。比如点击提交按钮,0.5 秒内可能会点击许多次按钮,不做优化的话,这样会频繁进行网络请求。防抖可以优化高频触发事件。

function debounce(fn,delay){
    var timer = null;
    return function(){
        clearTimeout(timer);
        var self = this,
            args = arguments;
        timer = setTimeout(function(){
            fn.apply(self,args);
        },delay || 500);
    }
}

7. 节流

节流和防抖作用很像,不同的是节流会在一定时间范围里均匀的触发某个事件,没有做优化的事件可能一秒中触发了10次时间函数,而节流之后肯能一秒钟只触发3次事件。节流也可用于动画效果。

var throttle = function(fn,interval){
    var first = true,
        timer = null;
    return function(){
        var _args = arguments,
            self = this;
        if(first){
            fn.apply(self,_args);
            return first = false;
        }
        if(timer){
            return false;
        }
        timer = setTimeout(function(){
            clearTimeout(timer);
            timer = null;
            fn.apply(self,_args);
        },interval || 400);
    }
}

8. 判断一个日期是不是今天

输入一个形如 “2019/10/9” 或者 “2019-10-9” 的日期格式,判断输入的日期是不是今天。这个函数不止一种实现方式。利用 Date.toLocaleDateString() 方法实现就是其中一种。

function isToday(str){
    var s = str.replace(/\-/g,"/"),
        // 考虑到 IE 兼容性
        // 在 IE 中,toLocaleString() 的日期格式是这样的:"xxxx年xx月xx日"
        nowDate = (new Date()).toLocaleString().replace(/年|月|日/g,"/");
    if(s === nowDate){
        return true;
    }
    return false;
}

在 IE 浏览器中,toLocaleDateString() 方法返回的日期格式为 “xxxx年xx月xx日”(当然,只能在中文页面适用),因此需要将“年”、“月”、“日”替换成“/”。为了兼容,输入 “xxxx-xx-xx” 的日期格式时,应把这种格式转成“xxxx/xx/xx” 的格式。因为除了 IE 之外,其它浏览器的 toLocaleDateString() 的方法默认返回的都是 “xxxx/xx/xx” 格式的日期。

还有一个方法,将输入的字符串的年月日拆解出来,比较年月日的大小是否相同来实现。

function isToday(str){
    str = str.replace(/\-/g,'/');
    var s = str.split("/");
    var now = new Date();
    return (
        Number(s[0]) === now.getFullYear() &&
        Number(s[1]) === now.getMonth() + 1 &&
        Number(s[2]) === now.getDate()
    );
}

为了让如输入的日期格式正确,可以使用正则表达式做判断:

const reg = /^\d{4}(-|\/)(1[0-2]|(0?[1-9]))\1([0-2][1-9]|[1-9]|30|31)/;
if(!reg.test(str)){
    throw Error("The arguments' format must be like 'xxxx-xx-xx' or 'xxxx/xx/xx'");
}

9. sleep(seconds) 函数

这个函数很像 setTimeout 函数,可以作为延时函数。

var sleep = function(delay){
    return new Promise(resolve => {
        setTimeout(resolve,delay);
    });
}

// test
function getName(name){
    sleep(1000)
    .then(() => {
        console.log(name);
    })
}
// 执行后,大概一秒后才能打印出 name 的值
getName();

或者使用 ES7 中的 async/await

async function getName(name){
    await sleep(1000);
    console.log(name);
}

10. url 解析与对象序列化

一个有查询字段的 URL,如何将 URL 的查询字段转成一个对象?

const url = 'https://www.example.com/info.json?name=王小明&age=18&sex=male';

console.log(parseURL(url)); // {name: "王小明",age: 18,sex: male}

或者给你一个对象,将这个对象序列化成查询字段?

const object = {
    name: "王小明",
    age: 18,
    sex: "male"
};

console.log(stringifyURL(object));  // "name=王小明&age=18&sex=male"

序列化对象:

function stringifyURL(object){
    var str = "";
    for(let p in object){
        str = str + p + "=" + object[p] + "&";
    }
    return str.replace(/&$/,"");
}

解析 URL,可以使用现成的 URL 类,对输入的 URL进行操作。

function parseURL(url){
    url = new URL(url);
    var res = {},
        // 把查询参数的第一字符是问号,应该去除
        search = decodeURIComponent(url.search.replace("?",""));
        arr = search.split("&");
    arr.forEach(item => {
        var a = item.split("=");
        res[a[0]] = a[1];
    });
    return res;
}

URL 类的实例中也有一个 searchParams 属性,这个属性返回一个 Set 对象。里面存储着查询字符串的信息。

var url = new URL("https://www.example.com/info.json?name=王小明&age=18&sex=male");
console.log(url.searchParams.get("name"));      // 王小明

需要注意的是,URL 类不兼容 IE 浏览器。因此该方法并不推荐使用。可以利用正则表达式写一个兼容的解析函数。

function parseURL(url){
    // 匹配 URL 查询字段的字符串
    var reg = /\?((.+)\=(.*)&?)/g;
        query = url.match(reg);
    if(query){
        // 将第一个问号去掉
        query = decodeURIComponent(query[0].replace("?",""));
        var arr = query.split("&"),
            res = {};
        arr.forEach(item => {
            var a = item.split("=");
            res[a[0]] = a[1];
        });
        return res;
    }
    return null;
}

在 Node.js 中已经实现了 url 的解析与序列化。

const url = require("url");
const qs = require("querystring");

// url 解析:
console.log(
    qs.parse(
    url.parse(
        "https://www.example.com/info.json?name=%E7%8E%8B%E5%B0%8F%E6%98%8E&age=18&sex=male").query
    )
);

// 对象序列化:
const obj = {
    name: "王小明",
    age: 18,
    sex: "male",
};

console.log(qs.stringify(obj));     // name=%E7%8E%8B%E5%B0%8F%E6%98%8E&age=18&sex=male

本文分享自微信公众号 - Neptune丶(Neptune_mh_0110),作者:多云转晴

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-10-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 队列实现

    有时候,队列会有优先级。比如 VIP 用户总是比普通用户服务优先一些,头等舱总比经济舱要好。实现这样一功能需要在原来的队列基础上加上优先级:当 push 操作时...

    多云转晴
  • HTML5 File API 使用技巧

    在 HTML5 的 input 标签中,新增了一个 type=file 属性的表单控件。这个控件可以让我们能调出文件选择窗口然后读取这些文件的内容成为可能。

    多云转晴
  • HTML5 File API

    在 HTML5 的 input 标签中,新增了一个 type=file 属性的表单控件。这个控件可以让我们能调出文件选择窗口然后读取这些文件的内容成为可能。

    多云转晴
  • java小技术之生成二维码

    把我们需要的链接或者内容生成二维码其实是一件非常容易的事情,有很多办法可以实现,这里我们采用JS方法生成。 <!DOCTYPE HTML PUBLIC "-//...

    生活创客
  • javascript:算法笔记

    入门级算法-线性查找-时间复杂度O(n)--相当于算法界中的HelloWorld //线性搜索(入门HelloWorld) //A为数组,x为要...

    菩提树下的杨过
  • JavaScript设计模式之单例模式

    单例模式是javascript中最简单也是最常用的模式之一。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问...

    用户6167509
  • 在Ocelot中使用自定义的中间件(二)

    在上文中《在Ocelot中使用自定义的中间件(一)》,我介绍了如何在Ocelot中使用自定义的中间件来修改下游服务的response body。今天,我们再扩展...

    李明成
  • Loadrunner 运行场景-场景中的全局变量与关联结果参数

    //--------------------------------------------------------------------

    授客
  • 如何对flv视频进行压缩,3种方法教你搞定

    很多人都喜欢在有无线网的情况下,喜欢把自己爱看的电视剧,综艺,电影,这些都给缓存下来,慢慢看,但是理想是美好的,现实很骨感,当你下载的过程中,发现视频还没下载完...

    高效办公
  • NIO复习(1):buffer

    <!-- p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; line-height: 19.0px; font: 13.0px 'H...

    菩提树下的杨过

扫码关注云+社区

领取腾讯云代金券