经典面试题【作用域、闭包、变量提升】,带你深入理解掌握!

news2025/1/18 10:57:12

前言:哈喽,大家好,我是前端菜鸟的自我修养!今天给大家分享经典面试题【作用域、闭包、变量提升】,并提供具体代码帮助大家深入理解,彻底掌握!原创不易,如果能帮助到带大家,欢迎收藏+关注哦 💕

🌈🌈文章目录

一、作用域

1.局部作用域

1.1 函数作用域

1.2 总结

1.3 块作用域

1.4 总结

2. 全局作用域

二、作用域链

三、闭包

四、变量提升


一、作用域

目标:了解作用域对程序执行的影响作用域链的查找机制,使用闭包函数创建隔离作用域避免全局变量污染。

作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问,作用域分为全局作用域局部作用域

1.局部作用域

局部作用域分为函数作用域块作用域

1.1 函数作用域

在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。

  <script>
    // 声明 counter 函数
    function counter(x, y) {
      // 函数内部声明的变量
      const s = x + y
      console.log(s) // 18
    }
    // 设用 counter 函数
    counter(10, 8)
    // 访问变量 s
    console.log(s)// 报错
  </script>

1.2 总结

  1. 函数内部声明的变量,在函数外部无法被访问

  2. 函数的参数也是函数内部的局部变量

  3. 不同函数内部声明的变量无法互相访问

  4. 函数执行完毕后,函数内部的变量实际被清空了

1.3 块作用域

在 JavaScript 中使用 {} 包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。

 
  <script>
    {
      // age 只能在该代码块中被访问
      let age = 18;
      console.log(age); // 正常
    }
    
    // 超出了 age 的作用域
    console.log(age) // 报错
    
    let flag = true;
    if(flag) {
      // str 只能在该代码块中被访问
      let str = 'hello world!'
      console.log(str); // 正常
    }
    
    // 超出了 age 的作用域
    console.log(str); // 报错
    
    for(let t = 1; t <= 6; t++) {
      // t 只能在该代码块中被访问
      console.log(t); // 正常
    }
    
    // 超出了 t 的作用域
    console.log(t); // 报错
  </script>

JavaScript 中除了变量外还有常量,常量与变量本质的区别是【常量必须要有值且不允许被重新赋值】,常量值为对象时其属性和方法允许重新赋值。

<script>
    // 必须要有值
    const version = '1.0.0';
  ​
    // 不能重新赋值
    // version = '1.0.1';
  ​
    // 常量值为对象类型
    const user = {
      name: '小明',
      age: 18
    }
  ​
    // 不能重新赋值
    user = {};
  ​
    // 属性和方法允许被修改
    user.name = '小小明';
    user.gender = '男';
  </script>

1.4 总结

  1. let 声明的变量会产生块作用域,var 不会产生块作用域

  2. const 声明的常量也会产生块作用域

  3. 不同代码块之间的变量无法互相访问

  4. 强烈推荐使用 letconst!尽量避免使用var!!!

:开发中 letconst 经常不加区分的使用,如果担心某个值会不小被修改时,则只能使用 const 声明成常量。

2. 全局作用域

<script> 标签  .js 文件的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。

  
  <script>
    // 此处是全局
    
    function sayHi() {
      // 此处为局部
    }
  ​
    // 此处为全局
  </script>

全局作用域中声明的变量,任何其它作用域都可以被访问,如下代码所示:

 <script>
      // 全局变量 name
      const name = '小明'
    
      // 函数作用域中访问全局
      function sayHi() {
        // 此处为局部
        console.log('你好' + name)
      }
  ​
      // 全局变量 flag 和 x
      const flag = true
      let x = 10
    
      // 块作用域中访问全局
      if(flag) {
        let y = 5
        console.log(x + y) // x 是全局的
      }
  </script>

总结:

  1. window 对象动态添加的属性默认也是全局的,不推荐

  2. 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!

  3. 尽可能少的声明全局变量,防止全局变量被污染

JavaScript 中的作用域是程序被执行时的底层机制,了解这一机制有助于规范代码书写习惯,避免因作用域导致的语法错误。

