var、let 和const区别

本文详细讲解了JavaScript中的变量作用域,包括函数作用域、全局变量的提升与污染,以及闭包问题。通过示例解释了变量提升现象,分析了let和const关键字如何避免这些问题。最后提到了const声明的常量特性及其注意事项。
摘要由CSDN通过智能技术生成

一、var作用域

1. 函数可以访问外部变量,但函数里面的变量,不能在全局中使用,除非函数将该变量返回。

var a = 10;
console.log(a);   // 10

function exam() {
  var b = 20;
  c = 30;
  console.log(a);
  console.log(b);
}
exam();   // 10  20  调用函数,函数里的值
console.log(c);   //30   函数内部只赋值,c是全局变量
console.log(b);   // ReferenceError: b is not defined  函数里面的变量,不能在全局中使用,要用的话写return返回某个变量,在函数调用时写一个变量接收函数返回的变量即可

2. 允许重复的变量声明,导致数据被覆盖。

var a = 1;
function print() {
  console.log(a);
}
var a = 2;
// console.log(print());    // undefined
print();   // 2

3. 变量提升:怪异的数据访问。

if (Math.random() < 0.5) {
  var a = 5;
  console.log(a);
} else {
  console.log(a);
}

相当于:

var a;
if (Math.random() < 0.5) {
  a = 5;
  console.log(a);
} else {
  console.log(a);
}

4. 变量提升:闭包问题。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="divButtons"></div>
  </body>
  
  // 获取div,往div里面追加按钮,并给按钮添加点击事件。
  <script>
    var div = document.getElementById("divButtons");
    for (var i = 1; i <= 10; i++) {
      var btn = document.createElement("button");
      btn.innerHTML = "按钮" + i;
      div.appendChild(btn);
      btn.onclick = function () {
        console.log(i);    // 不管点哪个都是   11
      };
    }
  </script>
  
</html>


原因:存在变量提升,如下代码:
使用的是同一个作用域中的i,循环很快,点击事件慢,是在循环结束后的

 <script>
    var i;  // 使用的同一个i
    var div = document.getElementById("divButtons");
    for (i = 1; i <= 10; i++) {
      var btn = document.createElement("button");
      btn.innerHTML = "按钮" + i;
      div.appendChild(btn);
      // 只有点击了btn,才会打印输出,此时,循换早已结束
      btn.onclick = function () {
        console.log(i);    // 不管点哪个都是   11
      };
    }
  </script>

(1)立即执行函数解决:

 <script>
    var i;  // 使用的同一个i
    var div = document.getElementById("divButtons");
    
    for (i = 1; i <= 10; i++) {
      var btn = document.createElement("button");
      btn.innerHTML = "按钮" + i;
      div.appendChild(btn);
      
      // 立即执行函数解决闭包问题
     (function(i){
      btn.onclick = function () {
        console.log(i);    // i
      };
     })(i)
    }
    
  </script>

(2)let关键字解决:

  <script>
    var div = document.getElementById("divButtons");
    for (let i = 1; i <= 10; i++) {
      var btn = document.createElement("button");
      btn.innerHTML = "按钮" + i;
      div.appendChild(btn);
      btn.onclick = function () {
        console.log(i); // i
      };
    }
  </script>

原理: 在循环中,用let声明的循环变量,会特殊处理,每次进入循环体,都会开启一个新的作用域,并将循环变量绑定到该作用域上(每次循环,使用的是一个全新的循环变量),且循环结束后,变量会被销毁

5. 全局变量挂载到全局对象:导致全局对象成员污染问题。
导致consol.log不能使用,而consol.log本是window全局对象已有的成员。

var console = "abc";
console.log(console);  //  TypeError: console.log is not a function

二、变量提升

如:

console.log(a);  // undefined
var a = '小琦';
console.log(a);  // 小琦

相当于:

var a;
console.log(a);  //只声明未赋值,返回结果undefined
a = '小琦';
console.log(a);  // 小琦

三、函数提升

