JS进阶(三) 闭包,作用域链,垃圾回收,内存泄露

简介: 闭包,作用域链,垃圾回收,内存泄露1、函数创建创建函数1、开辟一个堆内存(16进制的内存地址)2、声明当前函数的作用域(再哪个上下文创建的,它的作用域就是谁)3、把函数体内的代码当作字符串存储在堆内存当中(所以不执行没有意义)4、把函数的堆内存地址类似对象一样放到栈中供对象调用执行函数1、会形成一个全新的私有上下文(目的是供函数中的代码执行),然后进栈执行2、在私有上下文中有一个存放私有变量的变量对象 AO(xx)3、在代码执行之前要做的事情- 初始化它的作用域链<自己的上下文,函数的作用域>- 初始化this (箭头函数没有this)- 初始化Arguments实参

闭包,作用域链,垃圾回收,内存泄露

1、函数创建

创建函数 1、开辟一个堆内存(16进制的内存地址) 2、声明当前函数的作用域(再哪个上下文创建的,它的作用域就是谁) 3、把函数体内的代码当作字符串存储在堆内存当中(所以不执行没有意义) 4、把函数的堆内存地址类似对象一样放到栈中供对象调用 执行函数 1、会形成一个全新的私有上下文(目的是供函数中的代码执行),然后进栈执行 2、在私有上下文中有一个存放私有变量的变量对象 AO(xx) 3、在代码执行之前要做的事情 - 初始化它的作用域链<自己的上下文,函数的作用域> - 初始化this (箭头函数没有this) - 初始化Arguments实参集合(箭头函数没有) - 形参赋值(形参变量是函数的私有变量,需要存储在AO中的) - 变量提升(在私有上下文中声明的变量都是私有变量) 4、代码执行(把之前在函数中存储的字符串,拿过来在上下文中一次执行) 作用域链查找机制:在代码执行过程中,遇到一个变量,我们首先看一下是否为自己的私有变量,如果是,接下来的操作都是私有的和外界没有直接联系,如果不是私有的,那么按scope-chain,向上级上下文中查找(如果是上级私有的,接下来的操作都是操作上级的变量,。。。一直找,找到EC(G)为止) 5、根据实际情况确定当前上下文是否出栈释放 6、为了保证栈内存的大小,内存优化,一般情况下,如果当前函数执行产生的上下文在进栈且代码执行完成后,会把这次上下文移除栈(上下文释放后,之前上下文中存储的私有变量等信息也就跟着释放了), 全局上下文是在打开页面生成的,只有在页面关闭的时候才能释放掉。 特殊情况:只要上下文中的某些内容被上下文以外的东西占用,那么当前上下文是不会释放的(上下文中存储的变量等信息也保留下来了) 函数第二次执行,会形成一个全新的私有上下文,把之前做过的事,原封不动的在执行一遍,此时形成的上下文和上一次形成的上下文之间没有必要联系。

  var x = [12,23];
  function fn(y) {
     y[0] = 100;
     y = [100];
     y[1] = 200;
     console.log(y)
  }
  fn(x);
  console.log(x)

屏幕截图 2023-07-05 232659.png

2、闭包作用域

闭包: 函数运行时产生的一种机制,函数执行会形成一个全新的私有上下文,可以保护里面的私有变量和外界互不干扰(保护机制),但是大家普遍认为的闭包,认为当前上下文不能被出栈释放,这样私有变量及它的值不会被释放掉(保存机制)

作用: 保护和保存 , 保护里面的私有变量不被干扰,保存私有变量不被释放

3、经典面试题分析

// 题一
let x =1;
function A(y){
   let x = 2;
   function B(z){
      console.log(x + y +z)
   }
   return B;
}
let C = A(2)
C(3) 
// 7

屏幕截图 2023-07-05 232723.png

分析:

// 题二:
let x = 5;
function fn(x){
   return function(y){
      console.log(y+(++x))  
   }
}
let f = fn(6)
f(7)
fn(8)(9) 
f(10) 
console.log(x); 
// 14、18、18、5

屏幕截图 2023-07-05 232748.png

分析: i ++ 和 ++i 的区别 i ++ 是先运算在加一,++i 是先加一在运算 5+(++i)

