JavaScript 函数式编程

JavaScript 函数式编程

JavaScript 函数式编程一直存在很久,但似乎从 2016 年开始,它变得越来越火热。这可能是因为 ES6 语法对于函数式编程更为友好,也可能是因为诸如 RxJS ReactiveX 等函数式框架的流行。

概念的问题大家可以自己去查阅。重点展示在 JavaScript 中到底什么是函数式的代码、函数式代码与一般写法有什么区别、函数式的代码能给我们带来什么好处以及常见的一些函数式模型都有哪些。

理解函数式编程

以函数作为主要载体的编程方式,用函数去拆解、抽象一般的表达式

与命令式相比,这样做的好处在哪?主要有以下几点:

  • 语义更加清晰
  • 可复用性更高
  • 可维护性更好
  • 作用域局限,副作用少

基本的函数式编程

例子:实现字母的首字母大写

//一般写法
let arr = ['apple', 'pen', 'apple-pen'];
for(let i in arr){
    let c = arr[i][0];
    arr[i] = c.toUpperCase() + arr[i].slice(1);
}
console.log(arr);
for in 和 for of 的区别。以及在这里如何用 for of 去编写同样的方式

实现用函数式的方式去编写

function upperFirst(word) {
  return word[0].toUpperCase() + word.slice(1);
}

function wordToUpperCase(arr) {
  return arr.map(upperFirst);
}

console.log(wordToUpperCase(['apple', 'pen', 'apple-pen']));

当情况变得更加复杂时,表达式的写法会遇到几个问题:

表意不明显,逐渐变得难以维护

复用性差,会产生更多的代码量

会产生很多中间变量

当我们用函数式方式去写的时候,它利用了函数封装性将功能做拆解,并封装为不同的函数,而再利用组合的调用达到目的。这样做使得表意清晰,易于维护、复用以及扩展。其次利用 高阶函数,Array.map 代替 for…of 做数组遍历,减少了中间变量和操作。

链式优化

// 计算数字之和


// 一般写法
console.log(1 + 2 + 3 - 4)


// 函数式写法
function sum(a, b) {
  return a + b;
}

function sub(a, b) {
  return a - b;
}

console.log(sub(sum(sum(1, 2), 3), 4);

如果是1+2+3+5+6+8+11+29+22+55+11-1-2-2 呢?

会导致横向延展 的比较极端的情况,可读性比较差。还容易产生错误。

因此链式优化就显得比较重要了。

我们可以这样写。 ---- lodash的链式写法

const utils = {
  chain(a) {
    this._temp = a;
    return this;
  },
  sum(b) {
    this._temp += b;
    return this;
  },
  sub(b) {
    this._temp -= b;
    return this;
  },
  value() {
    const _temp = this._temp;
    this._temp = undefined;
    return _temp;
  }
};
console.log(utils.chain(1).sum(2).sum(3).sub(4).value());

这样写的好处是,结构清晰,每个方法做什么都会比较清晰。

在es5之前,我们处理并发的时候,通常是采用回调。但是在es6之后我们可以采用promise的写法。

var ok = 1;
var promise = new Promise((resolve,reject) =>{
    if(typeof ok === 'number'){
        resolve(ok)
    }else{
        reject(new TypeError("ok is not number"))
    }
}).then((result) =>{
    console.log(result);
}).then((result) =>{
    console.log(result)
}).catch((error)=>{
    console.log(error)
})

打印结果->提出问题

链式写法同函数式写法的结合

var func1 = new Promise((resolve,reject)=>{
resolve("this is func1");
})

var func2 = new Promise((resolve,reject)=>{
    reject("this is func2 but it's not done for the project");
})

var mainfunc = new Promise((resolve,reject)=>{
    resolve("mainfunc start ");
}).then((result)=>{
    console.log(result);
    return func1;
}).then((result) => {
    console.log(result);
    return func2;
}).then((result) => {
    console.log(result)
    console.log("mission complete")
}).catch((error) => {
    console.error("error "+error); 
})

常见的函数式编程模型。

闭包(Closure)

可以保留局部变量不被释放的代码块,被称为一个闭包

闭包的概念比较抽象,相信大家都或多或少知道,用到这个特性。那么闭包到底能给我们带来什么样子的好处?

先来看一个如何创建一个闭包:


// 创建一个闭包
function makeCounter() {
  let k = 0;

  return function() {
    return ++k;
  };
}

const counter = makeCounter();

console.log(counter());  // 1
console.log(counter());  // 2

makeCounter 这个函数的代码块,在返回的函数中,对局部变量 k ,进行了引用,导致局部变量无法在函数执行结束后,被系统回收掉,从而产生了闭包。而这个闭包的作用就是,“保留住“ 了局部变量,使内层函数调用时,可以重复使用该变量;而不同于全局变量,该变量只能在函数内部被引用。

换句话说,闭包其实就是创造出了一些函数私有的 ”持久化变量“。

所以从这个例子,我们可以总结出,闭包的创造条件是:

存在内、外两层函数
内层函数对外层函数的局部变量进行了引用

闭包的用途

闭包的主要用途就是可以定义一些作用域局限的持久化变量,这些变量可以用来做缓存或者计算的中间量等等。

接下来我们来写一个简单的缓存工具。使用匿名函数创造一个闭包。

const cache = (function(){
//匿名函数创建了一个闭包对象
const store = {};
return {
    get(key){
        return store[key];
    },
    set(key, val) {
        store[key] = val;
    }
    }
}());

cache.set('a',1);
console.log(cache.get('a'));

上面例子是一个简单的缓存工具的实现,匿名函数创造了一个闭包,使得 store 对象 ,一直可以被引用,不会被回收。

闭包的弊端

持久化变量不会被正常释放,持续占用内存空间,很容易造成内存浪费,所以一般需要一些额外手动的清理机制。

高阶函数

接受或者返回一个函数的函数称为高阶函数

JavaScript语言是原生支持高阶函数的,因为JavaScript的函数是一等公民,它既可以作为参数又可以作为另一个函数的返回值使用。

Array.filter Array.map Array.reduce

  • filter:对数组中的每一项运行给定函数,返回该函数会返回true的项组成的数组。

  • map:每次函数调用的结果组成的数组

  • reduce:(归并)方法接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始缩减,最终为一个值。

     arr.reduce([callback, initialValue])
     
     var max = (pre,cur)=>Math.max(pre,cur);
    
     var func = [1,2].reduce(max);
     //如果是 var func = [1,2].reduce(max,5);  呢?
     
     console.log(func);
    

下面以map为例(映射)

数组中每一个项加一,组成一个新的数组

//一般写法

const arr = [1,2,3];
const rs = [];
for(const n in arr){
  rs.push(++arr[n]);
}
console.log(rs)

//map
let arr1 = [2,4,6,8];
let ar1 = arr1.map(n=>++n);
console.log(ar1);

上面的写法,利用for...of循环的方式遍历数组会产生额外的操作,而且有改变原数组的风险

而 map 函数封装了必要的操作,使我们仅需要关心映射逻辑的函数实现即可,减少了代码量,也降低了副作用产生的风险。

参考文档-我眼中的 JavaScript 函数式编程 作者: 化辰

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

推荐阅读更多精彩内容