首页 > 系统相关 >前端JavaScript面试重难点: 闭包+内存泄漏+垃圾回收机制

前端JavaScript面试重难点: 闭包+内存泄漏+垃圾回收机制

时间:2024-09-17 19:25:14浏览次数:15  
标签:闭包 f1 f2 函数 function JavaScript 重难点 内存

前置知识!!!
闭包 是Javascript语言的一个重难点, 也是它的特色, 很多高级应用都要依靠闭包来实现。在各种专业文献上学习"闭包"的时候, 就一个感觉 – “抽象” !

特别是学习内存泄漏的时候, 没想明白为什么使用闭包的时候 不及时清除函数中的元素会导致内存泄漏, 直到我的第一次面试结束之后, 回顾的时候把这几个知识串联了起来, 一切都明朗了。

这里先给大家看个 解除闭包的引用, 释放数据内存 的例子(看不懂没关系, 后面才是正文开始)

function createClosure() {  
    let externalData = { /* 一些数据 */ };  
  
    return function innerFunction() {  
        // 使用externalData做一些事情  
    };  
}  
  
let myClosure = createClosure();  
// ... 使用myClosure做一些事情  
  
// 当你不再需要myClosure时  
myClosure = null; // 移除对闭包的引用  
  
// 注意:如果externalData仍然被其他闭包或外部代码引用,则它不会被回收  
// 你需要确保没有其他引用指向它,或者它本身也应该被设置为null

这里还要补充一点 垃圾回收机制 的知识, 方便后续理解:

前端中的垃圾回收机制(Garbage Collection, 简称GC)是一种 自动内存管理机制 ,它负责找出并释放那些不再被使用的内存空间,以防止内存泄漏,从而优化程序的性能。在JavaScript等前端技术中,垃圾回收机制扮演着至关重要的角色。

好了, 前面看不懂没关系, 现在正文开始:
一、变量的作用域:
想要理解闭包, 首先必须理解 Javascript 特殊的变量作用域。
变量的作用域分为两种: 全局变量 和 局部变量。
函数内部可以直接读取全局变量, 而函数外部自然无法读取函数内的局部变量

  var n=999;
  function f1(){
    alert(n);
  }
  f1(); // 999
  function f1(){
    var n=999;
  }
  alert(n); // error

这里有一个地方需要注意,函数内部声明变量的时候,一定要使用 var 命令。如果不用的话,你实际上声明了一个全局变量!

  function f1(){
    n=999;
  }
  f1();
  alert(n); // 999

二、如何从外部读取局部变量?
由于业务场景的不同,我们有时候需要得到函数内的局部变量, 由于该局部变量需要收到保护, 不能让外部环境直接更改。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。 如:

  function f1(){
    var n=999;
    function f2(){
      alert(n); // 999
    }
  }

