当前位置: 首页 > article >正文

想品客老师的第七天:闭包和作用域

闭包之前的内容写在这里

环境、作用域、回收

首先还是数据的回收问题,全局变量一般都是通过关闭页面回收的;而局部变量的值不用了,会被自动回收掉

像这种写在全局里的就不会被主动回收捏:

      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 = {}; // 覆盖之前的 sharedCity
    return 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 或 absoluteleft 样式才能生效。

<!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 = 1
                setInterval(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 = 1
            item.addEventListener('click', function () {

                setInterval(function () {
                    item.style.left = left++ + 'px'
                }, 10)
            })
        })

当你连点两次的时候,会发现按钮不抖动了,但是移动速度加快了!

为什么移动速度变快了?因为外面没有清除定时器,每点一下,就是新增了一个定时器,到最后越来越多的定时器再给按钮做left++的运算,所以越走越快!!

解决方法可以设置一个flag量,来判定是否创建过计时器:

 let btns = document.querySelectorAll('button')
        btns.forEach(function (item) {
            let left = 1
            let flag = false
            item.addEventListener('click', function () {
                if (!flag) {
                    flag = true
                    setInterval(function () {
                        item.style.left = left++ + 'px'
                    }, 10)
                }

            })
        })

反过来把left放在里面,也不会出现频闪的情况(因为定时器只能开一个):

    let btns = document.querySelectorAll('button')
        btns.forEach(function (item) {
            let flag = false
            item.addEventListener('click', function () {
                if (!flag) {
                    let left = 1
                    flag = 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=this
                    return 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())//后盾人

后盾人老师我恨你。。。


http://www.kler.cn/a/521104.html

相关文章:

  • 虚幻基础08:组件接口
  • CVE-2023-38831 漏洞复现:win10 压缩包挂马攻击剖析
  • 利用JSON数据类型优化关系型数据库设计
  • 01-硬件入门学习/嵌入式教程-CH340C使用教程
  • 【橘子Kibana】Kibana的分析能力Analytics简易分析
  • 动手学图神经网络(3):利用图神经网络进行节点分类 从理论到实践
  • 代码随想录算法训练营day30(补0123)
  • 基于 Ansible 的 Linux 服务器自动化运维实战
  • Java Web-Cookie与Session
  • 前端性能优化指标 - DCL(触发时机、脚本对 DCL 的影响、CSS 对 DCL 的影响)
  • RAG:实现基于本地知识库结合大模型生成(LangChain4j快速入门#1)
  • 【HarmonyOS之旅】基于ArkTS开发(三) -> 兼容JS的类Web开发(二)
  • ollama使用详解
  • JavaScript 的 Promise 对象和 Promise.all 方法的使用
  • 验证二叉搜索树(力扣98)
  • Pandas基础03(数据的合并操作/concat()/append()/merge())
  • 第五节 MATLAB命令
  • 【大厂面试AI算法题中的知识点】方向涉及:ML/DL/CV/NLP/大数据...本篇介绍Transformer相较于CNN的优缺点?
  • WPF基础 | WPF 常用控件实战:Button、TextBox 等的基础应用
  • 对比OpenAI的AI智能体Operator和智谱的GLM-PC,它们有哪些不同?
  • MongoDB的事务机制
  • 智慧园区解决方案助力数字化转型与智能生态系统建设
  • 基于SpringBoot电脑组装系统平台系统功能实现三
  • PostgreSQL技术内幕23:PG统计信息的收集和应用
  • 【Leetcode 热题 100】300. 最长递增子序列
  • [SWPUCTF 2022 新生赛]js_sign