1. 什么是函数式编程
函数式编程是一种编程范式,就是将运算过程封装成一个函数,通过组合各种函数来计算结果。讲到编程范式,现在主要有两种:命令式编程和声明式编程。
命令式编程是一种描述电脑所需作出的行为的编程范式,是目前使用最广泛的,其本质就是站在计算机的角度思考问题,关注计算机执行步骤,通过编写一条一条的指令让计算机执行一些动作。
声明式编程描述的是目标的性质,让计算机明白目标而不是具体的实现过程。通过定义具体的规则,以便系统底层可以自动实现具体功能。
声明式函数编程可以让我们专注于怎么实现代码,而不用考虑函数内部是如何实现的,降低了代码的复杂性。
2. 函数式编程的特点
2.1 函数是一等公民
在JavaScript中,函数是“第一等公民”,指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。
2.2 只用表达式,不用语句
函数式编程就是为了处理运算,而表达式意味着运算过程需要有返回值,如果使用语句就是执行某项操作,没有返回值。
2.3 没有副作用
副作用指的就是函数内部与外部互动,产生运算以外的其他结果。函数式编程调用函数时不会修改外部状态,也就是一个函数调用n次后依然返回相同的结果。
2.4 引用透明
指的是函数的运行不依赖外部的的变量,只会用到传递给它的变量以及自己内部创建的变量。
2.5 不可变变量
一个变量一旦创建以后,就不能再修改,任何修改都会创建一个新的变量。
3.闭包
3.1 概念
闭包是指有权访问另一个函数(通常是外部函数)作用域中变量的函数(注意:闭包是在使用时才会生成的,而非创建时)。
function hd() {
let n = 1
return function sum() {
console.log(n++)
}
}
let a = hd()
a() //1
a() //2
a() //3
解析:函数只有在调用的时候才会创建一块新的内存,hd()调用后创建一个父环境,里面包含变量n和sum函数,每次调用a()都会创建一个新的子环境,且都能访问到外部的变量n。
- 优点:
(1)保护:闭包可以保护函数的私有变量不受外部干扰,形成不被销毁的栈内存;
(2)保存:闭包可以将上级作用域的引用保存下来,实现方法/属性的私有化。 - 缺点: 若不清除闭包,闭包会一直占用内容,造成内存泄漏。
3.2 setTimeout问题
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i)
},1000)
}
console.log(i)
结果:
立即打出3
随后隔1s打出3个3
setTimeout是异步函数,所以先会执行主线程的console.log(i),此时for循环已经循环完毕,i=3退出循环,然后在从任务队列中调出setTimeout执行(有3个),但此时i=3,所以1s后会输出3个3
但是若想打印出3 -> 0,1,2,怎么实现?
for (var i = 0; i < 3; i++) {
(function (j) {
setTimeout(function () {
console.log(j)
}, 1000)
})(i)
}
console.log(i)
结果: 3 -> 0,1,2
利用立即执行函数,每次调用执行立即函数就会创建出一块新的内存,保证每次循环的i被保存下来
3.3 使用闭包获取商品区间
let arr = [1,3,6,8,9,24,7,45,32,67]
let res = arr.filter(function (price) {
return price > 10 && price < 50
})
let res1 = arr.filter(function (price) {
return price > 3 && price < 20
})
console.log(res) //[ 24, 45, 32 ]
console.log(res1) //[ 6, 8, 9, 7 ]
使用filter函数对数组进行筛选,但是这样每次修改区间都要写重复代码,因此可以将返回值部分封装成一个函数,提高代码复用。
function between(a,b) {
return function (v) {
return v > a && v < b
}
}
let res2 = arr.filter(between(10,50))
console.log(res2) //[ 24, 45, 32 ]
3.4 移动动画闭包的使用
需求:点击按钮,按钮每100ms向右移动1
<style>
button {
position: absolute;
}
</style>
<body>
<button>点击1</button>
<button>点击2</button>
</body>
版本一:
<script>
let btns = document.querySelectorAll('button')
btns.forEach(function (btn) {
btn.addEventListener('click', function () {
let left = 1
setInterval(function () {
btn.style.left = (left++) + 'px'
}, 100)
})
})
</script>
问题:但是这样的话连续点击按钮就会出现抖动,原因是每次点击,都会创建一个新环境,这个环境中包含变量left = 1 和 setInterval(),所以每次点击的left都为1,就会出现抖动情况。
解决:将let left = 1放到上一级环境中,闭包可以保存上级引用,这样就避免了left值每次都为1
版本二:
<script>
let btns = document.querySelectorAll('button')
btns.forEach(function (btn) {
let left = 1 //挪到这里了~
btn.addEventListener('click', function () {
setInterval(function () {
btn.style.left = (left++) + 'px'
}, 100)
})
})
</script>
问题:此时动画还有一个问题,就是频繁点击会加速的问题。每点击一次就会增加一个定时器,所以时间间隔就会< 100ms
解决:不让每次点击产生这么多定时器就好了
版本三:
<script>
let btns = document.querySelectorAll('button')
btns.forEach(function (btn) {
let left = 1
let flag = false //设置一个flag
btn.addEventListener('click', function () {
if (!flag) {
flag = true //当点击后,将flag变为true,下次点击就不会到后面的代码了
setInterval(function () {
btn.style.left = (left++) + 'px'
}, 100)
}
})
})
</script>
3.5 闭包内存泄露的解决方法
<body>
<div desc = 'food'>汉堡包</div>
<div desc = 'drink'>可乐</div>
</body>
<script>
let divs = document.querySelectorAll('div')
divs.forEach(function (item) {
item.addEventListener('click', function () {
console.log(item.getAttribute('desc'))
console.log(item)
})
})
</script>
输出:
food
<div desc='food'>汉堡包</div>
这样每次点击还会将上一级item打印出来,但实际我们只需要获得desc的值就可以,并不需要item
<script>
let divs = document.querySelectorAll('div')
divs.forEach(function (item) {
let desc = item.getAttribute('desc')
item.addEventListener('click', function () {
console.log(desc)
})
item = null //将item赋值为空,清除内存占用
})
</script>