闭包全面理解

闭包全面理解

一、JS作用域

1.1 作用域理解(范围)

1.1.1 全局作用域
  • 全局作用域在页面打开时被创建,页面关闭时被销毁

  • 编写在script标签中的变量和函数,作用域为全局,在页面的任意位置都可以访问到

  • 在全局作用域中有全局对象window,代表一个浏览器窗口,由浏览器创建,可以直接调用

  • 全局作用域中声明的变量和函数会作为window对象的属性和方法保存

1.1.2 函数作用域
  • 调用函数时,函数作用域被创建,函数执行完毕,函数作用域被销毁
  • 每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的
  • 在函数作用域中可以访问到全局作用域的变量,在函数外无法访问到函数作用域内的变量
  • 在函数作用域中访问变量、函数时,会先在自身作用域中寻找,若没有找到,则会到函数的上一级作用域中寻找,一直到全局作用域

1.2 作用域深度理解(预编译)

1.2.1 执行期的上下文
  • 当函数被调用的时候会先进行预编译且预编译的时候会创建内部对象(我们无法看到的对象)
  • 函数代码执行的前期,会创建一个执行期上下文的内部对象 AO(作用域)
  • 全局代码执行的前期,会创建一个执行期的上下文的内部对象GO
1.2.2 函数作用域预编译
  • 创建ao对象AO{}

  • 找形参和变量声明将变量和形参名当做AO对象的属性名,值为undefined

  • 实参形参相统一

  • 在函数体里面找函数声明,值赋予函数体

    函数声明: function a() { }

    函数表达式:var b = function () { }

1.2.3 全局作用域的预编译
  • 创建GO对象
  • 找变量声明将变量名作为GO对象的属性名值是undefined
  • 找函数声明值赋予函数体
1.2.4 作用域链图解
var global
function a() {
    var aa = 234
    function b() {
        var bb = 123
        }
    b()
}
a()
  • scope chain : 作用域链
  • AO:函数作用域预编译
  • GO:全局作用域预编译
  • 作用域链: 0(本身)-> 1(上一级)-> 2(全局)

a() 定义
在这里插入图片描述
a() 执行
在这里插入图片描述

b() 定义
在这里插入图片描述
b() 执行
在这里插入图片描述

1.2.5 预编译实际应用(阿里面试题)
// 函数调用后的打印结果?
function fn(a, c) {
    console.log(a)  // function a() { }
    var a = 123
    console.log(a)  // 123
    console.log(c)  // function c() { }
    function a() { }
    if (false) { 
      var d = 678 
    }
    console.log(d)  // undefined
    console.log(b)  // undefined
    var b = function () { }
    console.log(b)  // function () { }
    function c() { } 
    console.log(c)  // function c() { } 
}

fn(1,2)

//理解-函数作用域预编译
         //1. 创建ao对象AO{}
//      AO{
              //2. 找形参和变量声明将变量和形参名当做AO对象的属性名,值为undefined
              /*
               * a: undefined 
               * c: undefined 
               * d: undefined 
               * b: undefined 
               */

              //3. 实参形参相统一
              /*
               * a: undefined 1
               * c: undefined 2
               * d: undefined 
               * b: undefined 
               */

              //4. 在函数体里面找函数声明值赋予函数体
              /*
               * a: undefined 1 function a() { }
               * c: undefined 2 function c() { } 
               * d: undefined 
               * b: undefined 
               */
//      }

二、闭包基础学习

2.1 作用域链理解闭包

  • 图中叉掉部分为a()执行完后销毁的作用域链,直线部分为b()定义时创建的作用域链
var global
function a() {
    var aa = 234
    function b() {
        var bb = 123
        console.log(aa)
        }
    return b
}
let fn = a()
fn()  //234

在这里插入图片描述

  • 解释:

易知,a()执行、b()定义作用域链一样(可以访问的数据相同–AO,GO),当 a() 执行完,a作用域链销毁,此时在外部调用b() 函数时重新进行b定义且b()函数重新构建其本身的作用域链(图中直线部分),这就形成了闭包

2.2 闭包概念

  • 闭包就是能够读取其他函数内部变量的函数
  • (理解)定义在一个函数内部的函数

2.3 闭包作用

  • 可以读取函数内部的变量

  • 让变量始终保存在内存中

    function createIncrementor(start) {
      return function () {
        return start++;
      };
    }
    
    var inc = createIncrementor(5);
    
    inc() // 5
    inc() // 6
    inc() // 7
    

三、闭包实现单例模式

  • 单例模式也称为单体模式,规定一个类只有一个实例,并且提供可全局访问点;
  • 闭包实现登录弹框(单例)
<body>
  <button id="loginBtn">登录</button>

  <script>
    var createLogin = function () {
      var div = document.createElement('div')
      div.innerHTML = `
      <h1 class = "title">我是登录的弹窗</h1>
      <button class = "footerBtn">退出</button>
      `
      div.style.display = 'none'
      document.body.appendChild(div)
      return div
    }

    var getSingle = function (fn) {
      var result
      return function () {
        return result || (result = fn.apply(this, arguments))
      }
    }
    var create = getSingle(createLogin)
    document.getElementById("loginBtn").onclick = function () {
      var loginLay = create()
      loginLay.style.display = 'block'
    }
  </script>
</body>

/*
当点击登录的时候会出现如下内容
登录
我是登录的弹窗
退出
*/

四、闭包应用

4.1 防抖函数

  • 指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间(清除定时器)

  • 理解:在一段固定的时间内,只能触发一次函数,在多次触发事件时,只执行最后一次

//要求:输入框的结果只出现一次(是我在键盘抬起不在输入后的1s)

<body>
  <input type="text" id="input">
  <script>
    var input = document.getElementById('input')

    //防抖的函数
    function debounce(delay) {
      let timer
      return function (value) {
        //保证每一次调用都会清除之前的定时器
        /**
         * 1.我们想要清除的setTimeout
         * 2.所以timer需要一直保存在内存中,否则我们清除的timer为undefined
         */
        clearTimeout(timer)
        timer = setTimeout(function () {
          console.log(value)
        }, delay)
      }
    }
    var debounceFunc = debounce(1000)
    input.addEventListener('keyup',function(e){
      debounceFunc(e.target.value)
    })
  </script>
</body>
  • 防抖前

会打印每一次输入的结果

在这里插入图片描述

  • 防抖后

输入框的结果只出现一次(是我在键盘抬起不在输入后的1s)

在这里插入图片描述

4.2 节流函数

  • 限制一个函数在一定时间内只能执行一次
//要求:在2s内,不管你点击多少次,就打印一次结果

<body>
  <button id="btn">点击</button>
  <script>
    //防抖的函数
    function thro(func, wait) {
      let timerOut
      // 将timerOut保存到闭包中
      return function () {
        if (!timerOut) {
          timerOut = setTimeout(function () {
            func()
            //执行完函数之后进行timerOut初始化
            timerOut = null
          }, wait)
        }
      }
    }
    function handle() {
      //打印一个随机数
      console.log(Math.random())
    }
    document.getElementById('btn').onclick = thro(handle, 2000)
  </script>
</body>

五、 闭包优缺点

5.1 优点

可以重复使用变量,并且不会造成变量污染

5.2 缺点

比普通函数更占用内存,会导致网页性能变差,在IE下容易造成内存泄露。

  • 内存泄漏:

    指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果

  • 闭包造成的内存泄漏:

    一旦循环引用或创建闭包,就会占据大量内存,可能会引起内存泄漏

  • 解决方案:

    在退出函数之前,将不使用的局部变量全部删除,可以使变量赋值为null

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值