闭包是Javascript语言的一个难点,也是它的特色,很多高级应用都是依靠闭包实现。
1.什么是闭包?
最通俗的解释:函数嵌套函数,内部函数就是闭包。换句话说就是定义在一个函数内部的函数。
例如:
function foo(){
let name = "jason"; //局部变量
console.log('foo()...')
//fn就是闭包
function fn(){
console.log(name)
console.log('fn()...')
}
fn();
}
foo();
运行结果:
foo()...
jason
fn()...
需要强调的是: 闭包不一定非要返回内部函数,无论fn是否返回这个闭包已经形成了,这里表示的是一种状态。什么状态呢?这个状态就内部函数可以访问被创建时所处的上下文环境,强调的是内部函数可以访问外部函数的局部变量。
2.闭包的意义 - 可以在函数的外部访问到函数内部的局部变量。 - 让这些变量始终保存在内存中,不会随着函数的结束而自动销毁。
上面的例子,在foo函数外是无法访问name局部变量的。
function foo(){
let name = "jason"; //局部变量
console.log('foo()...')
//fn就是闭包
function fn(){
console.log(name)
console.log('fn()...')
}
fn();
}
foo();
//node环境下:error,undefined,
//浏览器环境下:空字符串,默认绑定了window.name
console.log(name);
foo函数返回内部函数后。
function foo(){
let name = "jason"; //局部变量
console.log('foo()...')
function fn(){
console.log('fn()...')
return name;
}
return fn;
}
let name = foo()();
console.log(name);
运行结果:
foo()...
fn()...
jason
这里需要重点强调的是闭包是通过持有外部函数变量的引用,从而实现这些变量始终保存在内存中,并且可以维持状态的改变。
看下面的例子:
function foo(){
let num = 100; //局部变量
console.log('foo()...')
num++;
function fn(){
console.log('fn()...')
return num;
}
return fn;
}
let f = foo();
console.log(f());
foo();
console.log(f())
运行结果:
foo()...
fn()...
101
foo()...
fn()...
101
虽然foo()函数执行了两遍,但是num++并不是在闭包之内,并不能改变闭包函数里num引用所指向的内容发生改变,所以两次输出都是101。 改写如下:
function foo(){
let num = 100; //局部变量
console.log('foo()...')
function fn(){
num++;
console.log('fn()...')
return num;
}
return fn;
}
let f = foo();
console.log(f());
foo();
console.log(f())
运行结果:
foo()...
fn()...
101
foo()...
fn()...
102
由于num++出现在闭包函数里面,所以每次执行foo()函数都会改变闭包函数里面num引用指向的内容发生改变,因此第二次输出102。
但是,这里有一个陷阱值得大家特点注意:函数体包裹的每块代码在每次执行都会产生一个新作用域,然后在函数体执行完后被释放,我们平时在执行这一个过程的时候常常忽略了这样一个事实,闭包也不例外,也就是说,每次执行foo()其实会产生一个新的闭包作用域,至于这个新作用域是否使用取决于你是否接收了这个返回值。
代码改写如下:
function foo(){
let num = 100; //局部变量
console.log('foo()...')
function fn(){
num++;
console.log('fn()...')
return num;
}
return fn;
}
let f = foo();
console.log(f());
f = foo(); //产生一个新的闭包作用域
console.log(f())
运行结果:
foo()...
fn()...
101
foo()...
fn()...
101
第二次执行foo()函数后,f已经指向了新的作用域,所以两次输出都是101。我们可以证明原来的f 作用域还存在。代码改写如下:
function foo(){
let num = 100; //局部变量
console.log('foo()...')
function fn(){
num++;
console.log('fn()...')
return num;
}
return fn;
}
let f = foo();
console.log(f());
let temp = f;
f = foo(); //产生一个新的闭包作用域
console.log(f())
console.log(temp());
运行结果:
foo()...
fn()...
101
foo()...
fn()...
101
fn()...
102
3.闭包的应用
1)实现模块化
mymoudule.js
let myModule = (function () {
function add(x, y) {
return x + y;
}
return {
add: add
}
})();
export default myModule;
在页面中引入模块。
<script type="module">
import myModule from "./mymodule.js";
console.log(myModule.add(10,7));
</script>
2)防抖和节流
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
我需要的是,首先,输入框的结果只出现一次,是在我键盘抬起不在输入后的1s之后
</div>
<input type="text" id="input"/>
<script>
let input = document.querySelector('#input')
// 防抖函数
function debounce(delay){
let timer
return function (value){
clearTimeout(timer)
timer = setTimeout(function(){
console.log(value)
},delay)
}
}
let res = debounce(1000)
input.addEventListener('keyup',(e)=>{
res(e.target.value)
})
</script>
</body>
</html>
3)单例
function Person(){
this.age=18;
}
//单例模式
const singleton=(function (){
let instance=null;//单例
return function(){
if(!instance)
instance=new Person();
return instance;
}
})();
let p1 = new Person();
let p2 = new Person();
console.log(p1===p2); //false
let p3 = singleton();
let p4 = singleton();
console.log(p3===p4) //true
总而言之,闭包函数的实际用途还有很多,在这里我就只简单列举以上应用。
小结: 1. 闭包就是定义在一个函数内部的函数。 2. 通过闭包可以在函数的外部访问到函数内部的局部变量。让这些局部变量始终保存在内存中,不会随着函数的结束而自动销毁。 3. 闭包的实际应用有:模块化,防抖节流函数、单例等等。