草庐IT

面试官:你说说var、let、const三者的区别

CoderBin 2023-03-28 原文
大家好,我是CoderBin

前言

面试官:“你说说var、let、const三者的区别”

紧张的萌新:“var没有块级作用域,let有......”

面试官:“...”

······

本次又来到了面试官系列,变量声明作为面试当中的高频考点,如果只是回答块级作用域是远远不够的,而它们涉及知识点也是我们准备面试时需要去注意的,这样才能比较全面的回答出它们之间的区别。所以本文将总结var、let、const相关的基础知识,帮助大家掌握这道面试题。如果是大佬就当做复习吧,哈哈,希望对大家有帮助,谢谢!

如果文中有不对、疑惑的地方,欢迎在评论区留言指正?

一、var 声明

  1. 在ES5中,顶层对象的属性和全局变量是等价的,用var声明的变量既是全局变量,也是顶层变量
var a = 10 console.log(window.a) // 10 注意:顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象

  1. 使用var声明的变量存在变量提升的情况
console.log(a) // undefined var a = 20
  1. 在编译阶段,编译器会将其变成以下执行
var a console.log(a) a = 20
  1. 使用var,我们能够对一个变量进行多次声明,后面声明的变量会覆盖前面的变量声明
var a = 20 var a = 30 console.log(a) // 30
  1. 在函数中使用使用var声明变量时候,该变量是局部的
var a = 20 function change(){ var a = 30 } change() console.log(a) // 20
  1. 而如果在函数内不使用var,该变量是全局的
var a = 20 function change(){ a = 30 } change() console.log(a) // 30

二、let 声明

let是ES6新增的命令,用来声明变量,作用和var差不多。最明显的区别是:let声明的范围是块作用域,而var声明的范围是函数作用域

  1. 用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效
{ let a = 20 } console.log(a) // ReferenceError: a is not defined.
  1. 不存在变量提升
console.log(a) // 报错ReferenceError let a = 2 这表示在声明它之前,变量a是不存在的,这时如果用到它,就会抛出一个错误

  1. 只要块级作用域内存在let命令,这个区域就不再受外部影响