二、作用域链

在解释什么是作用域链前先来看一段代码:

  <script>
    // 全局作用域
    let a = 1
    let b = 2
    // 局部作用域
    function f() {
      let c
      // 局部作用域
      function g() {
        let d = 'yo'
      }
    }
  </script>

函数内部允许创建新的函数,f 函数内部创建的新函数 g会产生新的函数作用域,由此可知作用域产生了嵌套的关系

如下图所示,父子关系的作用域关联在一起形成了链状的结构,作用域链的名字也由此而来。

作用域链本质上是底层的变量查找机制,在函数被执行时,会优先查找当前函数作用域中查找变量,如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域,如下代码所示:

 <script>
    // 全局作用域
    let a = 1
    let b = 2
  ​
    // 局部作用域
    function f() {
      let c
      // let a = 10;
      console.log(a) // 1 或 10
      console.log(d) // 报错
      
      // 局部作用域
      function g() {
        let d = 'yo'
        // let b = 20;
        console.log(b) // 2 或 20
      }
      
      // 调用 g 函数
      g()
    }
  ​
    console.log(c) // 报错
    console.log(d) // 报错
    
    f();
  </script>

总结:

  1. 嵌套关系的作用域串联起来形成了作用域链

  2. 相同作用域链中按着从小到大的规则查找变量

  3. 子作用域能够访问父作用域,父级作用域无法访问子级作用域

三、闭包

闭包是一种比较特殊和函数,使用闭包能够访问函数作用域中的变量。从代码形式上看闭包是一个做为返回值的函数,如下代码所示:

 <body>
    <script>
      // 1. 闭包 : 内层函数 + 外层函数变量
      // function outer() {
      //   const a = 1
      //   function f() {
      //     console.log(a)
      //   }
      //   f()
      // }
      // outer()
  ​
      // 2. 闭包的应用: 实现数据的私有。统计函数的调用次数
      // let count = 1
      // function fn() {
      //   count++
      //   console.log(`函数被调用${count}次`)
      // }
  ​
      // 3. 闭包的写法  统计函数的调用次数
      function outer() {
        let count = 1
        function fn() {
          count++
          console.log(`函数被调用${count}次`)
        }
        return fn
      }
      const re = outer()
      // const re = function fn() {
      //   count++
      //   console.log(`函数被调用${count}次`)
      // }
      re()
      re()
      // const fn = function() { }  函数表达式
      // 4. 闭包存在的问题: 可能会造成内存泄漏
    </script>
  </body>

总结:

1.怎么理解闭包?

  • 闭包 = 内层函数 + 外层函数的变量

2.闭包的作用?

  • 封闭数据,实现数据私有,外部也可以访问函数内部的变量

  • 闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来

3.闭包可能引起的问题?

  • 内存泄漏

四、变量提升

变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问

  
  <script>
    // 访问变量 str
    console.log(str + 'world!');
  ​
    // 声明变量 str
    var str = 'hello ';
  </script>

总结:

  1. 变量在未声明即被访问时会报语法错误

  2. 变量在声明之前即被访问,变量的值为 undefined

  3. let 声明的变量不存在变量提升,推荐使用 let

  4. 变量提升出现在相同作用域当中

  5. 实际开发中推荐先声明再访问变量

注:关于变量提升的原理分析会涉及js的执行上下文等知识,而开发中使用 let 可以轻松规避变量的提升,因此在此不做过多的探讨,有兴趣可以继续阅读我的另一篇文章👉彻底明白js的执行上下文、作用域。

 🚀 个人简介:7年开发经验,现任职某国企前端负责人,分享前端相关技术与工作常见问题~
💟 作    者:前端菜鸟的自我修养❣️
📝 专    栏:javascript深入研究
🌈 若有帮助,还请关注➕点赞➕收藏  ,不行的话我再努努力💪💪💪 

 更多专栏订阅推荐:

👍 前端工程搭建
💕 vue从基础到起飞

📝 前端工作常见问题汇总

