javascript思考分析(一):var let const function声明的区别

本文主要探讨JavaScript中var、let、const、function声明的区别。ES6前常用var声明变量,但有缺陷,ES6推出let和const替代。文中分析了各声明方式在变量提升、重复声明、作用域等方面的表现,建议尽量用let、const代替var,帮助初学者理清概念。

javascript思考分析(一):var let const function声明的区别
引言
在学习javascript的过程中,变量是无时无刻不在使用的。那么相对应的,变量声明方法也如是。变量是由自己决定,但变量声明方法是早已经定义好的。那么在使用变量之前,了解变量声明方法,就变得尤为重要。在ES6推出之前,最常用的声明变量方法就是var。但是由于var自身的缺陷,ES6推出了let和const替代var。虽然修正了var的缺陷,但不能改变的,是之前已经用var写了多少年的项目,这些项目是无法随着var的被取代而轻易更改的。所以仍存在着使用var的公司和项目,这也使得了解var、let、const的区别变得有必要。
一、var
在说明var并看代码之前,我们先统一思路,将变量的声明及使用过程分为:创建→初始化→赋值→修改四步。

首先,来看var的声明及使用:

function test() {
  /*
  预解析,初始化与赋值分离
var a = undefined // 创建变量a,并初始化为undefined。此时变量已经存在与环境中
*/
  
console.log(a) //undefined

var a = 0; //赋值为0
console.log(a); // 0

a = 1; //可以修改变量值
console.log(a) // 1
}

test();

以上代码可以看出,var在它的执行环境中的声明及操作过程为:
( 创建→初始化 )→赋值→修改

预解析:在环境最顶端,创建变量,并初始化为undefined—— 变量提升;
为变量赋值;
对变量进行操作,可以在后续操作中对变量值进行修改;

   通过console.log的打印结果,我们可以清晰的认识到一点——var的初始化与赋值是分离的,而且初始化的过程优先于执行环境中的所有操作。这就是为什么在var声明赋值前console.log变量,会打印出undefined,而不是报错的原因。
   var声明的初始化先于赋值的现象,就叫做变量提升。

然后着重说一下变量提升

if判断中的变量提升

function test() {
  /*
  预解析
虽然if判断没有执行,但var的变量提升已经发生,此时执行环境中,已经存在变量a,值为undefined;
*/
  
console.log(a) // undefined,变量提升
  
if (false) {
  var a = 1;
console.log(a) //不执行
}

console.log(a); //undefined,变量未赋值

a = 1; //注意,此时因为a的变量提升,未加声明符号的赋值,并没有提升到全局环境中
console.log(a); // 1
}

test();

console.log(a); // 报错,因为if中a的变量提升,a = 1的赋值并没有存在与全局中。如果注释掉if判断中的内容,a = 1因为没加变量声明符号,相当于在与全局中声明,那么最后的console.log(a)将打印1

2.for循环中的变量提升
function test() {
  /*
  预解析
虽然if判断没有执行,但var的变量提升已经发生,此时执行环境中,已经存在变量a,值为undefined;
*/
   
console.log(a) // undefined,变量提升
  
console.log(i); //undefined,变量提升
for (var i = 0;i < 0;i++) {
  var a = 1;
console.log(a) //未执行
}
console.log(i); //1,for中的初始化语句已经执行

console.log(a); //undefined,变量未赋值

a = 1;
console.log(a);
}

test();
console.log(a); //报错,变量不存在

   从上边代码中可以看出,当var声明存在于if和for循环中时,不管赋值有没有执行,创建及初始化的过程都已经提升到了执行环境中。

最后说明var的一个缺陷:

function test() {
  var a = 0;
var a = 1;
var a = 2;

console.log(a); // 2
}

test();

   由以上代码可以看出,var声明一个变量后,可以无限次的以同一个变量名不断的重复 创建→初始化→赋值,这跟直接修改变量值的结果是一样的。但是实际操作中不会有人通过这种方式操作变量,而且如果项目很大,很难保证不出现给不同变量声明同一个变量名的情况,很容易出现错误。

