闭包之前的内容写在这里
环境、作用域、回收
首先还是数据的回收问题,全局变量一般都是通过关闭页面回收的;而局部变量的值不用了,会被自动回收掉
像这种写在全局里的就不会被主动回收捏:
let title = '荷叶饭'function fn() {alert(title)}document.querySelector('button').addEventListener('click', fn)
求后盾人老师别举他那听不懂的栗子了。。。
在计算机里环境可以理解为一块内存的数据,就是块空间
let title='荷叶饭'function show(){let url='#'}show()console.log(url)
外面用不了里面的变量
里面用得了外面的变量:
let title = '荷叶饭'function show() {let url = '#孩子们我是个链接'function hd() {console.log(url)//里面可以访问外面}hd()}show()
函数环境生命周期
函数在调用之前的声明相对于建造城市的计划,调用函数相当于城市动工了,调用多次函数相当于建造多个一样的城市(开辟多块空间),但是不是同一个城市,彼此独立
function buildCity() {let city = {};return city;
}let city1 = buildCity(); // 创建第一个城市
let city2 = buildCity(); // 创建第二个城市
console.log(city1 === city2); // false,两个不同的城市
只有在显式修改共享状态时,才可能出现覆盖的情况。
let sharedCity = null;function buildCity() {sharedCity = {}; // 覆盖之前的 sharedCityreturn sharedCity;
}let city1 = buildCity(); // 创建并覆盖 sharedCity
let city2 = buildCity(); // 再次覆盖 sharedCity
console.log(city1 === city2); // true,因为 sharedCity 被覆盖了
如果一个函数不return,相当于没有被外部引用,每次调用都是单独创建新的一块:
function hd() {let n = 1;return function sum() {// console.log(++n);let m = 1;function show() {console.log("m:" + ++m);console.log("n:" + ++n);};show()};}let a = hd();a();a();a();
n被return了,所以n++,但是m是独立的没有被外部调用,所以不++
输入被return,相当于被外部引用,所以多次调用m会在原来的基础上++:
function hd() {let n = 1;return function sum() {// console.log(++n);let m = 1;return function show() {console.log("m:" + ++m);console.log("n:" + ++n);};};}let a = hd()();a();a();a();
延长了函数的生命周期
构造函数里的环境是什么
每执行一次构造函数就会创造出一个新的对象:
function Hd() {let n = 1;this.sum = function () {console.log(++n);};}let a = new Hd();a.sum();a.sum();let b = new Hd();b.sum();b.sum();
这方面和普通函数没什么区别:
块级作用域
没报错,这就说明这是两个块
{let a = 1}{let a = 1}
var没有块作用域的原因居然是块作用域出的比var晚,推出let和const可以适用块作用域
let-const-var在for循环
var没有块的特性:
for (var i = 1; i <= 3; i++) {console.log(i);}console.log(i)//可以访问
在for里写个定时器
如果是var的话:
for (var i = 1; i <= 3; i++) {setTimeout(function () {console.log(i);}, 1000);}console.log(i);
当 setTimeout
的回调函数执行时,for
循环已经执行完毕,此时 i
的值已经变成了 4
,所以所有回调函数都会输出 4
换成let结果就不一样了:
let是有块级作用域的概念,for里的定时器函数在i等于不同值的时候,会先向上一级找i,还没找到全局,就在父级找到i了,找到的i就等于此时i的值
for (let i = 1; i <= 3; i++) {setTimeout(function () {console.log(i);}, 1000);}
var模拟块作用域
用定时器+立即执行函数模拟块作用域,每次循环创建一个新的作用域,从而捕获当前的 i
值,确保 setTimeout
回调函数输出正确的值。
for (var i = 1; i <= 3; i++) {(function (i) {setTimeout(function () {console.log(i);//1,2,3}, 1000);})(i);}
多级作用域嵌套
不想被清掉的函数可以放在数组里:
let arr = [];for (var i = 1; i <= 3; i++) {arr.push(function () {return i;})}console.log(arr[2]());//4
但是i打印出来都是4,因为定时器比for执行的慢
可以保留作用域:
let arr = [];for (var i = 1; i <= 3; i++) {(function (i) {arr.push(function () {return i;});})(i)//把i传过去// console.log(i);}console.log(arr[2]())//3
闭包
哪个傻逼弹幕说的闭包和函数没有必然关系的
闭包(Closure)是 JavaScript 中一个非常重要的概念,也是函数式编程的核心特性之一。闭包是指一个函数能够记住并访问它的作用域,即使这个函数在其作用域之外执行。
求后盾人老师不要再温故而知新了
在做一个筛数组元素的函数的时候,可以发现筛的这个方法可以单独提出来:
let arr = [1, 23, 4, 5, 6, 7, 8, 9, 21, 10];function between(a, b) {return function(v) {return v >= a && v <= b;};}console.log(arr.filter(between(3, 9)))// [4, 5, 6, 7, 8, 9]
本来筛不同区间的数是这么做的:
let hd = arr.filter(function (v) {return v >= 2 && v <= 9;});console.log(hd);// [4, 5, 6, 7, 8, 9]let a = arr.filter(function (v) {return v >= 6 && v <= 10;});console.log(a)//[6, 7, 8, 9, 10]
现在使用闭包结构,让匿名函数访问上层函数的参数,然后再返回当作filter函数的回调函数传入
也可以实现筛选商品的效果:
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)));
应用
加深对闭包的印象:移动动画
按钮的 position
属性需要设置为 relative
或 absolute
,left
样式才能生效。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>button {position: absolute;}</style>
</head>
<button>点我 </button>
<button>溜了溜了。。。</button><body><script>let btns = document.querySelectorAll('button')btns.forEach(function (item) {item.addEventListener('click', function () {let left = 1setInterval(function () {item.style.left = left++ + 'px'//这里使用了闭包}, 10)})})</script>
</body></html>
如果多点几次,会触发频闪效果。。。会抖动
为什么呢?因为把left的定义写在了事件监听的函数里,所以每次点击left就会重新等于1
解决方案1就是把对left的定义放在外面一层:
let btns = document.querySelectorAll('button')btns.forEach(function (item) {let left = 1item.addEventListener('click', function () {setInterval(function () {item.style.left = left++ + 'px'}, 10)})})
当你连点两次的时候,会发现按钮不抖动了,但是移动速度加快了!
为什么移动速度变快了?因为外面没有清除定时器,每点一下,就是新增了一个定时器,到最后越来越多的定时器再给按钮做left++的运算,所以越走越快!!
解决方法可以设置一个flag量,来判定是否创建过计时器:
let btns = document.querySelectorAll('button')btns.forEach(function (item) {let left = 1let flag = falseitem.addEventListener('click', function () {if (!flag) {flag = truesetInterval(function () {item.style.left = left++ + 'px'}, 10)}})})
反过来把left放在里面,也不会出现频闪的情况(因为定时器只能开一个):
let btns = document.querySelectorAll('button')btns.forEach(function (item) {let flag = falseitem.addEventListener('click', function () {if (!flag) {let left = 1flag = true//写在里面setInterval(function () {item.style.left = left++ + 'px'}, 10)}})})
利用闭包特性做购物排序:
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") {return function(a, b) {if (type == "asc") return a[field] > b[field] ? 1 : -1;//可以自由更改升序降序return a[field] > b[field] ? -1 : 1;//并且改变根据click/price的排序};}let hd = lessons.sort(order("price"));console.table(hd);
闭包导致的内存泄漏
获取一整个对象会很臃肿,当你只想获取一个对象里的属性,可以使用一种过河拆桥的方法:获取整个对象->使用属性->销毁对象,还可解决闭包导致的内存泄漏
不是foreach是同步,先执行foreach里的同步,item是空也是同步,元素的事件绑定是异步的:
let divs = document.querySelectorAll("div");divs.forEach(function (item) {let desc = item.getAttribute("desc");item.addEventListener("click", function () {// console.log(item.getAttribute("desc"));console.log(desc);//获取属性console.log(item);});item = null;//销毁对象});
this在闭包中的历史遗留问题
this在闭包里指向混乱:
let hd = {user: "后盾人",get: function () {return function(){return this.user;}}};let a = hd.get();console.log(a())//undefined
这是个闭包结构,a是get方法,a()就是执行get方法,返回get方法里面的匿名函数的this.user,但是这个匿名函数是个函数啊,他的this是window,window里没有user这个属性,所以返回undefined
怎么让内部的匿名函数的this指向hd?声明一个this
let hd = {user: "后盾人",get: function () {let This=thisreturn function(){return This.user;}}};let a = hd.get();console.log(a())//后盾人
也可以用箭头函数,箭头函数的this指向get方法的this,也就是对象hd,这样就可以访问user了:
let hd = {user: "后盾人",get: function() {return () => {return this.user;};}};let a = hd.get();console.log(a())//后盾人
后盾人老师我恨你。。。