关于javascript文本验证的函数式编程思路处理

前端有一个简单的需求,验证用户输入的手机号是否符合要求,这个要求是11位数,首位必须以1开头,其余必须为数字

使用正则表达式可以立马写出验证函数

//验证手机文本是否正确
//正则写法
function testPhoneInputByReg(phoneStr) {
    let regPhone = /^1[0-9]{10}$/;
    let result = regPhone.test(phoneStr);
    return result;
}

在没有正则的时候,会怎么写呢?一般会写成如下形式

//如果不使用正则表达式,使用面向过程(Procedure Oriented)的写法
function testPhoneInputByPO(phoneStr) {
    let len = phoneStr.length;//获取输入字符串长度
    if (len === 11) {
        for (let i = 0; i < len; i++) {
            let tempCode = phoneStr[i];//缓存当前遍历的字符
            if (i === 0 && tempCode!=="1") {//如果首位不为1则立马返回false
                return false;
            } else if ("0123456789".indexOf(tempCode)===-1) {//如果其他位置不属于0-9任意一个字符也立马返回false
                return false;
            }
        }
        return true;//当顺利遍历完成就返回true
    } else {
        return false;//如果字符串长度不为11则返回false
    }
}

可以看出使用正则代码更简洁更可读,而不使用正则使用面向过程的写法会更繁琐,代码主体存在大量的判断语句,大量注释才能说明每一步所做的事情,写得越多,错误发生的记录就越多。

此时如果产品再增加一个第二位只能是3 5 7 9中4位数字的需求,那么负责维护代码的程序员就必须小心翼翼地进行修改测试,而正则只需要简单修改一下正则即可
let regPhone = /^1[3579]{1}[0-9]{9}$/;

这里利用函数式编程的思路去实现这个需求

//使用函数式编程(Functional programming)思想去实现

//主导思想:将问题拆分,再进行组合
//思路:
//1 输入字符串长度为11
//2 首位字符是为"1"
//3 非首位字符必须是"0-9"中其中一个
//4 当1、2、3全符合的时候则表示通过(或者是当1、2、3任意一个不符合时表示不通过)

//1 输入字符串长度为11
function checkLenIs11(str) {
    return str.length === 11;
}

//2 首位字符是为"1"
function checkFirstCodeIsNumOne(str) {
    return str[0] === "1";
}

//3 非首位字符必须是"0-9"中其中一个
function checkNotFirstCodeIsNumZeroToNine(str) {
    for (let i = 0; i < str.length; i++) {
        if ("0123456789".indexOf(str[i]) === -1) {
            return false;
        }
    }
    return true;
}
//4 当1、2、3全符合的时候则表示通过(代码易读)
function ifAllFnsReturnTrueThenPass(str) {
    return checkLenIs11(str) && checkFirstCodeIsNumOne(str) && checkNotFirstCodeIsNumZeroToNine(str);
}

//4 当1、2、3任意一个不符合时表示不通过(代码不易读,但为短路操作,节省计算资源)
function ifAnyFnsReturnFalseThenFail(str) {
    return !(!checkLenIs11(str) || !checkFirstCodeIsNumOne(str) || !checkNotFirstCodeIsNumZeroToNine(str));
}

这里可能有人会说,代码量也没少,只是将分支语句拆分成一个个小函数而已。
正是因为我们把判断条件拆分出来,使得我们在修改函数的时候会更可靠,如果需要增加上面的需求,则新定义一个checkSecondCodeIsNum3or5or7or9(str)后直接修改主体函数即可

//4 当1、2、3全符合的时候则表示通过(代码易读)
function ifAllFnsReturnTrueThenPass(str) {
    return checkLenIs11(str) && checkFirstCodeIsNumOne(str) && checkNotFirstCodeIsNumZeroToNine(str)&&checkSecondCodeIsNum3or5or7or9(str);
}

