From d444c8e56849b4ffb2eff33c7ec77d3072493205 Mon Sep 17 00:00:00 2001 From: qianguyihao Date: Sun, 31 Jul 2022 21:39:07 +0800 Subject: [PATCH] =?UTF-8?q?update:=20=E9=97=AD=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 04-JavaScript基础/26-闭包.md | 410 ++++++++++++- 07-JavaScript进阶/05-作用域和闭包.md | 833 --------------------------- 07-JavaScript进阶/作用域.md | 427 ++++++++++++++ 3 files changed, 821 insertions(+), 849 deletions(-) delete mode 100644 07-JavaScript进阶/05-作用域和闭包.md create mode 100644 07-JavaScript进阶/作用域.md 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. 将一个函数作为另一个函数的返回值 -![](https://img.smyhvae.com/20200102.png) +- 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 + + + + + 05_闭包的应用_自定义JS模块 + + + + + + + +``` + + +### 方式二 + +同样是实现方式一种的功能,这里我们采取另外一种方式。 + +(1)myModule2.js:(是一个立即执行的匿名函数) + +```javascript +(function () { + //私有数据 + var msg = 'Smyhvae Haha' + + //操作私有数据的函数 + function doSomething() { + console.log('doSomething() ' + msg.toUpperCase()) + } + + function doOtherthing() { + console.log('doOtherthing() ' + msg.toLowerCase()) + } + + //外部函数是即使运行的匿名函数,我们可以把两个方法直接传给window对象 + window.myModule = { + doSomething1: doSomething, + doOtherthing2: doOtherthing + } +})() +``` + + +(2)index.html: + +```html + + + + + 05_闭包的应用_自定义JS模块2 + + + + + + + + + + +``` + +上方两个文件中,我们在`myModule2.js`里直接把两个方法直接传递给window对象了。于是,在index.html中引入这个js文件后,会立即执行里面的匿名函数。在index.html中把myModule直接拿来用即可。 + +**总结:** + +当然,方式一和方式二对比后,我们更建议采用方式二,因为很方便。 + +但无论如何,两种方式都采用了闭包。 + + +## 闭包的缺点及解决 + +缺点:函数执行完后, 函数内的局部变量没有释放,占用内存时间会变长,容易造成内存泄露。 + + +解决:能不用闭包就不用,及时释放。比如: + +```javascript + f = null; // 让内部函数成为垃圾对象 -->回收闭包 +``` + +总而言之,你需要它,就是优点;你不需要它,就成了缺点。 + + +## 内存溢出和内存泄露 + +### 内存溢出 + +**内存溢出**:一种程序运行出现的错误。当程序运行**需要的内存**超过了剩余的内存时, 就出抛出内存溢出的错误。 + +代码举例: + +```javascript + var obj = {}; + for (var i = 0; i < 10000; i++) { + obj[i] = new Array(10000000); //把所有的数组内容都放到obj里保存,导致obj占用了很大的内存空间 + console.log("-----"); + } +``` + +### 内存泄漏 + +**内存泄漏**:**占用的内存**没有及时释放。 + +注意,内存泄露的次数积累多了,就容易导致内存溢出。 + +**常见的内存泄露**: + +- 1.意外的全局变量 + +- 2.没有及时清理的计时器或回调函数 + +- 3.闭包 + + +情况1举例: + +```javascript + // 意外的全局变量 + function fn() { + a = new Array(10000000); + console.log(a); + } + + fn(); +``` + +情况2举例: + +```javascript + // 没有及时清理的计时器或回调函数 + var intervalId = setInterval(function () { //启动循环定时器后不清理 + console.log('----') + }, 1000) + + // clearInterval(intervalId); //清理定时器 +``` + +情况3举例: + +```html + +``` diff --git a/07-JavaScript进阶/05-作用域和闭包.md b/07-JavaScript进阶/05-作用域和闭包.md deleted file mode 100644 index dc260f1..0000000 --- a/07-JavaScript进阶/05-作用域和闭包.md +++ /dev/null @@ -1,833 +0,0 @@ ---- -title: 05-作用域和闭包 -publish: true ---- - - - - - - -## 前言 - -面试问题: - -- 说一下对变量提升的理解 - -- 说明this的几种不同的使用场景 - -- 创建10个``标签,点击的时候弹出来对应的序号 - -- 如何理解作用域 - -- 实际开发中闭包的应用 - -涉及到的知识点: - -- 执行上下文 - -- this - -- 作用域 - -- 作用域链 - -- 闭包 - - -## 执行上下文 - -执行上下文主要有两种情况: - -- 全局代码: 一段` - - - - - -``` - -理解: - -- 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外) - -- 查找变量时就是沿着作用域链来查找的 - -查找一个变量的查找规则: - -```javascript - var a = 1 - - function fn1() { - var b = 2 - - function fn2() { - var c = 3 - console.log(c) - console.log(b) - console.log(a) - console.log(d) - } - fn2() - } - fn1() -``` - - -- 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2 - -- 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3 - -- 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常 - - -## 闭包 - -闭包就是能够读取其他函数内部数据(变量/函数)的函数。 - -只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。 - -上面这两句话,是阮一峰的文章里的,你不一定能理解,来看下面的讲解和举例。 - -### 如何产生闭包 - -**当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量或函数时, 就产生了闭包。** - -### 闭包到底是什么? - - -> 使用chrome调试查看 - - -- 理解一: 闭包是嵌套的内部函数(绝大部分人) - -- 理解二: 包含被引用变量 or 函数的对象(极少数人) - -注意: 闭包存在于嵌套的内部函数中。 - - -### 产生闭包的条件 - -- 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(); - -``` - - - - -## 常见的闭包 - -- 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 - - - - - 05_闭包的应用_自定义JS模块 - - - - - - - -``` - - -### 方式二 - -同样是实现方式一种的功能,这里我们采取另外一种方式。 - -(1)myModule2.js:(是一个立即执行的匿名函数) - -```javascript -(function () { - //私有数据 - var msg = 'Smyhvae Haha' - - //操作私有数据的函数 - function doSomething() { - console.log('doSomething() ' + msg.toUpperCase()) - } - - function doOtherthing() { - console.log('doOtherthing() ' + msg.toLowerCase()) - } - - //外部函数是即使运行的匿名函数,我们可以把两个方法直接传给window对象 - window.myModule = { - doSomething1: doSomething, - doOtherthing2: doOtherthing - } -})() -``` - - -(2)index.html: - -```html - - - - - 05_闭包的应用_自定义JS模块2 - - - - - - - - - - -``` - -上方两个文件中,我们在`myModule2.js`里直接把两个方法直接传递给window对象了。于是,在index.html中引入这个js文件后,会立即执行里面的匿名函数。在index.html中把myModule直接拿来用即可。 - -**总结:** - -当然,方式一和方式二对比后,我们更建议采用方式二,因为很方便。 - -但无论如何,两种方式都采用了闭包。 - - -## 闭包的缺点及解决 - -缺点:函数执行完后, 函数内的局部变量没有释放,占用内存时间会变长,容易造成内存泄露。 - - -解决:能不用闭包就不用,及时释放。比如: - -```javascript - f = null; // 让内部函数成为垃圾对象 -->回收闭包 -``` - -总而言之,你需要它,就是优点;你不需要它,就成了缺点。 - - -## 内存溢出和内存泄露 - -### 内存溢出 - -**内存溢出**:一种程序运行出现的错误。当程序运行**需要的内存**超过了剩余的内存时, 就出抛出内存溢出的错误。 - -代码举例: - -```javascript - var obj = {}; - for (var i = 0; i < 10000; i++) { - obj[i] = new Array(10000000); //把所有的数组内容都放到obj里保存,导致obj占用了很大的内存空间 - console.log("-----"); - } -``` - -### 内存泄漏 - -**内存泄漏**:**占用的内存**没有及时释放。 - -注意,内存泄露的次数积累多了,就容易导致内存溢出。 - -**常见的内存泄露**: - -- 1.意外的全局变量 - -- 2.没有及时清理的计时器或回调函数 - -- 3.闭包 - - -情况1举例: - -```javascript - // 意外的全局变量 - function fn() { - a = new Array(10000000); - console.log(a); - } - - fn(); -``` - -情况2举例: - -```javascript - // 没有及时清理的计时器或回调函数 - var intervalId = setInterval(function () { //启动循环定时器后不清理 - console.log('----') - }, 1000) - - // clearInterval(intervalId); //清理定时器 -``` - -情况3举例: - -```html - -``` - - - - - - - - - - - diff --git a/07-JavaScript进阶/作用域.md b/07-JavaScript进阶/作用域.md new file mode 100644 index 0000000..acabde5 --- /dev/null +++ b/07-JavaScript进阶/作用域.md @@ -0,0 +1,427 @@ +--- +title: 05-作用域和闭包 +publish: true +--- + + + + + + +## 前言 + +面试问题: + +- 说一下对变量提升的理解 + +- 说明this的几种不同的使用场景 + +- 创建10个``标签,点击的时候弹出来对应的序号 + +- 如何理解作用域 + +- 实际开发中闭包的应用 + +涉及到的知识点: + +- 执行上下文 + +- this + +- 作用域 + +- 作用域链 + +- 闭包 + + +## 执行上下文 + +执行上下文主要有两种情况: + +- 全局代码: 一段` + + + + + +``` + +理解: + +- 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外) + +- 查找变量时就是沿着作用域链来查找的 + +查找一个变量的查找规则: + +```javascript + var a = 1 + + function fn1() { + var b = 2 + + function fn2() { + var c = 3 + console.log(c) + console.log(b) + console.log(a) + console.log(d) + } + fn2() + } + fn1() +``` + + +- 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2 + +- 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3 + +- 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常 + + + + + + + + + +