Daily-Interview-Question icon indicating copy to clipboard operation
Daily-Interview-Question copied to clipboard

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

Open yygmind opened this issue 5 years ago • 27 comments

yygmind avatar May 28 '19 02:05 yygmind

先说说这三者的区别吧:

  • 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 引擎会尝试重新分配新的内存空间,所以会被拒绝,便会抛出异常。

坐等高手答疑。

wingmeng avatar May 28 '19 03:05 wingmeng

一、实现原理

(一)、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 命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。

四、参考文章

  • 【阮一峰-ECMAScript6入门】(http://es6.ruanyifeng.com/#docs/let)

luohong123 avatar May 28 '19 10:05 luohong123

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

attraction11 avatar May 29 '19 03:05 attraction11

变量生命周期:声明(作用域注册一个变量)、初始化(分配内存,初始化为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仅仅是在任何语句执行前只完成了声明

lhyt avatar May 29 '19 16:05 lhyt

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

5572189 avatar Jul 18 '19 08:07 5572189

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

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

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

wind4gis avatar Jul 31 '19 06:07 wind4gis

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

  • let只是创建过程提升,初始化过程并没有提升,所以会产生暂时性死区。
  • var的创建初始化过程都提升了,所以在赋值前访问会得到undefined
  • function 的创建、初始化、赋值都被提升了

fyZhang66 avatar Aug 05 '19 08:08 fyZhang66

变量生命周期:声明(作用域注册一个变量)、初始化(分配内存,初始化为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还未定义。

yaodongyi avatar Sep 12 '19 03:09 yaodongyi

转载: 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

yangwanging avatar Oct 17 '19 01:10 yangwanging

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

bbrucechen avatar Dec 25 '19 08:12 bbrucechen

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

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

BruceYuj avatar Feb 21 '20 10:02 BruceYuj

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

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

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

bbrucechen avatar Feb 21 '20 12:02 bbrucechen

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

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

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

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

BruceYuj avatar Feb 25 '20 10:02 BruceYuj

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。

whosesmile avatar Mar 14 '20 03:03 whosesmile

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

  1. var 声明变量会挂在window, let const 不会
  2. let, const 声明形成 作用域
  3. 同一作用域下 let const 不能声明 同名变量, 而var 可以
  4. 暂存死区
  5. const 声明后不得修改

coveyz avatar Apr 23 '20 02:04 coveyz

  • var会直接在栈里分配一个内存空间,等实际执行到语句时,再保存变量。如果遇到引用类型的变量,会去堆里开辟一个空间来保存对象,然后在栈里存储指向对象的指针。
  • let不会直接去栈里分配内存空间,而是做一个预检查,如果有同名变量就会报错。
  • const和let一样在分配内容空间之前会做检查。

soraly avatar Jul 10 '20 09:07 soraly

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

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

ghost avatar Dec 22 '20 20:12 ghost

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

zeybar avatar Jan 06 '21 11:01 zeybar

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/#sec-let-and-const-declarations 13.3.1和13.3.2

function test () {
    debugger // 这里在浏览器的scope local中可以查看到三者是undefined
    var a = 'a'
    let b = 'b'
    const c = 'c'
}
test()```

iJay avatar Aug 19 '21 12:08 iJay

   - var声明的变量只会存在于全局/局部作用域,如果声明在代码块(if/else/switch)中,会被提升出去,

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

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

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

Murphy-Tong avatar Oct 02 '21 07:10 Murphy-Tong

生命周期

https://rain120.github.io/study-notes/fe/javascript/key-concept/var-let-const-function-lifecycle

Rain120 avatar Feb 16 '22 07:02 Rain120

var,已废弃

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

let/const,更现代

  1. 块级作用域
  2. 不能重复声明
  3. 在声明后才可用,存在死区

Lingdeqing avatar Mar 19 '22 09:03 Lingdeqing

var 存在变量提升 可以重复赋值 不存在块级作用域

let 存在 TDZ,不存在变量提升,get 变量前必须声明 不可以重复声明 存在于块级作用域

const 存在 TDZ,不存在变量提升,get 变量前必须声明初始化 不可以重复声明 不可以重复赋值 (但对于引用对象的属性还是可以修改) 存在于块级作用域

Yangfan2016 avatar Aug 08 '22 10:08 Yangfan2016

var:

  1. 变量提升
  2. 重复声明
  3. 全局作用域绑定(var a = 1; 相当于 window.a = 1;)

let/const:

  1. 不再拥有 var 的缺陷特性
  2. 存在 TDZ 临时死区
  3. 块级作用域绑定

let:

  1. 变量声明

const:

  1. 常量声明
  2. 优先声明最佳实践(无需操作数据的情况下,一般采用 const 声明)

w-t-w avatar Nov 15 '22 23:11 w-t-w