用一篇文章让你搞清楚var、let、const声明变量和不用var声明变量的区别

前景:

在阅读之前需要理解:作用域函数作用域(这两个大家直接去百度就可以),以及 js代码运行过程声明变量变量提升,有助于理解他们的区别!

js代码运行过程

1、编译是把代码拿过来创建执行上下文,并创建变量环境、词法环境、可执行代码,将执行上下文压入执行栈。
2、执行是在当前执行上下文环境下执行可执行代码。

  • 变量环境:通过var声明function(){}声明的变量存在这里,有全局函数作用域
  • 词法环境:通过letconstwith()try-catch创建的变量存在这里,有全局函数作用域
  • 可执行代码:变量声明提前后,剩下的代码

例如正常代码是:

var global1=1;
let global2=2;
function func(){
    
    
  var func1=11;
  let func2=22;
  {
    
    
    var block1=111;
    let block2=222;
    console.log(block2,func2,global2,global1);
  }
  console.log(func1,block1,global1);
}
func();
console.log(global1,global2);

实际上的执行顺序为:

//全局代码执行过程
var global1;
let global2;
func=function(){
    
    }
global1=1;
global2=2;
func();
console.log(global1,global2);

//func执行过程
var func1;
let func2;
var block1;
func1=11;
func2=22;
{
    
    
  let block2;
  block1=111;
  block2=222;
  console.log(block2,func2,global2,global1);
}
console.log(func1,block1,global1);

声明变量

在 JavaScript 中创建变量通常称为"声明"变量。变量在脚本中第一次出现是在声明中。第一次用到时就设置于内存中,便于在后续中使用。

var a // 单个变量声明
console.log(a) // undefined
var b,c // 多个变量一起声明
var d = 4,f = 5 // 多个变量一起声明 + 初始化赋值

e = 6 // 脚本等同于:window.e = 6
console.log(e) // 6

我们可以在声明变量时直接初始化赋值(例如:var a = 1),也可以单纯只声明变量(例如:var a),这时其值实际上是 undefined
注意:如果不使用var、let、const去声明变量,它也是合法的JS语法,js解释器会将其直接声明至window下,给予该变量全局范围的可见度。

变量提升

JavaScript是单线程语言,所以执行肯定是按顺序执行。但是并不是逐行的分析和执行,而是一段一段地分析执行,会先进行编译阶段然后才是执行阶段,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)
注: 在编译阶段阶段,代码真正执行前的几毫秒,会检测到所有的变量和函数声明,所有这些函数和变量声明都被添加到名为Lexical Environment(词法环境)的JavaScript数据结构内的内存中。所以这些变量和函数能在它们真正被声明之前使用。

词法环境是一种规范类型,用于根据 ECMAScript 代码的词法嵌套结构定义标识符与特定变量和函数的关联。一个词法环境由一个环境记录和一个对外部词法环境的可能为空的引用组成。
简单地说,词法环境是一个包含标识符变量映射的结构。(这里的标识符是指变量/函数的名称,变量是对实际对象[包括函数对象和数组对象]或原始值的引用)。

为了方便理解词法环境,举个例子:

var a = 20;
var b = 40;
function foo() {
    
    
  console.log('bar');
}

上面代码在词法环境看来就是:

lexicalEnvironment = {
    
    
  a: 20,
  b: 40,
  foo: <ref. to foo function>
}

来延伸一点其他的知识点:
每个词法环境都包含2个组成部分:
1、
环境记录
:在词法环境中存储变量和函数声明的地方。(存储变量和函数声明的实际位置)
(注意 -对于函数代码,环境记录还包含一个arguments对象)
2、对外部词法环境的引用。(实际上就是对外部或者说是父级词法环境的引用。这对理解闭包是如何工作的尤为重要。)

变量的生命周期

1、声明阶段(Declaration phase)在范围内注册变量
2、初始化阶段(Initialization phase)分配内存并为作用域中的变量创建绑定
3、分配阶段(Assignment phase)为初始化变量分配一个值

var声明变量和不用var声明变量的区别

1. 作用域区别:

JavaScript变量分为 局部变量 和 全局变量,我们通过这个来学习一下:

  • 函数外(即全局)
var a = 123;
b = 456;
console.log(a); // 123
console.log(b); // 456
console.log(window.a); // 123
console.log(window.b); // 456
console.log(window); // 结果如下图

在这里插入图片描述
可以看出:在函数外,不管是使用var声明变量,还是不用var声明变量,它们都是全局变量,即:在window对象中添加属性并赋值

  • 函数内
function fn() {
    
    
    var a = 123;
    b = 456;
    console.log(a); // 123
    console.log(b); // 456
    console.log(window.a); // undefined
    console.log(window.b); // 456
}
fn();
console.log(window); // 结果如下图

在这里插入图片描述
可以看出:在函数内,使用var声明的变量为局部变量,不用var声明的变量为全局变量

总结:(区别一)
在函数外,用var声明的变量是全局变量,不用var声明的变量是全局变量
在函数内,用var声明的变量是局部变量,不用var声明的变量是全局变量

2、删除区别:

注: delete 用来删除对象的属性,如果是不可配置的属性返回false,其他情况返回true

var a = 123;
b = 456;
console.log(window.a); // 123
console.log(window.b); // 456
console.log(delete a); // false
console.log(delete b); // true
console.log(window.a); // 123
console.log(window.b); // undefined