创建函数有两种形式,1、函数声明;2、函数字面量(即函数表达式)。【而只有函数声明形式才有函数提升】,还有一种是方式:函数构造法:var a = new Fun(),技术角度来讲也是一个字面量形式。
比如:

console.log(a);   // f a(){ console.log(a) }
console.log(b);   // undefined

// 函数声明
function a() {
  console.log(a);
}
// 函数字面量
var b = function () {
  console.log(b);
};

相当于:

var a = "function";
var b;
console.log(a);   // function
console.log(b);   // undefined

参考:https://blog.csdn.net/hualvm/article/details/84395850
再比如:

console.log(a);     // [Function: a]
console.log(a());   // undefined
var a = 456;
function a() {
  console.log(123);   // 123
}
console.log(a);    //456
a = 789;
console.log(a);    // 789
console.log(a());  // TypeError: a is not a function

相当于:

// 函数提升,函数提升优先级高于变量提升
function a() {
  console.log(123);   // 123
}
// 变量提升,变量提升不会覆盖(同名)函数提升,只有变量再次赋值时,才会被覆盖
var a;
console.log(a);     // [Function: a]
console.log(a());   // undefined
// 变量赋值,覆盖同名函数字面量
a = 456;
console.log(a);    //456
// 再次赋值
a = 789;
console.log(a);    // 789
console.log(a());  // TypeError: a is not a function

函数提升要比变量提升的优先级要高一些,且不会被变量声明覆盖,但是会被变量赋值之后覆盖。

注意区别foo()foo

function foo() {
  //   console.log("函数声明");
 }
var foo;
console.log(foo());  // undefined 调用函数,返回函数执行的结果,函数没return,默认为:undefined
foo = "变量";
console.log(foo);    // 变量

function foo() {
  //   console.log("函数声明");
}
var foo;
console.log(foo);   // [Function: foo]
foo = "变量";
console.log(foo);   // 变量

四、let关键字

1. let声明的变量不会挂载到全局对象,则无污染全局对象的问题,如下代码:

let a = 123;
console.log(window.a);   // undefined

2. let声明的变量,不允许当前作用域范围内重复声明

let a = 123;  // 全局作用域定义a
// let a = 456;  //  报错,检查到当前作用域已有该变量声明
{
  let a = 789; //块级作用域定义a:代码执行时遇到花括号,花括号结束,块级作用域结束,块级作用域外面不可访问块级作用域里的变量
  console.log(a); // 789
}
console.log(a); // 123
if (Math.random() < 0.5) {
  let a = 5; // 定义在当前作用域中,当前作用域中的a
  console.log(a);
} else {
  // 这是另一个块级作用域,该作用域找不到a
  console.log(a);  // ReferenceError: a is not defined
}
console.log(a); // 这是全局作用域a,该作用域找不到a    ReferenceError: a is not defined

3. let声明变量之前,不可使用该变量

console.log(a);
let a = 123;    // 报错

底层实现上,let声明的变量实际上也会有提升,但是,提升后会将其放入到“暂时性死区”,如果访问的变量位于暂时性死区,则会报错:“Cannot access 'a' before initialization” 。当代码运行到该变量的声明语句时,会将其从暂时性死区中移除。

五、const关键字

constlet完全相同,仅在于const声明的变量,必须在声明时赋值,而且不可以重新赋值。实际上,在开发中,应尽量使用const来声明变量,以保证变量的值不会随意篡改,原因如下:

  1. 根据经验,开发中的很多变量,都是不会更改,也不应该更改的;
  2. 后续的很多框架或者第三方JS库,都要求数据不可变,使用常量一定程度上保证这一点;
  3. 注意的细节:
    (1)常量不可变,是指声明的常量的内存空间(内存地址)不可变,并不保证内存空间中的地址指向的其它空间不可变。
    (2)常量的命名:
    特殊的常量:该常量从字面意义上,一定是不可变得,如圆周率等。通常,该常量名要大写,多个单词之间下划线隔开。
    普通的常量:之前命名。
    (3)在for循环中,循环变量不可以使用常量;但for in可以,因为for in每次都是新的常量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值