不积跬步--漫谈JavaScript函数式编程

这里说一下javaScript的函数式编程,它其实和函数的特性有很大的关联,就是因为函数可以被当做变量,所以变量做的事儿,函数都可以做,比如放在对象里,放在数组里,被函数返回,被其他函数调用等。

JavaScript函数式编程

我们先从JavaScript的函数特性来开始说。

函数就是变量,可以添加到对象中

 const log = message => console.log(message);
 const obj = {
     message:"xxxxxx",
     log(message){
         console.log(message);
     }
 };

函数是变量,所以可以添加到数组中

const messages = [
    "They can be inserted into arrays",
    message=>console.log(message),
    "like variables",
    message=>console.log(message),
];

然后这样调用

messages[1](messages[0]);
messages[3](messages[2]);

函数可以像其他的变量那样,作为其他函数的参数进行传递:

const insideFn = logger => logger("They can be sent to other functions as arguments");

insideFn(message=>console.log(message));

函数可以像变量那样,作为其他函数的执行结果被返回

var createScream = function (logger) {
    return function (message) {
        logger(message.toUpperCase()+"!!!")
    }
}
const scream = createScream(messages=>console.log(messages));

scream('functions can be returned from other functions ');
scream('createScream returns a function');
scream('scream invokes than returned function');

用ES6的写法写高阶函数,就是一个函数返回一个函数

const createScreams = logger => message=>logger(message.toUpperCase()+"!!!!");
const scream1 = createScreams(messages=>console.log(messages));
scream1('functions can be returned from other functions ');
scream1('createScream returns a function');
scream1('scream invokes than returned function');

命令式编程的一个显著的特点就是对执行结果的描述远胜于执行过程。

例子:让字符串兼容URL格式

let string = "This is the midday show with Chery1 Waters";

let urlFriendly = "";

