1.闭包
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即便函数是在当前词法作用域之外执行。(本文中默认将被记住的词法作用域所属的函数称为外部函数,记住其他词法作用域的函数称为闭包)
常见例子
function foo() {
var a = 2
function bar() {
console.log(a)
}
return bar
}
var baz = foo()
baz() // 输出: 2
一般情况下,在 js 中通常是子作用域能访问父作用域中的变量,反之则不可。闭包的意义在于实现了父作用域对子作用域中变量的访问。本例中,foo 函数传递出了一个函数 bar,传递出来的 bar 被赋值给 baz 并调用,虽然这时 baz 是在 foo 作用域外执行的,但 baz 在调用的时候可以访问到前面的 bar 函数所在的 foo 的内部作用域。
注意:由于闭包会阻止 Javascript 的垃圾回收机制对废弃内存空间的回收,所以闭包在处理速度和内存消耗方面对性能有负面影响。因此,如非必要,尽量避免使用闭包。
2.高阶函数
高阶函数就是输入参数里有函数,或者输出是函数的函数
2.1 函数作为参数
这种类型的高阶函数的代表有 setTimeout,setInterval 等,作为参数的函数被称为回调函数。所有使用回调函数的方法都属于这种类型的高阶函数,其它的还有数组常用方法 map,filter 等。
2.2 函数作为返回值
此种情形下,主要是利用闭包来保持作用域。
function add() {
var num = 0
return function(a) {
return num = num + a
}
}
var adder = add()
adder(1) // 输出: 1
adder(2) // 输出: 3
这种情形常用于函数柯里化
柯里化:又称为部分求值,是把接受多个参数的原函数变换成接受一个单一参数(原函数的第一个参数)的函数,并且返回一个新函数,新函数能够接受余下的参数,最后返回同原函数一样的结果。核心思想是将多参函数转换成可链式调用的单参函数
function currying(fn) {
var rest1 = Array.prototype.slice.call(arguments)
rest1.shift()
return function() {
var rest2 = Array.prototype.slice.call(arguments)
return fn.apply(null, rest1.concat(rest2))
}
}
function sayHello(name, age, fruit) {
console.log(console.log(`我叫 ${name},我 ${age} 岁了, 我喜欢吃 ${fruit}`))
}
var curryingShowMsg1 = currying(sayHello, '小明')
curryingShowMsg1(22, '苹果') // 输出: 我叫 小明,我 22 岁了, 我喜欢吃 苹果
var curryingShowMsg2 = currying(sayHello, '小衰', 20)
curryingShowMsg2('西瓜') // 输出: 我叫 小衰,我 20 岁了, 我喜欢吃 西瓜
本文是在学习时所作的部分笔记,学习原文请参考 JavaScript 设计模式精讲