作用域与闭包 作用域 作用域有三种:
全局作用域
函数作用域
let\const 与 {}
形成的块级作用域
变量引用规则
编译器运行时会将变量定义在:当前所在的作用域
使用变量时会从当前作用域开始向上查找变量
作用域就像攀亲亲一样,晚辈总是可以向上辈要些东西
默认请况下:父级作用域不能够访问子级作用域的变量(使用闭包解决),但是子级作用域却可以访问祖先作用域的变量。
使用规范 作用域链只向上查找,找到全局window即终止,应该尽量不要在全局作用域中添加变量。
函数被执行后其环境变量将从内存中删除。下面函数在每次执行后将删除函数内部的total变量。
1 2 3 4 function count() { let total = 0; } count();
函数每次调用
都会创建一个新作用域
1 2 3 4 5 6 7 8 9 10 11 12 13 let site = '阿顺'; function a() { let as = 'ashuntefannao.com'; function b() { let cms = 'Ashun.com'; console.log(as); console.log(site); } b(); } a();
如果子函数被外部(window作用域)使用时,父级环境将被保留,因此在全局作用域能够间接访问
函数内的变量。这就是闭包
的特性
1 2 3 4 5 6 7 8 9 10 11 12 13 function as() { let n = 1; return function() { let b = 1; return function() { console.log(++n); console.log(++b); }; }; } let a = as()(); a(); //2,2 a(); //3,3
构造函数也是很好的环境例子,子函数被外部使用父级环境将被保留。
通过这两个例子可以发现,闭包可以保护某些变量,不被外部直接访问,而是让外部通过接口(子函数、方法)进行间接访问。
1 2 3 4 5 6 7 8 9 10 11 function User() { let a = 1; this.show = function() { console.log(a++); }; } let a = new User(); a.show(); //1 a.show(); //2 let b = new User(); b.show(); //1
let/const 使用 let/const
可以将变量声明在块作用域中(放在新的环境中,而不是全局中)
1 2 3 4 5 6 7 8 { let a = 9; } console.log(a); //ReferenceError: a is not defined if (true) { var i = 1; } console.log(i);//1
也可以通过下面的定时器函数来体验
1 2 3 4 5 for (let i = 0; i < 10; i++) { setTimeout(() => { console.log(i); }, 500); }
在 for
循环中使用let/const
会在每一次迭代中重新生成不同的变量
1 2 3 4 5 let arr = []; for (let i = 0; i < 10; i++) { arr.push((() => i)); } console.log(arr[3]()); //3 如果使用var声明将是10
在没有let/const
的历史中使用以下方式产生作用域
1 2 3 4 5 6 7 8 //自行构建闭包 var arr = []; for (var i = 0; i < 10; i++) { (function (a) { arr.push(()=>a); })(i); } console.log(arr[3]()); //3
闭包 闭包指子函数可以访问外部作用域变量的函数特性,即使在子函数所在的作用域外也可以访问。如果没有闭包那么在处理事件绑定,异步请求时都会变得困难。
闭包一般在子函数本身作用域以外执行。
闭包一般的形式:函数套函数,在子函数中使用父级作用域的变量,将子函数return出去。
但闭包的形式并不是固定的,其本质是:**当前作用域块存在对父级作用域块的引用
**
基本示例 前面在讲作用域时已经在使用闭包特性了,下面再次重温一下闭包。
以下三例代码虽然形式不同,但都实现了内部作用域的变量,间接的让外部访问。
1 2 3 4 5 6 7 8 9 10 11 function first() { let a = 1; return function second() { return ++a; }; } let func = first(); console.log(func());//2 console.log(func());//3 console.log(func());//4
1 2 3 4 5 6 7 8 9 10 11 12 function first() { let a = 1; function second() { return ++a; } window.func = second; } first(); console.log(func());//2 console.log(func());//3 console.log(func());//4
1 2 3 4 5 6 7 8 9 10 let fuc; function first() { let a = 0; fuc = function () { console.log(++a); }; } first(); fuc(); fuc();
使用闭包返回数组区间元素
1 2 3 4 5 6 7 let arr = [3, 2, 4, 1, 5, 6]; function between(a, b) { return function(v) { return v >= a && v <= b; }; } console.log(arr.filter(between(3, 5)));
下面是在回调函数中使用闭包,当点击按钮时显示当前点击的是第几个按钮。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <body> <button message="ashun">button</button> <button message="SHUN">button</button> </body> <script> var btns = document.querySelectorAll("button"); for (let i = 0; i < btns.length; i++) { btns[i].onclick = (function(i) { return function() { alert(`点击了第${i + 1}个按钮`); }; })(i); } </script>
1 2 3 4 5 6 7 8 9 …… var btns = document.querySelectorAll("button"); for (let i = 0; i < btns.length; i++) { btns[i].onclick = function () { return function () { alert(`点击了第${i + 1}个按钮`); }; }.call(btns[i], i); }
移动动画 计时器中使用闭包来获取独有变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <body> <button message="阿顺">Ashun</button> <button message="阿顺特烦恼">Ashuntefannao</button> </body> <script> let btns = document.querySelectorAll("button"); btns.forEach(function (elem) { let checked = false; let L = 0; elem.addEventListener("click", function () { !checked && (checked = setInterval(function () { elem.style.marginLeft = ++L + "px"; }, 40)); }); }); </script>
闭包排序 下例使用闭包按指定字段排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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) { return (a, b) => (a[field] > b[field] ? 1 : -1); } console.table(lessons.sort(order("price")));
内存泄漏 闭包特性中上级作用域会为函数保存数据,从而造成的如下所示的内存泄漏问题
1 2 3 4 5 6 7 8 9 10 11 12 <body> <div desc="ashun">阿顺</div> <div desc="Ashuntefannao">阿顺特烦恼</div> </body> <script> let divs = document.querySelectorAll("div"); divs.forEach(function(item) { item.addEventListener("click", function() { console.log(item.getAttribute("desc")); }); }); </script>
下面通过清除不需要的数据解决内存泄漏问题
1 2 3 4 5 6 7 8 let divs = document.querySelectorAll("div"); divs.forEach(function(item) { let desc = item.getAttribute("desc"); item.addEventListener("click", function() { console.log(desc); }); item = null; //及时清空引用。 });