在上面的代码中,函数 f2 就被包括在函数 f1 内部,这时 f1 内部的所有局部变量,对 f2 都是可见的。但是反过来就不行,f2 内部的局部变量,对 f1 就是不可见的。这就是 Javascript 语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们就可以在f1外部读取它的内部变量了!

  function f1(){
    var n=999;
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999

三、闭包的概念
上一节代码中的f2函数, 就是闭包
各种专业文献上的"闭包"(closure)定义非常抽象,很难看懂。我的理解总结成一句话就是:

闭包就是在全局环境中, 通过新建对象, 调用函数的方式 已达到读取父对象变量的目的, 而这个调用的函数就是闭包。

由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。

四、闭包的用途
可能这个时候我们就会想到, f1 函数里面的不是局部变量吗? 为什么他在全局环境中, 还能够一直存在?
要彻底理解, 很关键的点的是在 "var result=f1(); "这里!!!
这里的 f1() return返回的是 f2 函数! 而函数是引用数据类型, 内存是放在堆里面的, 而 result 其实只是一个地址, 指向 f2 函数内存存放的地址, 这个时候 f2 函数就成了全局变量 result 的依赖性, f2 函数就必须保存在内存当中, 不被 垃圾回收 清理掉。
而 f1 函数是 f2函数的父对象, f2 存在, f1 就也要存在, 所以 f1 函数的内部变量就不会被 垃圾回收 清理掉!!
所以说, 刚才 “解除闭包的引用, 释放数据内存” 例子中的 "myClosure = null; // 移除对闭包的引用 "就是用来释放 f1 函数的内部变量。提一个极端的例子就可以理解为什么内存泄漏需要被重视:

在一个网页应用中,你可能有一个循环,该循环不断创建新的闭包,而这些闭包都引用了外部作用域中的大型数据结构(比如大型数组或对象)。如果这些闭包被不当地存储(例如,作为全局变量或DOM元素的事件处理器),那么它们将阻止垃圾回收器回收那些大型数据结构的内存,从而导致内存泄漏。

五、使用闭包的注意点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

最后, 这里放我面试中考到的一道闭包代码题给大家练练:

/**
 * 不能改动原代码
 * f 函数里面的console.log只能执行一次
 * 第二次调用开始就只能返回undefined
 */
function once(fn) {

}
var f = function() {
    console.log('被执行了')
}

var onceF = once(f)
onceF() // 被执行了
onceF() // undefined

标签:闭包,f1,f2,函数,function,JavaScript,重难点,内存
From: https://blog.csdn.net/Mz0127/article/details/142177921

相关文章

  • Vue学习笔记3:对比纯JavaScript和Vue实现数据更新的实时视图显示
    0前言在页面中,要实现数据更新的视图实时显示,纯JavaScrip需要手动编写代码来处理数据和视图之间的更新。而Vue提供了数据绑定的能力,使得数据和视图保持同步。我们通过一个实例来体验两者的差别。我们设计一个页面,在页面里提供一个文本框,用户可以在文本框输入内容,然后我们在文本框下......
  • Js高级总结1 JavaScript数据类型
    文章目录数据类型判断引用变量赋值问题js引擎如何管理内存对象函数生命周期回调函数前端立即执行函数(IIFE)闭包函数中的this数据类型1.1基本数据类型string:任意字符串number:任意数字null:nullboolean:true/falseundefined:undefined1.2对象类型object:任意对......
  • 这段HTML、CSS和JavaScript代码构成了一个简单的网页游戏,名为“Catch The Insect”
    这段HTML、CSS和JavaScript代码构成了一个简单的网页游戏,名为“CatchTheInsect”。以下是对代码的详细分析和说明:HTML部分基础结构:使用<!DOCTYPEhtml>声明文档类型,确保浏览器以标准模式渲染页面。<htmllang="en">标签定义了文档的语言为英语。<head>部分包含了字符集......
  • SpreadJS 17.1.5 -JavaScript 电子表格组件
    SpreadJS JavaScript电子表格组件示例全球最畅销的JavaScript电子表格,包含500多个Excel函数快速提供真正类似Excel的电子表格体验-完全不依赖Excel。创建财务应用程序,仪表板,图表,数据透视表,性能基准,科学实验室笔记本以及其他类似的JavaScript电子表格应用程序......
  • JavaScript 上下文 和 执行栈
    执行上下文执行上下文是对JavaScript代码执行环境的概念抽象,只要有js代码运行,它就一定运行在执行上下文中执行上下文分为三种全局执行上下文:也就是浏览器的全局对象window函数执行上下文:每次函数被调用时都会(才会)创建一个新的执行上下文Eval执行上下文:运行在Eval函数中......
  • 深入理解 ECMAScript 和 JavaScript
    目录ECMAScript是什么?JavaScript是什么?示例ECMAScript示例JavaScript示例总结ECMAScript是什么?ECMAScript是一个由国际标准化组织ECMA(欧洲计算机制造商协会)维护的脚本语言标准。这个标准定义了一种脚本语言的基本特性,包括语法、类型系统、内置对象、关键字等......
  • JavaScript-apply、bind、call
    call、apply、bind作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this指向区别 applyapply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次当第一个参数......
  • JavaScript 手写new操作符
    new关键字的工作步骤创建一个新的对象obj将对象与构建函数通过原型链连接起来将构建函数中的this绑定到新建的对象obj上根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理constrecodeNew=function(Func,...args){//获取函数对象......
  • 【油猴脚本】00008 案例 Tampermonkey油猴脚本,动态渲染表格-实现页面动态-添加表格列,
    前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦......
  • kettle从入门到精通 第八十五课 ETL之kettle kettle中javascript步骤调用外部javascri
     场景:交流学习群里面有小伙伴咨询kettle中的javascript代码步骤如何调用外部js文件中的函数,觉得有点意思的,于是就抽时间整理了一下。 1、外部js文件为test.js,代码如下:functiontest(param){return"接收到了参数"+param;}2、当时没有过多考虑,在本地简单写了个demo测......