第 83 题:var、let 和 const 区别的实现原理是什么?

发布于 2022-08-13 12:29:38 字数 1046 浏览 949 评论 26

1、作用域不同

var 是函数作用域,let 是块作用域。

在函数中声明了 var,整个函数内都是有效的,比如说在 for 循环内定义的一个 var 变量,实际上其在 for 循环以外也是可以访问的。

而 let 由于是块作用域,所以如果在块作用域内定义的变量,比如说在 for 循环内,在其外面是不可被访问的,所以 for 循环推荐用 let。

在这里插入图片描述

2、let 不能在定义之前访问该变量,但是 var 可以

let 必须先声明,在使用。而 var 先使用后声明也行,只不过直接使用但没有定义的时候,其值是 undefined。var 有一个变量提升的过程,当整个函数作用域被创建的时候,实际上var定义的变量都会被创建,并且如果此时没有初始化的话,则默认为初始化一个 undefined。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

3、let 不能被重新定义,但是 var 是可以的

在这里插入图片描述
在这里插入图片描述

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(26

虐人心 2022-06-26 23:02:52

下面先让我们看看它和 var 之间用法的不同

var a = 10;
let b = 8;
a // 10
b // Referenceerror :b is not defined

上面在代码块中声明了两个变量并分别赋值输出到控制台,结果 a 的变量成功输出,b 的输出结果产生了报错。可见,let 声明的变量只在它所在的代码块中产生作用。同时,我们也能想到它最好的使用方法就是在 for 循环中使用。

番薯* 2022-05-04 13:56:02
  • 函数内部通过 var 声明的变量,在编译阶段全都被存放到变量环境里面了。
  • 通过 let 声明的变量,在编译阶段会被存放到词法环境(Lexical Environment)中。
  • 在函数的作用域内部,通过 let 声明的变量并没有被存放到词法环境中。

在词法环境内部,维护了一个小型栈结构,栈底是函数最外层的变量,进入一个作用域块后,就会把该作用域块内部的变量压到栈顶;当作用域执行完成之后,该作用域的信息就会从栈顶弹出
进入块级作用域不会有编译过程,只不过通过let或者const声明的变量会在进入块级作用域的时被创建,但是在该变量没有赋值之前,引用该变量JavaScript引擎会抛出错误---这就是“暂时性死区”。

半透明的墙 2022-05-04 13:56:02

const 实现
function _const(key, value) {
const desc = {
value,
writable: false
}
Object.defineProperty(window, key, desc)
}

倥絔 2022-05-04 13:56:02

var let const 都存在变量提升,而且都初始化为undefined,这个可以在浏览器控制台debugger Scope的Local中看到三者都是undefined。
js执行的时候会先编译 对于该作用域下的var变量会初始化为undefined ,var声明的变量存在于变量活动对象中,let const声明的变量也会初始化为undefined,但是是在词法环境中,而js引擎对词法环境中未初始化的let const声明的变量做了访问限制 因此才会存在“暂时性死区”。
这块内容标准中有写https://262.ecma-international.org/6.0/
13.3.1和13.3.2

function test () {
    debugger // 这里在浏览器的scope local中可以查看到三者是undefined
    var a = 'a'
    let b = 'b'
    const c = 'c'
}
test()```
要走干脆点丶 2022-05-04 13:56:02
   - var声明的变量只会存在于全局/局部作用域,如果声明在代码块(if/else/switch)中,会被提升出去,

比如模块或者一个方法中声明的var a=1,如果声明在模块中,则会默认挂载到window上;如果声明在方法中,则在方法内部任何地方都可以访问;如果声明在代码块里,则会提升到上一级作用域;

  • let/const声明的变量,会被保存在局部作用域中

声明在模块中,也不会挂载到window上,而是形成一个单独的作用域;如果声明在一个代码块中,则在代码块以外无法访问,不会被提升到父级作用域(与var是不同的),但是let/const声明的变量存在暂时性死区

后eg是否自 2022-05-04 13:56:02

var,已废弃

  1. 函数作用域 & 全局作用域
  2. 存在声明提升

let/const,更现代

  1. 块级作用域
  2. 不能重复声明
  3. 在声明后才可用,存在死区
溇涏 2022-05-04 13:56:01
  • var会直接在栈里分配一个内存空间,等实际执行到语句时,再保存变量。如果遇到引用类型的变量,会去堆里开辟一个空间来保存对象,然后在栈里存储指向对象的指针。
  • let不会直接去栈里分配内存空间,而是做一个预检查,如果有同名变量就会报错。
  • const和let一样在分配内容空间之前会做检查。
笑忘罢 2022-05-04 13:55:58

var 存在变量提升, let const 声明的变量不会

  1. var 声明变量会挂在window, let const 不会
  2. let, const 声明形成 作用域
  3. 同一作用域下 let const 不能声明 同名变量, 而var 可以
  4. 暂存死区
  5. const 声明后不得修改
半步萧音过轻尘 2022-05-04 13:55:54
function foo() {
  var a = 1;
  let b = 2;
}

我的理解:
代码执行分为两个阶段,create phase 和 execute phase,也就是代码解析和代码执行;
在create phase 阶段,其实var 和 let 定义的变量都已经初始化了:
var定义的变量被赋值为undefined
let定义的变量被赋值为uninitialized
var之所以被变量提升是因为已经赋有效值(undefined),但是引用uninitialized变量编译器会报错。

等到execute phase阶段,a赋值为1,b赋值为2,就没什么可说的了。

另外块级作用域也是因为通过不同方式声明的变量被赋值到不同的EnvironmentRecord,LexicalEnvironment和VariableEnvironment的区别,在一个方法内部,每一个块级代码都会新生成一个LexicalEnvironment,并保留上一个LexicalEnvironment的引用,但是一个方法内只有一个VariableEnvironment。

滿滿的愛 2022-05-04 13:54:04

怎么感觉大家回答的都是表层的东西...栈和堆这些都知道哪个可改变哪个不可改变...问的应该是怎么做到不可改变的吧?

其实题目想要考察的就是 javascript 表面语义的东西。
具体不同类型的声明产生的变量在内存中究竟如何存储,那要看 javascript engine 内部是如何实现的。这些实现细节对于上层前端开发者来讲了解即可。如果你真的想要了解究竟是分配在栈上还是堆上,去看 V8,spiderMonkey这些引擎的代码(个人觉得如果不做引擎这块,纯属看个人兴趣去深入了)。这些引擎内部是用 C 或 C++ 这种更加方面操作内存的语言写的。也就知道实现原理是什么了。

明白了 十分感谢解惑 因为总觉得这样回答似乎很简单 看来是我想复杂了

并且可能非常多前端开发者对于计算基础的知识并不牢靠。比如什么是栈和堆?操作系统的栈和堆概念和编程语言的栈、堆概念一致吗(一般我们了解程序的堆栈概念来自于 C 语言)?和传统的数据结构中的栈结构和二叉堆又有什么关联呢?等等这些概念。
了解了这些概念,是不是有必要去研究下 GC(garbage collection)?
前端可能更加偏向于体验一点。

温柔嚣张 2022-05-04 13:53:23

怎么感觉大家回答的都是表层的东西...栈和堆这些都知道哪个可改变哪个不可改变...问的应该是怎么做到不可改变的吧?

其实题目想要考察的就是 javascript 表面语义的东西。
具体不同类型的声明产生的变量在内存中究竟如何存储,那要看 javascript engine 内部是如何实现的。这些实现细节对于上层前端开发者来讲了解即可。如果你真的想要了解究竟是分配在栈上还是堆上,去看 V8,spiderMonkey这些引擎的代码(个人觉得如果不做引擎这块,纯属看个人兴趣去深入了)。这些引擎内部是用 C 或 C++ 这种更加方面操作内存的语言写的。也就知道实现原理是什么了。

明白了 十分感谢解惑 因为总觉得这样回答似乎很简单 看来是我想复杂了

ぽ尐不点ル 2022-05-04 13:53:12

怎么感觉大家回答的都是表层的东西...栈和堆这些都知道哪个可改变哪个不可改变...问的应该是怎么做到不可改变的吧?

其实题目想要考察的就是 javascript 表面语义的东西。
具体不同类型的声明产生的变量在内存中究竟如何存储,那要看 javascript engine 内部是如何实现的。这些实现细节对于上层前端开发者来讲了解即可。如果你真的想要了解究竟是分配在栈上还是堆上,去看 V8,spiderMonkey这些引擎的代码(个人觉得如果不做引擎这块,纯属看个人兴趣去深入了)。这些引擎内部是用 C 或 C++ 这种更加方面操作内存的语言写的。也就知道实现原理是什么了。

梦幻之岛 2022-05-04 13:49:40

怎么感觉大家回答的都是表层的东西...栈和堆这些都知道哪个可改变哪个不可改变...问的应该是怎么做到不可改变的吧?

半暖夏伤 2022-05-04 13:34:23

转载:
var和let的区别,面试老生常谈的问题,大多数人回答可能就是作用域和变量提升这两点不同,少有人能够知道内在原理,这样的回答面试官会满意吗?(手动滑稽)

我们就从声明过程,内存分配,和变量提升这三点来看这三者之间的区别。

一.声明过程
var:遇到有var的作用域,在任何语句执行前都已经完成了声明和初始化,也就是变量提升而且拿到undefined的原因由来
function: 声明、初始化、赋值一开始就全部完成,所以函数的变量提升优先级更高
let:解析器进入一个块级作用域,发现let关键字,变量只是先完成声明,并没有到初始化那一步。此时如果在此作用域提前访问,则报错xx is not defined,这就是暂时性死区的由来。等到解析到有let那一行的时候,才会进入初始化阶段。如果let的那一行是赋值操作,则初始化和赋值同时进行
const、class都是同let一样的道理
比如解析如下代码步骤:

{
// 没用的第一行
// 没用的第二行
console.log(a) // 如果此时访问a报错 a is not defined
let a = 1
}
步骤:

发现作用域有let a,先注册个a,仅仅注册
没用的第一行
没用的第二行
a is not defined,暂时性死区的表现
假设前面那行不报错,a初始化为undefined
a赋值为1
对比于var,let、const只是解耦了声明和初始化的过程,var是在任何语句执行前都已经完成了声明和初始化,let、const仅仅是在任何语句执行前只完成了声明。

二.内存分配
var,会直接在栈内存里预分配内存空间,然后等到实际语句执行的时候,再存储对应的变量,如果传的是引用类型,那么会在堆内存里开辟一个内存空间存储实际内容,栈内存会存储一个指向堆内存的指针

let,是不会在栈内存里预分配内存空间,而且在栈内存分配变量时,做一个检查,如果已经有相同变量名存在就会报错

const,也不会预分配内存空间,在栈内存分配变量时也会做同样的检查。不过const存储的变量是不可修改的,对于基本类型来说你无法修改定义的值,对于引用类型来说你无法修改栈内存里分配的指针,但是你可以修改指针指向的对象里面的属性

三.变量提升
let const 和var三者其实会存在变量提升

let只是创建过程提升,初始化过程并没有提升,所以会产生暂时性死区。
var的创建和初始化过程都提升了,所以在赋值前访问会得到undefined
function 的创建、初始化、赋值都被提升了
————————————————
版权声明:本文为CSDN博主「微 光」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Web_J/article/details/99591116

-残月青衣踏尘吟 2022-05-04 13:14:13

变量生命周期:声明(作用域注册一个变量)、初始化(分配内存,初始化为undefined)、赋值

  • var:遇到有var的作用域,在任何语句执行前都已经完成了声明和初始化,也就是变量提升而且拿到undefined的原因由来
  • function: 声明、初始化、赋值一开始就全部完成,所以函数的变量提升优先级更高
  • let:解析器进入一个块级作用域,发现let关键字,变量只是先完成声明,并没有到初始化那一步。此时如果在此作用域提前访问,则报错xx is not defined,这就是暂时性死区的由来。等到解析到有let那一行的时候,才会进入初始化阶段。如果let的那一行是赋值操作,则初始化和赋值同时进行
  • const、class都是同let一样的道理

比如解析如下代码步骤:

{
 //没用的第一行
//没用的第二行
console . log (a) //如果此时访问a报错a is not defined 
let a =  1 
}

步骤:

  1. 发现作用域有let a,先注册个a,仅仅注册
  2. 没用的第一行
  3. 没用的第二行
  4. a is not defined,暂时性死区的表现
  5. 假设前面那行不报错,a初始化为undefined
  6. a赋值为1

对比于var,let、const只是解耦了声明和初始化的过程,var是在任何语句执行前都已经完成了声明和初始化,let、const仅仅是在任何语句执行前只完成了声明

咋回事咧?这么明显的错误~
// 暂时性死区了兄弟 Cannot access 'a' before initialization
console.log(a)
let a = 1;

// is not defined 是未定义
console.log(temp);  // 此时temp还未定义。
手心的温暖 2022-05-04 13:10:28

let const 和var三者都会存在变量提升

  • let只是创建过程提升,初始化过程并没有提升,所以会产生暂时性死区。
  • var的创建初始化过程都提升了,所以在赋值前访问会得到undefined
  • function 的创建、初始化、赋值都被提升了
南城追梦i 2022-05-04 12:58:19

var的话会直接在栈内存里预分配内存空间,然后等到实际语句执行的时候,再存储对应的变量,如果传的是引用类型,那么会在堆内存里开辟一个内存空间存储实际内容,栈内存会存储一个指向堆内存的指针

let的话,是不会在栈内存里预分配内存空间,而且在栈内存分配变量时,做一个检查,如果已经有相同变量名存在就会报错

const的话,也不会预分配内存空间,在栈内存分配变量时也会做同样的检查。不过const存储的变量是不可修改的,对于基本类型来说你无法修改定义的值,对于引用类型来说你无法修改栈内存里分配的指针,但是你可以修改指针指向的对象里面的属性

人间☆小暴躁 2022-05-04 10:19:56

变量被重新覆盖之后,之前的内存地址和内存空间,会被垃圾回收机制回收的吧!

月亮邮递员 2022-05-03 12:02:18

变量生命周期:声明(作用域注册一个变量)、初始化(分配内存,初始化为undefined)、赋值

  • var:遇到有var的作用域,在任何语句执行前都已经完成了声明和初始化,也就是变量提升而且拿到undefined的原因由来
  • function: 声明、初始化、赋值一开始就全部完成,所以函数的变量提升优先级更高
  • let:解析器进入一个块级作用域,发现let关键字,变量只是先完成声明,并没有到初始化那一步。此时如果在此作用域提前访问,则报错xx is not defined,这就是暂时性死区的由来。等到解析到有let那一行的时候,才会进入初始化阶段。如果let的那一行是赋值操作,则初始化和赋值同时进行
  • const、class都是同let一样的道理

比如解析如下代码步骤:

{
// 没用的第一行
// 没用的第二行
console.log(a) // 如果此时访问a报错 a is not defined
let a = 1
}

步骤:

  1. 发现作用域有let a,先注册个a,仅仅注册
  2. 没用的第一行
  3. 没用的第二行
  4. a is not defined,暂时性死区的表现
  5. 假设前面那行不报错,a初始化为undefined
  6. a赋值为1

对比于var,let、const只是解耦了声明和初始化的过程,var是在任何语句执行前都已经完成了声明和初始化,let、const仅仅是在任何语句执行前只完成了声明

韶华倾负 2022-05-03 09:52:10

针对区别做了总结,实现原理属于推测,求真相。
2019.05.29面试题: var、let、const 的区别及实现原理?

高速公鹿^ε^ 2022-05-02 13:40:43

一、实现原理

(一)、var的实现原理

(二)、let的实现原理

(三)、const的实现原理

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

二、应用场景

(一)var应用场景

(二)let应用场景

  • for 循环的计数器,就很合适使用 let 命令

(三)const应用场景

三、var、let、const的区别

(一)var

  • var 命令会发生“变量提升”现象,即变量可以在声明之前使用,值为 undefined
  • 内层变量可能覆盖外层变量
  • 用来计数的循环变量泄露为全局变量

(二)let

  • 声明的�全局变量不会挂在顶层对象下面
  • 所声明的变量一定要在声明后使用,否则报错,报错 ReferenceError
  • 暂时性死区,只要块级作用域内存在 let 命令,它所声明的变量就“绑定”(binding )这个区域,不再受外部的影响,在代码块内,使用 let 命令声明变量之前,该变量都是不可用的。
  • 不允许重复声明

(三)const

  • 声明的�全局变量不会挂在顶层对象下面
  • const 声明之后必须马上赋值,否则会报错
  • const 简单类型一旦声明就不能再更改,�复杂类型(数组、对象等)指针指向的地址不能更改,内部数据可以更改。
  • const 一旦声明变量,就必须立即初始化,不能留到以后赋值。
  • const 命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。

四、参考文章

怀中猫帐中妖 2022-04-28 16:21:37

先说说这三者的区别吧:

  • var 和 let 用以声明变量,const 用于声明只读的常量;

  • var 声明的变量,不存在块级作用域,在全局范围内都有效,let 和 const 声明的,只在它所在的代码块内有效;

  • let 和 const 不存在像 var 那样的 “变量提升” 现象,所以 var 定义变量可以先使用,后声明,而 let 和 const 只可先声明,后使用;

  • let 声明的变量存在暂时性死区,即只要块级作用域中存在 let,那么它所声明的变量就绑定了这个区域,不再受外部的影响。

  • let 不允许在相同作用域内,重复声明同一个变量;

  • const 在声明时必须初始化赋值,一旦声明,其声明的值就不允许改变,更不允许重复声明;

    如 const 声明了一个复合类型的常量,其存储的是一个引用地址,不允许改变的是这个地址,而对象本身是可变的。

然后是原理,原理确实没认真研究过,在网上翻了一番资料,结合自己的理解简单说下(纯属个人愚见,高手轻喷):

变量与内存之间的关系,主要由三个部分组成:

  1. 变量名
  2. 内存地址
  3. 内存空间

JS 引擎在读取变量时,先找到变量绑定的内存地址,然后找到地址所指向的内存空间,最后读取其中的内容。当变量改变时,JS 引擎不会用新值覆盖之前旧值的内存空间(虽然从写代码的角度来看,确实像是被覆盖掉了),而是重新分配一个新的内存空间来存储新值,并将新的内存地址与变量进行绑定,JS 引擎会在合适的时机进行 GC,回收旧的内存空间。

const 定义变量(常量)后,变量名与内存地址之间建立了一种不可变的绑定关系,阻隔变量地址被改变,当 const 定义的变量进行重新赋值时,根据前面的论述,JS 引擎会尝试重新分配新的内存空间,所以会被拒绝,便会抛出异常。

坐等高手答疑。

~没有更多了~

关于作者

如此安好

暂无简介

0 文章
0 评论
589 人气
更多

推荐作者

已经忘了多久

文章 0 评论 0

15867725375

文章 0 评论 0

LonelySnow

文章 0 评论 0

走过海棠暮

文章 0 评论 0

轻许诺言

文章 0 评论 0

信馬由缰

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文