for(let i = 0;i

函数式编程的基本概念

不可变性

为了达成不可变性,我们通过复制传入数据的方式,拷贝一个副本来进行操作
定义一个颜色对象

let color_lawn = {
    title:"lawn",
    color:"#00ff00",
    rating:0
};
//对对象进行操作--修改了原对象
function reteColor(color,rating) {
    color.rating = rating
    return color
}
//使用Object.assign进行拷贝
var rateColor = function (color,rating) {
    return Object.assign({},color,{rating:rating})
};

console.log(rateColor(color_lawn,5).rating);

使用扩展运算符--完美

const rateColorLast =(color,rating)=>({...color,rating});

下面是对数组的不可变性的操作--一个数组我们要添加一个新的元素给它,那么

const colors = [
    {title:"Red red"},
    {title:"Lawn"},
    {title:"Party Pink"},
];

传统的做法是:--直接修改了原数组

var addColor = function (title,colors) {
    colors.push({title});
    return colors;
}

为了避免修改原数组--使用concat--它会返回一个原生数组的副本,不会改变原数组

const addColors = (title,array)=>array.concat({title});

使用ES6的

const addColorLast = (title,array)=>[...array,{title}]

纯函数

就是不改变外在状态,不会产生副作用,不会修改全局变量等
这里看一个不是纯函数的例子

var frederick = {
    name:"Frederick Douglass",
    canRead:false,
    canWrite:false
};

直接修改了世界变量,造成整个环境的改变

function selfEducate(){
    frederick.canRead = true;
    frederick.canWrite = true;
    return frederick;
}

或者是这样--但是它改变了传入的参数的值,也造成了全局变量的改变

const selfEducates = (person)=>{
    person.canRead = true;
    person.canWrite = true;
    return person;
}

使用ES6的扩展运算符

const selfEducateLast = (person)=>({
    ...person,
    canRead:true,
    canWrite:true,
})

数据转换

其实数据转换的目的是保证数据的不可变性,不会造成额外的影响。

这里用到Array.map和Array.reduce,Array.filter--返回新的数组,不会改变原有数组

const schools = [
    "Yorktown",
    "WashingTon & lee",
    "Wakefield"
];
const wSchools = schools.filter(school=>school[0] === "W");
console.log(wSchools);

Array.map

const highSchools = schools.map((school)=>({name:school}));
console.log(highSchools);

const ages = [21,18,42,40,64,63,34];

const maxAge = ages.reduce((max,age)=>{
    console.log(`${age} > ${max} =  ${age > max}` );
    if(age > max){
        return age;
    }else{
        return max;
    }
},0)

console.log("maxage==",maxAge);

高阶函数

const invokeIf = (condition,fnTrue,fnFalse)=>(condition) ? fnTrue() : fnFalse();

const showWelcome = ()=> console.log("Welcome!!!");

const showUnauthorized = ()=>console.log("Unauthorized!!!");

invokeIf(true,showWelcome,showUnauthorized);
invokeIf(false,showWelcome,showUnauthorized);

柯里函数

const userLogs = userName => message => console.log(`${userName} -> ${message}`);

const log = userLogs("你好啊!!!");
log("小雨");
log("小安");
log("小花");

const compose = (...fns)=>(arg)=>fns.reduce((composed,f)=>f(composed),arg);

const add = (number)=>number*5;
const adda = (number)=>number+10;

const both = compose(
    add,
    adda
)

console.log("both==",both(10));

用函数式编程实现一个滴答作响的时钟--需要注意的是:

  • 1.保持数据的不可变性
  • 2.确保尽量使用纯函数,只接收一个参数,返回数据或者其他的函数
  • 3.尽量使用递归处理循环,(如果有可能的话)

//首先使用命令式编程风格实现

setInterval(logClockTime,1000);
function logClockTime() {
    //获取本地的时间格式的时钟时间字符串
    // var time = getClockTime();
    // //清空控制台并记录时间
    // console.clear();
    // console.log(time);
};

function getClockTime() {
    //获取当前时间
    var date = new Date();
    //序列话时钟时间
    var time = {
        hours:date.getHours(),
        minutes:date.getMinutes(),
        seconds:date.getSeconds(),
        ampm:"AM"
    };

    //转换成当地时间
    if(time.hours == 12){
        time.ampm = "PM";
    }else if(time.hours > 12){
        time.ampm = "PM";
        time.hours = 12;
    };

    //为小时位置上预置0,以便构造双位数字
    if(time.hours < 10){
        time.hours = "0"+time.hours;
    }

    //为分钟位置预置0,以便构造成双位数字
    if(time.minutes < 10){
        time.minutes = "0" + time.minutes;
    }

    //为秒位置预置0,以便构造成双位数字
    if(time.seconds < 10){
        time.seconds = "0" + time.seconds;
    }

    //将时间格式化为一个字符串"hh:mm:ss tt"
    return time.hours + ":" + time.minutes + ":" + time.seconds + " " + time.ampm;
}

下面是函数式编程风格实现的。
首先我们需要准备一些备用函数

const oneSecond = ()=>1000;
const getCurrentTime=()=>new Date();
const clear = ()=>console.clear();
const log = (message)=>console.log(message);

//接收一个时间对象,为时钟构造一个包含,时,分,秒的对象
const serializeClockTime=(date)=>({
    ...date,
    hours:date.getHours(),
    minutes:date.getMinutes(),
    seconds:date.getSeconds()
});
//civilianHours 接收一个对象,返回一个小时被转换成本地时间的对象,比如:1300转为时钟上的一个点
const civilianHours = clockTIme =>({
    ...clockTIme,
    hours:(clockTIme.hours > 12) ?
        clockTIme.hours - 12 :
        clockTIme.hours
});

//接收一个时钟对象,然后在该对象中追加日期
const appendAPM = clockTime => ({
    ...clockTime,
    ampm:(clockTime.hours > 12) ? "下午":"上午"
});

//这三个函数可以保证在不改变数据的情况下转换数据

//display 获取目标函数,返回的函数将会把时间发送到目标,这里是console.log
const display = target => time => target(time);

//formatClock 获得一个模板字符串
const formatClock = format => time =>
    format.replace("hh",time.hours)
          .replace("mm",time.minutes)
          .replace("ss",time.seconds)
          .replace("tt",time.ampm)

//prependZero 补0操作

const prependZero=key=>clockTime=>
    ({
        ...clockTime,
        [key]:(clockTime[key]<10)?
            "0"+ clockTime[key] :
            clockTime[key]
    })

//下面我们使用合成函数来合成
//一个独立函数,将会获取时钟时间作为参数,并通过本地时间规范将时钟时间专病为本地时间
const convertToCivilianTime = clockTime =>
    compose(
        appendAPM,
     //   civilianHours,
    )(clockTime);

//一个独立的函数,将会获取本地时间,并确保时,分,秒是以双位数格式显示的,

const doubleDigits = civilianTime =>
    compose(
        prependZero("hours"),
        prependZero("minutes"),
        prependZero("seconds")
    )(civilianTime)

//startTicking 启动程序

const compose = (...fns)=>(arg)=>fns.reduce((composed,f)=>f(composed),arg);

const startTicking=()=>
    setInterval(
        compose(
            clear,
            getCurrentTime,
            serializeClockTime,
            convertToCivilianTime,
            doubleDigits,
            formatClock("hh:mm:ss tt"),
            display(log)
        ),oneSecond()
    )

startTicking();

你可能感兴趣的:(不积跬步--漫谈JavaScript函数式编程)