JavaScript变量声明的var、let、const详解

目录
  • 前言
  • 内容
    • JavaScript的变量声明
    • var的变量声明
      • 变量声明在函数作用域中
      • 变量重复声明
      • 变量声明提升
    • 怪异危险的var
    • let和const的变量声明
      • 块级作用域
      • 不可重复声明
      • 暂时性死区
    • 使用好let和const
  • 总结
  • 参考资料

前言

一个程序语言在运行的过程中,变量的声明在整个程序的生命周期中,是不断在进行的过程。任何程序的计算都会涉及至少一个变量,而计算的结果的则可能会涉及到另外的一个或者多个变量。变量在使用前是要声明,变量声明的过程在计算机的底层,牵涉到的是内存空间和内存地址的分配。当然啦,有内存空间的分配,就会有内存空间的回收和再分配。可以看出,变量声明是我们在接触一门程序语言中起始的也是很关键的一步。

而本篇文章想跟大家聊聊的,是关于JavaScript的变量声明中的一些内容。

内容

JavaScript的变量声明

虽然一门语言的语法发展,变量声明的语法和方式通常是比较起始的。程序语言的设计者通常都会在语言的初始的版本尽量根据自己对语言的设计思想,设计好语言的变量声明的语法和方式,以保证好程序语言在未来的很长的道路上有一个比较好的起始。但是,JavaScript却没有一个这么好的起始。

JavaScript的设计之初衷,就是一门为了解决简单的网页互动的脚本语言。它的设计,只用了短短的10天时间。就像初生的婴儿一样,稚嫩而没有太多的规划和愿景。设计者做梦都没有想到,JavaScript将来可以在整个互联网的发展中占有这么大的一个分量,不仅在浏览器端,而且在移动端、APP端、服务端、桌面应用上都扮演着重要的角色。

JavaScript的设计的雏形是稚嫩的,但是随着这么语言逐渐的受到关注和使用,语言本身的设计和语法,也在不断的发展着。而JavaScript的变量声明方式也从野蛮的var的声明方式,前行到了ECMAScript标准的letconst的声明方式。

接下来,我们一起来讨论它们。

var的变量声明

var用于声明一个函数范围或者全局范围的变量,并且可以为其初始化一个值。它是ECMAScript2015之前的变量声明的唯一关键字,曾经承载过一代前端开发者的青葱岁月以及水深火热。它具有如下的特性:

变量声明在函数作用域中

与大多数的语言不同,作为过去的JavaScript的唯一变量声明方式,var的所声明的变量并不是声明在块级作用域中 ,而是声明在最近的函数上下文中,也就是局部函数作用域。对于未在任何函数中声明的变量,其声明范围则是在全局对象global之中。

/* === 全局作用域 start === */
/*
  变量globalVar声明在任何函数之外,为全局作用域中的变量
  任何的函数和方法都能访问到这个变量
*/
var globalVar = 'global var';

/* === f1函数作用域 start === */
function f1() {
  /*
    变量localVar1声明在函数f1中,为f1的局部函数作用域中的变量
    只有代码词法分析中的f1函数的内部函数能够访问到这个变量
    在这个作用域中可以访问到全局作用域中的变量globalVar
  */
  var localVar1 = 'local var1';
  /* === f2函数作用域 start === */
  return function f2() {
    /*
      变量localVar2声明在函数f2中,为f2的局部函数作用域中的变量,
      只有代码词法分析中的f2函数的内部函数能够访问到这个变量,
      在这个作用域中可以访问到全局作用域中变量globalVar以及外层f1函数作用域中的localVar1
    */
    var localVar2 = 'local var2';
    return localVar2;
  /* === f2函数作用域 end === */
  }
/* === f1函数作用域 end === */
}

f1();
/* === 全局作用域 end === */

上面的代码中,globalVar作为定义在全局对象global中的一个全局变量,除了可以直接通过变量标识符globalVar访问到之外,也可以通过global.globalVar或者window.globalVar的对象属性访问方式来访问到。