//4 当1、2、3任意一个不符合时表示不通过(代码不易读,但为短路操作,节省计算资源)
function ifAnyFnsReturnFalseThenFail(str) {
    return !(!checkLenIs11(str) || !checkFirstCodeIsNumOne(str) || !checkNotFirstCodeIsNumZeroToNine(str))||!checkSecondCodeIsNum3or5or7or9(str);
}

此时代码已经变得更容易维护了,但还有优化的空间
现在我们先了解一下ES5中新增的every和some函数

///使用every和some的写法
//简单的例子
//如果数组中所有元素都大于1则返回true,否则返回false
[1, 2, 3, 4].every(function (item) { return item > 1; });//返回false,因为数组中含有1,其不大于1
//采用lambda表达式写法,在chrome中支持,IE11不支持
[1, 2, 3, 4].every(item => item > 1);

//如果数组中任意一个元素大于1则返回true,否则返回false
[1, 2, 3, 4].some(function (item) { return item > 1; });//返回true,因为当数组遍历到2的时候就已经检测有一个大于1,顾短路返回
[1, 2, 3, 4].some(item => item > 1);

//可见在程序可读的条件下,优先使用some函数,因为短路能够节省计算资源

按照使用方法我们将上面的 ifAllFnsReturnTrueThenPass和ifAnyFnsReturnFalseThenFail进行修改

//4 当1、2、3全符合的时候则表示通过 every写法(易读)
function ifAllFnsReturnTrueThenPassOther(str) {
    return [checkLenIs11(str), checkFirstCodeIsNumOne(str) , checkNotFirstCodeIsNumZeroToNine(str)].every(result => result);
}
//4 当1、2、3任意一个不符合时表示不通过 some写法(变得稍微易读)
function ifAnyFnsReturnFalseThenFailOther(str) {
    return ![checkLenIs11(str), checkFirstCodeIsNumOne(str), checkNotFirstCodeIsNumZeroToNine(str)].some(result => !result);
}

看起来程序更易读了,但是存在以下问题

可以理解为当调用ifAnyFnsReturnFalseThenFailOther("123")时,会将[checkLenIs11("123"), checkFirstCodeIsNumOne("123"), checkNotFirstCodeIsNumZeroToNine("123")].some
先转换成[false, false, false]后再调用some函数

当然存在解决方案

//因为js支持函数传递,所以我们可以略微修改一下ifAnyFnsReturnFalseThenFailOther函数,调用some函数的不再为调用后的结果,而是函数定义,在some函数里面再进行调用
function ifAnyFnsReturnFalseThenFailOtherUseFn(str) {
    return ![checkLenIs11, checkFirstCodeIsNumOne, checkNotFirstCodeIsNumZeroToNine].some(result => !result(str));
}

由于全符合的时候则表示通过和任意一个不符合时表示不通过这种情况实在太常见,我们可以将其提取出来
有了上面的思路,所以很容易得出其抽象后的代码

//全符合的时候则表示通过,其中fns为functions的意思
function isAllChcekFnsReturnTrueThenPass(str, fns) {
    return fns.every(fn => fn(str));
}
//任意一个不符合时表示不通过some模式,其中fns为functions的意思
function isAllChcekFnsReturnTrueThenPassBySomeMode(str, fns) {
    return !fns.some(fn => !fn(str));
}

有了这两个函数,我们可以进行自由组合了,如

 isAllChcekFnsReturnTrueThenPassBySomeMode("123", [checkLenIs11, checkFirstCodeIsNumOne, checkNotFirstCodeIsNumZeroToNine]);

// 对应的新增需求的组合如下,由于存在短路,所以判断的顺序需要调整
 isAllChcekFnsReturnTrueThenPassBySomeMode("123", [checkLenIs11, checkFirstCodeIsNumOne, checkSecondCodeIsNum3or5or7or9,checkNotFirstCodeIsNumZeroToNine]);

从上面的代码中我们可以很容易地读取到,将123依次调用函数数组中各个函数,如果全部通过则返回通过,无需知道函数内部是怎么实现的。
调用的函数中有:
1.检测长度11位
2.检测首位为1
3.检测次位为3 5 7 9任意一个
4.检测非首位为0到9任意一个