二、let

首先,来看var的声明及使用:

function test() {
/*
预解析,没有变量提升,啥也没有。创建、初始化与赋值同时进行
*/

console.log(a); //报错,变量仍未创建

let a = 0; // 创建变量a,初始化并赋值为0.不赋值的话,则为undefined;
console.log(a); // 0

a = 2; // 可以修改变量值
console.log(a); // 2

}

test();

同样,先分析过程:
( 创建→初始化→赋值 )→修改

预解析,啥也没有;在环境最顶端,创建变量,并初始化为undefined—— 变量提升;

创建变量、初始化并赋值;
对变量进行操作,可以在后续操作中对变量值进行修改;

   通过上方代码,我们可以看出let声明变量时,( 创建→初始化→赋值 )是在一步完成的,不存在变量提升的现象。所以在let声明前console.log(a),报错a is not defined,因为此时a还没有被创建。而let初始化前的执行区域就叫做暂存死区。

然后,来看let在重复声明时的表现:

function test() {
  let a = 1;
let a = 2;
let a = 3;

console.log(a); // 报错
}

test();

   以上说明了let区别于var的另一个特性——变量的唯一性。同一个变量名,不能在let中重复使用,所以执行上方代码操作的结果,就是报错Identifier 'a' has already been declared;

最后,let与var最大的区别——块级作用域

   首先说明,块级作用域的概念——简单理解,{}一个大括号就是一个代码块,一个单独的执行环境。那么它理应不受外部影响(如果不是刻意为之的话,它也不应该影响外部环境)。
   但是在let之前,JS中的变量声明是没有块级作用域的属性的。这其中最典型的案例,就是for循环中的var声明i。

function a() {
  for (var i = 1;i < 5;i++) {
  console.log(i);
for( var i = 10;i < 20;i++) {
  console.log(i);
}
}
}
a(); //只执行外部循环一次,因为内部循环由于var声明的缘故,执行过后i值已经为11,外部循环判断后,将不再执行

function testA() {
  /*
  预解析
创建i并赋值为undefined
注意,此时实际上是两个i的变量提升
*/
console.log(i); // undefined

var i = 0; // 赋值i = 0
console.log(i); // 0;

{
  console.log(i); // 0,访问第一个i
var i = 10 // 赋值i = 10,覆盖第一个i
console.log(i); // 10,访问第二个i
}

console.log(i); // 10,访问第二个i,i已经被覆盖
}
testA();

   以上代码,通过testA函数,侧面解释了一下a函数中的行为原因。这一行为模式,充分暴露了var声明的缺陷。
   然后再来看let声明中的块级作用域:

function b() {
  for (let i = 0;i < 5;i++) {
  console.log(i);
for(let i = 10;i < 20;i++) {
  console.log(i);
}
}
}
b(); //因为块级作用域的存在,两个循环的i互不影响,所有循环次数都会被执行。实际使用时,建议还是不要都用同一变量名

function testB() {   
/*预解析啥也没有 */
console.log(i); // 报错,暂存死区

let i = 0;
// 赋值i = 0
console.log(i); // 0;

{
console.log(i); // 报错,暂存死区,因为块中又声明了变量i。如果块中没有let i的话,则按作用域链向上查找,打印外部i值0
let i = 10 // 赋值i = 10,不覆盖第一个i
console.log(i); // 10,访问第二个i
}

console.log(i); // 0,访问第一个i,两个i相互不影响
}
testB();

   以上代码,通过testB函数,侧面解释了一下b函数中的行为原因。这一行为模式,体现了let生命中块级作用域的存在,并暴露出了let与var的最大区别。

三、const
function test() {
  /*
  预解析啥也没有,创建、初始化与赋值同时进行
*/
console.log(a); // 报错,变量仍未创建

const a = {
  name: “Lyu” // 创建对象a,并赋值为一个对象地址
}
console.log(a); //{name: “Lyu”}
console.log(a.name); // Lyu

a = 1; //报错,常量值不可修改

a.name = “Jack”;
  console.log(a); // {name: “Jack”}
console.log(a.name); // Jack
}