同时还能看到,上面的作用域定义构建成了一条global -> f1 -> f2的函数作用域链, 函数执行过程中的变量访问会根据这条函数作用域链进行规则查找和访问。

变量重复声明

var的变量声明支持在同一作用域中进行同一个变量的多次重复的声明,多次声明中只有首次的变量声明会被执行,其他声明会因为当前的执行上下文对象中已存在当前变量而被忽略。但是,声明变量同时如果对变量进行了初始赋值,赋值操作依旧会被执行。

/*
  变量的首次声明在当前执行上下文对象中新增一个value变量
*/
var value = 1;

/*
  除了变量的首次声明外,其他相同的声明,都不会被执行,
  但是,声明中的初始赋值依旧会被当作正常的赋值操作执行
*/
var value = 2;

console.log(value); // 2

变量声明提升

var的变量声明存在“提升”(hoisting)的特性。JavaScript在执行函数代码的前,首先会对函数内当前执行上下文中的所有var的变量声明进行扫描确认,并将所有的声明按顺序提前到当前执行上下文的顶部,也就是函数内的顶部。这种变量声明提升的特性,让我们在同一作用域中的可以直接访问和使用一个在后续的代码才进行了首次变量声明的变量,即在变量声明之前使用变量。

虽然,变量的声明在执行时被提升了,但是,变量声明中的初始赋值操作却并没有被提升,从而形成一种声明和初始赋值的执行逻辑上的割裂。也就是说,即使我们可以直接访问和使用在后续的代码才被声明和赋值的变量,然而,变量的值却是undefined。代码一直执行到声明和初始赋值语句后,变量的值才会被赋值为它的初始赋值。

/*
  由于变量声明提升了,可以在变量声明前访问使用变量,
  但是,访问到的变量值是undefined
*/
console.log(value); // undefined

/* 变量赋值并没有被提升,只有执行完这一句语句,变量才被赋值为1 */
var value = 1;

console.log(value); // 1

怪异危险的var

var的这些的怪异的特性,虽然在程序上都属于合法可运行,但是,对于编写程序的人来说,有着许多有违正常逻辑的地方,而且容易造成代码调试的困难。例如:面对变量的声明和管理,我们不得不以函数为基本的管理区进行管理;对变量的重复声明,让我们对代码从上而下的变量的安全访问和变量值的确认变得不容易控制;

虽然,为了防止这些危险的出现,我们可以通过编程习惯的方式来进行约束。但是,这些终究只是软约束,随着JavaScript的不断发展和使用来构建复杂的应用,这些约束就愈加捉襟见肘。

let和const的变量声明

黑暗和混沌,终于迎来了曙光的到来。ECMAScript2015的到来,就像一束光辉照耀进来,给JavaScript变量声明这一块带来了翻天覆地的变化。不仅带来了letconst这两个新的变量声明关键字,而且还直接一跃变成了最佳的变量声明方式。

而这些惊天动地变化的出现都是因为,相对于varletconst带来了如下新的共同特征:

块级作用域

letconst关键字带给我们的第一个让人兴奋的特性就是块级作用域。苦于之前var关键字的基于函数作用域的特性,我们不得在函数范围内小心的定义变量名称和使用变量,特别是在条件判断和循环逻辑中,甚至不得不在循环中使用上立即执行函数来进行变量值的读取和保存。

function f() {
  /*
    由于var基于函数作用域,回调函数内的i变量都为同一个副本,
    下面程序的输出结果为5、5、5、5、5
  */
  for (var i = 0; i < 5; i++) {
    setTimeout(() => { console.log(i); });
  }

  /*
    通过立即执行函数来构建基本数据类型的值传递,从而创建新的变量副本,
    下面程序的输出结果为0、1、2、3、4
  */
  for (var i = 0; i < 5; i++) {
    (function(i) {
      setTimeout(() => { console.log(i); }, 0);
    })(i);
  }
}

