闭包与作用域

闭包与作用域

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值