diff --git a/04-JavaScript基础/26-闭包.md b/04-JavaScript基础/26-闭包.md index 02c8479..efa9bf8 100644 --- a/04-JavaScript基础/26-闭包.md +++ b/04-JavaScript基础/26-闭包.md @@ -39,23 +39,79 @@ console.log(a); // 打印报错:Uncaught ReferenceError: a is not defined 上面这个概念,出自《JavaScript 高级程序设计(第 3 版)》这本书。上面的概念中指出,闭包是一种函数;当然,你可以**把闭包理解成是一种现象**。具体解释如下。 -简单理解就是:如果**这个作用域可以访问另外一个函数内部的局部变量**,那就产生了闭包(此时,你可以把闭包理解成是一种现象);而另外那个作用域所在的函数称之为**闭包函数**。注意,这里强调的是访问**局部变量**哦。 +简单理解就是:如果**这个作用域可以访问另外一个函数内部的局部变量**,那就产生了闭包(此时,你可以把闭包理解成是一种现象)。注意,这里强调的是访问**局部变量**哦。 + + + +### 产生闭包的条件 + +**当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量或函数时, 就产生了闭包。** + +- 条件1.函数嵌套 + +- 条件2.内部函数引用了外部函数的数据(变量/函数)。 + + + +来看看条件2: + +```javascript + function fn1() { + function fn2() { + + } + + return fn2; + } + + fn1(); +``` + +上面的代码不会产生闭包,因为内部函数fn2并没有引用外部函数fn1的变量。 + + +PS:还有一个条件是**外部函数被调用,内部函数被声明**。比如: + +```javascript + + function fn1() { + var a = 2 + var b = 'abc' + + function fn2() { //fn2内部函数被提前声明,就会产生闭包(不用调用内部函数) + console.log(a) + } + + } + + fn1(); + + function fn3() { + var a = 3 + var fun4 = function () { //fun4采用的是“函数表达式”创建的函数,此时内部函数的声明并没有提前 + console.log(a) + } + } + + fn3(); + +``` + ### 闭包代码举例 代码举例: ```js -function fn1() { - let a = 10; - - function fn2() { - console.log(a); - } - fn2(); +function fun1() { + const a = 10; + return function fun2() { + console.log(a); + }; } - -fn1(); +fun1(); +var result = fun1(); +result(); // 10 ``` 打印结果: @@ -64,7 +120,9 @@ fn1(); 10 ``` -上方代码中,函数 fn2 的作用域 访问了 fn1 中的局部变量,那么,此时在 fn1 中就产生了闭包,fn1 称之为闭包函数。 +上方代码中,函数 fun2 的作用域访问了 fun1 中的局部变量,那么,在 fn1 中就产生了闭包。 + +正常情况下作为函数内的局部变量,是无法被外部访问到的。但是通过闭包,我们最后还是可以拿到 a 变量的值。 ### 在 chrome 浏览器控制台中,调试闭包 @@ -74,7 +132,9 @@ fn1(); 上图中, Local 指的是局部作用域,Global 指的是 全局作用域;而 Closure 则是**闭包**,fn1 是一个闭包函数。 -## 闭包的作用:延伸变量的作用范围 + + +### 闭包的作用:延伸变量的作用范围 我们来看看下面这段闭包的代码: @@ -117,11 +177,329 @@ foo(); -## 我的公众号 -想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。 +## 常见的闭包 -扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: +- 1. 将一个函数作为另一个函数的返回值 - +- 2. 将函数作为实参传递给另一个函数调用。 +### 闭包1:将一个函数作为另一个函数的返回值 + +```javascript + function fn1() { + var a = 2 + + function fn2() { + a++ + console.log(a) + } + return fn2 + } + + var f = fn1(); //执行外部函数fn1,返回的是内部函数fn2 + f() // 3 //执行fn2 + f() // 4 //再次执行fn2 + +``` + + +当f()第二次执行的时候,a加1了,也就说明了:闭包里的数据没有消失,而是保存在了内存中。如果没有闭包,代码执行完倒数第三行后,变量a就消失了。 + +上面的代码中,虽然调用了内部函数两次,但是,闭包对象只创建了一个。 + +也就是说,要看闭包对象创建了一个,就看:**外部函数执行了几次**(与内部函数执行几次无关)。 + + +### 闭包2. 将函数作为实参传递给另一个函数调用 + + +```javascript + function showDelay(msg, time) { + setTimeout(function() { //这个function是闭包,因为是嵌套的子函数,而且引用了外部函数的变量msg + alert(msg) + }, time) + } + showDelay('atguigu', 2000) +``` + +上面的代码中,闭包是里面的function,因为它是嵌套的子函数,而且引用了外部函数的变量msg。 + + +## 闭包的作用 + +- 作用1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期) + +- 作用2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数) + +我们让然拿这段代码来分析: + +```javascript + function fn1() { + var a = 2 + + function fn2() { + a++ + console.log(a) + } + return fn2; + } + + var f = fn1(); //执行外部函数fn1,返回的是内部函数fn2 + f() // 3 //执行fn2 + f() // 4 //再次执行fn2 + +``` + +**作用1分析**: + +上方代码中,外部函数fn1执行完毕后,变量a并没有立即消失,而是保存在内存当中。 + + +**作用2分析:** + +函数fn1中的变量a,是在fn1这个函数作用域内,因此外部无法访问。但是通过闭包,外部就可以操作到变量a。 + +达到的效果是:**外界看不到变量a,但可以操作a**。 + +比如上面达到的效果是:我看不到变量a,但是每次执行函数后,让a加1。当然,如果我真想看到a,我可以在fn2中将a返回即可。 + + + +回答几个问题: + +- 问题1. 函数执行完后, 函数内部声明的局部变量是否还存在? + +答案:一般是不存在, 存在于闭包中的变量才可能存在。 + +闭包能够一直存在的根本原因是`f`,因为`f`接收了`fn1()`,这个是闭包,闭包里有a。注意,此时,fn2并不存在了,但是里面的对象(即闭包)依然存在,因为用`f`接收了。 + + +- 问题2. 在函数外部能直接访问函数内部的局部变量吗? + +不能,但我们可以通过闭包让外部操作它。 + + +## 闭包的生命周期 + +1. 产生: 嵌套内部函数fn2被声明时就产生了(不是在调用) + +2. 死亡: 嵌套的内部函数成为垃圾对象时。(比如f = null,就可以让f成为垃圾对象。意思是,此时f不再引用闭包这个对象了) + + + +## 闭包的应用:定义具有特定功能的js模块 + +- 将所有的数据和功能都封装在一个函数内部(私有的),只向外暴露一个包含n个方法的对象或函数。 + +- 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能。 + +### 方式一 + +(1)myModule.js:(定义一个模块,向外暴露多个函数,供外界调用) + +```javascript +function myModule() { + //私有数据 + var msg = 'Smyhvae Haha' + + //操作私有数据的函数 + function doSomething() { + console.log('doSomething() ' + msg.toUpperCase()); //字符串大写 + } + + function doOtherthing() { + console.log('doOtherthing() ' + msg.toLowerCase()) //字符串小写 + } + + //通过【对象字面量】的形式进行包裹,向外暴露多个函数 + return { + doSomething1: doSomething, + doOtherthing2: doOtherthing + } +} +``` + + +上方代码中,外界可以通过doSomething1和doOtherthing2来操作里面的数据,但不让外界看到。 + +(2)index.html: + +```html + + +
+ +