var a = 123 if (true) { a = 'abc' // ReferenceError let a; } 使用let声明变量前,该变量都不可用,也就是大家常说的“暂时性死区”

  1. let不允许在相同作用域中重复声明
let a = 20 let a = 30 // Uncaught SyntaxError: Identifier 'a' has already been declared 注意的是相同作用域,下面这种情况是不会报错的

let a = 20 { let a = 30 } 因此,我们不能在函数内部重新声明参数

function func(arg) { let arg; } func() // Uncaught SyntaxError: Identifier 'arg' has already been declared
  1. 防止变量泄露
在let出现之前,for循环用var定义的迭代变量会进入到循环体外部。改用let之后,这个问题就消失了,因为迭代变量的作用域仅限于for循环内部:

// 用var定义 for(var i = 0; i < 5; i++) { // 循环逻辑 } console.log(i) // 5 // ====================================== // 用let定义 for(let i = 0; i < 5; i++) { // 循环逻辑 } console.log(i) // ReferenceError: i is not defined

三、const 声明

const的行为与let基本相同,唯一一个重要的区别是用它声明变量必须同时初始化,且尝试修改const声明的变量会导致运行时错误

  1. const声明一个只读的常量,一旦声明,常量的值就不能改变
const a = 1 a = 3 // TypeError: Assignment to constant variable. 这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值

const a; // SyntaxError: Missing initializer in const declaration
  1. 如果之前用varlet声明过变量,再用const声明同样会报错
var a = 20 let b = 20 const a = 30 const b = 30 // 都会报错 const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动

对于简单类型的数据,值就保存在变量指向的那个内存地址,因此等同于常量

对于复杂类型的数据,变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的,并不能确保改变量的结构不变

const foo = {}; // 为 foo 添加一个属性,可以成功 foo.prop = 123; foo.prop // 123 // 将 foo 指向另一个对象,就会报错 foo = {}; // TypeError: "foo" is read-only 其它情况,constlet一致

四、它们之间的区别

varletconst三者区别可以围绕下面五点展开:

  • 变量提升
  • 暂时性死区
  • 块级作用域
  • 重复声明
  • 修改声明的变量
  • 使用

1. 变量提升

var 声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined

letconst不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错

// var console.log(a) // undefined var a = 10 // let console.log(b) // Cannot access 'b' before initialization let b = 10 // const console.log(c) // Cannot access 'c' before initialization const c = 10

2. 暂时性死区

var不存在暂时性死区

letconst存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量

// var console.log(a) // undefined var a = 10 // let console.log(b) // Cannot access 'b' before initialization let b = 10 // const console.log(c) // Cannot access 'c' before initialization const c = 10

3. 块级作用域

var不存在块级作用域

letconst存在块级作用域

// var { var a = 20 } console.log(a) // 20 // let { let b = 20 } console.log(b) // Uncaught ReferenceError: b is not defined // const { const c = 20 } console.log(c) // Uncaught ReferenceError: c is not defined

4. 重复声明

var允许重复声明变量

letconst在同一作用域不允许重复声明变量

// var var a = 10 var a = 20 // 20 // let let b = 10 let b = 20 // Identifier 'b' has already been declared // const const c = 10 const c = 20 // Identifier 'c' has already been declared

5. 修改声明的变量

varlet可以

const声明一个只读的常量。一旦声明,常量的值就不能改变

// var var a = 10 a = 20 console.log(a) // 20 //let let b = 10 b = 20 console.log(b) // 20 // const const c = 10 c = 20 console.log(c) // Uncaught TypeError: Assignment to constant variable

6. 使用

能用const的情况尽量使用const,其他情况下大多数使用let,避免使用var

五、风格声明及最佳实践

ECMAScript 6增加 let 和 const 从客观上为这门语言更精确地声明作用域和语义提供了更好的支持。行为怪异的 var 所造成的各种问题,已经让JavaScript社区为之苦恼了很多年。随着这两个新关键字的出现,新的有助于提升代码质量的最佳实践也逐渐显现。

1. 不使用 var

有了 letconst ,大多数开发者会发现自己不再需要 var 了。限制自己只使用 letconst 有助于提升代码质量,因为变量有了明确的作用域、声明位置,以及不变的值。

2. const 优先, let 次之

使用 const 声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。因此,很多开发者认为应该优先使用 const 来声明变量,只在提前知道未来会有修改时,再使用 let。这样可以让开发者更有信心地推断某些变量的值永远不会变,同时也能迅速发现因意外赋值导致的非预期行为。

六、最后总结

关于var、let、const的基础知识到这里的讲完了,最后来总结一下它们各自的特点吧

1. var特点

  • 声明作用域(函数作用域)
  • 变量提升
  • 可重复声明同个变量
  • 全局声明的变量会成为全局对象的属性

2. let特点

  • 块级作用域
  • 不存在变量提升
  • 不允许重复声明
  • 防止变量泄露(for循环的迭代变量)

3. const特点

  • 声明变量时,同时初始化
  • 块级作用域
  • 暂时性死区
  • 不可修改
  • 不允许重复声明
  • 不能用来声明迭代变量

每文一句:老骥伏枥,志在千里。烈士暮年,壮心不已——曹操

本次的分享就到这里,如果本章内容对你有所帮助的话欢迎点赞+收藏。文章有不对的地方欢迎指出,有任何疑问都可以在评论区留言。希望大家都能够有所收获,大家一起探讨、进步!

有关面试官:你说说var、let、const三者的区别的更多相关文章

  1. ruby - 触发器 ruby​​ 中 3 点范围运算符和 2 点范围运算符的区别 - 2

    请帮助我理解范围运算符...和..之间的区别,作为Ruby中使用的“触发器”。这是PragmaticProgrammersguidetoRuby中的一个示例:a=(11..20).collect{|i|(i%4==0)..(i%3==0)?i:nil}返回:[nil,12,nil,nil,nil,16,17,18,nil,20]还有:a=(11..20).collect{|i|(i%4==0)...(i%3==0)?i:nil}返回:[nil,12,13,14,15,16,17,18,nil,20] 最佳答案 触发器(又名f/f)是

  2. ruby-on-rails - `a ||= b` 和 `a = b if a.nil 之间的区别? - 2

    我正在检查一个Rails项目。在ERubyHTML模板页面上,我看到了这样几行:我不明白为什么不这样写:在这种情况下,||=和ifnil?有什么区别? 最佳答案 在这种特殊情况下没有区别,但可能是出于习惯。每当我看到nil?被使用时,它几乎总是使用不当。在Ruby中,很少有东西在逻辑上是假的,只有文字false和nil是。这意味着像if(!x.nil?)这样的代码几乎总是更好地表示为if(x)除非期望x可能是文字false。我会将其切换为||=false,因为它具有相同的结果,但这在很大程度上取决于偏好。唯一的缺点是赋值会在每次运行

  3. ruby - 这两个 Ruby 类初始化定义有什么区别? - 2

    我正在阅读一本关于Ruby的书,作者在编写类初始化定义时使用的形式与他在本书前几节中使用的形式略有不同。它看起来像这样:classTicketattr_accessor:venue,:datedefinitialize(venue,date)self.venue=venueself.date=dateendend在本书的前几节中,它的定义如下:classTicketattr_accessor:venue,:datedefinitialize(venue,date)@venue=venue@date=dateendend在第一个示例中使用setter方法与在第二个示例中使用实例变量之间是

  4. ruby - 怎么来的(a_method || :other) returns :other only when assigning to a var called a_method? - 2

    给定以下方法:defsome_method:valueend以下语句按我的预期工作:some_method||:other#=>:valuex=some_method||:other#=>:value但是下面语句的行为让我感到困惑:some_method=some_method||:other#=>:other它按预期创建了一个名为some_method的局部变量,随后对some_method的调用返回该局部变量的值。但为什么它分配:other而不是:value呢?我知道这可能不是一件明智的事情,并且可以看出它可能有多么模棱两可,但我认为应该在考虑作业之前评估作业的右侧...我已经在R

  5. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

    HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

  6. spring.profiles.active和spring.profiles.include的使用及区别说明 - 2

    转自:spring.profiles.active和spring.profiles.include的使用及区别说明下文笔者讲述spring.profiles.active和spring.profiles.include的区别简介说明,如下所示我们都知道,在日常开发中,开发|测试|生产环境都拥有不同的配置信息如:jdbc地址、ip、端口等此时为了避免每次都修改全部信息,我们则可以采用以上的属性处理此类异常spring.profiles.active属性例:配置文件,可使用以下方式定义application-${profile}.properties开发环境配置文件:application-dev

  7. ruby - 这两段代码有什么区别? - 2

    打印1:defsum(i)i=i+[2]end$x=[1]sum($x)print$x打印12:defsum(i)i.push(2)end$x=[1]sum($x)print$x后者是修改全局变量$x。为什么它在第二个例子中被修改而不是在第一个例子中?类Array的任何方法(不仅是push)都会发生这种情况吗? 最佳答案 变量范围在这里无关紧要。在第一段代码中,您仅使用赋值运算符=为变量i赋值,而在第二段代码中,您正在修改$x(也称为i)使用破坏性方法push。赋值从不修改任何对象。它只是提供一个名称来引用一个对象。方法要么是破坏性

  8. ruby - Ruby 中 .next 和 .succ 的区别 - 2

    Ruby中的Fixnum方法.next和.succ有什么区别?看起来它的工作原理是一样的:1.next=>21.succ=>2如果有什么不同,为什么有两种方法做同样的事情? 最佳答案 它们是等价的。Fixnum#succ只是Fixnum#next的同义词。他们甚至在thereferencemanual中共享同一block. 关于ruby-Ruby中.next和.succ的区别,我们在StackOverflow上找到一个类似的问题: https://stacko

  9. ruby - 在参数为 `yield self` 的方法中使用 `&block` 和在没有参数 `yield self` 的方法中使用 `&block` 有什么区别吗? - 2

    我明白了defa(&block)block.call(self)end和defa()yieldselfend导致相同的结果,如果我假设有这样一个blocka{}。我的问题是-因为我偶然发现了一些这样的代码,它是否有任何区别或者是否有任何优势(如果我不使用变量/引用block):defa(&block)yieldselfend这是一个我不理解&block用法的具体案例:defrule(code,name,&block)@rules=[]if@rules.nil?@rules 最佳答案 我能想到的唯一优点就是自省(introspecti

  10. ruby - 无法理解 `puts{}.class` 和 `puts({}.class)` 之间的区别 - 2

    由于匿名block和散列block看起来大致相同。我正在玩它。我做了一些严肃的观察,如下所示:{}.class#=>Hash好的,这很酷。空block被视为Hash。print{}.class#=>NilClassputs{}.class#=>NilClass为什么上面的代码和NilClass一样,下面的代码又显示了Hash?puts({}.class)#Hash#=>nilprint({}.class)#Hash=>nil谁能帮我理解上面发生了什么?我完全不同意@Lindydancer的观点你如何解释下面几行:print{}.class#NilClassprint[].class#A

随机推荐