可能有人注意到 3和4判断条件存在重复了,但没关系,可以定义一个checkNotFirstorSecondCodeIsNumZeroToNine然后替换第4个函数即可,只要保证每个小函数正确,那么组合后的主体函数就一定会返回正确的结果

可以看出使用函数式编程方式来组织代码有一定的好处,那我们试试以下这个需求。

密码检测
1.长度为6-8
2.只能是大小写字母或数字
3.大写字母、小写字母、数字、至少出现一次

//检查是否只含有大小写字母或数字,且长度为6到8
function checkAllCodeIsAlphaOrNumAndLenIs6To8(str) {
    return /^[a-zA-Z0-9]{6,8}$/.test(str);
}
//检查是否至少分别含有一个大写字母,小写字母和一个数字
function checkContainsAlphaAndNum(str) {
    let regLowerAlphaFn = (str)=>/[a-z]/.test(str);
    let regUpperAlphaFn = (str) => /[A-Z]/.test(str);
    let reg0To9Fn = (str) => /[0-9]/.test(str);
    return isAllChcekFnsReturnTrueThenPass(str, [regLowerAlphaFn, regUpperAlphaFn, reg0To9Fn]);
}
//验证密码
function checkPassword(str) {
    return isAllChcekFnsReturnTrueThenPass(str, [checkAllCodeIsAlphaOrNumAndLenIs6To8, checkContainsAlphaAndNum]);
}

我们可以将checkContainsAlphaAndNum进行拆分,更容易组合,且检测一个字符串是否存在至少一个字母或数字的需求也十分常见
修改后如下

//检查是否只含有大小写字母或数字,且长度为6到8
let checkAllCodeIsAlphaOrNumAndLenIs6To8=(str) =>/^[a-zA-Z0-9]{6,8}$/.test(str);
let regLowerAlphaFn = (str)=>/[a-z]/.test(str);
let regUpperAlphaFn = (str) => /[A-Z]/.test(str);
let reg0To9Fn = (str) => /[0-9]/.test(str);
//验证密码
function checkPassword(str) {
    return isAllChcekFnsReturnTrueThenPassBySomeMode(str, [checkAllCodeIsAlphaOrNumAndLenIs6To8, regLowerAlphaFn,regUpperAlphaFn,reg0To9Fn]);
}

仔细观察后,存在更简单的写法

//检查是否只含有大小写字母或数字,且长度为6到8
checkAllCodeIsAlphaOrNumAndLenIs6To8 = /^[a-zA-Z0-9]{6,8}$/;
let regLowerAlpha = /[a-z]/;
let regUpperAlpha = /[A-Z]/;
let reg0To9 = /[0-9]/;
function checkPasswordByRegs(str, regs) {
    return regs.every(reg => reg.test(str));
}
checkPasswordByRegs(["a34563A"], [checkAllCodeIsAlphaOrNumAndLenIs6To8, regLowerAlpha, regUpperAlpha, reg0To9]);//返回true

这一切又好像回到了起点...之前的函数传递也显得很多余。
但是,如果此时产品增加一个需求
修改密码时不能与原密码重复
那么上面的做法就不适用了,但是可以在checkPassword上进行修改,修改的方法也很简单
定义一个验证原密码与当前修改密码的函数,签名如下

function checkDbPasswordSame(password){
    //验证代码
    return true;
}
然后往checkPassword增加即可
//验证密码
function checkPassword(str) {
    return isAllChcekFnsReturnTrueThenPassBySomeMode(str, [checkAllCodeIsAlphaOrNumAndLenIs6To8, regLowerAlphaFn,regUpperAlphaFn,reg0To9Fn,checkDbPasswordSame]);
}

整个密码验证需求实现完成。

此时新增一个需求,要返回不通过的提示信息。
如:当密码不是6到8位时,将返回“密码长度必须6到8位”提示
当密码不存在大写字母时,则返回“密码必须含有至少一位大写字母”提示等等

