目录javascript
引入(闭包和块做用域)html
理解闭包:java
常见的闭包:chrome
闭包的做用:闭包
闭包的生命周期:less
点赞再看,养成好习惯,总结不易,老铁多多支持~
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>00_引入</title> </head> <body> <button>测试1</button> <button>测试2</button> <button>测试3</button> <!-- 需求: 点击某个按钮, 提示"点击的是第n个按钮" --> <script type="text/javascript"> var btns = document.getElementsByTagName('button'); //遍历加监听 /* for (var i = 0,length=btns.length; i < length; i++) { var btn = btns[i] btn.onclick = function () { alert('第'+(i+1)+'个'); // 永远是第btns.length个,由于for循环已结束 } }*/ // 方法一,利用对象属性 /* for (var i = 0,length=btns.length; i < length; i++) { var btn = btns[i] //将btn所对应的下标保存在btn上 btn.index = i btn.onclick = function () { alert('第'+(this.index+1)+'个') } }*/ // 方法二,利用闭包 // for (var i = 0, length = btns.length; i < length; i++) { // // (function (j) { // // var btn = btns[j] // // btn.onclick = function () { // // alert('第' + (j + 1) + '个') // // } // // })(i); // // 等同于上面一段 // (function () { // var j = i; // 由于当前做用域是没有i的,i在做用域以外,这样onclick就持有了该做用域的引用,这个引用就叫作闭包 // var btn = btns[j]; // btn.onclick = function () { // alert('第' + (j + 1) + '个'); // } // })(); // } // 或者利用ES6的let声明和块做用域结合起来 // for循环头部的let声明会在每次循环迭代的过程当中被声明 for (let i = 0, length = btns.length; i < length; i++) { let btn = btns[i]; btn.onclick = function () { alert('第' + (i + 1) + '个'); // 持有外部做用域的引用,造成闭包 } } // 块做用域和闭包联手即可以天下无敌 </script> </body> </html>
运行效果:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>01_理解闭包</title> </head> <body> <!-- 1. 如何产生闭包? * 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包 2. 闭包究竟是什么? * 使用chrome调试查看 * 理解一: 闭包是嵌套的内部函数(绝大部分人) * 理解二: 包含被引用变量(函数)的对象(极少数人) * 注意: 闭包存在于嵌套的内部函数中 3. 产生闭包的条件? * 函数嵌套 * 内部函数引用了外部函数的数据(变量/函数) --> <script type="text/javascript"> function fn1() { var a = 2; var b = 'abc'; function fn2() { //执行函数定义就会产生闭包(不用调用内部函数) console.log(a); } // fn2(); } fn1(); function fun1() { var a = 3; var fun2 = function () { console.log(a); } } fun1(); </script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>02_常见的闭包</title> </head> <body> <!-- 1. 将函数做为另外一个函数的返回值 2. 将函数做为实参传递给另外一个函数调用 --> <script type="text/javascript"> // 1. 将函数做为另外一个函数的返回值 function fn1() { var a = 2 function fn2() { a++ console.log(a) } return fn2 } var f = fn1() f() // 3 f() // 4 // 2. 将函数做为实参传递给另外一个函数调用 function showDelay(msg, time) { setTimeout(function () { alert(msg) }, time) } showDelay('atguigu', 2000) </script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>03_闭包的做用</title> </head> <body> <!-- 1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期) 2. 让函数外部能够操做(读写)到函数内部的数据(变量/函数) 问题: 1. 函数执行完后, 函数内部声明的局部变量是否还存在? 通常是不存在, 存在于闭中的变量才可能存在 2. 在函数外部能直接访问函数内部的局部变量吗? 不能, 但咱们能够经过闭包让外部操做它 --> <script type="text/javascript"> function fn1() { var a = 2 function fn2() { a++ console.log(a) // return a } function fn3() { a-- console.log(a) } return fn3 } var f = fn1() f() // 1 f() // 0 </script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>04_闭包的生命周期</title> </head> <body> <!-- 1. 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用) 2. 死亡: 在嵌套的内部函数成为垃圾对象时 --> <script type="text/javascript"> function fn1() { //此时闭包就已经产生了(函数提高, 内部函数对象已经建立了) var a = 2 function fn2() { a++ console.log(a) } return fn2 } var f = fn1() f() // 3 f() // 4 f = null //闭包死亡(包含闭包的函数对象成为垃圾对象) </script> </body> </html>
let lessons = [ { title: "媒体查询响应式布局", click: 89, price: 12 }, { title: "FLEX 弹性盒模型", click: 45, price: 120 }, { title: "GRID 栅格系统", click: 19, price: 67 }, { title: "盒子模型详解", click: 29, price: 300 } ]; function between(a, b) { return function(v) { return v.price >= a && v.price <= b; } } console.table(lessons.filter(between(10, 100)));
执行结果
<!DOCTYPE html> <html lang="en"> <body> <style> button { position: absolute; } </style> <button>按钮</button> </body> <script> let btns = document.querySelectorAll("button"); btns.forEach(function (item) { let bind = false; item.addEventListener("click", function () { // if (!bind) { let left = 1; bind = setInterval(function () { item.style.left = left++ + "px"; }, 100); // } }); }); </script> </html>
运行结果
这竟然?鬼畜了???
就是由于你的left写在了click回调函数里面。由于每点击一次就会建立一块function空间,里面left变量去定时改变style,每改变一次style.left就会致使一次回流从而再渲染一次。每次点击left初始值为1,上一次的已经为+了不少次,上上次的已经为+了很是屡次。渲染的时候你就会看到一会1px一会不少px的鬼畜状况,也就是动画抖动(渲染一次抖动一次)。
那么能够把left变量提到click上面一行就解决了吧?
...... let bind = false; let left = 1; item.addEventListener("click", function () { // if (!bind) { bind = setInterval(function () { item.style.left = left++ + "px"; }, 100); // } }); ......
运行结果
这???竟然加速了 ,愈来愈快了!!!由于每点击一次就会有一个定时器100ms轮询改变left变量,这个left变量对于click回调函数来讲是的共有的一块做用域。因此愈来愈多的定时器不断的left++,你就看到了加速现象。
正确作法以下:
<!DOCTYPE html> <html lang="en"> <body> <style> button { position: absolute; } </style> <button>按钮</button> </body> <script> let btns = document.querySelectorAll("button"); btns.forEach(function (item) { let bind = false; item.addEventListener("click", function () { if (!bind) { let left = 1; bind = setInterval(function () { item.style.left = left++ + "px"; }, 100); } }); }); </script> </html>
现象就正常了,没有抖动也没有加速。
<script> let lessons = [ { title: "媒体查询响应式布局", click: 89, price: 12 }, { title: "FLEX 弹性盒模型", click: 45, price: 120 }, { title: "GRID 栅格系统", click: 19, price: 67 }, { title: "盒子模型详解", click: 29, price: 300 } ]; function order(field, type = 'asc') { // 默认asc升序 return (a, b) => { if (type == "asc") return a[field] > b[field] ? 1 : -1; return a[field] > b[field] ? -1 : 1; } } console.table(lessons.sort(order("price"))); // order("price", "desc")能够降序 </script>
闭包特性中上级做用域会为函数保存数据,从而形成的以下所示的内存泄漏问题
<!DOCTYPE html> <html lang="en"> <body> <div desc="zaixianxuexi">在线学习</div> <div desc="kaiyuanchanpin">开源产品</div> </body> <script> let divs = document.querySelectorAll("div"); divs.forEach(function (item) { item.addEventListener("click", function () { console.log(item.getAttribute("desc")); }); }); </script> </html>
下面经过清除不须要的数据解决内存泄漏问题
let divs = document.querySelectorAll("div"); divs.forEach(function(item) { let desc = item.getAttribute("desc"); item.addEventListener("click", function() { console.log(desc); }); item = null; });
再给一个例子加深印象
<script type="text/javascript"> function fn1() { var arr = new Array[100000] function fn2() { console.log(arr.length) } return fn2 } var f = fn1() f() f = null //让内部函数成为垃圾对象-->回收闭包 </script>
let hd = { user: "lcy", get: function() { console.log(this); // 这里的this是hd对象 return function() { // 这里的this是window对象 return this.user; }; } }; var a = hd.get(); // 执行get(),返回里面的function // this 老是指向调用该函数的对象,即函数在搜索this时只会搜索到当前活动对象。 // 下面是函数由于是在全局环境下调用的,因此this指向window console.log(a()); // this.user为undefined,由于this指向window
this 老是指向调用该函数的对象,即函数在搜索this时只会搜索到当前活动对象。因此这里get()里面的function的this是Window。
顺带总结一下this记忆方法
若是你定义的是对象,对象里面定义了函数,这个函数叫作方法,方法里面的this是指向当前对象,若是方法里面还有函数2,那么函数2的this指向Window,理由就是上面这个例子。
若是你定义的函数,而你执行的时候是new 函数,那么认为你建立了对象,this判断同上。
若是你定义的函数,执行的时候直接调用,好比function a(){...},调用为a(),那么a里面的this指向Window
上面犀利的总结通常人我不告诉他。
闭包的自定义js模块:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>05_闭包的应用_自定义JS模块</title> </head> <body> <!-- 闭包的应用2 : 定义JS模块 * 具备特定功能的js文件 * 将全部的数据和功能都封装在一个函数内部(私有的) * 只向外暴露一个包信n个方法的对象或函数 * 模块的使用者, 只须要经过模块暴露的对象调用方法来实现对应的功能 --> <script type="text/javascript" src="myModule.js"></script> <script type="text/javascript"> var module = myModule() module.doSomething() module.doOtherthing() </script> </body> </html>
myModule.js
function myModule() { //私有数据 var msg = 'My atguigu' //操做数据的函数 function doSomething() { console.log('doSomething() ' + msg.toUpperCase()) } function doOtherthing() { console.log('doOtherthing() ' + msg.toLowerCase()) } //向外暴露对象(给外部使用的方法) return { doSomething: doSomething, doOtherthing: doOtherthing } }
闭包的自定义js模块2:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>05_闭包的应用_自定义JS模块2</title> </head> <body> <!-- 闭包的应用2 : 定义JS模块 * 具备特定功能的js文件 * 将全部的数据和功能都封装在一个函数内部(私有的) * 只向外暴露一个包信n个方法的对象或函数 * 模块的使用者, 只须要经过模块暴露的对象调用方法来实现对应的功能 --> <script type="text/javascript" src="myModule2.js"></script> <script type="text/javascript"> myModule2.doSomething() myModule2.doOtherthing() </script> </body> </html>
myModule2.js
(function () { //私有数据 var msg = 'My atguigu' //操做数据的函数 function doSomething() { console.log('doSomething() '+msg.toUpperCase()) } function doOtherthing () { console.log('doOtherthing() '+msg.toLowerCase()) } //向外暴露对象(给外部使用的方法) window.myModule2 = { doSomething: doSomething, doOtherthing: doOtherthing } })()
这个应用还有新玩法,见这里: js除了当即执行函数,你还能够这么玩 (预计阅读 1 min)
关注、留言,咱们一块儿学习。
===============Talk is cheap, show me the code================