var、let、const 变量提升 暂时性死区

1. var、let、const

是JavaScript声明变量的三种方式,其中let和const是ES6为JavaScript新增的两种方式

var, let声明变量分为三部分:1.创建,2.初始化undefined,3.赋值。

const声明的是一个常量,一旦声明,常量的值(内存地址不能改变)就不能改变 ,所以const声明必须立即进行初始化,不能留到以后赋值,所以const声明变量分为两部分:1.创建,2.初始化,没有赋值操作,相当于把初始化赋值整合成了一步,在初始化的时候进行赋值。

  • let和const 的「创建」过程被提升了,但是初始化没有提升。
  • var 的「创建」和「初始化」都被提升了。
  • function 的「创建」「初始化」和「赋值」都被提升了。如果函数声明和 var 声明同名,只提升函数声明,忽略 var声明
  • var声明是全局作用域或函数作用域,而let和const是块作用域。
  • var变量可以在其作用域内更新和重新声明;let变量可以被更新但不能重新声明; const变量既不能更新也不能重新声明(const用来声明常量)。

不加var声明的全局变量
不加var 无论在什么作用域下,声明的变量都是全局变量。无变量提升。
但是与var 声明的全局变量不同,无var 声明的全局变量可以用delete关键字删除。

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,前者是变量声明,带不可删除属性,因此无法被删除;后者为全局变量的一个属性,因此可以从全局变量中删除。
————————————————
版权声明:本文为CSDN博主「网络真危险!!」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_55846296/article/details/126604513

2.变量提升

变量提升

通俗来说,变量提升是指在 JavaScript 代码执行过程中,JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的行为
JavaScript编译器在编译阶段会搜集所有的变量声明,并将变量声明提前到变量当前所在作用域的顶部,也就是说,变量声明在编译阶段已经执行,而赋值则在执行阶段执行到对应语句时才会执行。所以才会出现所谓的“变量提升”。

console.log(a); // undefined
var a = 1;

//等价于==

var a;
console.log(a); // undefined
a = 1;
------------------------------------------

console.log(b); // ReferenceError: b is not defined
function foo () {
    console.log(b); // undefined
    var b = 1;
}
foo()

//等价于==

console.log(b); // ReferenceError: b is not defined
function foo () {
	var b;
    console.log(b); // undefined
    b = 1;
}
foo()

函数提升

JavaScript中具名的函数的声明形式有两种:

//函数声明式:
function foo () {}
//变量形式声明: 
var fn = function () {}

当使用变量形式声明函数时,和普通的变量一样会存在提升的现象,而函数声明式会提升到作用域最前边,并且将声明内容一起提升到最上边。如下:

fn()
var fn = function () {
 console.log(1)  
}
// 输出结果:Uncaught TypeError: fn is not a function

foo()
function foo () {
 console.log(2)
}
// 输出结果:2

函数提升会优先于变量提升

console.log(foo); // [Function: foo]
var foo = 10;
function foo () {}

//等价于==

function foo () {}
var foo;
console.log(foo);
foo = 10;

同名函数和变量

为对于同名的变量声明,Javascript采用的是忽略原则,后声明的会被忽略。对于同名的函数声明,Javascript采用的是覆盖原则,先声明的会被覆盖。对于同名的函数声明和变量声明,采用的是忽略原则,为了确保函数能够被引用到,在提升时函数声明会提升到变量声明之前,变量声明会被忽略,但是变量赋值以后会被覆盖。

/**同名变量**/
var a = 1;
var a =2;
// 等价于==
var a;
var a; // 被忽略
a = 1;
a = 2;
/**同名函数**/
function foo () {
    console.log(1)
}
function foo () { // 覆盖前一个
    console.log(2)
}
foo(); // 2

/**同名函数和变量**/ 
console.log(foo); // [Function: foo]
var foo = 10;
function foo () {}
console.log(foo); // 10
// 等价于==
function foo () {}
var foo; // 被忽略
console.log(foo);
foo = 10;
console.log(foo); //10

变量提升的好处

  • 解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间;

在JS代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。这么做就是为了提高性能,如果没有这一步,那么每次执行代码前都必须重新解析一遍该变量(函数),这是没有必要的,因为变量(函数)的代码并不会改变,解析一遍就够了。