那我们要怎么修改之前写好的函数呢?是每一个函数都增加一个提示语句文本参数,然后返回字符串类型吗?

让我们看看如果是函数式编程的话,那应该是怎么做的。
这里要使用高阶函数的概念,高阶函数有两种,一种是接收函数作为参数,一种是返回类型是函数,
这里我们定义了一个函数,此函数接收一个验证文本,一个函数,和一个提示文本,返回另外一个接收验证文本的函数,函数主要的作用是,如果验证的文本通过函数调用后不为true,则返回提示文本,否则返回空字符串,详细实现如下

function checkPasswordReturnTip(str,fn,tip) {
    return function (str) {
        if (!fn(str)) {
            return tip;
        } else {
            return "";
        }
    };
}

我们还需要定义一个新函数,此函数接收一个验证文本,和一个函数数组,当使用全部函数验证str后,每个函数的结果只有两种可能,一个是空字符串,一个是非空字符串,最后留下非空字符串,组成数组即可。

//map和filter用法请自行查询资料理解
function returnTipsByFns(str, fns) {
    return fns.map(fn => fn(str)).filter(result => result !== "");
}

有了这个函数,我们可以继续使用之前定义好的函数了

//检查是否只含有大小写字母或数字,且长度为6到8
let checkAllCodeIsAlphaOrNumAndLenIs6To8Fn=(str) =>/^[a-zA-Z0-9]{6,8}$/.test(str);
let regLowerAlphaFn = (str)=>/[a-z]/.test(str);
let regUpperAlphaFn = (str) => /[A-Z]/.test(str);
let reg0To9Fn = (str) => /[0-9]/.test(str);
function checkPasswordReturnTips(str) {
    return returnTipsByFns(str,[
        checkPasswordReturnTip(str, checkAllCodeIsAlphaOrNumAndLenIs6To8Fn, "密码必须6到8位且只能由大小写字母或数字组成"),
        checkPasswordReturnTip(str, regLowerAlphaFn, "密码必须含有至少一位小写字母"),
        checkPasswordReturnTip(str, regUpperAlphaFn, "密码必须含有至少一位大写字母"),
        checkPasswordReturnTip(str, reg0To9Fn, "密码必须含有至少一位数字"),
        checkPasswordReturnTip(str, checkDbPasswordSame, "密码不能与之前设置的重复")
    ]
    );
}

console.log(checkPasswordReturnTips("123"));
//调用结果如下
//["密码必须6到8位且只能由大小写字母或数字组成","密码必须含有至少一位小写字母","密码必须含有至少一位大写字母"]

由于增加了需求,但我们的验证函数并没有被破坏性修改,而是不断增加新的函数。这使得修改出来的代码更可靠。

好了,现在有最后一个需求,由于请求数据库是一个昂贵的操作,当前面的请求都通过的时候,才去数据库进行密码验证。

相信大家已经有组合的概念了,我们完全可以利用之前定义好的方法来处理这个新需求

//定义本地的验证函数
function checkPasswordReturnTipsLocal(str) {
    return returnTipsByFns(str, [
        checkPasswordReturnTip(str, checkAllCodeIsAlphaOrNumAndLenIs6To8Fn, "密码必须6到8位且只能由大小写字母或数字组成"),
        checkPasswordReturnTip(str, regLowerAlphaFn, "密码必须含有至少一位小写字母"),
        checkPasswordReturnTip(str, regUpperAlphaFn, "密码必须含有至少一位大写字母"),
        checkPasswordReturnTip(str, reg0To9Fn, "密码必须含有至少一位数字")
    ]
    );
}
//定义请求数据库的验证函数
function checkPasswordReturnTipsDb(str) {
    return returnTipsByFns(str, [checkPasswordReturnTip(str, checkDbPasswordSame, "密码不能与之前设置的重复")]
    );
}
//检查密码V1版本
function checkPasswordReturnTipsV1(str) {
    let tips = checkPasswordReturnTipsLocal(str);
    if (tips.length===0) {
        tips = tips.concat(checkPasswordReturnTipsDb(str));
    }
    return tips;
}

