前端开发核心知识进阶 1.2 “老手”也会在闭包上翻车

闭包是JS中最基本、最重要的概念之一,闭包绝不是一个单一的概念,它涉及作用域、作用域链、执行上下文、内存管理等多重知识点。

1.作用域

首先是作用域,在ES6之前只有函数作用域和全局作用域之分,ES6中通过let和const声明变量的块级作用域,使得JS的作用域更加丰富。下面说说变量提升和暂时性死区。

下面看个var声明变量的例子

function f1(){
  console.log(b);//undefined
  var b = 2
}
f1()

var声明的变量会提前声明,但是不赋值,所以输出undefined,而这里的var换成let、const的话,会报referenceError错误。原因是let、const声明的变量,在未声明之前的区域会形成一个“死区”,专业名为TDZ(Temporal Dead Zone)它始于函数开头,终于变量声明的语句,在这个“死区”中调用变量都会报错,在死区外就可以正常访问变量。暂时性死区有种极端的情况就是,函数的参数默认值设置也会受它的影响

例子如下

function fan(a1 = a2, a2){
  console.log(a1,a2);
}
fan(1,2)
fan(null,2)
fan(undefined,2)

调用了三次fan,分别输出什么呢,第一次是1,2,第一次是null,2,第三次报错了。第三次因为传的是undefined,默认没传,a1赋值为a2,但此时a2没有声明,所以报错。

2.执行上下文和调用栈

执行上下文就是当前代码的执行环境/作用域,和作用域相辅相成,但又是俩个完全不同的概念。

2.1 代码执行的俩个阶段

JS代码执行分俩个阶段,一个是代码预编译阶段,一个是代码执行阶段

在预编译阶段,会进行变量的声明,对变量进行提升,但值为undefined,对所有非表达式的函数声明进行提升

在执行阶段,会执行代码逻辑,执行上下文会在这个阶段全部创建完成

下面看个例子

function fbn(){
  console.log('a');
}

var fbn = function(){
  console.log('b');
}

fbn()

这里会输出b,如果调换顺序呢

var fbn = function(){
  console.log('b');
}

function fbn(){
  console.log('a');
}

fbn()

这里输出还是b

为什么呢?因为预编译阶段对fbn进行变量提升,并未赋值,然后对函数fbn进行创建声明,接下来才轮到fbn的赋值,fbn被赋值的内容是函数体为console.log('b')的函数,所以输出b

作用域在预编译阶段确定,,但作用域链是在执行上下文的创建完成生成的,因为函数调用才会创建相应的执行上下文。执行上下文包括变量对象,作用域链及this的指向

2.2 调用栈

在a函数中调用了b函数,在b函数中调用了c函数,这样便形成了一系列的调用栈,a、b、c依次入栈,c执行完出栈,b执行完出栈,最后a执行完出栈,形成调用栈。

正常来讲,在函数执行完毕并出栈时,函数内的局部变量在下一个垃圾回收(GC)节点被回收,该函数的执行上下文会被销毁,这也是外界无法访问函数内定义的变量的原因,也就是说,只有在函数执行时,相关函数才可以访问函数内的变量,变量会在预编译阶段被创建,在执行阶段被激活,在函数执行结束被销毁

3.闭包

讲了这么多前置概念,相信你大概理解闭包的工作原理了吧。

在前述内容中,我们知道正常情况下外界无法访问函数内的变量,函数执行后,其变量会被销毁。但如果在a函数内,返回一个b函数,且这个返回的b函数使用a函数内的变量,那么外界可以通过b函数访问a函数的变量了,这就是闭包的原理。

3.1 内存管理

内存空间分为堆空间和栈空间

undefined,string,null,number,boolean等等基本数据类型存放在栈空间,有固定的内存大小

object、array、function等等引用类型存放在堆空间,内存大小并不固定

3.2 内存泄漏

内存泄漏是指内存空间明明已经不再被使用,但由于某种原因没有被释放的现象。

内存泄漏的危害非常直观,它会导致程序运行缓慢,甚至崩溃

看个例子

