你真的了解var/let/const的区别吗?

首先我们知道 var\let\const 都是声明变量用的,但是它们三个除了这个共同点之外,其他部分都各不相同,下面我们来看下。

var

var 关键词,是用来声明一个变量的,并可选地将其初始化为一个值。

js 复制代码
var num = 1;
var str = 'hello world';
var fn = () => {};

// 也可以声明多个变量,中间用逗号隔开
var a = 1, b = 2, c = 3;

特点:

  1. 它的作用域是它当前的执行上下文,本身是根据声明位置决定是全局变量还是局部变量。作用域限制在其声明位置的上下文中。

  2. 变量提升:无论发生在何处,都在执行任何代码之前进行处理。所以在代码中的任意位置声明变量总是等效于在代码开头声明。

    • 建议始终在作用域顶部声明变量(全局代码的顶部或者函数代码的顶部)
    • 变量提升不会影响值的初始化,当代码执行到赋值语句时,会将值分配给变量

来看一个例子:

js 复制代码
function varFor() {
	for (var i =0; i < 10; i++) {
  	console.log(i);  // 0,1,2,3,4,5,6,7,8,9
    setTimeout(() => {
    	console.log(i);  // 10个10
    }, 100)
  }
}

我们知道 for 循环语句部分是一个父级作用域,而循环体内部是一个子作用域,当在循环体内打印 i 时,是可以正常打印出循环的每个值的;但是在定时器中,由于事件循环机制的缘故,会导致定时器中的代码要等循环结束后才会执行,而这个时候 i 的值就成了10,所以最后打印出了10个10。那要怎样才能正确在定时器中打印出值呢?我们来看看 let

let

let 关键词也是用来声明变量的,但与 var 不同的是,它声明一个块级作用域 的本地变量。

那么这里对块级作用域有没有一个规则呢?我们来看一个例子:

js 复制代码
function varDemo() {
	var x = 1;
  {
  	var x = 2;  // 注意:这里是同样的变量,会覆盖最初始的变量
    console.log(x); // 2
  }
  console.log(x); // 2
}

function letDemo() {
	let x = 1;
  {
  	let x = 2; // 不同于外层的变量
    console.log(x);  //  2
  }
  console.log(x); // 1
}

可以看到 var 声明的变量,它的作用域将应用到整个函数,包括块里的变量也会被覆盖掉。而 let 则是只会在其声明的块或者子块当中。

还有一点就是,let 哪怕声明到 JavaScript 文件的最顶部,也不会和 var 一样,在全局对象 window 里新建一个属性,它就是一个全局变量,和全局对象属性是不同的。

js 复制代码
var x = 'hello';
let y = 'world';
console.log(window.x);  // 'hello'
console.log(window.y);  // undefined

这个时候我们再回头看之前的那个定时器的例子,可以看到已经能正确打印出值了,这就是因为块级作用域的关系。

js 复制代码
function varFor() {
	for (let i =0; i < 10; i++) {
  	console.log(i);  // 0,1,2,3,4,5,6,7,8,9
    setTimeout(() => {
    	console.log(i);  // 0,1,2,3,4,5,6,7,8,9
    }, 100)
  }
}

let 还可以在一些场景中模拟私有变量,比如在构造函数中:

js 复制代码
var Demo;
{
	let privateScope = new WeakMap();
  let counter = 0;
  
  Demo = function() {
    this.someProperty = 'foo';

    privateScope.set(this, {
      hidden: ++counter,
    });
  }
  
  Demo.prototype.showPublic = function() {
    return this.someProperty;
  };

  Demo.prototype.showPrivate = function() {
    return privateScope.get(this).hidden;
  };
}

console.log(typeof privateScope);
// "undefined"

var demo = new Demo()

console.log(demo);
// Demo {someProperty: "foo"}

demo.showPublic();
// "foo"

demo.showPrivate();
// 1

这里我们还需要提到一个概念:暂存死区(暂时性死区)

var 不同的是,var 是有变量提升的概念的,而 let 没有,通过 let 声明的变量直到它们的定义被执行时才初始化,也就是说,在 let 声明的变量之前去访问它们会导致 ReferenceError 。我们可以理解为从作用域顶部到变量初始化这个区域为"暂时性死区",是访问不到该变量的。

有个例子,我们来看一下:

js 复制代码
function test(){
   var foo = 33;
   if (foo) {
      let foo = (foo + 55);
   }
}
test();

这里的迷惑行为在第四行,可能有很多同学会直接给出 88 的答案,但其实不然,因为在这里 if 块中使用了 let 声明了一个专属用 if 块中的 foo 变量,所以后面这个 foo+55 中的 foo 是不会继承到外层的 foo 变量的,而当代码被执行到这一句时,因为还没有到达它的初始化,所以会抛出 ReferenceError 的错误。

有一种情况要避免,就是在外层使用 let 进行变量声明,又在子块中使用 var 声明,且变量名一样,这会导致抛出 SyntaxError ,这是因为 var 这里有变量提升,会导致隐式地重新声明变量。而在一个块中重复声明变量,是不被允许的。

const

const 关键词也是用来声明变量的,但是这个变量有点特殊,在声明之后就是不可更改的,也就是说 const 是用来声明常量的。

它与 let 语句定义的变量是非常类似的,唯一的不同就是上面说到的常量。而既然说到了被声明之后就不可再进行修改,那我们在使用 const 进行变量声明时,就必须要在声明的同时进行赋值

如果是用 const 定义了一个引用数据类型的值,那我们也是可以钻下空子的,虽然我们无法直接更改变量本身,但是可以更改变量里的属性。

js 复制代码
const obj = { a: 1 };

obj = { b: 2 };  // invalid assignment to const 'obj'
obj.a = 2;

在定义常量时,会有个通俗的约定,那就是常量声明全部用大写字母(虽然也可以用小写)。

异同点

相同点:

  1. 三者都是用来声明变量的
  2. letconst 都有暂时性死区和块级作用域的概念,且不能重复声明
    不同点:
  3. var 有变量提升的概念,其他两者没有
  4. var 会挂载到全局对象上去,其他两者没有
  5. const 声明后的变量不可更改,是常量
  6. letconst 都有暂时性死区和块级作用域的概念,且不能重复声明
相关推荐
贵州晓智信息科技3 小时前
如何优化求职简历从模板选择到面试准备
面试·职场和发展
百罹鸟3 小时前
【vue高频面试题—场景篇】:实现一个实时更新的倒计时组件,如何确保倒计时在页面切换时能够正常暂停和恢复?
vue.js·后端·面试
古木201911 小时前
前端面试宝典
前端·面试·职场和发展
码农爱java18 小时前
设计模式--抽象工厂模式【创建型模式】
java·设计模式·面试·抽象工厂模式·原理·23种设计模式·java 设计模式
Jiude19 小时前
算法题题解记录——双变量问题的 “枚举右,维护左”
python·算法·面试
长安05111 天前
面试经典题目:LeetCode134_加油站
c++·算法·面试
正在绘制中1 天前
Java重要面试名词整理(一):MySQL&JVM&Tomcat
java·开发语言·面试
沉默王二1 天前
虾皮开的很高,还有签字费。
后端·面试·github
Do1 天前
时间系列三:实现毫秒级倒计时
前端·javascript·面试
小蜗牛慢慢爬行1 天前
有关异步场景的 10 大 Spring Boot 面试问题
java·开发语言·网络·spring boot·后端·spring·面试