JavaScript:了解一下函数式编程

一、简介

在JavaScript中,函数就是第一类公民,它可以像字符串、数字等变量一样,使用var修饰并作为数据使用。它可以作为数值、可以作为参数、还可以作为返回结果。可以说JavaScript就是函数式编程。ES6新语言特性中,箭头函数、扩展运算符会极大地帮助开发者利用函数式编程技术进行开发。

//定义一个箭头函数
 var print = (message) => console.log(message)

1、函数作为数值使用

//将函数作为数值使用
 const array = ["XYQ",print]
 console.log(array) //["XYQ", function] (2)

2、函数作为参数使用

//将函数作为参数使用
 //定义一个函数lg,它接收一个参数logger函数,lg的函数体内部会执行这个参数logger函数
 //由于传入的print函数可以接收一个参数,所以logger函数在执行时,默认传入了一个字符串
 const lg = logger => logger("I Am XYQ")
 lg(print) //I Am XYQ

3、函数作为返回值使用

//将函数作为返回值使用
 //定一个函数fcVale,它接收一个logger函数,同时它的返回值为新的函数,新的函数可以传入
 //一个变量进行打印。最终,用函数fcVale创建一个函数fc,并给fc函数传入字符串进行调用
 // var fcVale = function (logger) {
 //     return (message) => logger(message.toUpperCase())
 // }
 const fcVale = logger => message => logger(message.toUpperCase()) //高阶函数, 函数既可以接收函数参数,也可以作为其他函数返回值
 const fc = fcVale(print)
 fc("i am xyq") //I AM XYQ

二、风格

在JavaScript开发中,对于函数的编程分为两种风格,分别是命令式和声明式,其中函数式编程就是声明式的一部分。所谓命令式,就是更加重视函数为达到目标的执行过程而不是结果,也即重执行轻结果。而声明式,则恰恰相反,对执行结果的描述远胜于执行过程,声明式的函数很容易理解它的用途是什么,至于具体的执行细节则被隐藏了。在JavaScript中,声明式函数的编程使用及其广泛。

//定义一个字符串变量
 var userString = "My Name is XYQ"

1、命令式

//命令式:遍历字符串,将字符串的空格全部用下划线替换 【开发者必须看完这个代码快才能知道函数的作用是替换功能】
 var temp = ""
 for (var i=0; i<userString.length; i++){
    if (userString[i] === " "){
         temp += "_"
    } else {
         temp += userString[i]
    }
 }
 console.log(temp) //My_Name_is_XYQ

2、声明式

//声明式:使用正则表达式,将字符串的空格全部用下划线替换 【开发者看到replace就基本知道函数的用途就是替换功能】
 const user_string = userString.replace(/ /g,"_")
 console.log(user_string) //My_Name_is_XYQ

三、概念

函数式编程是JavaScript中的核心功能,它的核心概念一共有四种,分别是不可变性、纯函数、数据转换、高阶函数以及递归。

1、不可变性,说的是在函数式编程中,数据是不可以改变的,他们永远无法修改,实现不可变的工作机制就是对原生数据进行拷贝编辑,然后取代使用。

