【JavaScript】关于闭包

前言

这篇文章是有关 JS 闭包概念以及优缺点和经典使用场景

在这里插入图片描述

1. 闭包是什么?

  • 官方说法:闭包就是指有权访问另一个函数作用域中的变量的函数。
  • MDN 说法:闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。
  • 其他理解:本质就是上级作用域内变量的生命周期,因为被下级作用域内引用,而没有被释放。就导致上级作用域内的变量,等到下级作用域执行完以后才正常得到释放。
function makeFunc() {
    var name = "Mozilla"
    function displayName() {
        alert(name)
    }
    return displayName
}

var myFunc = makeFunc() 
myFunc()  // Mozilla

var myFunc = makeFunc()中的makeFunc()创建了相应的函数执行上下文,在该上下文中声明了一个局部变量name 并且声明了一个函数displayname,最后返回displayname函数的指针(或引用),即把function displayName(){alert(name);} 返回给了myFunc变量
var myFunc = makeFunc()执行结束后,相应的函数执行上下文就从栈中弹出,一般情况下,其变量也会随之销毁 ,但是myFunc()调用了myFunc,即执行了function displayName(){alert(name);},这里的name引用着makeFunc里的变量name, 所以其变量并不会随着销毁,相当于封装了一个私有变量.
这就是闭包。

2. 闭包的执行顺序

 1: function createCounter() {
 2:   let counter = 0
 3:   const myFunction = function() {
 4:     counter = counter + 1
 5:     return counter
 6:   }
 7:   return myFunction
 8: }
 9: const increment = createCounter()
10: const c1 = increment()
11: const c2 = increment()
12: const c3 = increment()
13: console.log('example increment', c1, c2, c3)

第 1-8 行。我们在全局执行上下文中创建了一个新的变量createCounter,并赋值了一个的函数定义。

第9行。我们在全局执行上下文中声明了一个名为increment的新变量。

第9行。我们需要调用createCounter函数并将其返回值赋给increment变量。

第 1-8行。调用函数,创建新的本地执行上下文。

第2行。在本地执行上下文中,声明一个名为counter的新变量并赋值为 0;

第 3-6行。声明一个名为myFunction的新变量,变量在本地执行上下文中声明,变量的内容是为第4行和第5行所定义。

第7行。返回myFunction变量的内容,删除本地执行上下文。变量myFunction 和counter不再存在。此时控制权回到了调用上下文。

第9行。在调用上下文(全局执行上下文)中,createCounter返回的值赋给了increment,变量increment现在包含一个函数定义内容为createCounter返回的函数。它不再标记为myFunction````,但它的定义是相同的。在全局上下文中,它是的标记为labeledincrement```。

第10行。声明一个新变量 c1。

继续第10行。查找increment变量,它是一个函数并调用它。它包含前面返回的函数定义,如第4-5行所定义的。

创建一个新的执行上下文。没有参数,开始执行函数。

第4行。counter=counter + 1。在本地执行上下文中查找counter变量。我们只是创建了那个上下文,从来没有声明任何局部变量。让我们看看全局执行上下文。这里也没有counter变量。Javascript会将其计算为counter = undefined + 1,声明一个标记为counter的新局部变量,并将其赋值为number 1,因为undefined被当作值为 0。

第5行。我们变量counter的值 1,我们销毁本地执行上下文和counter变量。

回到第10行。返回值1被赋给c1。

第11行。重复步骤10-14,c2也被赋值为1。

第12行。重复步骤10-14,c3也被赋值为1。

第13行。我们打印变量c1 c2和c3的内容。

详细请看链接我从来不理解JavaScript闭包,直到有人这样向我解释它

2. 闭包的优缺点?

优点:私有化数据,在私有化数据的基础上保持数据
缺点:可能会导致内存泄漏,内部的变量不会被自动回收掉(不正当的闭包会导致内存泄漏,这时面试官可能会问到关于垃圾回收机制

3.闭包的作用

  • 保护函数的私有变量不受外部的干扰。形成不销毁的栈内存。
  • 保存,把一些函数内的值保存下来。闭包可以实现方法和属性的私有化

4.闭包的经典应用场景

防抖,节流 …
vue :响应式原理

  • return 回一个函数
var n = 10
function fn(){
    var n =20
    function f() {
       n++;
       console.log(n)
     }
    return f
}

var x = fn()
x() // 21
  • 函数作为参数
var a = '千堆雪'
function foo(){
    var a = 'foo'
    function fo(){
        console.log(a)
    }
    return fo
}

function f(p){
    var a = 'f'
    p()
}
f(foo())
/* 输出
 *   foo
/

使用 return fo 返回回来,fo() 就是闭包,f(foo()) 执行的参数就是函数 fo,因为 fo() 中的 a 的上级作用域就是函数foo(),所以输出就是foo

  • IIFE(自执行函数)
var n = '千堆雪';
(function p(){
    console.log(n)
})()
/* 输出
 *   千堆雪
/

同样也是产生了闭包p(),存在 window下的引用 n。

  • 循环赋值
for(var i = 0; i<10; i++){
  (function(j){
       setTimeout(function(){
        console.log(j)
    }, 1000) 
  })(i)
}

因为存在闭包的原因上面能依次输出1~10,闭包形成了10个互不干扰的私有作用域。将外层的自执行函数去掉后就不存在外部作用域的引用了,输出的结果就是连续的 10。为什么会连续输出10,因为 JS 是单线程的遇到异步的代码不会先执行(会入栈),等到同步的代码执行完 i++ 到 10时,异步代码才开始执行此时的 i=10 输出的都是 10。

  • 使用回调函数就是在使用闭包
window.name = '千堆雪'
setTimeout(function timeHandler(){
  console.log(window.name);
}, 100)

经典面试题

  • for 循环和闭包(号称必刷题)
var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0]();
data[1]();
data[2]()
/* 输出
    3
    3
    3
/

这里的 i 是全局下的 i,共用一个作用域,当函数被执行的时候这时的 i=3,导致输出的结构都是3。
使用闭包改善上面的写法达到预期效果,写法1:自执行函数和闭包

var data = [];

for (var i = 0; i < 3; i++) {
    (function(j){
      setTimeout( data[j] = function () {
        console.log(j);
      }, 0)
    })(i)
}

data[0]();
data[1]();
data[2]()

写法2:使用 let

var data = [];

for (let i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0]();
data[1]();
data[2]()

let 具有块级作用域,形成的3个私有作用域都是私有的互不干扰。

闭包和作用域

简单来说,作用域 指程序中定义变量的区域,它决定了当前执行代码对变量的访问权限。

javascript 中大部分情况下,只有两种作用域类型:

  • 全局作用域:全局作用域为程序的最外层作用域,一直存在。
  • 函数作用域:函数作用域只有函数被定义时才会创建,包含在父级函数作用域 / 全局作用域内。

当函数被执行时,总是先从函数内部找寻局部变量,如果找不到相应的变量,则会向创建函数的上级作用域寻找,直到找到全局作用域为止,这个过程被称为作用域链

闭包的本质是利用了作用域的机制,来达到外部作用域访问内部作用域的目的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纵有千堆雪与长街

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值