test();

分析过程:
( 创建→初始化→赋值 )→修改

预解析,啥也没有;预解析在环境最顶端,创建变量,并初始化为undefined—— 变量提升;

创建变量、初始化并赋值。必须赋值,不赋值会报错;
对变量进行操作,可以在后续操作中对变量值进行修改,不可以对变量进行修改,但是可以对变量的属性进行修改;

   为了说明const的特性,特意声明了一个对象。在理解了var和let的过程之后,再来看const的整个过程,会发现在( 创建→初始化→赋值 )的过程中,const和let是没有区别的。唯一的区别在于→修改。如果执行了上方的代码,在a = 1那步会报错Assignment to constant variable。其中的constant就是const的英文全拼,它的意思的不变的、恒定的、恒量。那么从字面上就能理解,通过const声明的变量,是一个恒定值,即无法更改的值。所以当通过a = 1试图修改a的值时,报错。
   虽然a本身的值无法修改,但是a为对象,值为地址,所以a的属性是可以修改的。从代码最后一步可以看出。a.name的值成功修改为"Jack"。这就是const 的第二个特性。
   同let一样的,const的第三个特性也是变量的唯一性,不再过多阐述。

四、function

首先,来看function的声明及使用:

/*
  funtcion test() {…}
预解析,创建、初始化、赋值三位一体
此时函数已经完成变量声明的所有操作,在执行环境的任何位置都可以调用函数
*/

test(); //调用函数,只要函数确实存在并可用,那么在执行环境任何位置都可以调用

function test() {…}

分析过程:
( 创建→初始化→赋值 )→执行/修改

预解析,在环境最顶端,创建函数,初始化并赋值为函数定义;
执行函数,无论函数在何位置,只要可用,就可以调用;

   function是专门用于函数声明的方法,由于函数的复杂性,以及利用性。function声明的函数,会在整个环境变量最顶端完成创建、初始化、赋值三位一体的操作。这样一来,不管在何处声明了函数,可以在任何地方调用函数方法。这是比较合乎常理的性质。

然后,function同var一样,同样存在变量声明的特性:

if判断中的变量提升

function test() {
  /*
  预解析
  虽然if判断没有执行,但function的变量提升已经发生,此时执行环境中,已经存在变量a,值为undefined;
*/
  
console.log(a) // undefined,变量提升
  
if (false) {
  function a(){}; // 变量提升
console.log(a) //不执行
}

console.log(a); //undefined,变量未赋值

a = 1; //注意,此时因为a的变量提升,未加声明符号的赋值,并没有提升到全局环境中
console.log(a); // 1
}

test();

console.log(a); // 报错,因为if中a的变量提升,a = 1的赋值并没有存在与全局中。如果注释掉if判断中的内容,a = 1因为没加变量声明符号,相当于在与全局中声明,那么最后的console.log(a)将打印1

for循环中的变量提升

function test() {
  /*
  预解析
虽然if判断没有执行,但function的变量提升已经发生,此时执行环境中,已经存在变量a,值为undefined;
*/
   
console.log(a) // undefined,变量提升
  
console.log(i); //报错,暂存死区
for (let i = 0;i < 0;i++) {
  function a() {}; //变量提升
console.log(a) //未执行
}
console.log(i); //报错,let不存在变量提升

console.log(a); //undefined,变量未赋值

a = 1;
console.log(a);
}

test();
console.log(a); //报错,变量不存在

最后,function同var一样,它也可以对同一变量重复声明,而且后边的函数定义会覆盖前边的函数定义:

test(); // 2

function test() {
  console.log(1);  
}

function test() {
  console.log(2);
}

   结果打印2,说明前边声明的函数方法被后边所覆盖。