✍️ GIS地图与大数据可视化

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1853250.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Codeforces Round 953 (Div. 2) A~F

A.Alice and Books&#xff08;思维&#xff09; 题意&#xff1a; 爱丽丝有 n n n本书。第 1 1 1本书包含 a 1 a_1 a1​页&#xff0c;第 2 2 2本书包含 a 2 a_2 a2​页&#xff0c; … \ldots …第 n n n本书包含 a n a_n an​页。爱丽丝的操作如下&#xff1a; 她把所有的…

CTO的职责是什么?

看《架构思维》作者是这样讲的&#xff1a; CTO 到底是做什么的&#xff1f; 我当下的答案是&#xff1a;“CTO 就是一个从技术视角出发&#xff0c;为公司或者所在的部门做正确决策的 CEO。”怎么理解这句话呢&#xff1f;作为一个 CTO&#xff0c;其长期目标和决策优先级与…

【Linux】系统文件IO·文件描述符fd

前言 C语言文件接口 C 语言读写文件 1.C语言写入文件 2.C语言读取文件 stdin/stdout/stderr 系统文件IO 文件描述符fd&#xff1a; 文件描述符分配规则&#xff1a; 文件描述符fd&#xff1a; 前言 我们早在C语言中学习关于如何用代码来管理文件&#xff0c;比如文件的…

搜狐视频全自动工具

项目介绍&#xff1a; 首先我们要用自己的一个号&#xff0c;作为主号&#xff0c;主号上发作品&#xff0c;利用不实名的小号通过脚本自动去浏览视频 小号看视频的同时会出现搜狐官方强行插入的广告&#xff0c;从而获得收益 收益介绍&#xff1a; 小号看一个视频会出现3-…

使用SpringCache实现Redis缓存

目录 一 什么是Spring Cache 二 Spring Cache 各注解作用 ①EnableCaching ②Cacheable ③CachePut ④CacheEvict 三实现步骤 ①导入spring cache依赖和Redis依赖 ②配置Redis连接信息 ③在启动类上加上开启spring cache的注解 ④ 在对应的方法上加上需要的注解 一 什么…

虚拟机IP地址频繁变化的解决方法

勾八动态分配IP&#xff0c;让我在学习redis集群的时候&#xff0c;配置很多的IP地址&#xff0c;但是由于以下原因导致我IP频繁变动&#xff0c;报错让我烦恼&#xff01;&#xff01;&#xff01;&#xff01; 为什么虚拟机的IP地址会频繁变化&#xff1f; 虚拟机IP地址频繁…

【Python机器学习】利用t-SNE进行流形学习

虽然PCA通常是用于变换数据的首选方法&#xff0c;使你能够用散点图将其可视化&#xff0c;但这一方法的性质限制了其有效性。 有一类用于可视化的算法叫做流形学习算法&#xff0c;它允许进行更复杂的映射&#xff0c;通常也可以给出更好的可视化。其中特别有用的一个就是t-S…

MySQL学习笔记-进阶篇-锁

概述 概念 全局锁 表级锁 介绍 表锁 读锁 只允许加锁客户端读操作禁止写操作。允许其他客户端的读操作&#xff0c;阻塞其他客户端的写操作。 lock tables xxx read unlock tables&#xff1b; 写锁 允许加锁客户端的读写操作。既阻塞其他客户端的读&#xff0c;又阻塞其他客…

ARM功耗管理框架之PPU

安全之安全(security)博客目录导读 思考&#xff1a;功耗管理框架&#xff1f;SCP&#xff1f;PPU&#xff1f;LPI&#xff1f;之间的关系&#xff1f;如何配合&#xff1f; 目录 一、功耗管理框架中的PPU 二、PPU的结构与连接关系 三、PPU操作模式和电源模式及其之间的转…

尹会生:从零开始部署翻译助手【总结】

安装docker安装dify 工具准备 Docker 简介&#xff1a;可以在不同电脑上运行相同的容器&#xff0c;类似于把软件装在便携箱子里&#xff0c;随身携带。 优点&#xff1a;安装Docker可以简化部署过程&#xff0c;避免安装许多依赖性软件。 网址&#xff1a;https://www.docke…

