一文搞懂javascript中的var、let、const
简介
var, let and const是JavaScript中三种定义变量的方式,它们之间有什么区别呢?这是前端面试中常见的一道题,今天我们来一文说透它。
let和const区别不大,主要是const声明的是常量,不可修改,而let声明的变量是可修改的。所以我们重点放在var和let上。
变量初始化
声明变量的同时为其赋值叫做初始化。
var和let声明的变量都可以不赋值,此时变量的值为undefined。const声明的变量必须赋值,否则会报错。
// `var`和`let`声明的变量可以不赋值,此时变量的值为`undefined`。
var num; // num的值是undefined
num = 1; // num的值是1
let str; // str的值是undefined
str = 'hello'; // str的值是'hello'
// `const`声明的变量必须赋值,否则会报错。
const a; // SyntaxError: Missing initializer in const declaration
变量提升 - Hoisting
Hoisting这个词中文译为提升,就是将变量的声明提升到其作用域的顶部,注意提升的是声明,而不是赋值。
var声明的变量会被提升至其作用域顶部。let和const声明的变量不会被提升。(注意这个说法有争议,详见MDN)- 提升只针对变量声明,不包括赋值。
如果var是在全局作用域声明的,那么它会被提升到全局作用域的顶部。
console.log(name); // undefined
var name = 'Philip';
以上代码等价于:
var name; // `var`声明的变量会被提升到其作用域顶部。
console.log(name); // undefined
name = 'Philip';
如果var是在函数作用域声明的,那么它会被提升到函数作用域的顶部。
function printName() {
console.log(name); // undefined
var name = 'Philip';
}
printName();
以上代码等价于:
function printName() {
var name; // `var`声明的变量会被提升到其作用域顶部。
console.log(name); // undefined
name = 'Philip';
}
printName();
let和const声明的变量不会被提升。
对于let和const,它们不会被提升,所以下面代码会报错。
console.log(num); // ReferenceError: Cannot access 'num' before initialization
const num = 1;
前面说过,关于let和const是否被提升有争议。
- 一种说法是
let和const不会被提升,所以在声明之前访问会报错。 - 另一种说法是
let和const会被提升,但是在声明之前访问会抛出Temporal Dead Zone错误。
比如下面的代码:
const x = 1;
{
console.log(x); // ReferenceError: Cannot access 'x' before initialization
const x = 2;
}
这段代码会报错,但是如果我们把{}内的const x = 2;注释掉,那么代码就不会报错。如果const x = 2没有被提升的话,那么console.log(x)应该可以访问到全局的const x = 1,而不会报错。换句话说:因为const x = 2被提升了,所以console.log(x)访问的是提升后的x,而此时x还没有被初始化,所以报错。
提升只针对变量声明,不包括赋值。
下面的代码会报错,因为x = 1是赋值,并不是声明,所以不会提升。(注意:如果变量声明前没有加var, let或const,那么其实产生的是一个意外的全局变量。)
console.log(x); // ReferenceError: x is not defined
x = 1;
如果有同名函数和变量,那么提升后,变量位于函数之前(或者说函数会覆盖变量)。
以下代码中有一个同名的函数和变量。
console.log(foo); // [Function: foo], not undefined.
function foo() {
console.log('function foo');
}
var foo = 1;
提升后代码如下:
var foo;
function foo() {
console.log('function foo');
}
console.log(foo);
foo = 1;
面试题
看几道面试题,以下几段代码输出什么?
- 第一题
a = 2;
var a;
console.log(a); // 2
解决var提升的问题很简单,就是按照提升规则将代码重写一下,上面的代码等价于如下代码,结果一目了然。
var a;
a = 2;
console.log(a); // 2
- 第二题
var a = true;
foo();
function foo() {
if (a) {
var a = 10;
}
console.log(a);
}
只要函数内部有var声明的变量,那么所有全局声明的var变量都会被忽略,以上代码提升后等价于如下代码(注意function也有提升),函数内部的var永远会覆盖全局的var。
var a = true;
function foo() {
var a; // value of a is `undefined`
if (a) {
a = 10; // never executed.
}
console.log(a);
}
foo();
- 第三题
function fn() {
console.log(typeof foo);
var foo = 'variable';
function foo() {
return 'function';
}
console.log(typeof foo);
}
fn();
还是那句话,此类题目的解法就是按照提升规则把代码重新写一遍,以上代码提升后等价于如下代码:
function fn() {
var foo;
function foo() {
return 'function';
}
console.log(typeof foo);
foo = 'variable';
console.log(typeof foo);
}
fn();
所以输出结果是function和string。
变量的作用域
var声明的变量有只两种作用域:全局作用域和函数作用域。(没有块级作用域)let和const声明的变量有三种作用域:全局作用域,函数作用域和块级作用域。var声明的全局变量会挂载到window对象上,而let和const不会。let和const有临时性死区,而var没有。
面试题
第一题
以下代码输出什么?
let x = 1;
{
let x = 2;
}
console.log(x);
答案:1,因为let有块级作用域,所以let x = 2只在{}内有效。
第二题
以下代码输出什么?
var x = 1;
{
var x = 2;
}
console.log(x);
答案:2,因为var没有块级作用域,所以var x = 2会覆盖外部的var x = 1。
第三题
以下代码输出什么?
let name = 'zdd';
{
console.log(name);
let name = 'Philip';
}
答案:ReferenceError: Cannot access 'name' before initialization。因为let有块级作用域,所以console.log(name);访问的是let name = 'Philip';之前的name,而此时name还没有被初始化,处于暂时性死区中,所以报错。
第四题
以下代码输出什么?
'use strict';
{
function foo() {
console.log('foo');
}
}
foo();
答案:ReferenceError: foo is not defined。因为foo是在块级作用域内声明的,所以在外部无法访问。但是如果我们把'use strict';去掉,那么代码就可以正常运行。因为在非严格模式下,函数声明会被提升到全局作用域。
第五题
以下代码输出什么?
(() => {
let x;
let y;
try {
throw new Error();
} catch (x) {
x = 1;
y = 2;
console.log(x);
}
console.log(x);
console.log(y);
})();
答案:1 undefined 2。因为catch中的x是一个新的变量,不是外部的x,所以x = 1只会改变catch中的x,而不会改变外部的x。而y = 2不是catch的参数,只是在catch中赋值的,所以会改变外部的y。
暂时性死区 - Temporal Dead Zone
TDZ即Temporal Dead Zone - 中文名暂时性死区,是指let和const声明的变量在其作用域开始到变量声明之间的这段区域。在暂时性死区内无法访问变量,访问会报错。
function foo() {
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let a = 1;
const b = 2;
}
foo();
对于以上代码,常量b的暂时性死区开始于函数的第一行,终止于b的声明,而console.log(b);这句恰恰在暂时性死区内访问了b,所以会报错。
面试题
以下代码输出什么?
function foo() {
console.log(typeof bar);
const bar = 1;
}
foo();
答案:
ReferenceError: Cannot access 'bar' before initialization
因为console.log(typeof bar);这句在bar的暂时性死区内访问了bar,所以会报错。可以看到,即使强如typeof这种几乎不会报错的操作符也无法规避暂时性死区。
如果我们把const bar = 1;去掉,那么代码就不会报错。typeof操作符对于没有声明的变量不会报错,而是返回undefined。
function foo() {
console.log(typeof bar); // 输出undefined
}
重新声明- Redeclaration
var声明的变量可以被重复声明,后声明的覆盖先声明的。let和const声明的变量不可以被重复声明。
面试题
看几道面试题,以下几段代码输出什么?
- 第一题
var a = 1;
function foo() {
var a = 2;
{
var a = 3;
console.log(a);
}
console.log(a);
}
foo();
console.log(a);
答案:3 3 1, 这个题主要考察两个知识点:
var声明的变量没有块级作用域。var声明的变量可以被重复声明,后声明的会覆盖先声明的。
所以var a = 3会覆盖外部的var a = 2,但是var a = 2不会覆盖最外面的var a = 1。因为var有函数作用域。
以上代码提升后等价于如下代码:
var a;
a = 1;
function foo() {
var a;
var a; // redeclaration
a = 2;
{
a = 3;
console.log(a);
}
console.log(a);
}
foo();
console.log(a);
注意:面试题中凡事用{}包裹var的都是障眼法,var没有块级作用域。
第二题
这道题比较简单,考察的是let的块级作用域,代码输出2, 1。因为let有块级作用域。let a = 2只在{}内有效。
function foo() {
let a = 1;
{
let a = 2;
console.log(a);
}
console.log(a);
}
foo();
意外的全局变量
如果我们声明变量的时候忘记了写var, let或者const,那么这个变量就是所谓的Accidental Global Variables,意思是意外的全局变量。
function f1() {
b = 2; // accident global variable
}
f1();
console.log(b); // 2
面试题
以下代码输出什么?
for (var i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i);
})
}
答案:3 3 3
因为var没有块级作用域,所以setTimeout内的i都是指向同一个i,而setTimeout是异步的,其回调函数代码需要先进入宏任务队列,待for循环结束后才能执行,此时i已经是3了。关于这道题的详细解释,请看这篇。
最佳实践
如今ES6已经普及,对于业务代码来说,基本不需要使用
var了,var目前只有JS框架或者底层工具库才会使用。对于
let和const,优先使用const,只有在需要修改变量的情况下才使用let。经典for循环使用
let,因为循环变量会被修改。for (let i = 0; i < 5; i++) {
console.log(i);
}
for...in和for...of使用const,因为循环变量不会被修改。const arr = [1, 2, 3];
for (const item of arr) {
console.log(item);
}
const obj = {a: 1, b: 2};
for (const key in obj) {
console.log(key);
}
祝大家编程愉快,如果觉得有用就点个关注,每篇文章都是纯古法手打,在AI大行其道的当下,认认真真写文章的人不多了,您的点赞转发评论就是对我最大的支持!
一文搞懂javascript中的var、let、const的更多相关文章
- 一文搞懂 js 中的各种 for 循环的不同之处
一文搞懂 js 中的各种 for 循环的不同之处 See the Pen for...in vs for...of by xgqfrms (@xgqfrms) on CodePen. for &quo ...
- 来一轮带注释的demo,彻底搞懂javascript中的replace函数
javascript这门语言一直就像一位带着面纱的美女,总是看不清,摸不透,一直专注服务器端,也从来没有特别重视过,直到最近几年,javascript越来越重要,越来越通用.最近和前端走的比较近,借此 ...
- 一文彻底搞懂JavaScript中的prototype
prototype初步认识 在学习JavaScript中,遇到了prototype,经过一番了解,知道它是可以进行动态扩展的 function Func(){}; var func1 = new Fu ...
- 彻底搞懂JavaScript中的继承
你应该知道,JavaScript是一门基于原型链的语言,而我们今天的主题 -- "继承"就和"原型链"这一概念息息相关.甚至可以说,所谓的"原型链&q ...
- 一张图搞懂 Javascript 中的原型链、prototype、__proto__的关系 转载加自己的总结
1. JavaScript内置对象 所谓的内置对象 指的是:JavaScript本身就自己有的对象 可以直接拿来就用.例如Array String 等等.JavaScript一共有12内置对象 ...
- 一文搞懂js中的typeof用法
基础 typeof 运算符是 javascript 的基础知识点,尽管它存在一定的局限性(见下文),但在前端js的实际编码过程中,仍然是使用比较多的类型判断方式. 因此,掌握该运算符的特点,对于写出好 ...
- 一文搞懂JavaScript数组的特性
前言 数组是几乎所有编程语言的基础语法,JavaScript因为语法特性,之前缺少一些集合类对象,对数组的使用就会更多一些,因此我们更需要理解数组知识. 然而大部分人对数组都已经非常熟悉了,所以本文将 ...
- javascript中的var,let,const关键字
文章:JavaScript 中 var 和 let 和 const 关键字的区别 比较全面的文章.
- 彻底搞懂javascript中的match, exec的区别
在工作中经常发现一些同学把这两个方法搞混,以致把自己弄的很郁闷.所以我和大家一起来探讨一下这两个方法的奥妙之处吧. 我们分以下几点来讲解: 相同点: 1.两个方法都是查找符合条件的匹配项,并以数组形式 ...
- 一分钟搞懂JavaScript中的JSON对象
JSON(JavaScript Object Notation)是表示值和对象的通用格式. JavaScript 提供了如下方法: JSON.stringify 将对象转换为 JSON. JSON.p ...
随机推荐
- "油猴脚本""篡改猴"领域的一些基本常识
本文简要介绍本人对"油猴脚本","篡改猴"领域的一些见解,内容注定不可能一步到位和事无巨细,欢迎各位仁人志士对我批评指正,提出意见建议.另外转载前请务必注明作者 ...
- Windows7、Windows10跳过创建用户并直接用Administrator身份登录
windows7 windows10跳过创建用户并直接用Administrator身份登录 一.操作方法: 在界面设置按 按 shift+f10 然后输入 lusrmgr.msc 用户管理控制台开启a ...
- sonarqube+gitlab+jenkins+maven集成搭建(二)
SonarQubeScanner 下载[root@localhost ~]# wget https://binaries.sonarsource.com/Distribution/sonar-scan ...
- 拆解 Cursor Pro 自动化工具,看看它是怎么实现的?
深入解析Cursor Pro自动化工具的核心实现 从源码角度剖析关键技术 完整解读:注册.认证.机器码重置的自动化方案 项目概述 大家好,我是松哥.这篇文章将为大家详细解析一个Cursor自动化管 ...
- 27.4K Star!这个LLM应用宝库让你秒变AI全栈高手,RAG和AI Agent一网打尽!
嗨,大家好,我是小华同学,关注我们获得"最新.最全.最优质"开源项目和高效工作学习方法 想要快速入门LLM应用开发?想要了解最新的RAG和AI Agent技术?这个收获27.4K ...
- 漏洞预警 | Apache NiFi信息泄露漏洞
0x00 漏洞编号 CVE-2024-56512 0x01 危险等级 中危 0x02 漏洞概述 Apache NiFi是一个强大的.易于使用的数据集成平台,旨在自动化和管理数据流,尤其是在大数据环境中 ...
- CatBoost算法原理及Python实现
一.概述 CatBoost 是在传统GBDT基础上改进和优化的一种算法,由俄罗斯 Yandex 公司开发,于2017 年开源,在处理类别型特征和防止过拟合方面有独特优势. 在实际数据中,存在大 ...
- 5个让你眼前一亮的JavaScript装饰器技巧
@charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...
- C# HttpListener 和 HttpServer区别
HttpListener 和 HttpServer 都是 C# 中用于创建 HTTP 服务器的类库,它们的作用都是监听 HTTP 请求,并向客户端发送 HTTP 响应.它们的主要区别在于实现方式和使用 ...
- 【语义分割专栏】:FCN原理篇
目录 前言 语义分割 背景介绍 FCN核心剖析 全卷积(Fully Convolution) 反卷积(deconvolution) 最近邻插值法 双线性插值 反卷积 跳跃连接(Skip Connect ...