传说中面试必考的闭包概念,我来啦~
1、闭包,closure,是指函数本身和该函数声明时所处的环境状态的组合。
要理解这个加粗的字“该函数声明时”,我个人jio得闭包概念讲的最明白的是《JavaScript权威指南》最新版原书第七版,让我来引用一下。
JavaScript使用词法作用域。
这意味着函数执行时使用的是定义函数时生效的变量作用域,而不是调用函数时生效的变量作用域,为了实现词法作用域,JavaScript函数对象的内部状态不仅要包括函数代码,还要包括对函数定义所在作用域的引用。
这种函数对象与作用域(即一组变量绑定)组合起来解析函数变量的机制,在计算机科学文献中被称作闭包(closure)。
就是说函数能够“记忆”其定义时所处的环境。即使函数不在其定义的环境中被调用,也能访问定义时所处环境的变量。
2、来举个面试题的栗子理解一下
作用域应用的特殊情况,有两种表现:
函数作为参数被传递
函数作为返回值被返回
(1)函数作为参数被传递
// 函数作为返回值
function create() {
const a = 100;
return function () {
console.log(a);
}
}
const fn = create();
const a = 200;
fn();// 100
这里输出的就是100,而不是200
(2)函数作为返回值被返回
// 函数作为参数
function print(fn) {
let b = 100;
fn();
}
let b = 200;
function fn() {
console.log(b);
}
print(fn);// 200
这里输出的是200,而不是100
所以,一定要理解刚才第一点说的,变量到底取什么值,要去函数定义的地方,向上级作用域查找,不是在执行的地方查找!!!
3、来观察一下刚才的闭包
我们在刚才这个小栗子里打个debugger看下
// 函数作为返回值
function create() {
debugger
const a = 100;
return function () {
console.log(a);
}
}
const fn = create();
const a = 200;
fn();// 100
我们在浏览器里会发现跑着跑着就出现了闭包变量
你看它都有闭包变量了,它还不是闭包?那必须就是闭包。
4、其实所有JavaScript函数都是闭包
《JavaScript权威指南》
严格来说,所有JavaScript函数都是闭包。但由于多数函数调用与函数定义都在同一个作用域内,所以闭包的存在无关紧要。闭包真正值得关注的时候,是定义函数与调用函数的作用域不同的时候。最常见的一个情形就是一个函数返回了在它内部定义的嵌套函数。
5、闭包的其他理解
不会还有人不理解闭包吧~
闭包并不是JavaScript里面才有的概念,有很多定义,我都打出来看看
有一种定义是,函数嵌套函数时,内层函数引用了外层函数作用域下的变量,并且内层函数在全局环境下可访问,进而形成的。
红宝书里说的是
闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。
如果还不理解的话,可以再去MDN逛一下,反正我是已经懂了
MDN的闭包
闭包是很有用的一个东东~
因为它允许我们将数据与操作该数据的函数关联起来。
1、记忆性
当闭包产生时,函数所处环境的状态会始终保持在内存中,不会在外层函数调用后被自动清除。这就是闭包的记忆性。
比如写一个体温判断的功能
function createCheckTemp(standardTemp) {
function checkTemp(n) {
if(n <= standardTemp) {
console.log('体温正常哒');
} else{
console.log('发烧了,快隔离!');
}
}
return checkTemp;
}
// 这里会记住这个standardTemp是37.3,后续调用都以这个为标准温度对比
let checkTemp_func = createCheckTemp(37.3);
checkTemp_func(38.1);// 发烧了,快隔离!
checkTemp_func(36.5);// 体温正常哒
2、模拟私有变量
就是说可以隐藏数据
比如写一个计数器
const number = {
}
number.counter = 0;
function add() {
return number.counter++;
}
console.log(add());// 0
console.log(add());// 1
console.log(add());// 2
但是这样的话,number.counter就在全局作用域,就不私有了
但是用闭包的话
let add2 = (function() {
let counter = 0;
return function() {
return counter++;
}
})();
console.log(add2());// 0
console.log(add2());// 1
console.log(add2());// 2
这样纸的话,就可以保证counter是私有的了
闭包不可以滥用,可能会有内存泄露的问题
因为一般函数执行后,上下文就被销毁了,但是闭包会保留它们包含函数的作用域,所以比其他函数更占内存。
不合理的引用有可能会造成内存泄露
1、问,这个会输出什么?
var fn = null;
const foo = () => {
var a = 2;
function innerFoo() {
console.log(c);
console.log(a);
}
fn = innerFoo;
}
const bar = () => {
var c = 100;
fn();
}
foo();
bar();
哈哈,答案是会报错啦,Uncaught ReferenceError: c is not defined
因为这个c根本就不在innerFoo()函数的作用域上
那比如这样改一下,这样输出啥呢?
var fn = null;
const foo = (c) => {
var a = 2;
function innerFoo() {
console.log(c);// undefined
console.log(a);// 2
}
fn = innerFoo;
}
const bar = () => {
var c = 100;
fn(c);
}
foo();
bar();
这样输出的c是undefined,a没啥,是2
这里说明箭头函数的形参是没有传给innerFoo里的c变量的
但是
var fn = null;
const foo = (c) => {
var a = 2;
function innerFoo() {
console.log(arguments[0]);// 100
console.log(c);// undefined
console.log(a);// 2
}
fn = innerFoo;
}
const bar = () => {
var c = 100;
fn(c);
}
foo();
bar();
如果在innerFoo函数里打印arguments[0],又能拿到100的值
如果真的要输出c的值,这样传一下就好了
var fn = null;
const foo = () => {
var a = 2;
function innerFoo(c) {
console.log(c);// 100
console.log(a);// 2
}
fn = innerFoo;
}
const bar = () => {
var c = 100;
fn(c);
}
foo();
bar();