<script type="text/javascript">
  //定义一个person对象
   var person = {
       name: "XYQ",
       age: 25,
       sex: "male"
   }

   //方法一:通过Obejct.assign方法拷贝机制,创建一个空白对象,并将当前对象拷贝到空白对象上,接着重写对象值
   var copyPerson = function (person, name) {
       return Object.assign({}, person, {name:name}) // {} 为一个空白对象, person拷贝到{}上,重写name值
   }
   var newPerson = copyPerson(person,"YPX")
   console.log(newPerson.name) //YPX
   console.log(person.name)    //XYQ,可以看到原对象没有发生改变

   //方法二:通过扩展运算符特性对对象进行拷贝
   const copyPerson2 = (person, name) => ({
       ...person,
       name
   })
   var newPerson2 = copyPerson(person,"YPX")
   console.log(newPerson2.name)  //YPX
   console.log(person.name)      //XYQ,可以看到原对象没有发生改变

   //---------------------------------------------------------------------------------//
        
   //定义一个数组对象
   var persons = [
       {name:"XYQ"},
       {name:"YPX"}
   ]

   //方法一:使用数组的Array.concat方式将数组串联起来,生成一个新的对象并添加原生数组的副本上,不可以用Array.push这个可变函数
   const addPerson = (name, persons) => persons.concat({name})
   var newPersons = addPerson("HXF", persons)
   console.log(newPersons) //[{name: "XYQ"}, {name: "YPX"}, {name: "HXF"}] (3)
   console.log(persons)    //[{name: "XYQ"}, {name: "YPX"}] (2),可以看到原数组没有发生改变

   //方式二:使用扩展运算法特性对数组进行拷贝
   const addPerson2 = (name, persons) => [...persons, {name}]
   var newPersons2 = addPerson2("HXF", persons)
   console.log(newPersons2) //[{name: "XYQ"}, {name: "YPX"}, {name: "HXF"}] (3)
   console.log(persons)     //[{name: "XYQ"}, {name: "YPX"}] (2),可以看到原数组没有发生改变
</script>

2、纯函数,是一个返回结果只依赖输入参数的函数,它至少需要接收一个参数并且总是返回一个值或者其他函数,它把参数当做不可变数据使用,不做任何修改。

纯函数特点:
1、函数应该至少接收一个参数;
2、函数应该返回一个值或者其他函数;
3、函数不应该修改或者影响任何传给它的参数
<script type="text/javascript">

  //定义一个person对象
  var person = {
      name: "XYQ",
      age: 25,
      sex: "male"
  }

  //创建一个纯函数,返回值是根据参数preson生成的一个新的person,它没有改变参数person,更改的返回的新的person。
  const updatePerson = person => ({
      ...person,
      name:"YPX",
      sex:"feMale"
  })

  //打印
  var newPerson = updatePerson(person)
  console.log(newPerson) // {name: "YPX", age: 25, sex: "feMale"}
  console.log(person)    // {name: "XYQ", age: 25, sex: "male"}, 原对象person没有发生改变

</script>

3、数据转换,函数式编程中由于数据的不可变性,它会将一种数据转换成另一种数据,使用函数生成转换后的副本进行状态的转换。

//定义一个city数组
 const cities = ["BeiJing","ChongQing","ChongDu"]

 //使用系统函数Array.join()将数组用分隔符连接成字符串
 var cityString = cities.join(",")
 console.log(cityString) // BeiJing,ChongQing,ChongDu
 console.log(cities)     // ["BeiJing", "ChongQing", "ChongDu"] (5)

 //使用系统函数Array.filter()进行谓词过滤,这个谓词始终是一个返回布尔值的函数
 //会访问数组每一个元素,匹配C开头的城市,如果返回true,则将其添加到新的数组中
 const newCities = cities.filter(city => city[0] === "C")
 console.log(newCities) // ["ChongQing", "ChongDu"] (2)
 console.log(cities)    // ["BeiJing", "ChongQing", "ChongDu"] (5)

 //仍然使用系统函数Array.filter(),代替Array.pop()和Array.splice()函数删除元素。因为后面的两个方法是可变的。
 const deleteCity = (deletedCity, cities) => cities.filter(city => city !==    deletedCity)
 const newCities2 = deleteCity("BeiJing", cities)
 console.log(newCities2) // [ "ChongQing", "ChongDu"] (4)
 console.log(cities)     // ["BeiJing","ChongQing", "ChongDu"] (5)

 //使用系统函数Array.map(),参数也是一个函数,在函数在访问数组中每一个元素时会执行
 const  newCities3 = cities.map(city => `${city} China`)
 console.log(newCities3) // ["BeiJing China","ChongQing China", "ChongDu China"] (5)
 console.log(cities) //["BeiJing", "ChongQing", "ChongDu"] (5)

 // 仍然使用系统函数Array.map(),她还可以构造任意对象、数值、数组、函数等
 // 1、将数组转对象
 let objectCities = cities.map(city => ({cityName : city}))
 console.log(objectCities) // [{cityName: "BeiJing"}, {cityName: "ChongQing"}, {cityName: "ChongDu"}] (3)
 console.log(cities) // ["BeiJing", "ChongQing", "ChongDu"] (3)

 // 2、修改对象元素
 const updateCityName = (originCityName, cityName, cities) =>
            (cities.map(city => (city.cityName === originCityName) ? ({...city, cityName}) : city))
 const newObjectCities = updateCityName("BeiJing","TianJin",objectCities)
 console.log(objectCities) //[{cityName: "BeiJing"}, {cityName: "ChongQing"}, {cityName: "ChongDu"}] (3)
 console.log(newObjectCities) //[{cityName: "TianJin"}, {cityName: "ChongQing"}, {cityName: "ChongDu"}] (3)

 // 3、将对象转数组(配合Object.key函数使用)
 //定义一个city对象
 const cityObject = {"BeiJing":"China","NewYork":"USA"}
 const cityObjects = Object.keys(cityObject).map(key =>
     ({cityName: key, country: cityObject[key]})
 )
 console.log(cityObject) //{BeiJing: "China", NewYork: "USA"}
 console.log(cityObjects)//[{cityName: "BeiJing", country: "China"}, {cityName:  "NewYork", country: "USA"}](2)

 // 系统函数Array.reduce()和rArray.reduceRight()可以用来将数组转成任意值,比如数字、字符串、布尔值、对象甚至函数
 // Array.reduce()函数, 从数组头部开始处理元素 ; Array.reduceRight()函数,从数组尾部 开始处理元素
 // 求最值
 const nums = [10,5,30,24,78,60,100]
 const max = nums.reduce((max, num) => (num > max) ? num : max)
 const min = nums.reduce((min, num) => (num < min) ? num : min)
 console.log(max) // 100
 console.log(min) // 5

 // 去重,items为新的空数组
 const names = ["XYQ","YPX","XXF","XYQ","XYQ"]
 const deletedNames = names.reduce((items, name)=> items.indexOf(name) !== -1 ? items : [...items,name], [])
 console.log(names) //["XYQ", "YPX", "XXF", "XYQ", "XYQ"] (5)
 console.log(deletedNames) //["XYQ", "YPX", "XXF"] (3)

4、高阶函数,是可以操作其他函数的函数,它们可以将函数当做参数传递,也可以返回一个函数,或者二者兼有之。柯里化就是典型的应用。

//定义高阶函数,把函数当做参数
 const printer = () => console.log("---printer---")
 const logger  = () => console.log("---logger---")
 const show = (selectPrint, printer, logger) => (selectPrint) ? printer() : logger()

 //调用高阶函数
 show(true,printer,logger)  //---printer---
 show(false,printer,logger) //---logger---
        
 //定义高阶函数,把函数当做返回值
 const log = (message) =>( () => console.log(message) )
 const logFunction = log("I Am XYQ")

 //调用高阶函数
 logFunction() //I Am XYQ

5、递归,它是用户创建的函数调用自身的一种技术方案,在开发中遇到循环时,通过递归可以极其精简的缩短代码量,是一种优势替代方案。

//定义一个倒序遍历的函数
 const countDown = (number, log) => {
      log(number)
      return (number > 0) ? countDown(number-1, log) : number
 }

 //调用递归函数
 const log = (number) => console.log(number)
 countDown(5,log)// 5 4 3 2 1 0

 四、链式

函数式编程是将具体的业务逻辑拆分成一个个小型的简单的纯函数,方便进行功能聚焦,最终,开发者通过串联或者并联的方式将这些小型函数合成在一起进行调用即可。这个合成的过程其实就是链式调用。如之前介绍的Promise对象的应用。

//使用系统函数replace实现链式调用
 const formmater = "hh:mm:ss tt"
 const currentTime = formmater.replace("hh", "22").replace("mm","07").replace("ss","00").replace("tt","PM")
 console.log(currentTime) //22:07:00 PM

相关推荐