From 5d2d9b98a20fef3bc4272a45f39938da73130837 Mon Sep 17 00:00:00 2001 From: xiejie <745007854@qq.com> Date: Tue, 28 Dec 2021 14:57:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=97=AD=E5=8C=85=E8=AF=BE?= =?UTF-8?q?=E4=BB=B6=EF=BC=8C=E4=BF=AE=E6=94=B9=E6=95=B4=E4=BD=93=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../垃圾回收与内存泄漏.md | 0 10. 闭包/闭包.js | 80 ---- 10. 闭包/闭包.md | 430 ------------------ 11. 闭包/闭包.md | 274 +++++++++++ .../DOM 事件的注册和移除.html | 0 .../DOM 事件的注册和移除.md | 0 .../DOM 事件的传播机制.html | 0 .../DOM 事件的传播机制.md | 0 .../阻止事件默认行为.html | 0 .../阻止事件默认行为.md | 0 {14. 递归 => 15. 递归}/递归.js | 0 {14. 递归 => 15. 递归}/递归.md | 0 .../属性描述符.js | 0 .../属性描述符.md | 0 .../class 和构造函数区别.js | 0 .../class 和构造函数区别.md | 0 .../浮点数精度问题.js | 0 .../浮点数精度问题.md | 0 {18. 严格模式 => 19. 严格模式}/严格模式.js | 0 {18. 严格模式 => 19. 严格模式}/严格模式.md | 0 .../函数防抖和节流.html | 0 .../函数防抖和节流.md | 0 25. eval/eval.js | 40 ++ 26. 尺寸和位置/尺寸和位置.html | 97 ++++ 26. 尺寸和位置/尺寸和位置.md | 10 +- 知识图谱.dio | 102 ++--- 26 files changed, 471 insertions(+), 562 deletions(-) rename {20. 垃圾回收与内存泄漏 => 10. 垃圾回收与内存泄漏}/垃圾回收与内存泄漏.md (100%) delete mode 100644 10. 闭包/闭包.js delete mode 100644 10. 闭包/闭包.md create mode 100644 11. 闭包/闭包.md rename {11. DOM事件的注册和移除 => 12. DOM事件的注册和移除}/DOM 事件的注册和移除.html (100%) rename {11. DOM事件的注册和移除 => 12. DOM事件的注册和移除}/DOM 事件的注册和移除.md (100%) rename {12. DOM事件的传播机制 => 13. DOM事件的传播机制}/DOM 事件的传播机制.html (100%) rename {12. DOM事件的传播机制 => 13. DOM事件的传播机制}/DOM 事件的传播机制.md (100%) rename {13. 阻止事件的默认行为 => 14. 阻止事件的默认行为}/阻止事件默认行为.html (100%) rename {13. 阻止事件的默认行为 => 14. 阻止事件的默认行为}/阻止事件默认行为.md (100%) rename {14. 递归 => 15. 递归}/递归.js (100%) rename {14. 递归 => 15. 递归}/递归.md (100%) rename {15. 属性描述符 => 16. 属性描述符}/属性描述符.js (100%) rename {15. 属性描述符 => 16. 属性描述符}/属性描述符.md (100%) rename {16. Class和普通构造器的区别 => 17. Class和普通构造器的区别}/class 和构造函数区别.js (100%) rename {16. Class和普通构造器的区别 => 17. Class和普通构造器的区别}/class 和构造函数区别.md (100%) rename {17. 浮点数精度问题 => 18. 浮点数精度问题}/浮点数精度问题.js (100%) rename {17. 浮点数精度问题 => 18. 浮点数精度问题}/浮点数精度问题.md (100%) rename {18. 严格模式 => 19. 严格模式}/严格模式.js (100%) rename {18. 严格模式 => 19. 严格模式}/严格模式.md (100%) rename {19. 函数防抖和节流 => 20. 函数防抖和节流}/函数防抖和节流.html (100%) rename {19. 函数防抖和节流 => 20. 函数防抖和节流}/函数防抖和节流.md (100%) create mode 100644 25. eval/eval.js create mode 100644 26. 尺寸和位置/尺寸和位置.html diff --git a/20. 垃圾回收与内存泄漏/垃圾回收与内存泄漏.md b/10. 垃圾回收与内存泄漏/垃圾回收与内存泄漏.md similarity index 100% rename from 20. 垃圾回收与内存泄漏/垃圾回收与内存泄漏.md rename to 10. 垃圾回收与内存泄漏/垃圾回收与内存泄漏.md diff --git a/10. 闭包/闭包.js b/10. 闭包/闭包.js deleted file mode 100644 index bcd367f..0000000 --- a/10. 闭包/闭包.js +++ /dev/null @@ -1,80 +0,0 @@ -// 外部函数 -// function eat(){ -// var food = "鸡翅"; -// return function(){ -// console.log(food); -// } -// } -// // 执行到第 8 行代码时,形成了一个闭包(封闭的空间) -// // 在该闭包中,引用了 food 这个变量 -// // 所以 food 是没有被销毁的 -// var i = eat(); -// i(); - -// 作用域是在函数创建的时候就确定下来了 -// var food = "rice"; -// // function eat(){ -// // console.log(food); -// // } -// (function(){ -// var food = "noodle"; -// function eat(){ -// console.log(food); -// } -// eat(); -// })() - -// 这里就是一个普通的计数器 -// var count = 0; // 全局变量 -// function counter() { -// count++; -// console.log(count); -// } -// for (var i = 0; i < 10; i++) { -// counter(); -// } -// 上面的代码有一个最大的问题,就是全局污染 - -// ... -// var count = 5; -// console.log(count); - -// 接下来我们使用闭包来解决上面的全局污染的问题 -// function counter() { -// var count = 0; // 计数器变量 -// return function(){ -// count++; -// console.log(count); -// } -// } -// var func = counter(); -// for (var i = 0; i < 10; i++) { -// func(); -// } - -// var count = "aaa"; - - -// 下面这段代码也是一个闭包 -// 一个函数中要嵌套一个内部函数,并且内部函数要访问外部函数的变量 -// 内部函数要被外部引用 -// var a = 100; -// setTimeout(function () { -// console.log(++a); -// }, 1000); - -// 有些时候因为闭包所存在的问题 -for (let i = 1; i <= 3; i++) { - setTimeout(function () { - console.log(i); - }, 1000); -} - - -// for (var i = 1; i <= 3; i++) { -// (function (i) { -// setTimeout(function () { -// console.log(i); -// }, 1000); -// })(i); -// } diff --git a/10. 闭包/闭包.md b/10. 闭包/闭包.md deleted file mode 100644 index d908133..0000000 --- a/10. 闭包/闭包.md +++ /dev/null @@ -1,430 +0,0 @@ -# 闭包 - - - -## 经典真题 - - - -- 闭包是什么?闭包的应用场景有哪些?怎么销毁闭包? - - - -## 为什么需要闭包 - - - -首先我们来看一下为什么需要闭包。先看下嘛的例子: - -```js -function eat(){ - var food = "鸡翅"; - console.log(food); -} -eat(); // 鸡翅 -console.log(food); // 报错 -``` - -在上面的例子中,我们声明了一个名为 *eat* 的函数,并对它进行调用。 - -*JavaScript* 引擎会创建一个 *eat* 函数的执行上下文,其中声明 *food* 变量并赋值。 - -当该方法执行完后,上下文被销毁,*food* 变量也会跟着消失。这是因为 *food* 变量属于 *eat* 函数的局部变量,它作用于 *eat* 函数中,会随着 *eat* 的执行上下文创建而创建,销毁而销毁。所以当我们再次打印 *food* 变量时,就会报错,告诉我们该变量不存在。 - - - -那么,如何在函数销毁后也能继续使用变量 *food* 呢? - -这就涉及到了要使用闭包。 - - - -## 什么是闭包 - - - -要解释闭包,可以从**广义**和**狭义**上去理解。 - - - -- 广义上的闭包:所有的函数就是闭包。 -- 狭义上的闭包:需要满足两个条件。 - - 一个函数中要嵌套一个内部函数,并且内部函数要访问外部函数的变量 - - 内部函数要被外部引用 - - - -关于广义上闭包的含义,估计很多人很难理解,我就正常写个函数,怎么这玩意儿就变成闭包了? - -关于这一点,我们稍后再来解释。 - -我们先来看一下狭义上的闭包。 - -```js -function eat(){ - var food = '鸡翅'; - return function(){ - console.log(food); - } -} -var look = eat(); -look(); // 鸡翅 -look(); // 鸡翅 -``` - -在这个例子中,*eat* 函数返回一个函数,并在这个内部函数中访问 *food* 这个局部变量。调用 *eat* 函数并将结果赋给 *look* 变量,这个 *look* 指向了 *eat* 函数中的内部函数,然后调用它,最终输出 *food* 的值。 - -按照之前的说法,这个 *food* 变量应该当 *eat* 函数调用完后就销毁,后续为什么还能通过调用 *look* 方法访问到这个变量呢? - -这就是因为闭包起了作用。返回的内部函数和它外部的变量 *food* 实际上就是一个闭包。 - -闭包的实质,就是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使离开了创造它的环境也不例外。 - - - -这里提到了自由变量,它又是什么呢? - -**自由变量可以理解成跨作用域的变量,比如子作用域访问父作用域的变量。** - -如下代码中,*console.log(a)* 要得到 *a* 变量,但是在当前的作用域中没有定义 *a*(可对比一下 *b*)。当前作用域没有定义的变量,这成为自由变量 。 - -```js -var a = 100 -function fn() { - var b = 200 - console.log(a) // 这里的 a 就是一个自由变量,需要顺着作用域链来查找 a 变量的值 - console.log(b) -} -fn() -``` - - - -## 闭包的原理 - - - -接下来,我们来看一下闭包的原理。 - -要解释闭包的原理,这里需要回答 *2* 个问题。 - -**(1)为什么函数内部可以访问外部函数的变量?** - -原因很简单,当一个函数上下文产生的时候,会确定 *3* 个东西:变量对象、作用域链条以及 *this* 指向。 - -正因为有作用域链的存在,所以能够通过作用域链来访问到外部函数的变量。 - - - -**(2)为什么当外部函数的上下文执行完以后,其中的局部变量还是能通过闭包访问到呢?** - -其实用上一个问题的答案再延伸一下,这个问题的答案就出来了。 - -在介绍作用域的时候,我们有介绍过作用域是在函数创建的时候就确定下来了(参阅《作用域》章节)。 - -所以即使外部函数的上下文结束了,但内部的函数只要不销毁(被外部引用了,就不会销毁),就会一直引用着刚才上下文的作用域链对象,那么包含在作用域链中的变量也就可以一直被访问到。 - - - -综上所述,闭包其实就是利用到了作用域链的知识。 - - - -把这个理解了,闭包的原理也就明白了。 - -那么为什么说每一个函数都是一个闭包呢? - -因为每一个函数都能通过作用域链访问到全局上下文中的变量,例如: - -```js -var stuName = "zhangsan"; -function test(){ - console.log(stuName); -} -test(); -``` - -在上面的代码中,我们在 *test* 函数中访问了自由变量 *stuName*,这个被引用的自由变量将和这个函数一同存在。 - - - -## 闭包的优缺点 - - - -**闭包的优点** - -先来看看闭包的优点,主要有以下 *2* 点: - -- 通过闭包可以让外部环境访问到函数内部的局部变量。 -- 通过闭包可以让局部变量持续保存下来,不随着它的上下文环境一起销毁。 - -看下面这个例子: - -```javascript -var count = 0; // 全局变量 -function compute() { // 将计数器加 1 - count++; - console.log(count); -} -for (var i = 0; i < 100; i++) { - compute(); // 循环 100 次 -} -``` - -这个例子是对一个全局变量进行加 *1* 的操作,一共加 *100* 次,得到值为 *100* 的结果。 - -但是因为使用了全局变量,所以存在全局变量污染的问题。 - -下面用闭包的方式重构它: - -```javascript -function compute() { - var count = 0; // 局部变量 - return function () { - count++; // 内部函数访问外部变量 - console.log(count); - } -} -var func = compute(); // 引用了内部函数,形成闭包 -for (var i = 0; i < 100; i++) { - func(); -} -// 在外面新创建一个 count 的变量,完全不冲突 -var count = "Hello"; -console.log(count); -for (var i = 0; i < 100; i++) { - func(); -} -``` - -这个例子就不再使用全局变量,其中 *count* 这个局部变量依然可以被保存下来。我们甚至可以在外面新创建一个 *count* 变量,完全不会和内部的 *count* 变量产生冲突。 - - - -**闭包的缺点** - -说完闭包的优点,接下来来看一下闭包的缺点。 - -局部变量本来应该在函数退出时被解除引用,但如果局部变量被封闭在闭包形成的环境中,那么这个局部变量就能一直生存下去。也就是说,闭包会将局部变量保存下来。如果大量使用闭包,而其中的变量又未得到清理,闭包的确会使一些数据无法被及时销毁,从而造成内存泄漏。 - -但是使用闭包的一部分原因,是我们选择主动把一些变量封闭在闭包中,因为可能在以后还需要使用这些变量。 - -把这些变量放在闭包中和放在全局作用域中,对内存方面的影响是一样的,所以这里并不能说成是内存泄漏。如果在将来需要回收这些变量,我们可以手动把这些变量设置为 *null*。 - - - -如果要说闭包和内存泄漏有关系的地方,那就是使用闭包的同时比较容易形成循环引用,如果闭包的作用域中保存着一些 *DOM* 节点,这个时候就有可能造成内存泄漏。 - -但这本身并非闭包的问题,也并非 *JavaScript* 的问题。在 *IE* 浏览器中,由于 *BOM* 和 *DOM* 中的对象是使用 *C++* 以 *COM* 对象的方式实现的,而 *COM* 对象的垃圾收集机制采用的是引用计数策略。在基于引用计数策略的垃圾回收机制中,如果两个对象之间形成了循环引用,那么这两个对象都无法被回收,但循环引用造成的内存泄漏在本质上也不是闭包造成的。 - -同样,如果要解决循环引用带来的内存泄漏问题,我们只需要把循环引用中的变量设为 *null* 即可。将变量设置为 *null* 意味着切断变量与它此前引用的值之间的连接。当垃圾收集器下次运行时,就会删除这些值并回收它们占用的内存。 - - - -## 闭包的应用场景 - - - -最后,我们来看一下闭包的一些实际的应用场景。 - - - -```js -var a = 100; -setTimeout(function () { - console.log(++a); -}, 1000); -``` - -上面是一段很简单的代码,但是这实际上就在你毫无察觉的情况下使用用到了闭包。 - -在这个例子中,用到了时间函数 *setTimeout*,并在等待 *1* 秒钟后对变量 *a* 进行加 *1* 的操作。 - -之所以说这是闭包,是因为 *setTimeout* 中的匿名函数对外部变量(自由变量)进行访问,然后该函数又被 *setTimeout* 方法引用。满足了形成闭包的两个条件。所以你看,即使外部上下文结束了,*1* 秒后仍然能对变量 *a* 进行加 *1* 操作。 - - - -在 *DOM* 的事件操作中,也经常用到闭包,比如下面这个例子: - -```html - -``` - -```js -(function(){ - var cnt = 0; // 计数器 - var count = document.getElementById("count"); - count.onclick = function(){ - console.log(++cnt); - } -})() -``` - -*onclick* 指向的函数中访问了外部变量 *cnt*,同时该函数又被 *onclick* 事件引用了,满足 *2* 个条件,是闭包。 - -所以当外部上下文结束后,你继续点击按钮,在触发的事件处理方法中仍然能访问到变量 cnt。 - -再比如,*img* 对象经常用于进行数据上报,如下所示: - -```js -const report = function (src) { - var img = new Image(); - img.src = src; -} -report('http://xxx.com/getUserInfo'); -``` - -但是通过查询后台的记录我们得知,因为一些低版本的浏览器的实现存在 *bug*,在这些浏览器下使用 *report* 函数进行数据上报时会丢失 *30%* 左右的数据,也就是说,*report* 函数并不是每一次都成功发起了 *HTTP* 请求。 - -丢失数据的原因是 *img* 是 *report* 函数中的局部变量,当 *report* 函数在调用结束后,*img* 局部变量随即被销毁,而此时或许还没来得及发出 *HTTP* 请求,所以此次请求就会丢失掉。 - -现在我们把 *img* 变量用闭包封闭起来,便能解决请求丢失的问题,如下: - -```js -const report = (function () { - var imgs = []; - return function (src) { - var img = new Image(); - imgs.push(img); - img.src = src; - } -})(); -``` - - - -在有些时候,闭包还会引起一些奇怪的问题,比如下面这个例子: - -```js -for (var i = 1; i <= 3; i++) { - setTimeout(function () { - console.log(i); - }, 1000); -} -``` - -我们预期的结果是过 *1* 秒后分别输出 *i* 变量的值为 *1,2,3*。但是,执行的结果是:*4,4,4*。 - -实际上,问题就出在闭包身上。你看,循环中的 *setTimeout* 访问了它的外部变量 *i*,形成闭包。 - -而 *i* 变量只有 *1* 个,所以循环 *3* 次的 *setTimeout* 中都访问的是同一个变量。循环到第 *4* 次,*i* 变量增加到 *4*,不满足循环条件,循环结束,代码执行完后上下文结束。但是,那 *3* 个 *setTimeout* 等 *1* 秒钟后才执行,由于闭包的原因,所以它们仍然能访问到变量 *i*,不过此时 *i* 变量值已经是 *4* 了。 - -既然是闭包引起的问题,那么解决的方法就是去掉闭包。 - -我们知道形成闭包有两个条件,只要不满足其一,那就不再是闭包。 - -条件之一,内部函数被外部引用,这个我们没办法去掉。条件二,内部函数访问外部变量。这个条件我们有办法去掉,比如: - -```js -for (var i = 1; i <= 3; i++) { - (function (index) { - setTimeout(function () { - console.log(index); - }, 1000); - })(i) -} -``` - -这样 *setTimeout* 中就可以不用访问 *for* 循环声明的变量 *i* 了。而是采用调用函数传参的方式把变量 *i* 的值传给了 *setTimeout*,这样它们就不再形成闭包。也就是说 *setTimeout* 中访问的已经不是外部的变量 *i*,所以即使 *i* 的值增长到 *4*,跟它内部也没关系,最后达到了我们想要的效果。 - -当然,解决这个问题还有个更简单的方法,就是使用 *ES6* 中的 *let* 关键字。 - -它声明的变量有块作用域,如果将它放在循环中,那么每次循环都会有一个新的变量 *i*,这样即使有闭包也没问题,因为每个闭包保存的都是不同的 *i* 变量,那么刚才的问题也就迎刃而解。 - -```js -for (let i = 1; i <= 3; i++) { - setTimeout(function () { - console.log(i); - }, 1000); -} -``` - - - -另外,使用闭包还可以模拟出面向对象中的私有方法。 - -过程与数据的结合是形容面向对象中的“对象”时经常使用的表达。 - -对象以属性的形式包含了数据,以方法的形式包含了过程。 - -而闭包则是在过程中以环境的形式包含了数据。通常用面向对象思想能实现的功能,用闭包也能够实现,反之亦然。 - -在 *JavaScript* 语言的祖先 *Scheme* 语言中,甚至都没有提供面向对象的原生设计,但却可以使用闭包来实现一个完整的面向对象的系统。 - -下面我们来看看这段跟闭包相关的代码: - -```js -function Test(){ - var value = 0; // 相当于是对象的属性 - return { - call : function(){ - value++; - console.log(value); - } - } -} -const test = new Test(); -test.call(); // 1 -test.call(); // 2 -test.call(); // 3 -``` - -如果换成面向对象的写法,那就是如下: - -```js -const test = { - value: 0, - call: function () { - this.value++; - console.log(this.value); - } -} -test.call(); // 1 -test.call(); // 2 -test.call(); // 3 -``` - -或者 - -```js -function Test() { - this.value = 0; -} -Test.prototype.call = function () { - this.value++; - console.log(this.value); -} -const test = new Test(); -test.call(); // 1 -test.call(); // 2 -test.call(); // 3 -``` - - - -## 真题解答 - - - -- 闭包是什么?闭包的应用场景有哪些?怎么销毁闭包? - -> 参考答案: -> -> 闭包是指有权访问另外一个函数作用域中的变量的函数。 -> -> 因为闭包引用着另一个函数的变量,导致另一个函数已经不使用了也无法销毁,所以**闭包使用过多,会占用较多的内存,这也是一个副作用,内存泄漏。** -> -> 如果要销毁一个闭包,可以 把被引用的变量设置为 *null*,即手动清除变量,这样下次 *JS* 垃圾回收机制回收时,就会把设为 *null* 的量给回收了。 -> -> 闭包的应用场景: -> -> 1. 匿名自执行函数 -> 2. 结果缓存 -> 3. 封装 -> 4. 实现类和继承 -> 5. 解决全局污染 - - - --*EOF*- \ No newline at end of file diff --git a/11. 闭包/闭包.md b/11. 闭包/闭包.md new file mode 100644 index 0000000..16c1a6a --- /dev/null +++ b/11. 闭包/闭包.md @@ -0,0 +1,274 @@ +# 闭包 + + + +## 经典真题 + + + +- 闭包是什么?闭包的应用场景有哪些?怎么销毁闭包? + + + +## 什么是闭包 + +闭包,是 *JavaScript* 中一个非常重要的知识点,也是我们前端面试中较高几率被问到的知识点之一。 + +打开《*JavaScript* 高级程序设计》和《 *JavaScript* 权威指南》,会发现里面针对闭包的解释各执一词,在网络上搜索关于闭包的内容,也发现众说纷纭,这就导致了这个知识点本身显得有点神秘,甚至还有一点玄幻。 + + + +那么这个知识点真的有那么深奥么? + +非也!其实要理解 *JavaScript* 中的闭包,非常容易,但是在此之前你需要先知道以下两个知识点: + +- *JavaScript* 中的作用域和作用域链 +- *JavaScript* 中的垃圾回收 + + + +这里我们来简单回顾一下这两个知识点: + +**1. *JavaScript* 中的作用域和作用域链** + +- 作用域就是一个独立的地盘,让变量不会外泄、暴露出去,不同作用域下同名变量不会有冲突。 +- 作用域在定义时就确定,并且不会改变。 +- 如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。 + + + +**2. *JavaScript* 中的垃圾回收** + +- *Javascript* 执行环境会负责管理代码执行过程中使用的内存,其中就涉及到一个垃圾回收机制 +- 垃圾收集器会定期(周期性)找出那些不再继续使用的变量,只要该变量不再使用了,就会被垃圾收集器回收,然后释放其内存。如果该变量还在使用,那么就不会被回收。 + + + +*OK*,有了这 *2* 个知识点的铺垫后,接下来我们再来看什么是闭包。 + +> 闭包不是一个具体的技术,而是一种现象,是指在定义函数时,周围环境中的信息可以在函数中使用。换句话说,执行函数时,只要在函数中使用了外部的数据,就创建了闭包。 +> +> 而作用域链,正是实现闭包的手段。 + + + +什么?只要在函数中使用了外部的数据,就创建了闭包? + +真的是这样么?下面我们可以证明一下: + +image-20211227145016552 + +在上面的代码中,我们在函数 *a* 中定义了一个变量 *i*,然后打印这个 *i* 变量。对于 *a* 这个函数来讲,自己的函数作用域中存在 *i* 这个变量,所以我们在调试时可以看到 *Local* 中存在变量 *i*。 + + + +下面我们将上面的代码稍作修改,如下图: + +image-20211227145521272 + +在上面的代码中,我们将声明 *i* 这个变量的动作放到了 *a* 函数外面,也就是说 *a* 函数在自己的作用域已经找不到这个 *i* 变量了,它会怎么办? + +学习了作用域链的你肯定知道,它会顺着作用域链一层一层往外找。然而上面在介绍闭包时说过,如果出现了这种情况,也就是函数使用了外部的数据的情况,就会创建闭包。 + +仔细观察调试区域,我们会发现此时的 *i* 就放在 *Closure* 里面的,从而证实了我们前面的说法。 + + + +所以你看,闭包其实也没有那么难理解,当你觉得一个词对你来说特别难的时候,你还可以使用拆词法,这也是我比较推荐的屡试不爽的一种方法。 + + + +“闭”可以理解为“封闭,闭环”,“包”可以理解为“一个类似于包裹的空间”,因此闭包实际上可以看作是一个封闭的空间,那么这个空间用来干啥呢?实际上就是用来存储变量的。 + +image-20211227163947135 + +那么是一个函数下所有的变量声明都会被放入到闭包这个封闭的空间里面么? + +倒也不是,放不放入到闭包中,要看其他地方有没有对这个变量进行引用,例如: + +![image-20211227164333723](https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2021-12-27-084334.png) + +在上面的代码中,函数 c 中一个变量都没有创建,却要打印 *i、j、k* 和 *x*,这些变量分别存在于 *a、b* 函数以及全局作用域中,因此创建了 *3* 个闭包,全局闭包里面存储了 *i* 的值,闭包 *a* 中存储了变量 *j* 和 *k* 的值,闭包 *b* 中存储了变量 *x* 的值。 + +但是你仔细观察,你就会发现函数 *b* 中的 *y* 变量并没有被放在闭包中,所以要不要放入闭包取决于该变量有没有被引用。 + + + +当然,此时的你可能会有这样的一个新问题,那么多闭包,那岂不是占用内存空间么? + +实际上,如果是自动形成的闭包,是会被销毁掉的。例如: + +![image-20211227174043786](https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2021-12-27-094043.png) + +在上面的代码中,我们在第 *16* 行尝试打印输出变量 *k*,显然这个时候是会报错的,在第 *16* 行打一个断点调试就可以清楚的看到,此时已经没有任何闭包存在,垃圾回收器会自动回收没有引用的变量,不会有任何内存占用的情况。 + + + +当然,这里我指的是自动产生闭包的情况,关于闭包,有时我们需要根据需求手动的来制造一个闭包。 + +来看下面的例子: + +```js +function eat(){ + var food = "鸡翅"; + console.log(food); +} +eat(); // 鸡翅 +console.log(food); // 报错 +``` + +在上面的例子中,我们声明了一个名为 *eat* 的函数,并对它进行调用。 + +*JavaScript* 引擎会创建一个 *eat* 函数的执行上下文,其中声明 *food* 变量并赋值。 + +当该方法执行完后,上下文被销毁,*food* 变量也会跟着消失。这是因为 *food* 变量属于 *eat* 函数的局部变量,它作用于 *eat* 函数中,会随着 *eat* 的执行上下文创建而创建,销毁而销毁。所以当我们再次打印 *food* 变量时,就会报错,告诉我们该变量不存在。 + + + +但是我们将此代码稍作修改: + +```js +function eat(){ + var food = '鸡翅'; + return function(){ + console.log(food); + } +} +var look = eat(); +look(); // 鸡翅 +look(); // 鸡翅 +``` + +在这个例子中,*eat* 函数返回一个函数,并在这个内部函数中访问 *food* 这个局部变量。调用 *eat* 函数并将结果赋给 *look* 变量,这个 *look* 指向了 *eat* 函数中的内部函数,然后调用它,最终输出 *food* 的值。 + +为什么能访问到 *food*,原因很简单,上面我们说过,垃圾回收器只会回收没有被引用到的变量,但是一旦一个变量还被引用着的,垃圾回收器就不会回收此变量。在上面的示例中,照理说 *eat* 调用完毕 *food* 就应该被销毁掉,但是我们向外部返回了 *eat* 内部的匿名函数,而这个匿名函数有引用了 *food*,所以垃圾回收器是不会对其进行回收的,这也是为什么在外面调用这个匿名函数时,仍然能够打印出 *food* 变量的值。 + + + +至此,闭包的一个优点或者特点也就体现出来了,那就是: + +- 通过闭包可以让外部环境访问到函数内部的局部变量。 +- 通过闭包可以让局部变量持续保存下来,不随着它的上下文环境一起销毁。 + + + +通过此特性,我们可以解决一个全局变量污染的问题。早期在 *JavaScript* 还无法进行模块化的时候,在多人协作时,如果定义过多的全局变量 有可能造成全局变量命名冲突,使用闭包来解决功能对变量的调用将变量写到一个独立的空间里面,从而能够一定程度上解决全局变量污染的问题。 + +例如: + +```js +var name = "GlobalName"; +// 全局变量 +var init = (function () { + var name = "initName"; + function callName() { + console.log(name); + // 打印 name + } + return function () { + callName(); + // 形成接口 + } +}()); +init(); // initName +var initSuper = (function () { + var name = "initSuperName"; + function callName() { + console.log(name); + // 打印 name + } + return function () { + callName(); + // 形成接口 + } +}()); +initSuper(); // initSuperName +``` + + + +好了,在此小节的最后,我们来对闭包做一个小小的总结: + + + +- 闭包是一个封闭的空间,里面存储了在其他地方会引用到的该作用域的值,在 *JavaScript* 中是通过作用域链来实现的闭包。 + + + +- 只要在函数中使用了外部的数据,就创建了闭包,这种情况下所创建的闭包,我们在编码时是不需要去关心的。 + + + +- 我们还可以通过一些手段手动创建闭包,从而让外部环境访问到函数内部的局部变量,让局部变量持续保存下来,不随着它的上下文环境一起销毁。 + + + +## 闭包经典问题 + +聊完了闭包,接下来我们来看一个闭包的经典问题。 + +```js +for (var i = 1; i <= 3; i++) { + setTimeout(function () { + console.log(i); + }, 1000); +} +``` + +在上面的代码中,我们预期的结果是过 *1* 秒后分别输出 *i* 变量的值为 *1,2,3*。但是,执行的结果是:*4,4,4*。 + +实际上,问题就出在闭包身上。你看,循环中的 *setTimeout* 访问了它的外部变量 *i*,形成闭包。 + +而 *i* 变量只有 *1* 个,所以循环 *3* 次的 *setTimeout* 中都访问的是同一个变量。循环到第 *4* 次,*i* 变量增加到 *4*,不满足循环条件,循环结束,代码执行完后上下文结束。但是,那 *3* 个 *setTimeout* 等 *1* 秒钟后才执行,由于闭包的原因,所以它们仍然能访问到变量 *i*,不过此时 *i* 变量值已经是 *4* 了。 + +要解决这个问题,我们可以让 *setTimeout* 中的匿名函数不再访问外部变量,而是访问自己内部的变量,如下: + +```js +for (var i = 1; i <= 3; i++) { + (function (index) { + setTimeout(function () { + console.log(index); + }, 1000); + })(i) +} +``` + +这样 *setTimeout* 中就可以不用访问 *for* 循环声明的变量 *i* 了。而是采用调用函数传参的方式把变量 *i* 的值传给了 *setTimeout*,这样它们就不再创建闭包,因为在我自己的作用域里面能够找到 *i* 这个变量。 + +当然,解决这个问题还有个更简单的方法,就是使用 *ES6* 中的 *let* 关键字。 + +它声明的变量有块作用域,如果将它放在循环中,那么每次循环都会有一个新的变量 *i*,这样即使有闭包也没问题,因为每个闭包保存的都是不同的 *i* 变量,那么刚才的问题也就迎刃而解。 + +```js +for (let i = 1; i <= 3; i++) { + setTimeout(function () { + console.log(i); + }, 1000); +} +``` + + + +## 真题解答 + + + +- 闭包是什么?闭包的应用场景有哪些?怎么销毁闭包? + +>闭包是一个封闭的空间,里面存储了在其他地方会引用到的该作用域的值,在 *JavaScript* 中是通过作用域链来实现的闭包。 +> +>只要在函数中使用了外部的数据,就创建了闭包,这种情况下所创建的闭包,我们在编码时是不需要去关心的。 +> +>我们还可以通过一些手段手动创建闭包,从而让外部环境访问到函数内部的局部变量,让局部变量持续保存下来,不随着它的上下文环境一起销毁。 +> +>使用闭包可以解决一个全局变量污染的问题。 +> +>如果是自动产生的闭包,我们无需操心闭包的销毁,而如果是手动创建的闭包,可以把被引用的变量设置为 *null*,即手动清除变量,这样下次 *JavaScript* 垃圾回收器在进行垃圾回收时,发现此变量已经没有任何引用了,就会把设为 *null* 的量给回收了。 + + + +------ + + + +-*EOF*- \ No newline at end of file diff --git a/11. DOM事件的注册和移除/DOM 事件的注册和移除.html b/12. DOM事件的注册和移除/DOM 事件的注册和移除.html similarity index 100% rename from 11. DOM事件的注册和移除/DOM 事件的注册和移除.html rename to 12. DOM事件的注册和移除/DOM 事件的注册和移除.html diff --git a/11. DOM事件的注册和移除/DOM 事件的注册和移除.md b/12. DOM事件的注册和移除/DOM 事件的注册和移除.md similarity index 100% rename from 11. DOM事件的注册和移除/DOM 事件的注册和移除.md rename to 12. DOM事件的注册和移除/DOM 事件的注册和移除.md diff --git a/12. DOM事件的传播机制/DOM 事件的传播机制.html b/13. DOM事件的传播机制/DOM 事件的传播机制.html similarity index 100% rename from 12. DOM事件的传播机制/DOM 事件的传播机制.html rename to 13. DOM事件的传播机制/DOM 事件的传播机制.html diff --git a/12. DOM事件的传播机制/DOM 事件的传播机制.md b/13. DOM事件的传播机制/DOM 事件的传播机制.md similarity index 100% rename from 12. DOM事件的传播机制/DOM 事件的传播机制.md rename to 13. DOM事件的传播机制/DOM 事件的传播机制.md diff --git a/13. 阻止事件的默认行为/阻止事件默认行为.html b/14. 阻止事件的默认行为/阻止事件默认行为.html similarity index 100% rename from 13. 阻止事件的默认行为/阻止事件默认行为.html rename to 14. 阻止事件的默认行为/阻止事件默认行为.html diff --git a/13. 阻止事件的默认行为/阻止事件默认行为.md b/14. 阻止事件的默认行为/阻止事件默认行为.md similarity index 100% rename from 13. 阻止事件的默认行为/阻止事件默认行为.md rename to 14. 阻止事件的默认行为/阻止事件默认行为.md diff --git a/14. 递归/递归.js b/15. 递归/递归.js similarity index 100% rename from 14. 递归/递归.js rename to 15. 递归/递归.js diff --git a/14. 递归/递归.md b/15. 递归/递归.md similarity index 100% rename from 14. 递归/递归.md rename to 15. 递归/递归.md diff --git a/15. 属性描述符/属性描述符.js b/16. 属性描述符/属性描述符.js similarity index 100% rename from 15. 属性描述符/属性描述符.js rename to 16. 属性描述符/属性描述符.js diff --git a/15. 属性描述符/属性描述符.md b/16. 属性描述符/属性描述符.md similarity index 100% rename from 15. 属性描述符/属性描述符.md rename to 16. 属性描述符/属性描述符.md diff --git a/16. Class和普通构造器的区别/class 和构造函数区别.js b/17. Class和普通构造器的区别/class 和构造函数区别.js similarity index 100% rename from 16. Class和普通构造器的区别/class 和构造函数区别.js rename to 17. Class和普通构造器的区别/class 和构造函数区别.js diff --git a/16. Class和普通构造器的区别/class 和构造函数区别.md b/17. Class和普通构造器的区别/class 和构造函数区别.md similarity index 100% rename from 16. Class和普通构造器的区别/class 和构造函数区别.md rename to 17. Class和普通构造器的区别/class 和构造函数区别.md diff --git a/17. 浮点数精度问题/浮点数精度问题.js b/18. 浮点数精度问题/浮点数精度问题.js similarity index 100% rename from 17. 浮点数精度问题/浮点数精度问题.js rename to 18. 浮点数精度问题/浮点数精度问题.js diff --git a/17. 浮点数精度问题/浮点数精度问题.md b/18. 浮点数精度问题/浮点数精度问题.md similarity index 100% rename from 17. 浮点数精度问题/浮点数精度问题.md rename to 18. 浮点数精度问题/浮点数精度问题.md diff --git a/18. 严格模式/严格模式.js b/19. 严格模式/严格模式.js similarity index 100% rename from 18. 严格模式/严格模式.js rename to 19. 严格模式/严格模式.js diff --git a/18. 严格模式/严格模式.md b/19. 严格模式/严格模式.md similarity index 100% rename from 18. 严格模式/严格模式.md rename to 19. 严格模式/严格模式.md diff --git a/19. 函数防抖和节流/函数防抖和节流.html b/20. 函数防抖和节流/函数防抖和节流.html similarity index 100% rename from 19. 函数防抖和节流/函数防抖和节流.html rename to 20. 函数防抖和节流/函数防抖和节流.html diff --git a/19. 函数防抖和节流/函数防抖和节流.md b/20. 函数防抖和节流/函数防抖和节流.md similarity index 100% rename from 19. 函数防抖和节流/函数防抖和节流.md rename to 20. 函数防抖和节流/函数防抖和节流.md diff --git a/25. eval/eval.js b/25. eval/eval.js new file mode 100644 index 0000000..e76e3e6 --- /dev/null +++ b/25. eval/eval.js @@ -0,0 +1,40 @@ +"use strict" +// eval('console.log("Hello World")') + +var str = ` + var a = 1; + var b = 2; + if(a > b) { + console.log('a > b'); + } else { + console.log('a + + + + + + + Document + + + + +
Lorem ipsum dolor sit amet consectetur adipisicing elit. + Dolores provident + mollitia distinctio cupiditate earum, odio delectus quis sapiente! Veritatis, minus, praesentium suscipit + adipisci impedit ipsa eos dolor amet possimus illo eum aperiam! Atque at eveniet corrupti quas non voluptate? + Neque rerum alias numquam totam mollitia nemo nihil asperiores animi pariatur dolorum cum magni nesciunt, quasi + eius obcaecati fugiat, officia maxime ullam ducimus quia! Perferendis unde rerum, a consequatur incidunt + accusantium recusandae dolorum aspernatur. Suscipit ipsam inventore id. Repellendus ad vitae dolores ipsam + quibusdam architecto ex magni illum quisquam? Sunt ullam error eum a magni architecto consequatur sed nobis + obcaecati quo.
+ + + + + \ No newline at end of file diff --git a/26. 尺寸和位置/尺寸和位置.md b/26. 尺寸和位置/尺寸和位置.md index 4d7ca4a..d3c227d 100644 --- a/26. 尺寸和位置/尺寸和位置.md +++ b/26. 尺寸和位置/尺寸和位置.md @@ -34,7 +34,7 @@ -在 *DOM* 对象所提供的尺寸和位置相关属性中,可以分为只读属性和可读可写属性。 +在 *DOM* 对象所提供的尺寸和位置相关属性中,可以分为**只读属性**和**可读可写属性**。 @@ -417,3 +417,11 @@ body{ 2021-11-05 16.06.06 + + +------ + + + +-*EOF*- + diff --git a/知识图谱.dio b/知识图谱.dio index 063852c..49c6607 100644 --- a/知识图谱.dio +++ b/知识图谱.dio @@ -1,125 +1,125 @@ - + - + - - + + - - + + - + - + - - + + - + - + - + - + - + - + - - + + - - + + - - + + - - + + - - + + - + - + - - + + - - + + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - +