广东中山建设信息网站,成都旅游必去推荐,精准客源,常德本地网络论坛文章目录 一、闭包是什么#xff1f; #x1f926;♂️二、闭包 #x1f60e;三、使用场景 #x1f601;四、使用场景#xff08;2#xff09; #x1f601;五、闭包的原理六、思考总结一、 更深层次了解闭包#xff0c;分析以下代码执行过程二、闭包三、闭包定义四、… 文章目录 一、闭包是什么 ♂️二、闭包 三、使用场景 四、使用场景2 五、闭包的原理六、思考总结一、 更深层次了解闭包分析以下代码执行过程二、闭包三、闭包定义四、闭包导致的内存泄漏面试题 一、闭包是什么 ♂️ 当一个函数的返回值又是一个函数作为返回值的那个函数叫做闭包 它利用了 作用域的嵌套 将原本的局部变量进化成私有变量的环境叫闭包 闭包由两部分组成 函数可以访问的自由变量一个普通的函数function如果它可以访问外层作用于的自由变量那么这个函数就是一个闭包 二、闭包 const f (function() {let a 0;return function() {a;console.log(a);};})();f();f();f(); 好兄弟看这是一个闭包函数我们先来分析下这个函数 ()() 他叫做立即运行的匿名函数当一个匿名函数被括起来然后再在后面加一个括号这个匿名函数就能立即运行起来假设我们删去 这个匿名函数f 是这个最外层的Function,当f()执行时返回里层的function,f()()才能调用里层的function假设去掉咱们接下往下走f 其实就是这个函数的返回值 return 出来的函数 第一段代码执行 f 之后发现a了因为里层的函数执行了那为什么我再最下面console.log(a) 报错呢 ......console.log(a);我们都知道let 声明的变量有作用域不会变量提升而对于内部的function来说他是可以拿到a的因为他们同一作用域外层a是个局部变量所以在外层拿不到 这也是闭包的 特点 将原本局部作用域中的局部变量临时保存起来不受外部的作用域的影响可以被外部反复使用刚好说明了我们第二条定义利用了 “ 作用域的嵌套 ” 将原本的局部变量进化成私有变量的环境叫闭包 三、使用场景 举两个小例子让我们看看闭包的使用场景看完以下两个例子好兄弟你能不能想起来对闭包的定义以及对闭包特点作用 var allLi document.querySelectorAll(li);for (var i 0; i allLi.length; i) {console.log(i); //这里我每次都能拿到iallLi[i].onclick (function(i) {return function() {console.log(i);};})(i);}// 当我点击的时候真正触发的时retrun出来的function// 借助匿名函数帮助我们创建了一个作用域// 不管里层函数什么时候执行都能拿到匿名函数里的变量for (let i 0; i allLi.length; i) {console.log(i); //这里我每次都能拿到iallLi[i].onclick function(i) {console.log(i)}}// 这个其实也是一个闭包因为let 有作用域 函数的返回值又是一个函数 利用 作用域的嵌套 将 原本的局部变量 进化成 私有变量的环境 叫闭包 四、使用场景2 给不能传参的 内置函数的回调参数 传参 let a hello;setTimeout(function(a) {console.log(a);}, 1000); 三个问题
setTimeout 是什么类型函数内置函数可以传参吗如果可以怎么传就像是上面那个案例吗
直接说明上个案例是错误写法首先setTimeout是window内置的定时器函数其次他不能传参因为是内置那有没有解决方案呢yesyesyes! ✌
看答案之前先思考两个问题
setTimeout 接收两个参数第一参数是什么为什么上个案例返回undifine
setTimeout(fn(hello), 5000);function fn(a) {return function() {console.log(a);};}
说明setTimeout是第一个参数是一个函数因为是内置函数没有接收值所以不能传参。之所以上个案例打印undifined是因为上一个函数没有返回值返回值的标志就是retrun。 既然这样那我们把这个函数拿出来而且retrun出去哎嘿巧了不是意外写了个闭包呐呐这就是闭包使用场景二给不能传参的 内置函数的回调参数 传参 ✌ 这一刻你有没有爱上闭包
五、闭包的原理 大篇说了很多闭包我想好兄弟应该对闭包有很深的印象了吧如果 没有, 那好的是你出去还是我出去开个玩笑啦让好兄弟没能理解是我的 “锅” 烦请好兄弟评论留言小妹我加以改正一起进步好兄弟如果有收获还请多多点赞关注你的大拇指就是小妹更新的动力爱你 biu biu biu ~ 言归正传我们来看看闭包的原理
从计算机的角度分析利用了计算机的垃圾回收机制。比如我们电脑桌上的回收站他是先保存到一个临时空间如果再使用就可以重复使用确定不用最终删除js特性分析利用了函数的定义作用域和函数的执行上下文 (作用域链)不管函数在哪里执行函数都可以操作自身定义域中存在的变量 六、思考 我们在使用闭包时可以在作用外部操作作用域内部的数据有些我们也不知道什么时候调用了影响了结果使用闭包可以不用定义很多全局变量但是根据计算机的垃圾回收机制有些不确定删除的变量会占用内存所以也比较消耗内存对于低版本的ie浏览器ie8可能会造成内存泄漏就会产生蓝屏卡死等啥的好在现在浏览器会有内存保护机制如果满了就会报错我们修改后刷新下即可 总结 通篇说了很多次闭包的概念这里我还是想再强调一下闭包不是一种固定的写法它是一种环境利用作用域的嵌套, 得到一种效果原本的局部变量进化成私有变量重复使用, 闭包能少用就少用像递归能不用就不用 你学废了吗 谢谢阅读谢谢点赞并关注没错我就要道德绑架你 一、 更深层次了解闭包分析以下代码执行过程 1. function foo(){
2. function bar(){
3. console.log(吃饭饭)
4. }
5. return bar
6. }
7. var fnfoo()
8. fn() 原理
调用栈是代码运行环境在编译前会有个GO对象存放当前环境内置方法类等在编译时会生成一个全局执行上下文 GEC会被关联到一个变量环境VO全局执行上下文的VO 对应的是全局变量GO在编译时代码中的变量和函数都会被作为属性添加到VO中不同的是编译时变量为undi 函数对象对应一个地址同时也会为函数对象生成一个内存空间当代码运行时再执行函数对象时会创建对应的函数执行上下文 FEC同样也会被关联到一个变量环境VO函数执行上下文的VO对应的是AO当函数执行完毕这个函数执行上下文也会弹出调用栈
代码
开始分析执行过程代码至上而下执行先编译后执行第一行 发现是个函数GO 存放一个 foo:地址 第七行发现是个变量 GO 存放一个 fn:undi编译完成开始执行代码代码应该是从第7行开始执行的。 foo() 发现foo对应的是个内存地址找到内存地址并创建一个函数执行上下文因为foo里面包括了定义了一个函数bar这是这个AO会定义一个bar:内存地址且开辟一个bar的内存空间。并把函数执行上下文放到调用栈将bar的内存地址返回给fn这是fn 赋值为一个函数当fn执行时同样的道理找到对应bar 的内存地址并创建 bar 这个函数的函数执行上下文因为函数里只有console操作所以对应的AO 为空当函数执行完毕对应的函数执行上下文弹出调用框
二、闭包 1. function foo(){
2. var namehaha
3 function bar(){
4 console.log(吃饭饭 , name)
5 }
6 return bar
7 }
8 var fnfoo()
9 fn()
依旧开始分析执行过程代码至上而下执行第一行发现是个函数好的GO添加一个属性 foo:内存地址1-7 函数体就会跳过第八行发现声明了一个fn ,好的GO添加一个属性 fn:unde编译完成开始执行代码他会直接从第八行开始解析foo()执行在GO里找到foo发现是个地址找到对应的内存地址foo() 所以创建了一个函数执行上下文在foo里发现定义了一个name,这是这个函数执行上下文对应的AO里就会创建一个name:unde又发现创建了一个bar是个函数所以对应创建了内存空间和函数执行上下文编译好了以后开始执行首先name赋值为haha这时foo对应AO里的namew为haha 并且把bar retrun出去这时 GO里的fn 赋值为一个内存地址foo执行完毕被销毁弹出调用栈。fn执行同样的道理先找到对应的内存地址创建函数执行上下文编译bar发现没有什么变量定义console.log(name)在bar这个AO里是没有任何值的因为每个函数内存地址里存放当前作用域和父级作用域所以根据父级作用域进行查找此时的name值为Foo里的name哎 foo不是被销毁了吗这就是闭包 闭包由两部分组成 函数可以访问的自由变量 三、闭包定义
闭包由两部分组成 函数可以访问的自由变量一个普通的函数function如果它可以访问外层作用于的自由变量那么这个函数就是一个闭包从广义的角度来说 : JavaScript中的函数都是闭包从狭义的角度来说 : JavaScript中一个函数如果访问了外层作用于的变量那么它是一个闭包闭包在实现上是一个结构体对象它存储了一个函数和一个关联的环境 ( 相当于一个符号查找表 );闭包跟函数最大的区别在于当捕捉闭包的时候它的 自由变量 会在捕捉时被确定这样即使脱离了捕捉时的上下文它也能照常运行 var name 夏夏function my(){console.log(name)}根据1234定义来说当一个函数内部访问到外部的变量就成为闭包其实上面例子就是一个闭包我们无意间写了很多闭包哈哈哈哈
四、闭包导致的内存泄漏 我们经常会说闭包是造成内存泄露的为什么呢
比如在上面的案例中如果后续我们不再使用 foo 函数了那么该函数对象应该要被销毁掉并且其引用着的父 作用域AO也应该被销毁掉但是目前因为在全局作用域下 fn 变量对 0xb00 的函数对象有引用而 0xb00 的作用域中 AO 有用所以最终会造成这些内存都是无法被释放的所以我们经常说的闭包会造成内存泄露其实就是刚才的引用链中的所有对象都是无法释放那么怎么解决这个问题呢当将 foo 设置为 null 时就不再对函数对象 0xb00 有引用那么对应的 AO 对象也就不被销毁了在 GO 的下一次检测中它们就会被销毁掉直接把家连根拔起
foo null 面试题
为什么会有内存泄漏