Part1 · JavaScript【深度剖析】
函数式编程与Js异步编程、手写Promise
文章说明:本专栏内容为本人参加【拉钩大前端高新训练营】的学习笔记以及思考总结,学徒之心,仅为分享。如若有误,请在评论区支出,如果您觉得专栏内容还不错,请点赞、关注、评论。
共同进步!
一、为什么要学习函数式编程
函数式编程功能是非常古老的一个概念,早于第一台计算机的诞生,函数式编程的历史
学习函数式编程的目的:
- 函数式编程是随着React的流行受到越来越多的关注;
- Vue3也开始走入函数式编程的怀抱;
- 函数式编程可以抛弃this
- 打包过程中可以更好的利用tree shaking过滤无用的代码
- 方便测试、方便并行处理
- 有很多库可以帮助我们进行函数式开发:lodash、underscore、ramda
二、什么是函数式编程
函数式编程(Functional Programming,FP),FP时编程范式之一,常说的编程范式还有面向过程编程、面向对象编程。
- 【面向对象编程】的思维模式:把现实世界中的失误抽象成程序视界中的类和对象,通过封装、继承和多态来演示事务事件的联系(人类-小明)
- 【函数式编程】的思维方式:把现实世界的事务和事务之间的联系抽象到程序世界(对运算过程进行抽象)
- 程序的本质:根据输入通过某种运算获得相应的输出,程序开发过程中会涉及很多有输入和输出的函数
- x -> f(联系、映射) -> y, y=f(x)
- 函数式编程中的函数指的不是程序中的函数(或方法),而是数学中的函数即映射关系,例如:y=sin(x),x和y的关系
- 相同的输入始终得到相同的输出(纯函数)
- 函数式编程用来描述数据(函数)之间的映射
// 非函数式
let num1 = 2;
let num2 = 3;
let sum = num1 + num2;
console.log(sum);
// 函数式
function add(n1, n2) {
return n1 + n2
}
let sum = add(2, 3);
console.log(sum);
三、前置知识
1.函数是一等公民
MDN First-class Function
在JavaScript中函数就是一个普通的对象(可以通过new Function()),我们可以把函数存储到变量、数组中,它还可以作为另一个函数的参数和返回值,甚至我们可以在程序运行的时候通过new Function('alert(1)')来构造一个新的函数
函数可以存储在变量中
函数作为参数
函数作为返回值
把函数赋值给变量
// 把函数赋值给变量
let fn = function () {
console.log('Hello First-class Function')
}
fn() // 函数调用
// 示例
const BlogController = {
index(posts) {return views.index(ports)},
show (post) { return Views.show(post) },
create (attrs) { return Db.create(attrs) },
update (post, attrs) { return Db.update(post, attrs) },
destroy (post) { return Db.destroy(post) }
}
// 示例优化
const BlogController = {
index: Views.index,
show: Views.show,
create: Db.create,
update: Db.update,
destroy: Db.destroy
}
- 函数是一等公民是后面学习告诫函数、函数柯里化的基础
2.高阶函数
什么是高阶函数
- 高阶函数(Higher-order Function)
- 可以把函数作为参数传递给另一个函数
- 可以把函数作为另一个函数的返回结果
- 函数作为参数
// 模拟forEach
function forEach(array, fn){
for (let i = 0;i < array.length; i++) {
fn(array[i])
}
}
// 模拟filter
function filter(array, fn) {
let results = [];
for (let i = 0; i < array.length; i++){
if (fn(array[i])) {
results.push(array[i])
}
}
return results;
}
- 函数作为返回值
function makeFn(){
let msg = 'Hello Function';
return function () {
console.log(msg)
}
}
const fn = makeFn();
fn()
// once模拟支付,业务场景中用户支付次数只允许为一次
function once(fn){
let done = false; // 默认置为false
return function (){
if (!done){
// 若未支付,则进入函数
done = true; // 进入后,将done置为true,确保下次不进入函数
return fn.apply(this, arguments)
}
}
}
let pay = once(function (money)){
console.log('支付:' + money + 'RMB';
}
// 只会打印一次
pay(5);
pay(5);
pay(5);
apply函数提示
apply() 方法调用一个具有给定
this
值的函数,以及以一个数组(或类数组对象)的形式提供的参数。注意:call()方法的作用和 apply() 方法类似,区别就是
call()
方法接受的是参数列表,而apply()
方法接受的是一个参数数组。const numbers = [5, 6, 2, 3, 7]; const max = Math.max.apply(null, numbers); console.log(max); // expected output: 7 const min = Math.min.apply(null, numbers); console.log(min); // expected output: 2 // 详情查看MDN: // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
使用高阶函数的意义
- 抽象可以帮助我们屏蔽细节(黑盒),只需要关注我们的目标
- 高阶函数是用来抽象通用的问题
// 面向过程的方式
let array = [1, 2, 3, 4];
for (let i = 0; i < array.length;i++) {
console.log(array[i])
}
// 高阶函数
let array = [1, 2, 3, 4];
forEach(array, item) =>{
console.log(item)
}
let r = filter(array, item) =>{
return irem % 2 === 0
}
常用的高阶函数
- forEach
- map
- filter
- every
- some
- find/findIndex
- reduce
- sort
- ......
// map函数
const map = (array, fn) => {
let results = []
for (const value of array) {
results.push(fn(value))
}
return results
}
// every函数
const every = (array, fn) =>{
let result = true;
for (const value of array) {
result = fn(value);
if (!result) {
break
}
}
return result
}
// some函数
const some = (array, fn) =>{
let result = false;
for (conse value of array) {
result = fn(value);
if (result) {
break
}
}
return result
}
3.闭包
-
闭包(Closure):函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包。
- 可以在另一个作用域中调用一个函数的内部函数并访问到高函数的作用域中的成员
// 函数作为返回值 function makeFn() { let msg = 'Hello Function'; return function () { console.log(msg) } } const fn = makeFn(); fn() // once function once(fn) { let done = false; return function (){ if (!done) { return fn.apply(this, arguments) } } } let pay = once(function (money) { console.log('支付:' + money + 'RMB') }) // 只会支付一次 pay(5); pay(5);
闭包的本质:函数在执行的时候会放到一个执行栈上,当函数执行完毕后会从执行栈上移出,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员
闭包案例
// 生成计算数字多少次幂的函数
function makePower(power) {
return function (x) {
return Matn.pow(x, power)
}
}
let power2 = makePower(2);
let power3 = makePower(3);
console.log(power2(4))
console.log(power3(4))
// 第一个函数是基本工资,第二个函数是绩效工资
function makeSalary(x) {
return function (y) {
return x + y
}
}
let salaryLevel1 = makeSalary(1500);
let salaryLevel2 = makeSalary(2000);
console.log(alaryLevel1(2000))
console.log(alaryLevel1(3000))
今日分享截止到这里,明天继续更新后续部分:纯函数、柯里化、函数组合、Functor。