五、总结
综上所述,可以总结为如下几点:
1.var与let、const的区别在于变量提升,以及变量的唯一性;
2.const与let的区别,除了变量值不能修改,其他性质一样;
3.function由于其自身的需要,创建→初始化→赋值三位一体,在环境最顶端完成;也正因为这种性质,函数声明的函数可以在任何位置被调用;
4.如果可以,尽量使用let、const代替var;
本文仅仅简单罗列了在函数声明及操作方面,var、let、const、function的区别,意在为初学者理清概念偏差,少走弯路,并通过理解学习,早日跨入JS门槛。
以上,如有错误或是纰漏,欢迎批评指正~

转载于
链接:https://www.jianshu.com/p/e6908920e63d

### JavaScript 中 `let`、`const` 和 `var` 的用法及区别 #### 1. 基本概念 在 JavaScript 中,`var` 是最早的变量声明方式,而随着 ES6 的推出,新增了 `let` 和 `const` 关键字。它们之间的主要差异体现在作用域规则、提升机制以及是否可重新赋值等方面[^1]。 --- #### 2. 作用域的不同 - **`var`**: 使用 `var` 声明的变量具有函数作用域(Function Scope),即它的生命周期局限于最近的函数体内。如果是在全局范围内声明,则该变量会被绑定到全局对象(如浏览器环境中的 `window`)上[^1]。 - **`let` 和 `const`**: 这两者都具备块级作用域(Block Scope),这意味着它们的作用范围仅限于 `{}` 所包围的代码块内。这种设计减少了因意外覆盖变量而导致的错误发生概率[^3]。 ```javascript function testScope() { if (true) { var scopeVar = 'var'; let scopeLet = 'let'; const scopeConst = 'const'; } console.log(scopeVar); // 输出 "var", 因为 var 不受块级限制影响 console.log(scopeLet); // 抛出 ReferenceError 错误 console.log(scopeConst); // 同样抛出 ReferenceError 错误 } testScope(); ``` --- #### 3. 提升行为对比 - **`var`**: 存在变量提升现象,在实际执行前已被初始化为 `undefined`。因此可以在声明之前就对其进行访问,尽管这样做通常被认为是不良实践[^2]。 - **`let` 和 `const`**: 虽然也会经历类似的预解析阶段,但在进入相应作用域直到正式声明完成之间的时间窗口里处于“暂时性死区”(Temporal Dead Zone),尝试在此期间引用这些变量将会引发异常[^2]。 ```javascript console.log(varExample); // undefined, 变量已提升但未赋初值 var varExample = 'example'; // 下列两行都会报错:"ReferenceError: Cannot access..." console.log(letExample); let letExample = 'another example'; console.log(constExample); const constExample = 'final example'; ``` --- #### 4. 是否支持重定义与修改 - **`var` 和 `let`**: 允许多次赋予新值给同名称下的变量,只要不在相同上下文中再次显式地使用相同的标识符去创建另个实体即可[^3]。 - **`const`**: 强调不可变性,旦确立就不能更改其指代关系。需要注意的是对于基本数据类型来说确实如此;然而当涉及到复杂的数据结构像对象或数组时,“不变”的含义仅仅是指不能替换整个引用本身,而非阻止对其成员属性的操作[^3]。 ```javascript var variableWithVar = 0; variableWithVar = 1; // 成功变更数值 let variableWithLet = 2; variableWithLet = 3; // 同理可行 const constantNumber = 4; constantNumber = 5; // TypeError: Assignment to constant variable. const person = { name: 'Alice' }; person.name = 'Bob'; // 修改字段没问题 person = {}; // 再次分配则失败 ``` --- #### 5. 推荐使用场景总结 | **关键字** | **优点** | **缺点/注意事项** | **最佳适用场合** | |------------|---------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|------------------------------------------------------| | `var` | 简单易懂,兼容旧版浏览器 | 容易引起命名冲突和难以追踪的行为 | 需要考虑向后兼容性的项目中 | | `let` | 更精确地控制局部变量可见性和生存期 | 如果滥用可能导致不必要的多次声明 | 循环计数器或其他可能需要更新的临时状态 | | `const` | 清晰表达意图——某些东西不应该改变 | 初学者可能会误解它完全冻结任何关联的对象 | 凡是确定下来不会再变化的东西 | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值