文章目录
闭包与作用域
1、什么是环境与作用域
- js的函数里面,可以引用全局变量
- 在函数里面定义的变量,在函数外部无法调用
- 在PHP里面,就不能在函数里面引用全局变量
上面三点,讲的就是作用域
2、函数的环境与作用域原理
- 函数定义出来,会在内存中开辟一个新的环境范围,默认会在内部使用(就是视频里老师经常画的图)
- 函数在使用之后,变量会被销毁,每次执行都是新的
- 定义的变量,外面还是访问不到(在默认的情况下)
3、延伸函数环境生命周期
- 函数没被盗用,就不会创建内存空间
- 如果创建多个执行函数,内存空间会被重复调用
function test() {
let n = 1;
function sum() {
console.log(++n)
}
sum()
}
test() // 执行打印2
test() // 执行打印2
test() // 执行打印2
- 如果有被外部的变量定义,就会有改变,如下面代码显示
function test() {
let n = 1;
return function sum() {
console.log(++n)
}
}
let a = test()
a() // 此时打印的就是2
a() // 3
a() // 4
- 但是如果有新的被定义,则还是打印2,如下面代码显示
function test() {
let n = 1;
return function sum() {
console.log(++n)
}
}
let a = test()
a() // 此时打印的就是2
a() // 3
a() // 4
let b = test()
b() // 此时打印的就是2
b() // 3
- 如果在函数内部使用,则用完就会销毁
- 但是如果在外面有调用,则会被保存下来
- 还有一种,见下面代码,只是继续往下面调用了
function test() {
let n = 1;
return function sum() {
// console.log(++n)
let m = 1;
return function show() {
console.log(++m)
}
}
}
let a = test()() // 这种是类似上面的,只是再说明了一遍
a() // 这里打印的是2
a() // 3
// 而且在这里,m能够被保留,实际上,n也是可以保留的
其实上面的代码已经设计到闭包了
4、构造函数中的作用域的使用形态
- 每次构造函数都会开辟新的内存空间
let a = HD();
let b = HD();
// 这两个创建了两个构造函数,然后他们之间互不影响
- return的是一个函数,在外部调用的时候执行console.log,会发现是一个函数,所以使用的使用需要使用a(),b()
5、什么是块级作用域
- 下面的对象中,就是一个作用域,在外部是不能调用的
{let a = 1} // 在外部调用console.log(a),会报错,未定义
- var刚出现的时候,还没有作用域的说法,到后来的let,const,都有块级作用域
- 所以下面的代码,在外面也是可以调用的
{var a = 1} // 在外部调用console.log(a),不会报错,并且打印出1
6、let-const-var 在for循环中执行原理
- 下面的代码可以打印i,因为前面定义的i,使用var定义的
for (var i=1;i <= 3 ; i++){
console.log(i)
}
console.log(i) // 这里面可以打印i,因为前面定义的i,使用var定义的
- 如果将var改成let,则访问不了
- 下面还有一个代码,讲的是用setTimeout,这个在web前端面试题的 “蛋老师”有讲,这里就不说明了
7、模拟出var的伪块作用域
- 与上面说的类似,这里面的代码,唯一的不同就是加了伪块作用域,就是包裹了一个function函数并立即执行
8、多级作用域嵌套详解
let arr = []
for (let i = 1; i <= 3; i++) {
arr.push(function() {
return i;
})
}
// console.log(arr[0]) // 此时arr列表里面放着三个函数
for (item of arr ){ // 循环函数,并打印结果,
console.log(item()) // 打印出来都是 1,2,3
}
- 如果将上面的let改成var,则最终打印的结果为4,4,4,引用的是window.i,即全局变量i
9、什么是闭包及其他语言对比实例
- 函数可以访问其他函数里面的数据,就是闭包
- 或者说子函数可以访问父函数的变量
- 在PHP里面,如果子函数想要获取父函数的变量,是获取不到的
- 其他的实例需要在其他视频内展示
10、使用闭包获取区间商品
let arr = [1,2,3,4,32,34,12,32]
let res = arr.filter(function (v) {
return v <= 6 && v>= 2
})
console.log(res); //[2,3,4]
- 上面是原始的数组过滤,使用的是filter
- 但是上面的函数不能复用,所以需要下面的代码进行优化,使用了between函数
let arr = [1,2,3,4,32,34,12,32]
function between(a,b) {
return function (v) {
return v <= a && v>= b
}
}
let res = arr.filter(between(20,2))
console.log(res);
let lessons = [
{
title: "三国演义",
click: 288,
price: 48
},
{
title: "水浒传",
click: 20,
price: 58
},
{
title: "红楼梦",
click: 400,
price: 68
},
{
title: "西游记",
click: 100,
price: 49
},
]
let res = lessons.filter(function (v) {
return v.price >= 20 && v.price <= 50
})
console.table(res); //console.table :是将字典里面的值以表格的形式输出
- 上面的代码也是不能够复用,所以需要使用一个between的函数
function between(a,b) {
return function (v) {
return v.price >= a && v.price <= b
}
}
let res = lessons.filter(between(20,70))
console.table(res);
11、移动动画的闭包使用
- 设定两个button按钮
- setInterval与setTimeout类似,但是前者会重复执行,后者只会执行一次
let button_obj = document.querySelectorAll("button"); // 通过querySelectAll获取所有的button
button_obj.forEach(function (item) { // 使用forEach循环
item.addEventListener('click',function () { //增加点击事件
let left = 1;
setInterval(function () {
item.style.left = left++ + 'px'; // 每隔5毫秒进行left++操作,并复制给style.left
},5)
})
})
- 但是上面的代码会出现一个问题,就是当重复点击button时,button会出现抖动
12、动画为什么会抖动
- 给button设定left值,需要将left值定义在click的外面,这样在重复点击的时候就不会重复定义left值
let button_obj = document.querySelectorAll("button");
button_obj.forEach(function (item) {
let left = 1; // 将left的值定义到click的外面,不会重复定义left的值
item.addEventListener('click',function () {
setInterval(function () {
item.style.left = left++ + 'px';
},100)
})
})
- 但是这样会有新的问题,重复点击的时候,会让button加快
13、动画加速的原因
- 动画加速的原因,是setInterval会一直给left值增加
- 所以我们定义一个interval = false,当点击的时候会让interval = true,这样就会让click代码只执行一次
let button_obj = document.querySelectorAll("button");
button_obj.forEach(function (item) {
let left = 1;
let interval = false; // 定义一个变量,boolean类型,控制click点击事件只会产生一次
item.addEventListener('click', function () {
if (!interval) {
setInterval(function () {
item.style.left = left++ + 'px';
}, 100);
interval = true; // interval为True,则click不会再执行
}
})
})
14、利用闭包根据字段排序商品
- 利用sort对lessons进行排序
let hd = lessons.sort(function (a, b) {
return a.price > b.price ? -1:1
});
console.table(hd); // 此时输出的,就是排序后的数组对象
- 但是上面的代码无法复用,需要修改成下面的代码
function order(field) {
return function (a, b) {
return a[field] > b[field] ? 1:-1
}
}
let hd = lessons.sort(order("price"))
console.table(hd);
- 上面的代码虽然已经变得灵活了,但是却还没有排序的功能,下面的代码就比较完善了
function order(field,type="asc") { // 增加type排序的功能,默认传参asc
return function (a, b) {
if (type === "asc") return a[field] > b[field] ? 1:-1;
if (type === "desc") return a[field] > b[field] ? -1:1;
}
}
let hd = lessons.sort(order("price","desc"));
console.table(hd);
15、闭包的内存泄露解决方法
- 正常两个div标签,获取里面的desc值,如下图所示
<body>
<div desc="hdcms">开源产品</div>
<div desc="hdcms2">开源产品</div>
</body>
<script>
let div_obj = document.querySelectorAll("div");
div_obj.forEach(function(item){ // 使用forEach来循环所有div_obj内的元素
item.addEventListener("click",function () {
console.log(item.getAttribute("desc"))
console.log(item)
})
})
</script>
- 上面的item值,会一直存在,所以会有点内存浪费,所以可以使用下面的代码来解决内存泄露的问题
let div_obj = document.querySelectorAll("div");
div_obj.forEach(function(item){
let desc = item.getAttribute("desc") // 先使用变量保存需要的值
item.addEventListener("click",function () {
console.log(desc)
console.log(item)
})
item = null // 再将item,也就是div元素为空,可以避免内存浪费的问题
})
16、this在闭包中的历史遗留问题
- this的指向
let hd = {
user: "后盾人",
get: function () {
console.log(this); // 此时的this,指的是hd整个对象
return function () {
return this.user
}
}
};
let a = hd.get(); // 这里a获取的,是hd对象里面的get方法,但只是获得方法
console.log(a()); // 所以在这里打印的结果为 undefined
- 上面代码的问题,可以使用箭头函数解决,如下面代码所示:
let hd = {
user: "后盾人",
get: function () {
console.log(this);
return () => { // 这里面用的是箭头函数,不会创建
return this.user
}
}
};
let a = hd.get();
console.log(a());
- 也可以使用
let that = this
,如下面代码所示:
let hd = {
user: "后盾人",
get: function () {
console.log(this);
let that = this; // 将this赋值给that,则可以使用that来指定user
return function() {
return that.user
}
}
};
let a = hd.get();
console.log(a());
代码素材来源:https://www.bilibili.com/video/BV1YJ411R7ap