在解析的过程中,还会为函数生成预编译代码。在预编译时,会统计声明了哪些变量、创建了哪些函数,并对函数的代码进行压缩,去除注释、不必要的空白等。这样做的好处就是每次执行函数时都可以直接为该函数分配栈空间(不需要再解析一遍去获取代码中声明了哪些变量,创建了哪些函数),并且因为代码压缩的原因,代码执行也更快了。

  • 声明提升还可以提高JS代码的容错性,使一些不规范的代码也可以正常执行。

a = 1;
var a;
console.log(a); // 1
如果没有变量提升,这两行代码就会报错,但是因为有了变量提升,这段代码就可以正常执行。

虽然在可以开发过程中,可以完全避免这样写,但是有时代码很复杂,可能因为疏忽而先使用后定义了,而由于变量提升的存在,代码会正常运行。当然,在开发过程中,还是尽量要避免变量先使用后声明的写法。

变量提升导致的问题

(1)变量被覆盖

var name = "JavaScript"
function showName(){
  console.log(name);
  if(0){
   var name = "CSS"
  }
}
showName() //undefined

在函数执行过程中,JavaScript 会优先从当前的执行上下文中查找变量,由于变量提升的存在,当前的执行上下文中就包含了if(0)中的变量 name,其值是 undefined,所以获取到的 name 的值就是 undefined。
这里输出的结果和其他支持块级作用域的语言不太一样,比如 C 语言输出的就是全局变量,所以这里会很容易造成误解。

(2)变量没有被销毁

function foo(){
  for (var i = 0; i < 5; i++) {
  }
  console.log(i); 
}
foo() // 5

使用其他的大部分语言实现类似代码时,在 for 循环结束之后,i 就已经被销毁了,但是在 JavaScript 代码中,i 的值并未被销毁,所以最后打印出来的是 5。这也是由变量提升而导致的,在创建执行上下文阶段,变量 i 就已经被提升了,所以当 for 循环结束之后,变量 i 并没有被销毁。

禁用变量提升

为了解决上述问题,ES6 引入了 let 和 const 关键字,从而使 JavaScript 也能像其他语言一样拥有块级作用域。let 和 const 是不存在变量提升的。下面用 let 来声明变量:

console.log(num) 
let num = 1

// 输出结果:Uncaught ReferenceError: num is not defined

变量提升机制会导致很多误操作:那些忘记被声明的变量无法在开发阶段被明显地察觉出来,而是以 undefined 的形式藏在代码中。为了减少运行时错误,防止 undefined 带来不可预知的问题,ES6 特意将声明前不可用做了强约束。不过,let 和 const 还是有区别的,使用 let 关键字声明的变量是可以被改变的,而使用 const 声明的变量其值是不可以被改变的。

function fn() {
  let num = 1;
  if (true) {
    let num = 2;  
    console.log(num);  // 2
  }
  console.log(num);  // 1
}
fn()

执行这段代码,其输出结果就和预期是一致的。这是因为 let 关键字是支持块级作用域的,所以,在编译阶段 JavaScript 引擎并不会把 if 中通过 let 声明的变量存放到变量环境中,这也就意味着在 if 中通过 let 声明的关键字,并不会提升到全函数可见。所以在 if 之内打印出来的值是 2,跳出语块之后,打印出来的值就是 1 了。这就符合我们的习惯了 :作用块内声明的变量不影响块外面的变量。

3.暂时性死区TDZ

ES6 规定:如果区块中存在 let 和 const,这个区块对这两个关键字声明的变量,从一开始就形成了封闭作用域。假如尝试在声明前去使用这类变量,就会报错。这一段会报错的区域就是暂时性死区。也就是从创建被提升到初始化这中间的部分

   ...
    if (true) {
       // a的创建被提升,TDZ的开始
       a = 2; // ReferenceError;  
       let a; // 完成a的初始化,TDZ的结束
       a = 3; // 完成对a的赋值操作。
   }

参考引用文章:
https://zhuanlan.zhihu.com/p/438563024
https://juejin.cn/post/6993676334635417614
https://www.xinran001.com/frontend/214.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值