f();

现在,我们可以通过花括号{}以及letconst关键词声明限制于块级作用域中的变量了。if块、while块、function块甚至单独的{}都是良好的块级作用域声明区块。还有一点,相对于var,在全局作用域中使用letconst声明的变量并不会添加到全局上下文对象global中。综合看来,块级作用域的特性使我们的变量的声明和使用更加的简洁和安全。

/*
  由于使用的基于跨级作用域的let,每次循环体都会产生一个i变量的副本,
  下面程序的输出结果为0、1、2、3、4
*/
for (let i = 0; i < 5; i++) {
  setTimeout(() => { console.log(i); }, 0);
}

if (true) {
  let a = 1;
}
/*
  抛出ReferenceError,
  因为用let定义的a变量限制于块级作用域,只在前面方括号中可以访问
*/
console.log(a); // ReferenceError

/*
  let和const声明在全局作用域中声明的变量,不会被隐晦添加到global对象中,
  保证了全局环境的安全,
*/
let b = 1;
console.log(window.b); // undefined

不可重复声明

相比于var的重复声明会被忽略,letconst在同一作用域中对同一标识符的变量不能重复声明。这种变量声明执行上的约束,避免了同作用域下不同位置的变量声明冲突,消除了许多不容易预见的运行问题,让JavaScript在构建复杂应用中的变量声明过程变得更加的安全。

let a = 1;
{
  /* 不在同一块级作用域内,而在子级块级作用域内 */
  let a = 1;
}
/*
  和最上面的声明在同一块级作用域内,属于重复声明,
  这条语句执行会抛出SyntaxError
*/
let a = 1; // SyntaxError

暂时性死区

相对于var的变量声明提前导致的变量在函数作用域中可以“先使用后声明”的现象。letconst关键字声明的变量也具有执行中变量声明提前的特性,但是却不能在变量完成声明之前进行访问和修改,否则会抛出ReferenceError的错误。

这种变量声明所绑定的块级作用域的顶部,一直到变量声明语句执行完成之前的,变量不能访问和修改的区域,称为这个变量的暂时性死区(TDZ,temporal dead zone)。暂时性死区的引入,在代码执行上约束了变量必须遵循“声明后才能使用”的原则,使JavaScript的变量声明和使用更加安全和具有更好的可读性。

{
  /* === 变量a和b的TDZ start === */
  /*
    抛出ReferenceError,
    TDZ范围内不能读取和修改变量b
  */
  console.log(b); // ReferenceError
  let a = 1;
  /* === 变量a的TDZ end === */
  let b = 2;
  /* === 变量b的TDZ end === */
  console.log(a); // 1
}

值得注意的是,暂时性死区是基于执行顺序(时间)上的,而不是编写代码顺序(位置)上的。只要变量的访问和修改的代码的执行,是在变量声明之后,就是合法的。

{
  /* === 变量value的TDZ start ===*/
  function f() {
    console.log(value);
  }

  /* 在DZT内访问和修改变量value,会抛出错误 */

  let value = 1;
  /* === 变量value的TDZ end ===*/
  /*
    函数f中对value的访问发生在TDZ外面,
    所以访问合法
  */
  f(); // 1
}

使用好let和const

letconst两者的这些新的共同特性,让它们直接变成了变量声明的最佳方式和实践。但是,两者的除了上面说道的共同特性,还存在一个两者的差异。

简单来说,letconst的区别在于,let用于声明基于块级作用域的变量,而const用于声明基于块级作用域的常量。

  • 使用let声明的变量,在变量的整个生命周期中,能够对变量随时进行赋值修改。
  • 使用const声明的变量为引用常量,必须在声明的同时进行初始赋值,在变量的整个生命周期中,无法再通过赋值的方式来修改变量值。

