闭包是什么?
什么是闭包,要真正的理解闭包我们需要就要先去理解作用域。
作用域
什么是作用域?就是这个变量和函数在程序中使用的范围。作用域分为函数作用域(局部作用域)和全局作用域。
全局作用域
在代码中任何地方都能访问到的对象拥有全局作用域。
- 优点是可以重复的使用
- 缺点是全局污染,容易出现错误,变量容易出现被其他的地方窜改,代替的问题。
如图,在浏览器的调试scope中三种类型的值都在全局的scope中。
函数作用域
在函数内部创建的作用域,其内部的变量和函数对外不可见
function foo() {
var a = 1
}
foo()
console.log(a) // ReferenceError: a is not defined
作用域链
- 一个函数可用的所有作用域串联起来, 就形成了当前函数的作用域。
- 使用解释: 当在javascript中使用一个变量的时候,首先javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上级作用域寻找,以此类推直到找到该变量或是已经到了全局作用域,如果在全局作用域里仍然找不到该变量,它就会直接报错。
var a = 100
function foo() {
var b = 200
console.log(a) // 100
console.log(b) // 200
console.log(c) // ReferenceError: c is not defined
}
foo()
闭包是什么?
- 1.小'黄'书(你不知道的javascript): 当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
- 2.红宝书(javascript高级程序设计): 闭包是指有权访问另一个函数作用域的变量的函数
- 3.MDN对闭包的定义为:闭包是指那些能够访问自由变量的函数。这里的自由变量是外部函数作用域中的变量。
形成闭包的原因
内部的函数存在外部作用域的引用就会导致闭包。
闭包的优缺点
-
闭包的优点
- 保护变量:闭包可以将变量封装在函数内部,避免全局污染,保护变量不被外部访问和修改。
- 延长变量生命周期:闭包使得函数内部的变量在函数执行完后仍然存在,可以在函数外部继续使用。
- 返回函数:闭包通常以函数的形式返回,使得外部函数的变量仍然可以被内部函数引用和使用。
-
闭包的缺点
- 内存占用:闭包会导致外部函数的变量无法被垃圾回收,从而增加内存占用。如果滥用闭包,会导致内存泄漏问题。
- 性能损耗:闭包涉及到作用域的查找过程,会带来一定的性能损耗。
闭包使用的场景
-
- 使用return 返回函数
var n = 10 function foo() { var n = 50 return function fn() { n++ console.log(n) } } var f = foo() f() // 51 f() // 52这里的return f, f()就是一个闭包,存在上级作用域的引用,闭包函数执行完后外部作用域变量仍然存在,并保持状态。
-
- IIFE自执行函数
var foo = (function () {
var n = 0
return function() {
++n;
return `id_${n}`
}
})()
console.log(foo());
在IIFE之外无法访问函数内部的n变量,除了从IIFE中返回的函数,别处无法读写该变量,这样就能创建真正的私有状态变量。
-
- 函数作为参数
function foo() {
var a = 'foo'
function fn() {
console.log(a)
}
return fn
}
function f(p) {
var a = 'f'
p()
}
f(foo)
return fn就产生闭包, f(foo)执行的是fn, fn的上级作用域是函数foo(), 所以输出就是foo
-
- 循环赋值
for(var i = 0; i < 5; i++) {
(function(j){
setTimeout(function() {
console.log(j)
}, 1000)
})(i)
}
存在闭包,上面依次输出1-5,闭包形成了互不干扰的私有作用域。
-
- 使用回调函数就是在使用闭包
var name = 'foo'
setTimeout(function timeHandler(){
console.log(name);
}, 100)
- 6. 节流防抖
防抖(debounce)就是在一定时间内,如果连续触发事件, 则只会执行最后一次事件的回调函数。 节流(throttle)就是连续触发的事件限制在一个特定的时间段内,在这个时间段内只执行一次回调函数。
1. 防抖
function debounce(fn, delay) {
let timer = null
return function (...args) {
if(timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, arg)
}, delay)
}
}
2. 节流
function throttle(fn, delay) {
let timer = null
return function (...args) {
if(timer) return
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, delay)
}
}
- 7. 函数柯里化
函数柯里化(currying)是一种将多个参数的函数转换为一系列接受单个参数的函数的过程。目的在于避免频繁调用具有相同参数函数的同时,又能够轻松的重用
function curry(fn, len=fn.length) {
return _curry(fn, len)
}
function _curry(fn, len, ...arg) {
return function(...params) {
let _arg = [...arg, ...params]
if(_arg.length >= len) {
return fn.apply(this, _arg)
}else {
return _curry.call(this, fn, len, ..._arg)
}
}
}
let fn1 = curry(function(a, b, c, d, e) {
console.log(a+b+c+d+e)
})
fn1(1)(2)(3)(4)(5)
fn1(1, 2, 3, 4, 5) // 15
function getArea(width) {
return function(height) {
return width * height
}
}
const areaWidth = getArea(10)
const areaHeight = areaWidth(20)
console.log('areaHeight :>> ', areaHeight);