了解 JavaScript 函数式编程

1,621 阅读3分钟

函数式编程(Function Programming, FP) 是编程范式之一。

编程范式即编程规范,编程风格

我们常听听说的编程范式还有:

  • 面向对象:典型的就是 java 语言,目前 javascript 的超集 typescript 也是走这条路线。也就是把事物抽象成程序世界中的类和对象,通过封装、继承和多态来演示事物之间的联系。
  • 面向过程:典型的就是 C 语言。在面向过程程序设计中,问题被看成是一些列需要完成的任务,函数则用于完成这些任务,可以看成一步步升级打怪。

而我们今天要讨论的编程范式 -- 函数式编程把事物和事物直接的联系抽象到程序世界,强调的是函数的计算,对运算过程进行抽象。使用函数进行编程

纯函数

纯函数 指相同的输入永远得到相同的输出,而且没有副作用。

如下: slice 是纯函数,splice 就不是。

let arr = [1, 2, 3];
console.log(arr.slice(0,1)); // 1
console.log(arr.slice(0,1)); // 1
console.log(arr.slice(0,1)); // 1

let another_arr = [3, 4, 5];
console.log(another_arr.splice(0,1)); // 3
console.log(another_arr.splice(0,1)); // 4
console.log(another_arr.splice(0,1)); // 5

纯函数是函数编程的重点。函数是函数编程的一等公民。

一等公民,指函数跟其他类型具有相同的地位,也就是说 function 可以当作令一个 function 的参数,也可以作为 function 的返回值,也可以作为变量。

比如:

function demo(cb) {
 cb()
}

function add(x) {
 return function(y) {
   return x + y
 }
}

const val = () => 'jimmy';

声明式编程

函数式编程属于声明式编程范式。

声明式编程范式:会描述一些列的操作,但是并不会暴露他们是如何实现的或者数据流是如何传递的。

比如:

let arr = ['hello', 'world', '!']
// 命令式,暴露具体实现
for(let i = 0; i < arr.length; i++) {
  arr[i] = arr[i].toUpperCase()
}

// 声明式,隐藏细节
arr.map(str => str.toUpperCase())

引用透明

引用透明(Referential Transparency)指的是一个函数穿传入相同的参数,不管运算多少次,永远会得到相同的值,并且不对外部世界产生任何改变(上面我们已经提到过,就是不产生副作用)。

例子可以参考上面纯函数的例子。

又比如:

function change_style() {
  let dom = document.getElementById('demo');
  dom.style.color = 'red';
}
// change_style 对外界产生的副作用,更改了 dom 的样式

不可变性

不可变性,就是数据一旦创建后就不会再改变的,所有对不可变性的数据操作返回的是另一个不可变性的数据。这好比操作 const 定义变量一样。

又比如:

let obj = {
  name: 'jimmy'
}
let person = obj;
person.name = 'Jimmy';
console.log(obj.name); // Jimmy
// 此时,对象 obj.name 数据已经发生了改变(可变)

let obj1 = {
  name: 'jimmy'
}
let person1 = { ...obj, name: 'Jimmy'};
console.log(obj1.name); // jimmy
// 此时,对象 obj.name 数据没发生变化(不可变)

好了,我们来总结下函数式编程应该具有的特征:

  • 纯函数
  • 函数是一等公民
  • 声明式编程
  • 引用透明
  • 不可变性

以下面的节流函数应用结束本文:

function throttle(fn) {
  let canRun = true;
  return function() {
    if(!canRun) {
      return;
    }
    canRun = false;
    setTimeout(() => {
      fn.call(this, arguments);
      canRun = true;
    }, 1000)
  }
}

【完】✅