const的这个无法再赋值的特性,在不同的变量类型下会有不同的表现:

  • 如果const声明并且初始化赋值是基础数据类型变量(StringNumberBooleanSymbol),那么该变量之后就不能再进行任何值的修改了。因为基础数据类型变量的值必须通过变量赋值来进行修改。
  • 如果const声明并且初始化赋值是引用数据类型变量(ArrayObjectMapSet),那么我们则可以随意对该变量的字段属性进行修改。因为引用数据类型变量的内容修改(新增、修改、删除属性)并不会出现所声明变量的赋值操作.
/* let声明的变量可以任意赋值和修改 */
let value1 = 1;
value1 = 2;

/*
  抛出SyntaxError,
  const声明的变量必须赋值初始值
*/
const value2; // SyntaxError

/*
  抛出TypeError,
  const声明的变量在整个变量生命周期内不能再赋值
*/
const value3 = 3;
value3 = 4 // TypeError

/* const声明的引用数据类型变量,依旧可以修改其字段值 */
const value4 = {
  key1: 1,
  key2: 2,
};
value4.key = 3;

在实践中,我们应该尽可能的多使用const进行变量声明,当确定一个变量需要重新赋值的时候才将其改用let进行声明,这样可以让我们对程序执行中的可能发生重新赋值的变量有一个清晰的了解,减少可能出现的bug。

总结

ECMAScript2015带来了letconst这两个新的变量声明关键字,带来了块级作用域和暂时性死区等新特性,解决过去var的变量声明中的一些怪异问题,在语法层面和运行层面上,保证了变量声明的“声明后才可以使用”的安全特性,提高了JavaScript在编写大型应用时的变量声明和使用安全。

当前,letconst已经是我们日常开发中的变量声明的最佳时间方式。了解它们,才能用好它们。

参考资料

  • 《JavaScript高级程序设计》
  • 《你不知道的JavaScript》
  • MDN