可以看出:变量 a、b 都是全局变量,同为window对象的其中一个属性,但是,a 不可以删除,b 可以删除,那么这是为什么呢?我查了很多资料,总结一下原因:
注意:不使用var声明变量:它并不是声明了一个全局变量,而是创建了一个全局对象的属性。

即便如此,可能你还是很难明白“变量声明”跟“创建对象属性”在这里的区别。事实上,Javascript的变量声明、创建属性以及每个Javascript中的每个属性都有一定的标志说明它们的属性----如只读(ReadOnly)、不可枚举(DontEnum)、不可删除(DontDelete)等等。

由于变量声明自带不可删除属性,比如var num = 1 跟 num = 1,前者是变量声明,带不可删除属性,因此无法被删除;后者为全局变量的一个属性,因此可以从全局变量中删除。

我们来验证一下:

首先我们先来了解两个方法:

  • Object.getOwnPropertyDescriptor(); // 方法返回某个对象的属性的描述对象
    该描述对象包含以下信息
    1、value 属性的值
    2、writable 属性是否可读写
    3、enumerable 属性是否可枚举
    4、configurable 属性是否可配置
  • Object.defineProperty(); // 方法会直接在某个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象
  • 例一:
    在这里插入图片描述
    可以看出:
    属性 a 和 b 的描述对象区别在于 是否可配置 这个特性
    属性 a 为 configurable: false 不可配置
    属性 b 为 configurable: true 可配置
  • 例2:
    在这里插入图片描述
    变量b原本是可以删除的,但是通过把它的configurable 属性更改为false,他就变成不可删除的,所以我们可以得出一个结论:对象的属性是否可删除,取决于对象的描述对象属性configurable

总结:(区别二)
用var声明的变量默认带不可删除属性
不用var声明的变量默认带可删除的属性

var、let、const的区别

先总结一下:

  • var声明是全局作用域或函数作用域,而let和const是块作用域。
  • var变量可以在其范围内更新和重新声明;let变量可以被更新但不能重新声明; const变量既不能更新也不能重新声明(const用来声明常量)。
  • 均存在变量提升。但是,var变量会使用变量undefined初始化变量,但let和const未初始化变量。
  • var和let可以在不初始化的情况下声明变量,const在声明期间必须初始化

已上是最后的结论,接下来我们来细细区分一下:

var(声明变量)

1、var可以在全局范围声明或函数/局部范围内声明。

  • 当在最外层函数的外部声明var变量时,作用域是全局的。这意味着在最外层函数的外部用var声明的任何变量都可以在windows中使用
  • 当在函数中声明var时,作用域是局部的。这意味着它只能在函数内访问。

2、var 变量可以重新声明和修改

可以在相同的作用域内执行下面的操作,并且不会出错

3、存在变量提升

将var声明的变量会被提升到其作用域的顶部,并使用 undefined 值对其进行初始化.

注意:这几个特点也导致var存在一个缺点,导致我们需要新的方法来声明变量,造就了let 和 const 的出现,用一个示例来进行说明:

var greeter = "hey hi";
var times = 4;
if (times > 3) {
    
    
    var greeter = "say Hello instead";
}
console.log(greeter) // "say Hello instead"

由于times> 3成立,对greeter 进行重新定义赋值。如果你是故意重新定义greeter,这段代码是没有问题的,但是当你不知道之前已经定义了变量greeter时,这将成为产生问题,就会存在变量污染的风险,为了降低这种风险,在块作用域中使用let来代替var,这样不会污染块作用域的外部作用域,降低 bug率,使代码更安全。let和const实际上为ES6引入了"块级作用域"的概念。

let

let目前已经成为变量声明的首选,他是对var声明的改进,也解决了上面所说的var的问题

1、let是块级作用域

块是由 {} 界定的代码块,大括号中有一个块。大括号内的任何内容都包含在一个块级作用域中.

2、let可以被修改但不可以重新声明

像var一样,用let声明的变量可以在其范围内被修改。但与var不同的是,let变量无法在其作用域内被重新声明。

3、let存在变量提升,但是不会初始化

就像var一样,let声明也被提升到作用域顶部。 但不同的是:

  • var声明的变量会被提升到其作用域的顶部,并使用 undefined 值对其进行初始化
  • let声明的变量会被提升到其作用域的顶部,不会对值进行初始化
    因此,如果你尝试在声明前使用let变量,则会收到Reference Error

const(声明常量)

1、const声明的变量是块级作用域

2、const 不能被修改并且不能被重新声明

这意味着用const声明的变量的值保持不变,不能修改或重新声明。
因此,每个const声明都必须在声明时进行初始化

3、const存在变量提升,但是不会初始化

小拓展:

我们在最后讨论一个问题:let是否存在变量提升?

let、const 的「创建」过程被提升了,但是初始化没有提升。
var 的「创建」和「初始化」都被提升了。
function 的「创建」「初始化」和「赋值」都被提升了。

提示:let,const变量声明便随着变量的词法环境,在**未实例化之前不允许任何方式访问 也就是暂死区(TDZ)**
在变量声明之前引用块中的变量会导致 ReferenceError,因为从块开始到处理声明之前,该变量处于“临时死区”中。

对于let的所谓的暂时性死区怎么解释?let和const到底有没有变量提升?这个面试极易被cue的问题,我在这个给大家推荐一篇文章:https://zhuanlan.zhihu.com/p/28140450,希望可以对大家有所帮助!!!

猜你喜欢

转载自blog.csdn.net/weixin_55846296/article/details/126604513