分布式定时任务系列10:XXL-job源码分析之路由策略

传送门 分布式定时任务系列1&#xff1a;XXL-job安装 分布式定时任务系列2&#xff1a;XXL-job使用 分布式定时任务系列3&#xff1a;任务执行引擎设计 分布式定时任务系列4&#xff1a;任务执行引擎设计续 分布式定时任务系列5&#xff1a;XXL-job中blockingQueue的应用 …

SLAM ORB-SLAM2(27)词袋模型

SLAM ORB-SLAM2(27)词袋模型 1. 词袋模型1.1. 词汇树1.2. 逆向索引表1.3. 逆向索引表2. 词袋向量3. 匹配候选帧3.1. 找出和当前帧具有公共单词的所有关键帧3.2. 找出和当前帧最多公共单词的关键帧3.3. 剔除共享单词数较少的关键帧3.4. 计算关键帧的共视关键帧组的总得分3.5. …

CentOS系统查看版本的各个命令

cat /etc/centos-release 查看CentOS版本 uname -a 命令的结果分别代表&#xff1a;当前系统的内核名称、主机名、内核发型版本、节点名、系统时间、硬件名称、硬件平台、处理器类型以及操作系统名称 cat /proc/version 命令用于查看Linux内核的版本信息。执行该命令后&#xf…

Ubuntu/Linux系统安装JDK1.8(带jdk1.8资源和操作教程)

文章目录 前言一、JDK1.8下载二、上传三、安装四、配置环境变量五、查看总结 前言 &#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;Ubuntu/Linux jdk1.8安装包&#xff…

JetBrains PyCharm 2024 mac/win版编程艺术,智慧新篇

JetBrains PyCharm 2024是一款功能强大的Python集成开发环境(IDE)&#xff0c;专为提升开发者的编程效率和体验而设计。这款IDE不仅继承了前代版本的优秀特性&#xff0c;还在多个方面进行了创新和改进&#xff0c;为Python开发者带来了全新的工作体验。 JetBrains PyCharm 20…

初阶《操作符详解》 3. 移位操作符

3. 移位操作符 <<  左移操作符 >>  右移操作符 注&#xff1a;移动的是二进制位&#xff0c;移位操作符的操作数只能是整数。 一个存储的二进制码分原码、反码、补码 1.十进制数据的二进制表现形式就是原码&#xff0c;原码最左边的一个数字就是符号位&#xff…

leetcode 二分查找·系统掌握

题目&#xff1a; 题解&#xff1a; 在阶梯数达到某一值后已有的硬币数量就小于了阶梯可以装的硬币数量&#xff0c;根据题意可以使用~10~泛型查找出最后一个可以被填满的阶梯。对于这类型可以二分答案的题目关键在于二分答案的上下界&#xff0c;本题的下界就是1上界就是硬币…

Redis-数据类型-Set(不允许重复)

文章目录 1、查看redis是否启动2、通过客户端连接redis3、切换到2数据库4、给key指定的set集合中存入数据&#xff0c;set会自动去重5、返回可以指定的set集合中所有的元素6、返回集合中元素的数量(set cardinality)7、检查当前指定member是否是集合中的元素8、从集合中删除元素…

[JS]数据类型

介绍 在计算中一切事物都是数据, 为了提高数据的存储和使用效率, 要对数据进行类型的分类 栈(操作系统): 由操作系统自动分配释放函数的参数值, 局部变量的值等, 其操作方式类似于数据结构中的栈; 基本数据类型存放在栈里面string, number, boolean, undefined, null 堆(操作…

C++STL 初阶(5)vector的简易实现(上)

不同于string只实现一个最简单的版本&#xff0c;vector在此处我们要实现的是模版类&#xff0c;类模版的声明和定义分离非常不方便&#xff08;会在链接时报错&#xff09;&#xff0c;所以我们都只在一个vector.h下去实现声明和定义。后续我们提及到的库里面实现的vector也是…