var ele = document.querySelector('div')
ele.index = '0'
ele.parentNode.removeChild(ele)

这里只是把节点移除了,但变量ele依然存在,该变量占用的内存无法释放

所以应该添加 ele = null 更加稳妥

再来一个例子

var ele = document.querySelector('div')
ele.innerHTML = '<button id="btn">点击</button>'
var btn = document.querySelector('#btn')
btn.addEventListener('click',function(){
  //...
})
ele.innerHTML = ''

这里button已经在DOM中移除了,但是事件处理句柄还在,该节点变量无法回收,所以还需要添加removeEventListener函数,防止内存泄漏

除了开发者主动保证回收外,大部分场景下浏览器都会依靠标记清除和引用计数俩种算法回收

一个闭包的例子

function fcn(){
  let value = 1

  function fdn(){
    console.log(value);
  }

  return fdn
}
var fdn = fcn()
fdn()

在谷歌浏览器的V8引擎中执行,在fdn函数中设置断点,会发现存在闭包变量value

最后,来个例题实战

var fn = null
const fun = () =>{
   var a = 1
  function ffn(){
    console.log(a);
    console.log(b);
  }
  fn = ffn


}
let bar = () =>{
  var b = 2
  fn()
}
fun()
bar()

运行结果是什么呢,运行结果是 1和ReferenceError:b is not defined,fn被赋值为ffn,变量b并不在它的作用域链上,如果在fun里或全局声明b=2,那便会输出2

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 什么是盒模型? 盒模型是指一个 HTML 元素在渲染时所占据的空间,包括元素的内容、内边距、边框和外边距。盒模型可以通过 CSS 的盒模型属性来控制。 2. 什么是浏览器渲染机制? 浏览器渲染机制指的是浏览器解析 HTML、CSS 和 JavaScript 代码并将其转化为可视化的网页的过程。浏览器渲染过程包括解析 HTML 文档、构建 DOM 树、构建 CSSOM 树、合并 DOM 树和 CSSOM 树、布局和绘制等几个段。 3. 什么是闭包? 闭包是指在函数内部定义一个函数,并返回这个函数,从而形成一个封闭的作用域,使内部函数可以访问到外部函数的变量和参数。闭包可以用来实现函数的柯里化、模块化等一些高级应用。 4. 什么是 CSS 预处理器? CSS 预处理器是一种扩展了 CSS 语言的工具,可以在 CSS 中使用变量、嵌套、混合、继承等功能,从而提高代码的可维护性和可扩展性。常见的 CSS 预处理器有 Sass、Less 和 Stylus 等。 5. 什么是响应式设计? 响应式设计是指网站或应用能够根据用户设备的不同屏幕尺寸和分辨率,自动调整布局和样式,以适应不同的设备和浏览环境。响应式设计可以提升用户体验、提高页面的可访问性,并有利于 SEO。 6. 什么是 AJAX? AJAX 是 Asynchronous JavaScript and XML 的缩写,是一种通过 JavaScript 和 XML 实现异步通信的技术。通过 AJAX,可以在不刷新整个页面的情况下,向服务器发送请求并获取响应,从而实现动态更新页面内容的效果。 7. 什么是 MVC 模式? MVC 模式是一种软件架构模式,将应用程序分为三个部分:模型(Model)、视图(View)和控制器(Controller)。模型负责处理数据,视图负责展示数据,控制器负责处理用户输入和业务逻辑。MVC 模式可以提高代码的可维护性和可扩展性。 8. 什么是跨域? 跨域是指在浏览器中,一个网页的 JavaScript 代码试图访问另一个网页的数据时,由于浏览器的同源策略,导致无法访问。常见的跨域解决方案包括 JSONP、CORS、代理等。 9. 什么是 Webpack? Webpack 是一个现代化的前端构建工具,它可以将多个 JavaScript、CSS、HTML 文件打包成一个或多个静态资源文件,并提供了丰富的插件和 loader,用于优化代码、压缩文件、处理图片等。Webpack 可以帮助开发者提高开发效率和代码质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值