JS----闭包

执行上下文/作用域链/闭包

1. 对闭包的理解

闭包是指有权访问另一个函数作用域中变量的函数。创建闭包最常见的方法就是在一个函数中创建另一个函数,创建的函数可以访问到当前函数的局部变量。
闭包有两个常用的用途:

  • 在函数外部能够访问到函数内部的变量。可以通过外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。
  • 是已经运行结束的函数中的上下文变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收

在JS中,闭包存在的意义就是让我们可以在函数外部访问函数内部的变量。
比如,函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。

function A() {
  let a = 1
  window.B = function () {
      console.log(a)
  }
}
A()
B() // 1

当我们将B作为返回值,就可以得到A中的变量的值了。

下面有一个在面试中关于必报的典型例子:

for (var i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i)
  }, i * 1000)
}

因为setTimeout()是一个异步函数,所以会先执行完for,此时i已经=6,所以会打印5个6。可以使用三种方式解决上述问题:

  1. 使用let定义i
for (let i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i)
  }, i * 1000)
}
  1. 使用闭包
for (var i = 1; i <= 5; i++) {
  (function(j) {
    setTimeout(function timer() {
      console.log(j)
    }, j * 1000)
  })(i)
}

在上面的代码中,使用立即执行函数将i传入函数内部,这个时候值就被固定在参数j上面不会改变,当执行timer时,就可以使用外部函数的变量j。

  1. 使用setTimeout的回调函数
for (var i = 1; i <= 5; i++) {
  setTimeout(function timer(j) {
    console.log(j)
  }, i * 1000, i)
}

使用闭包要注意的点:

  1. 因为使用闭包函数中的变量会一直保存到内存中,内存消耗很大,容易浪费资源,解决办法是在退出函数之前,将不用的变量全部删除
  2. 闭包会在父函数外部,改变父函数内部的值。

闭包的优点和缺点

优点:可以在全局重复使用变量,便不会造成变量污染。可以用来定义私有属性和私有方法。

缺点:比普通函数更消耗内存,会导致网页性能变差。

闭包的应用

  1. 防抖与节流
    防抖
function debounce(fn, delay) {
  let timer = null   // 借助闭包
  return function () {
    if (timer) {
      clearTimeout(timer)   // 取消由 setTimeout() 方法设置的定时操作。
    }
    timer = setTimeout(fn, delay)
  }
}

节流

// 定时器版
function throttleTime(fn, delay) {
  let timer = null   // 表示当前函数是否在执行
  return function () {
    if (!timer) {
      timer = setTimeout(() => {
        fn()
        timer = null
      }, delay)
    }
  }
}

// 时间戳版
function throttleTimeStamp(fn, delay) {
  let preTime = Date.now()
  return function () {
    let nowTime = Date.now()
    if (nowTime - preTime >= delay) {
      // 保存函数的执行时间
      preTime = Date.now()
      return fn()
    }
  }
}
  1. 函数柯里化
function add() {
  // 将参数赋值给args
  // 因为arguments是对象,要将它转化为数组
  let args = Array.prototype.slice.call(arguments)

  let inner = function () {
    // 将inner接收到的参数加到args中
    args.push(...arguments)
    // 因为不知道有多少次add调用,所以要一直递归
    return inner
  }

  // 因为在返回的inner函数之前被调用了toString()
  // 所以返回的其实是一个字符串
  // 这里重写toString()方法,进行累加求和
  inner.toString = function () {
    return args.reduce(function (pre, cur) {
      return pre += cur
    })
  }
  return inner
}

const res = add(1, 5, 6)(2)(3)(4).toString()
console.log(res)

2.作用域、作用域链

全局作用域

  • 最外层函数和最外层函数外面定义的变量拥有全局作用域
  • 所有未定义直接赋值的变量自动声明为全局作用域
  • 所有window对象的属性拥有全局作用域
  • 全局作用域容易引起命名冲突问题,过多的全局作用域变量会污染全局命名空间
    函数作用域
  • 函数作用域声明在函数内部
  • 内层作用域可以访问到外部,反之不行

块级作用域

  • 使用let和const指令可以声明块级作用域
  • let和const声明的变量不会有变量提升,但不可重复声明

作用域链
在当前作用域中查找所需变量,但该作用于没有这个变量,那么这个变量就是自由变量,如果在自己的作用域找不到该变量就去父级作用域查找,知道访问到window的作用域终止,这一层层的关系就是作用域链。

3.执行上下文

  1. 全局执行上下文
    任何不在函数内部的都是全局执行上下文,它首先会创建一个全局window对象,并且设置this的值等于这个全局对象,一个程序中只有一个全局上下文
  2. 函数执行上下文
    当一个函数被调用时,就会为该函数创建一个新的执行上下文,函数的上下文可以有任意多个

执行上下文栈

  • JS使用执行上下文栈来管理执行上下文
  • 当JS执行代码时,首先遇到全局代码,会创建一个全局执行上下文并且压入执行栈中,每当遇到一个函数调用,就会为该函数创建一个新的执行上下文并压入栈顶,引擎会执行位于执行上下文栈顶的函数,当函数执行完成之后,执行上下文从栈中弹出,继续执行下一个上下文。当所有的代码都执行完毕之后,从栈中弹出全局执行上下文。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值