感谢你的观看,希望对你有帮助。

特别提醒:

里面的方法均为了理解而进行了简化,都是玩具例如函数调用时的上下文绑定
例如验证密码函数中通常不只是传入str既可以了,还要传入用户名等信息,这个可以使用柯里化或部分应用来解决多参数的问题

如需构建更可靠的程序,推荐使用以下JS库

lodash

rxjs

ramda

附测试的JS代码

//获取方法名
Function.prototype.getName = function () {
    return this.name || this.toString().match(/function\s*([^(]*)\(/)[1];
};

//往body中添加html内容
function addHtmlToBody(html) {
    let bodyHtml = document.getElementById("container");
    html = bodyHtml.innerHTML + html;
    bodyHtml.innerHTML = html;
}
//在body中添加分割线
function addHrHtml() {
    addHtmlToBody("<hr/>");
}

///朴素的单元测试函数
function torAssert(fn, args, assertResult, context) {
    if (context === undefined) {
        context = window;
    }

    let applyResult = fn.apply(context, args);

    if (args[1] === undefined) {
        args = args.filter((item, index) => index === 0);
    } else {
        args[1] = args[1].map(arg => arg.getName && arg.getName() || arg);
    }
    let resultHtml = "<p><span>方法名:" + fn.getName() + "</span><br/><span>参数:" +
        JSON.stringify(args) + "</span><br/><span >断言:" + JSON.stringify(assertResult) + "</span><br/>结果:<span class='{{isSuccess}}' >{{resultChinese}}</span></p>";

    

    let isSuccess = applyResult === assertResult ? "success" : "fail";
    let resultChinese = { "success": "通过", "fail": "不通过" };

    addHtmlToBody(resultHtml.replace("{{isSuccess}}", isSuccess).replace("{{resultChinese}}", resultChinese[isSuccess]));
}

function testCollection(fn, args) {
    addHrHtml();
    torAssert(fn, ["15332", args], false);
    torAssert(fn, ["12345678902", args], true);
    torAssert(fn, ["dsf32847897", args], false);
    torAssert(fn, ["dsf32834$#7", args], false);
    torAssert(fn, ["29302847333", args], false);
}



//验证手机文本是否正确
//正则写法
function testPhoneInputByReg(phoneStr) {
    let regPhone = /^1[0-9]{10}$/;
    let result = regPhone.test(phoneStr);
    return result;
}
testCollection(testPhoneInputByReg);


//如果不使用正则表达式,使用面向过程(Procedure Oriented)的写法
function testPhoneInputByPO(phoneStr) {
    let len = phoneStr.length;//获取输入字符串长度
    if (len === 11) {
        for (let i = 0; i < len; i++) {
            let tempCode = phoneStr[i];//缓存当前遍历的字符
            if (i === 0 && tempCode!=="1") {//如果首位不为1则立马返回false
                return false;
            } else if ("0123456789".indexOf(tempCode)===-1) {//如果其他位置不属于0-9任意一个字符也立马返回false
                return false;
            }
        }
        return true;//当顺利遍历完成就返回true
    } else {
        return false;//如果字符串长度不为11则返回false
    }
}
testCollection(testPhoneInputByPO);


//使用函数式编程(Functional programming)思想去实现

//主导思想:将问题拆分,再进行组合
//思路:
//1 输入字符串长度为11
//2 首位字符是为"1"
//3 非首位字符必须是"0-9"中其中一个
//4 当1、2、3全符合的时候则表示通过(或者是当1、2、3任意一个不符合时表示不通过)

//1 输入字符串长度为11
function checkLenIs11(str) {
    return str.length === 11;
}

//2 首位字符是为"1"
function checkFirstCodeIsNumOne(str) {
    return str[0] === "1";
}

//3 非首位字符必须是"0-9"中其中一个
function checkNotFirstCodeIsNumZeroToNine(str) {
    for (let i = 0; i < str.length; i++) {
        if ("0123456789".indexOf(str[i]) === -1) {
            return false;
        }
    }
    return true;
}
//4 当1、2、3全符合的时候则表示通过(代码易读)
function ifAllFnsReturnTrueThenPass(str) {
    return checkLenIs11(str) && checkFirstCodeIsNumOne(str) && checkNotFirstCodeIsNumZeroToNine(str);
}

//4 当1、2、3任意一个不符合时表示不通过(代码不易读,但为短路操作,节省计算资源)
function ifAnyFnsReturnFalseThenFail(str) {
    
    return !(!checkLenIs11(str) || !checkFirstCodeIsNumOne(str) || !checkNotFirstCodeIsNumZeroToNine(str));
}

testCollection(ifAllFnsReturnTrueThenPass);
testCollection(ifAnyFnsReturnFalseThenFail);

///使用every和some的写法
//简单的例子

//如果数组中所有元素都大于1则返回true,否则返回false
[1, 2, 3, 4].every(function (item) { return item > 1; });//返回false,因为数组中含有1,其不大于1
//采用lambda表达式写法,在chrome中支持,IE11不支持
[1, 2, 3, 4].every(item => item > 1);

//如果数组中任意一个元素大于1则返回true,否则返回false
[1, 2, 3, 4].some(function (item) { return item > 1; });//返回true,因为当数组遍历到2的时候就已经检测有一个大于1,顾短路返回
[1, 2, 3, 4].some(item => item > 1);

//可见在程序可读的条件下,优先使用some方法,因为短路能够节省计算资源

//4 当1、2、3全符合的时候则表示通过 every写法(易读)
function ifAllFnsReturnTrueThenPassOther(str) {
    return [checkLenIs11(str), checkFirstCodeIsNumOne(str) , checkNotFirstCodeIsNumZeroToNine(str)].every(result => result);
}
//4 当1、2、3任意一个不符合时表示不通过 some写法(变得稍微易读)
function ifAnyFnsReturnFalseThenFailOther(str) {
    return ![checkLenIs11(str), checkFirstCodeIsNumOne(str), checkNotFirstCodeIsNumZeroToNine(str)].some(result => !result);
}
testCollection(ifAllFnsReturnTrueThenPassOther);
testCollection(ifAnyFnsReturnFalseThenFailOther);

//注意:在ifAnyFnsReturnFalseThenFailOther方法中,虽然使用了some,但永远会先调用3个检测方法,再把结果传入到some回调函数里面去,达不到我们想要的节省计算资源的效果
//可以理解为当调用ifAnyFnsReturnFalseThenFailOther("123")时,会将[checkLenIs11("123"), checkFirstCodeIsNumOne("123"), checkNotFirstCodeIsNumZeroToNine("123")].some
//先转换成[false, false, false]后再调用some函数

//因为js支持函数传递,所以我们可以略微修改一下ifAnyFnsReturnFalseThenFailOther方法,调用some函数的不再为调用后的结果,而是函数定义,在some函数里面再进行调用
function ifAnyFnsReturnFalseThenFailOtherUseFn(str) {
    return ![checkLenIs11, checkFirstCodeIsNumOne, checkNotFirstCodeIsNumZeroToNine].some(result => !result(str));
}
testCollection(ifAnyFnsReturnFalseThenFailOtherUseFn);


//由于全符合的时候则表示通过和任意一个不符合时表示不通过这种情况实在太常见,我们可以将其提取出来
//由于有了上年的思路,所以很容易得出其抽象代码

//全符合的时候则表示通过,其中fns为functions的意思
function isAllChcekFnsReturnTrueThenPass(str, fns) {
    return fns.every(fn => fn(str));
}
//任意一个不符合时表示不通过some模式,其中fns为functions的意思
function isAllChcekFnsReturnTrueThenPassBySomeMode(str, fns) {
    return !fns.some(fn => !fn(str));
}

//有了这两个函数,我们可以进行自由组合了,如
//调用方式 isAllChcekFnsReturnTrueThenPassBySome("123", [checkLenIs11, checkFirstCodeIsNumOne, checkNotFirstCodeIsNumZeroToNine]);
testCollection(isAllChcekFnsReturnTrueThenPassBySomeMode, [checkLenIs11, checkFirstCodeIsNumOne, checkNotFirstCodeIsNumZeroToNine]);


///=======================

let checkAllCodeIsAlphaOrNumAndLenIs6To8Fn = (str) => /^[a-zA-Z0-9]{6,8}$/.test(str);
let regLowerAlphaFn = (str) => /[a-z]/.test(str);
let regUpperAlphaFn = (str) => /[A-Z]/.test(str);
let reg0To9Fn = (str) => /[0-9]/.test(str);
//验证密码
function checkPassword(str) {
    return isAllChcekFnsReturnTrueThenPassBySomeMode(str, [checkAllCodeIsAlphaOrNumAndLenIs6To8Fn, regLowerAlphaFn, regUpperAlphaFn, reg0To9Fn]);
}

function testPasswordCollection(fn, args) {
    addHrHtml();
    torAssert(checkPassword, ["1256"], false, this);
    torAssert(checkPassword, ["123456"], false, this);
    torAssert(checkPassword, ["aA3456"], true, this);
    torAssert(checkPassword, ["a34563A"], true, this);
}


testPasswordCollection(checkPassword);


checkAllCodeIsAlphaOrNumAndLenIs6To8 = /^[a-zA-Z0-9]{6,8}$/;
let regLowerAlpha = /[a-z]/;
let regUpperAlpha = /[A-Z]/;
let reg0To9 = /[0-9]/;
function checkPasswordByRegs(str, regs) {
    return regs.every(reg => reg.test(str));
}

var checkPasswordByRegsResult = checkPasswordByRegs(["a34563A"], [checkAllCodeIsAlphaOrNumAndLenIs6To8, regLowerAlpha, regUpperAlpha, reg0To9]);

function checkDbPasswordSame(password) {
    //验证代码
    return true;
}

function checkPasswordReturnTip(str,fn,tip) {
    return function (str) {
        if (!fn(str)) {
            return tip;
        } else {
            return "";
        }
    };
}

function returnTipsByFns(str, fns) {
    return fns.map(fn => fn(str)).filter(result => result !== "");
}

function checkPasswordReturnTips(str) {
    return returnTipsByFns(str,[
        checkPasswordReturnTip(str, checkAllCodeIsAlphaOrNumAndLenIs6To8Fn, "密码必须6到8位且只能由大小写字母或数字组成"),
        checkPasswordReturnTip(str, regLowerAlphaFn, "密码必须含有至少一位小写字母"),
        checkPasswordReturnTip(str, regUpperAlphaFn, "密码必须含有至少一位大写字母"),
        checkPasswordReturnTip(str, reg0To9Fn, "密码必须含有至少一位数字"),
        checkPasswordReturnTip(str, checkDbPasswordSame, "密码不能与之前设置的重复")
    ]
    );
}

console.log(JSON.stringify(checkPasswordReturnTips("123")));


function checkPasswordReturnTipsLocal(str) {
    return returnTipsByFns(str, [
        checkPasswordReturnTip(str, checkAllCodeIsAlphaOrNumAndLenIs6To8Fn, "密码必须6到8位且只能由大小写字母或数字组成"),
        checkPasswordReturnTip(str, regLowerAlphaFn, "密码必须含有至少一位小写字母"),
        checkPasswordReturnTip(str, regUpperAlphaFn, "密码必须含有至少一位大写字母"),
        checkPasswordReturnTip(str, reg0To9Fn, "密码必须含有至少一位数字")
    ]
    );
}
function checkPasswordReturnTipsDb(str) {
    return returnTipsByFns(str, [checkPasswordReturnTip(str, checkDbPasswordSame, "密码不能与之前设置的重复")]
    );
}

function checkPasswordReturnTipsV1(str) {
    let tips = checkPasswordReturnTipsLocal(str);
    if (tips.length===0) {
        tips = tips.concat(checkPasswordReturnTipsDb(str));
    }
    return tips;
}


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容