JS核心原理理解闭包

Dax
• 阅读 1539

前置概念 在正式看闭包之前,我们先来学习一下前置知识,那就是JS中的作用域,我们知道,在ES5之中,作用域分为两种:全局作用域和函数作用域,随着ES6的到来,新增了块级作用域,想更好的理解闭包,那么搞清楚作用域是首要条件

全局作用域

我们知道,对于变量而言,我们一般会分成两类:全局变量和局部变量,一般定义在最外围环境的为全局变量,定义在函数当中的为局部变量,在web浏览器中,全局变量一般挂载在window对象上,所以全局变量在任何地方都可以进行访问,但是局部变量便只能在所在作用域内才可以被访问

我们结合一个例子来简单的理解一下: var globalVar = '全局变量' function func() { console.log(globalVar) // 全局变量 var localVar = '局部变量' console.log(localVar) // 局部变量 } func() console.log(globalVar) // 全局变量 console.log(localVar) // 报错:localVar is not defined

========分割线================ function func() { globalVar = '全局变量' } console.log(globalVar) // 全局变量 console.log(window.globalVar) // 全局变量 从这段代码我们可以发现,globalVar作为全局变量和localVar作为局部变量的特点:

全局变量拥有全局作用域,无论在哪都可以访问,在web浏览器端是挂载在window对象上面的 局部变量是被定义在特有的局部作用域内,并且只能在所在的作用域内被访问 JS中没有经过定义直接被赋值的变量默认为全局变量(不考虑严格模式下) 函数作用域

函数作用域,顾名思义,那就是函数内部的作用域,在函数内部定义的变量称之为函数变量,那么此时函数内部定义的变量便只能在该函数中被调用

一样看一个简单的例子: function fun() { var funVar = '函数内变量' console.log(funcVar) // 函数内变量 } fun() console.log(funcVar) // funcVar is not defined 如果只是根据前面的作用域的内容来看,这个程序是无法正常运行的,会报错,说变量a没有定义,但是我们结合了闭包的定义之后我们发现它是可以正常打印的,也就是说在func2里面访问到了func1中的变量,结合这些现在你是否理解了红宝书中对闭包的定义了呢?

为什么会产生闭包

了解了闭包的定义和基本概念,那接下来我们再来具体分析一下为什么会产生闭包呢?我们先来了解一个概念:作用域链,其实比较好理解,比如我们在访问一个变量的时候,会首先在所在作用域内查找,如果没有找到就会往上找到上层作用域内,一层层向上直到找到或者到达顶层作用域window(web浏览器端)为止,这整个形成的一个链条状的就是作用域链

我们也来看一个简单的例子: var b = '全局作用域变量' function func1() { var b = 'func1作用域变量' function func2() { var b = 'func2作用域变量' console.log(b) // func2作用域变量 } return func2 } func1()() 我们来看这个🌰,此时func1的作用域就指向全局作用域和自己本身作用域,而func2就从下往上依次链接自己本身->func1作用域->全局作用域

所以还记得MDN中那句话吗:“一个函数和对其周围状态的引用捆绑在一起”,也就是说产生闭包的原因就是需要当前函数内保持对上层作用域的引用

闭包的具体应用

前面两部分分析了一下闭包的主要内容,开篇我们就说起闭包在我们日常开发其实是非常常见的,只是可能我们平时并没有太过注意,那接下来我们就来盘点一下闭包的一些具体场景

1、我们平时肯定都有用过定时器、事件监听以及Ajax请求等这类使用回调函数的,基本都利用到了闭包,使用定时器的例子,如防抖/节流: // 防抖 const debounce = (fn,delayTime) => { let timerId, result return function(...args) { timerId && clearTimeout(timerId) timerId = setTimeout(()=>result=fn.apply(this,args),delayTime) return result } } // 节流 const throttle = (fn, delayTime) => { let timerId return function(...args) { if(!timerId) { timerId = setTimeout(()=>{ timerId = null return result = fn.apply(this,args) },delayTime) } } } 2、IIFE(立即执行函数),这种函数比较特别,它拥有独立的作用域,不会污染全局环境,但是同时又可以防止外界访问内部的变量,所以很多时候会用来做模块化或者模拟私有方法

举个例子: var global = '全局变量' let Anonymous = (function() { var local = '内部变量' console.log(global) // 全局变量 })() console.log(Anonymous.local) // local is not defined

=======分割线==============

var global = '全局变量' let Anonymous = (function() { var local = '内部变量' console.log(global) // 全局变量 return { afterLocal: local } })() console.log(Anonymous.afterLocal) // 内部变量 3、函数作为参数传递的形式 var a = '全局变量' function func1() { var a = 'func1内部变量' function func2() { console.log(a) } func3(func2) // func1内部变量 } function func3(fn) { // 闭包产生 fn() }

func1() 经典面试题 我们先来看具体代码: for(var i = 1; i < 6; i++){ setTimeout(function() { console.log(i) }, 0) } 这道题相信我们很多人曾经都遇到过,我们打印出来结果发现是打印的5个6,那为什么是这个结果呢?那如果我想改造之后让他打印12345该怎么做呢?

首先我们来回答为什么会是这个结果,以前我们主要是是站在eventLoop的角度来说的,现在我们可以从两部分来说:

因为setTimeout是宏任务,但是JS是单线程,由于eventLoop机制,需要先执行主线程同步代码之后才会执行宏任务,所以会打印全是6 因为setTimeout是一种闭包,它引用上层作用域中的全局变量i,而此时i已经是6了,所以就全部打印的都是6 那我们怎么改造让他按照顺序打印结果呢,这里提供几种常见的方法:

1、ES6的let:这是改造成本最小的一种方法,因为let创造了块级作用域,代码的执行以块为单位来进行,便可以达到我们的要求

for(let i = 1; i < 6; i++){ setTimeout(function() { console.log(i) }, 0) } 2、IIFE(立即执行函数):利用这种方法每次循环的时候,都将此时的变量i传入到setTimeout当中

for(let i = 1; i < 6; i++){ (function(j) { setTimeout(function() { console.log(j) }, 0) })(i) } 3、利用setTimeout的第三个参数:我们一般就只用前两个参数,第三个参数其实就是可以进行传参数给函数

for(let i = 1; i < 6; i++){ setTimeout(function(j) { console.log(j) }, 0, i) } 等等。。。。。。。。。。。。。。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
你不可不知的JS面试题(第三期)
1、什么是闭包?如图所示,闭包就是一个定义在函数内部的函数,其作用是将函数内部和函数外部连接起来。大家知道,作用域的问题,就是在函数内部定义的变量称为局部变量,外部取不到值。下面我们通过代码来更加详细地看一下:function A()       let x  1;       return function B()           c
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Jacquelyn38 Jacquelyn38
3年前
你所知道的JS变量作用域
变量的作用域,指的是变量在脚本代码中的可读、可写的有效范围,也就是脚本代码中可以使用这个变量的区域。在ES6之前,变量的作用域主要分为全局作用域、局部作用域(也称函数作用域)两种;在ES6及其之后,变量的作用域主要分为全局作用域、局部作用域、块级作用域这3种。相应作用域变量分别称为全局变量、局部变量、块级变量。全局变量声明在所有函数之外;局部变量是在函数体内
Wesley13 Wesley13
3年前
ES6 简单整理
1.变量声明let和constlet与const都是块级作用域,letfunctionname(){letage12;//age只在name()函数中存在}constconstname'tom'name'jack'//
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
9个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这