到此这篇关于JavaScript变量声明的var、let、const详解的文章就介绍到这了,更多相关JS变量声明var、let、const内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JavaScript es6中var、let以及const三者区别案例详解

    首先,一个常见的问题是,ECMAScript 和 JavaScript 到底是什么关系?         ECMAScript是一个国际通过的标准化脚本语言.JavaScript由ECMAScript和DOM.BOM三者组成.可以简单理解为:ECMAScript是JavaScript的语言规范,JavaScript是ECMAScript的实现和扩展.         2011 年,ECMAScript 5.1 版发布.之前我们大部分人用的也就是ES5         2015 年 6 月,ECM

  • javascript 变量声明 var,let,const 的区别

    目录 作用域(Scope)是什么 var 声明 提升(Hoisting) let 声明 const 声明 作用域(Scope)是什么 作用域是程序的执行环境,它包含在当前位置可访问的变量和函数.在 ES5 语法中,有全局作用域和局部作用域,ES6 则新增了块级作用域. 全局作用域是最外层的作用域,在函数外面定义的变量属于全局作用域,可以被任何其他子作用域访问.在浏览器中,window 对象就是全局作用域.在编写前端代码过程中,其中有一条优化规则就是少使用全局变量,因为全局变量容易导致程序BUG,

  • javascript的var与let,const之间的区别详解

    目录 作为全局变量时 变量提升 暂时性死区 块级作用域 重复声明 修改声明的变量(常量与变量声明) 总结 说到JavaScript中声明变量的几种方法也就是var.let.const了,let和const是es6中新增的命令.那么它们之间有什么区别呢? 我们先整体说一下三者的区别,在详细介绍,var.let.const的区别主要从以下几点分析: 作为全局变量时的不同 变量提升 暂时性死区 块级作用域 重复声明 修改声明的变量 作为全局变量时 在ES5中,顶层对象的属性和全局变量是等价的,用var

  • JavaScript ES6语法中let,const ,var 的区别

    一.变量声明的方式let / const let / const 共同点 1.都是块级作用域2.在同一个作用域下,变量名不允许重复3.他们声明的全局变量并没有挂在 window对象上4.都没有预编译 let / const 不同点 1.let 声明的变量值可以改变2.const 声明的变量值不能改变,必须声明后立即赋值 (如:const a = 3.14;)3.const 存引用数据类型时,内容可以发生改变(地址不能改变) 优先考虑使用const , 如果变量会发生改变,就使用let , 最后使

  • javascript中let和var以及const关键字的区别

    1.声明后未赋值,表现相同 //一个例子 'use strict'; (function() { var varTest; let letTest; console.log(varTest); //输出undefined console.log(letTest); //输出undefined }()); 2.使用未声明的变量,表现不同 //一个例子 (function() { console.log(varTest); //输出undefined(注意要注释掉下面一行才能运行) console.

  • 详解javascript中var与ES6规范中let、const区别与用法

    随着ES6规范的到来,Js中定义变量的方法已经由单一的 var 方式发展到了 var.let.const 三种之多.var 众所周知,可那俩新来的货到底有啥新特性呢?到底该啥时候用呢?下面就是小编总结出的关于javascript中var与ES6规范中let.const区别详解 我们先来絮叨絮叨 var 方式定义变量有啥 bug ? Js没有块级作用域 请看这样一条规则:在JS函数中的var声明,其作用域是函数体的全部. for(var i=0;i<10;i++){ var a = 'a'; }

  • JavaScript中var let const的用法有哪些区别

    目录 1.重复声明 1.1 var 1.2 let 1.3 const 2.变量提升 2.1 var 2.2 let 2.3 const 3.暂时性死区 3.1 var 3.2 let 3.3 conset 4. window对象的属性和方法 5.块级作用域 1.重复声明 var支持重复声明,let.const不支持重复声明. 1.1 var var a = 1; var a = 2; console.log(a); 输出结果: 2 1.2 let let b = 3; let b = 4; c

  • 浅谈JS中var,let和const的区别

    目录 区别1 区别2 区别3 区别4 区别5 区别6 区别7 区别1 let和var用来声明变量,const用来声明常量. 变量就是赋值后可以改变它的值,常量就是赋值后就不能改变它的值. 当声明为对象时,可以直接修改对象内的属性值 const age = 26; age = 36; // TypeError: 给常量赋值 // const声明的作用域也是块 const name = 'Matt'; if (true) { const name = 'Nicholas'; } console.lo

  • JavaScript变量声明的var、let、const详解

    目录 前言 内容 JavaScript的变量声明 var的变量声明 变量声明在函数作用域中 变量重复声明 变量声明提升 怪异危险的var let和const的变量声明 块级作用域 不可重复声明 暂时性死区 使用好let和const 总结 参考资料 前言 一个程序语言在运行的过程中,变量的声明在整个程序的生命周期中,是不断在进行的过程.任何程序的计算都会涉及至少一个变量,而计算的结果的则可能会涉及到另外的一个或者多个变量.变量在使用前是要声明,变量声明的过程在计算机的底层,牵涉到的是内存空间和内存

  • [c++]变量声明与定义的规则详解

    声明与定义分离 Tips:变量能且仅能被定义一次,但是可以被多次声明. 为了支持分离式编译,C++将定义和声明区分开.其中声明规定了变量的类型和名字,定义除此功能外还会申请存储空间并可能为变量赋一个初始值. extern 如果想声明一个变量而非定义它,就使用关键字extern并且不要显式地初始化变量: extern int i; // 声明i而非定义i extern int i = 1; // 定义i, 这样做抵消了extern的作用 static 当我们在C/C++用static修饰变量或函数

  • Golang语言的多种变量声明方式与使用场景详解

    目录 01介绍 02变量声明方式 标准声明变量 不显式赋初始值声明变量 省略类型声明变量 短变量声明 显式类型转换 变量列表声明 变量声明块 03使用场景 包级变量 全局变量 局部变量 04注意事项: 05总结 01介绍 在程序设计中,编译器必须将代表数据的变量名称替换成该数据所在的内存地址.变量的名称.类型及内存地址通常会维持固定,但该内存地址所存储的数据在程序执行期间则可能会改变. Golang 语言编译器需要先明确变量的内存边界,才可以使用变量.通过声明变量使用的类型,编译器可以明确变量的

  • Javascript变量的作用域和作用域链详解

    工作这几年,js学的不是很好,正好周末有些闲时间,索性买本<js权威指南>,大名鼎鼎的犀牛书,好好的把js深入的看一看.买过这本书的第一印象就是贼厚,不过后面有一半部分都是参考手册. 一:作用域 说起变量第一个要说到的肯定就是作用域,正是因为不熟悉JS的作用域,往往就会把面向对象的作用域张冠李戴,毕竟有些东西总是习惯性的这样,但是并不是每次照搬都是可以的,那么下一个问题就来了,js到底是什么作用域,当然是函数作用域了,我们的浏览器就是一个被实例化的window对象,如果在window下定义一个

  • JavaScript变量声明var,let.const及区别浅析

    var声明变量的作用域限制在其声明位置的上下文中 var x = 0; // x是全局变量,并且赋值为0. console.log(typeof z); // undefined,因为z还不存在. function a() { // 当a被调用时, var y = 2; // y被声明成函数a作用域的变量,然后赋值成2. console.log(x, y); // 0 2 function b() { // 当b被调用时, x = 3; // 全局变量x被赋值为3,不生成全局变量. y = 4;

  • JavaScript变量or循环中的var和let详解

    目录 在for循环中使用var声明初始化带来的问题 解决方法 使用闭包 使用let变量初始化 for循环怎么处理用let和var声明的初始化变量? 总结 在for循环中使用var声明初始化带来的问题 // 一道经典面试题: var funcs = []; for (var i = 0; i < 3; i++) { funcs[i] = function() { console.log("My value: " + i) }; } for (var j = 0; j < 3;

  • JavaScript中变量声明有var和没var的区别示例介绍

    本文来论述JavaScript中变量声明有var和没var的区别,关于Js中的变量声明的作用域是以函数为单位,所以我们经常见到避免全局变量污染的方法是 (function(){ // ... })(); 在函数内部,有var和没var声明的变量是不一样的.有var声明的是局部变量,没var的,声明的全局变量,所以可以借此向外暴露接口东东. 在全局作用域内声明变量时,有var 和没var看起来都一样,我们知道,声明的全局变量,就是window的属性,究竟是否一样,我们通过ECMAScrpit5提供

  • JavaScript中变量提升和函数提升的详解

    第一篇文章中提到了变量的提升,所以今天就来介绍一下变量提升和函数提升.这个知识点可谓是老生常谈了,不过其中有些细节方面博主很想借此机会,好好总结一下. 今天主要介绍以下几点: 1. 变量提升 2. 函数提升 3. 为什么要进行提升 4. 最佳实践 那么,我们就开始进入主题吧. 1. 变量提升 通常JS引擎会在正式执行之前先进行一次预编译,在这个过程中,首先将变量声明及函数声明提升至当前作用域的顶端,然后进行接下来的处理.(注:当前流行的JS引擎大都对源码进行了编译,由于引擎的不同,编译形式也会有

  • javascript变量声明实例分析

    本文实例讲述了javascript变量声明的方法.分享给大家供大家参考.具体分析如下: js中使用一个变量之前应当先声明.变量使用关键字var来声明. 如果未在var声明语句中给变量指定初始值,则该变量值为undefined. 不用在声明变量时指定变量类型,js变量可以是任意数据类型. 使用var语句重复声明变量是合法且无害的.如果重复声明带有初始化器,则就和简单的赋值语句没啥区别. 如果试图读取一个没有声明的变量,则js会报错.在ECMAScript5严格模式下,给一个没有声明的变量赋值也会报

随机推荐