函数是一等公民
函数是一等公民,也可以叫做头等函数,它有下面的几种使用场景。
- 函数可以存储在变量中
- 函数作为参数
- 函数作为返回值
在JavaScript的世界中,函数就是一个普通的对象,我们可以把函数存储到变量/数组/对象中,还可以作为另一个函数的参数和返回值,甚至我们可以在运行程序时通过 new Function("alert('hello function')") 来构造一个新的函数。
下面是一些示例:
// 函数赋值给变量
let add = function (a, b) {
return a + b
}
add(1, 2) // 3
// 赋值给对象
const GoodsController = {
index(posts){ return views.index(props)},
show (post) { return Views.show(post) }
}
// 可以做一下小优化
const GoodsController = {
index: views.index,
show: views.show
}
函数是一等公民是 高阶函数 和 函数柯里化 的重要基础。
高阶函数
什么是高阶函数
高阶函数(Higher-order function)
- 可以把函数作为参数传递给另一个函数
- 可以把函数作为另一个函数的返回结果
函数作为参数
// 函数作为参数 (模拟 map 方法)
function map(array, fn){
let result = []
for(let item of array){
let res = fn(item)
if(res){
result.push(item)
} else {
result.push(null)
}
}
return result
}
let mapArr = map([1,2,3],(item)=>{
return item%2 === 0
})
mapArr // [null,2,null]
函数作为返回值
// 函数作为返回值(闭包)
function resFun(a){
return function(b){
return a + b
}
}
let res = resFun(1)
res(2) // 3
// 一个once 的例子
function once(fn){
let done = false
return function(){
if(!done){
done = true
return fn.apply(null,arguments)
}
}
}
let payMoney = once(function(money){
console.log('支付了'+money+'元')
})
// 只会调用一次
payMoney(10)
payMoney(10)
使用高阶函数的意义
可以帮我们屏蔽细节,只需要关注与我们的目标相关的逻辑,常用来抽象通用问题。
下面是普通面向过程编程和使用高阶函数编程的对比:
// 面向过程
let arr = [1,2,3,4]
for(let i = 0;i<arr.length;i++){
console.log(arr[i])
}
// 高阶函数
function forEach(array,fn){
for(let i = 0;i<arr.length;i++){
fn(arr[i])
}
}
forEach(arr,(item)=>{
console.log(item)
})
下面是常用的数组相关的高阶函数
- forEach
- map
- filter
- every
- some
- find/findIndex
- reduce
- ...
我们简单实现一下其中的某些方法
// every
function every(array,fn){
let result = true
for(let val of array){
if(!fn()){
result = false
break
}
}
return result
}
// some
function every(array,fn){
let result = false
for(let val of array){
if(fn()){
result = true
break
}
}
return result
}
闭包(closure)(MDN)
函数和其周围的状态(词法环境)的引用捆绑在一起形成了闭包。它可以在另一个作用域中调用一个函数的内部函数并访问到该函数作用域中的成员。
当函数作为返回值时,就实现了闭包。
举例
// 函数作为返回值
function makeFn () {
let msg = 'Hello function'
return function () {
console.log(msg)
}
}
const fn = makeFn()
fn()
上图中 makFn 函数体内声明的 msg 变量,如果没有在 return 的方法中引用,在 makeFn()执行完成之后,msg 就会被系统回收,但是它在返回的方法中被引用,被返回的方法就和这个变量进行了"捆绑",返回的方法被调用的时候,依然可以访问到msg 变量的值。
具体来说,JavaScript在执行函数时,会把函数放到一个执行栈上,当函数执行完毕之后会从执行栈上移除,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问到外部函数的成员。