EC(G)——VO(G)  => 【 let x = 5 ;  fn=>AAAFFF000 ,  f = fn(6) 执行fn 返回 ,所以 f  => BBBFFF000;】 AAAFFF000 => [[scope]] : EC(G) 【 形参 x  "return function(y){console.log(y+(++x))  }" 】 fn(6) => AAAFFF000(6) => EC(FN1) 【作用域链<EC(FN1).EC(G)>,形参 x = 6(会被下面修改++) , 代码执行: return BBBFFF000 】 BBBFFF000 => [[scope]] : EC(FN1)   【 形参 y  “console.log(y+(++x))” 】 f(7) => BBBFFF000(7)  => EC(F1) 【作用域链<EC(F1).EC(FN1)>, 形参 y=7,代码执行:console.log(y+(++x) 】AAAFFF000中的x被修改为了7,不会释放,所以结果为14

f(8)(9) => AAAFFF(8) => EC(FN2)  【作用域链<EC(FN1).EC(G)>,形参 x = 8(被下文变9) , 代码执行: return BBBFFF111 】 BBBFFF111 => [[scope]] : EC(FN1)   【 形参 y  “console.log(y+(++x))” 】 AAAFFF(8) 临时不被释放,BBBFFF111(9)  => 形成EC(NA) =>  【作用域链<EC(NA).EC(FN2)>,形参 y = 9 , 代码执行: return BBBFFF111 】,没人用BBBFFF111了会被释放,所以结果为18

f(10) => BBBFFF000(10) =>EC(F2) => 【作用域链<EC(F2).EC(FN1)>, 形参 y=10,代码执行:10+ EC(FN1)中的x变为了++7】结果为18

全局上下文中的x还是5

// 题三:
let a = 0,
    b = 0;
function A(a){
      A = function(b){
         alert(a+b++)
     };
     alert(a++)
 }
 A(1);  // 1
 A(2);  // 4
 // "1" , "4"

屏幕截图 2023-07-05 232815.png

分析

4、JS中的内存优化

  • 栈内存【执行上下文】栈内存的优化:一般情况下,函数执行完,所形成的上下文会被出栈释放掉, 特殊情况 1、当前上下文中的某些内容被上下文以外的事物占用了,此时不能出栈释放 2、全局上下文,加载页面创建的,也只有页面关闭才会被释放掉
  • 堆内存浏览器的垃圾回收机制 1、引用计数(IE为主、某些情况下不能正常计数,导致不能被释放——内存泄漏) 2、检测引用(占用)(谷歌为主), 浏览器在空闲时会一次检测所有的堆内存,把没有被任何事物占用的内存释放掉,以此优化内存 手动释放内存一般就是解除占用,解除指针的指向,一般赋值为null (空对象指针)
  • 闭包的使用大量使用闭包会造成性能损耗,但是闭包的保护和保存,我们还是需要的,所以开发中要合理使用闭包,使用后需要解除占用
  • 闭包的应用1、实战用途 2、 高阶编程:柯里化函数、惰性函数、compose函数 3、 自己封装插件组件的时候, 4、  源码分析JQ/Lodash/React(Redux/高阶组件/HOOKS)

5、闭包在实际中应用的场景

完成一下点击按钮,完成换肤的效果,循环点击获取当前索引的五种方式

屏幕截图 2023-07-05 232850.pnghtml文件

   <style>
      html,
      body {
        padding: 0;
        margin: 0;
        height: 100%;
      }
    </style>
    <body>
      <button index="0">红</button>
      <button index="1">绿</button>
      <button index="2">蓝</button>
      <button index="3">黑</button>
      <button index="4">粉</button>
    </body>
解决方式一
 /**
     *  在ES6之前只有全局上下文和私有上下文
     *  EC(G) arr ... , btns
     *  循环中给每个按钮都添加了点击事件行为,但是都没有执行,循环结束时 i=5,此时
     *  当点击按钮执行函数的时候,形成私有上下文,scope[[EC(G)]],没有形参赋值,代码执行时 i 不是自己的私有变量,所以找全局i
     *  解决方式1: 用函数包一层,
     */
    let btns = document.getElementsByTagName("button");
    let arr = ["red", "green", "blue", "black", "pink"];
    for (var i = 0; i < btns.length; i++) {
      // 子执行函数形成一个私有上下文,
      (function (i) {
        /**
         *  EC(AN)
         *  作用域链 <EC(AN),EC(G)>
         *  形参赋值 i=0
         */
        // btn[0].onclick = AAAFFF000([[scope]]:EC(AN))
        btns[i].onclick = function () {
          /**
           * 私有上下文 EC(EV)
           *   作用域链:  <EC(EV), EC(AN)>
           *   这里的再遇到的i,不再是全局的i,而是上级作用域中EC(AN)的闭包中保存的i
           */
          var color = arr[i];
          document.body.style.backgroundColor = color;
        };
      })(i); // 把每一轮的实参I传给私有上下文中的形参i,第一轮传递的是0
    }
解决方式二
    /**
     *  解决方式2: 用函数包一层
     */
    let btns = document.getElementsByTagName("button");
    let arr = ["red", "green", "blue", "black", "pink"];
    for (var i = 0; i < btns.length; i++) {
      btns[i].onclick = (function (i) {
        return function () {
          var color = arr[i];
          document.body.style.backgroundColor = color;
        };
      })(i);
    }
解决方式三
    /**
     *  解决方式3: 用let, 类似于闭包的机制
     *  let 的特点
     *  在ES6中基于let、const创建的函数,如果出现在非函数和对象的大括号当中,大括号里包裹的是一个全新的作用域
     *  ES5以前没有块级作用域的概念,所以除函数外,都是全局作用域
     *  ES6产生块级作用域,形成私有作用域
     */
    // 每一轮都会形成一个私有的作用域,并且又一个私有变量i,分别存储每一轮的索引
    // 循环5轮,会形成6个作用域(加一个for循环的父作用域) —— 也不是全局的
    // let的块级作用域是浏览器底层自己实现的,比我们实现的闭包要好一些
    let btns = document.getElementsByTagName("button");
    let arr = ["red", "green", "blue", "black", "pink"];
    for (let i = 0; i < btns.length; i++) {
      btns[i].onclick = function () {
        var color = arr[i];
        document.body.style.backgroundColor = color;
      };
    }
解决方式四
    /**
     *  解决方式4: 用let, 类似于闭包的机制
     * 真实项目中遇到循环事件绑定的,我们最好告别闭包机制,包括let,ES6
     */
    let btns = document.getElementsByTagName("button");
    let arr = ["red", "green", "blue", "black", "pink"];
    for (var i = 0; i < btns.length; i++) {
      btns[i].myIndex = i;
      btns[i].onclick = function () {
        // => this 为当前操作的按钮
        var color = arr[this.myIndex];
        document.body.style.backgroundColor = color;
      };
    }
解决方式五
    /**
     *  解决方式4: 最好的方式 —— 利用事件委托和target事件源(性能最好)
     *  在结构上就存储元素索引
     */
    let btns = document.getElementsByTagName("button");
    let arr = ["red", "green", "blue", "black", "pink"];
    document.body.onclick = function (ev) {
      let target = ev.target,
        targetName = target.tagName;
      // 当前点了按钮,target事件源就是这个按钮
      if (targetName === "BUTTON") {
        var index = target.getAttribute("index");
        this.style.backgroundColor = arr[index];
      }
    };

6、写在最后

相关文章
|
24天前
|
监控 JavaScript 算法
如何使用内存监控工具来定位和解决Node.js应用中的性能问题?
总之,利用内存监控工具结合代码分析和业务理解,能够逐步定位和解决 Node.js 应用中的性能问题,提高应用的运行效率和稳定性。需要耐心和细致地进行排查和优化,不断提升应用的性能表现。
173 77
|
24天前
|
监控 JavaScript
选择适合自己的Node.js内存监控工具
选择合适的内存监控工具是优化 Node.js 应用内存使用的重要一步,它可以帮助你更好地了解内存状况,及时发现问题并采取措施,提高应用的性能和稳定性。
115 76
|
24天前
|
监控 JavaScript 数据库连接
解读Node.js内存监控工具生成的报告
需要注意的是,不同的内存监控工具可能会有不同的报告格式和内容,具体的解读方法可能会有所差异。因此,在使用具体工具时,还需要参考其相关的文档和说明,以更好地理解和利用报告中的信息。通过深入解读内存监控报告,我们可以不断优化 Node.js 应用的内存使用,提高其性能和稳定性。
100 74
|
26天前
|
存储 缓存 JavaScript
如何优化Node.js应用的内存使用以提高性能?
通过以上多种方法的综合运用,可以有效地优化 Node.js 应用的内存使用,提高性能,提升用户体验。同时,不断关注内存管理的最新技术和最佳实践,持续改进应用的性能表现。
116 62
|
26天前
|
监控 JavaScript Java
Node.js中内存泄漏的检测方法
检测内存泄漏需要综合运用多种方法,并结合实际的应用场景和代码特点进行分析。及时发现和解决内存泄漏问题,可以提高应用的稳定性和性能,避免潜在的风险和故障。同时,不断学习和掌握内存管理的知识,也是有效预防内存泄漏的重要途径。
121 52
|
22天前
|
存储 缓存 监控
如何使用内存监控工具来优化 Node.js 应用的性能
需要注意的是,不同的内存监控工具可能具有不同的功能和特点,在使用时需要根据具体工具的要求和操作指南进行正确使用和分析。
65 31
|
26天前
|
缓存 监控 JavaScript
避免在Node.js中出现内存泄漏
总之,避免内存泄漏需要在开发过程中保持谨慎和细心,遵循最佳实践,不断优化和改进代码。同时,定期进行内存管理的检查和维护也是非常重要的。通过采取这些措施,可以有效地降低 Node.js 应用中出现内存泄漏的风险,确保应用的稳定和性能。
|
28天前
|
存储 缓存 自然语言处理
掌握JavaScript闭包,提升代码质量与性能
掌握JavaScript闭包,提升代码质量与性能
|
28天前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包(Closures)
深入理解JavaScript中的闭包(Closures)
|
28天前
|
存储 自然语言处理 JavaScript
深入理解JavaScript的闭包(Closures)
深入理解JavaScript的闭包(Closures)
23 0