JS let var const的不同

let 用於宣告一个「只作用在当前区块的变数」,初始值可选择性的设定。

以 let 宣告的变数,其作用域是「区块作用域(block scope)」,也就是 { } 包住的区域,一但离开 { }范围,这个变数就不会被存取到。

function:

function varTest() {
  var x = 1;
  {
    var x = 2;  // 这里的 x 与 function 区块内部的 x 是一样的,因此会影响 function 区块内所有的 x
    console.log(x);  // 2
  }
  console.log(x);  // 2
}

function letTest() {
  let x = 1;
  {
    let x = 2;  // 这里的 x 与 function 区块内部的 x 是不同的,只会作用在这层 block 区块中
    console.log(x);  // 2
  }
  console.log(x);  // 1
}
Copy to Clipboard
在上列例子里的最前行 let 和 var 不同,let 并不会在全域物件中建立变数。举例来说:

var x = 'global';
let y = 'global';
console.log(this.x); // "global"
console.log(this.y); // undefined

var

以 var 宣告的变数,其作用域是「函式作用域(function scope)」,也就是在function 内宣告的 var,要在该 function 才有作用 ,但是如果在函式外宣告的话,其作用范围则为 全域性(global)。

在函数之外使用以 var 宣告的变数是非强制的 (optional); 如果对一个未经宣告的变数赋值, 它会被暗中 (implicitly) 宣告成为一个全域变数 (亦即成为全域物件的属性)。其中差异在於, 已宣告的变数是全域物件里的一个无法变更 (non-configurable) 的属性, 而未宣告的变数则是可变更的 (configurable)。

因此, 建议你一定要宣告你的变数, 不管你要将它使用於全域范围内或者函数内。
开发时用var宣告变数会容易导致意外污染全域变数的问题,例如是区域变数覆盖全域变数。

var food = 'apple';
function func(){
    var result = 'I eat ' + food
    console.log(result)

}
func(); //I eat apple

这个例子很简单,在func函式里用到全域变数food,组合字串及回传。但如果程序码变得更复杂时,又或者另一个开发者没注意到food已经在第一行宣告过了,就可能会出现以下的问题:

var food = 'apple';

// 200行code之後

function func(){
    var result = 'I eat ' + food
    console.log(result) // I eat undefined

    // 100行code之後,我忘了之前已经宣告过food
    var food = 'banana';
}
func(); 

在func这个函式裹的最後一行程序码,我们再次宣告food这个变数及重新赋值,根据hoisting(提升)的概念,在函式裹的var food会提升至函式作用域裹的最高处,在「提升之後(var food)」到「赋值之前food = 'banana'」,这段期间food的值会是undefined。我们可以把整个过程想像如下图:

var food = 'apple';

function func(){
    var food 
    // 提升之後 
    var result = 'I eat ' + food
    console.log(result) // I eat undefined

    // 赋值之前
    food = 'banana';
}

func(); 

所以在函式裹第二行的food会变成undefined。

澄清一点,food变成了undefined这个问题,并不是因为重复宣告变数。我在函式内再次宣告在函式外的变数是不会报错的,如下面的做法:

var food = 'apple';

// 200行code之後

function func(){
    // 100行code之後,我忘了之前已经宣告过food
    var food = 'banana';
    var result = 'I eat ' + food
    console.log(result) // I eat banana
}
func();

以上的例子,我把var food = 'banana'放在var result = 'I eat ' + food前面,这裹会成功回传I eat banana。因为我是在变数food被赋值'banana'之後(用=去赋予),才提取food这个变数,这时候food已经是'banana'。总括来说,这个问题出现与否是取决於你在什麽时候提取这个变数。重复一次,如果你在「提升之後(var food)」到「赋值之前food = 'banana'」提取变数,就会变成undefined。

const

宣告 const 会对於它的值建立一个唯读的参考。并不是说这个值不可变更,而是这个变数不能再一次指定值。例如,假设常数的内容(值)是个物件,那麽此物件的内容(物件的参数)是可以更改的。

// 注意: 常数可以宣告成大写或小写,
// 但习惯上使用全部大写的字母。

// 定义一个常数 MY_FAV 并赋予它的值为7
const MY_FAV = 7;

// 这里会发生错误 - Uncaught TypeError: Assignment to constant variable.
MY_FAV = 20;

// MY_FAV 是 7
console.log('我喜欢的数字是: ' + MY_FAV);

// 尝试重复宣告同名的常数,将会发生错误 -  Uncaught SyntaxError: Identifier 'MY_FAV' has already been declared
const MY_FAV = 20;

// MY_FAV 这个名称已经保留给上面的常数, 所以这里也会错误。
var MY_FAV = 20;

// 这式子也会错误
let MY_FAV = 20;

// 很重要,请注意区块可视范围的特性。
if (MY_FAV === 7) {
    // 以下式子没有问题,并且会建立一个名叫 MY_FAV 的具有区块可视范围的变数。
    // (等同於使用 let 来宣告一个具有区块可视范围的非常数变数。)
    let MY_FAV = 20;

    // MY_FAV 现在变成 20
    console.log('我喜欢的数字是:' + MY_FAV);

    // 这会将变数悬挂於全域,而导致错误。(与常数同名)
    var MY_FAV = 20;
}

// MY_FAV 仍然是 7
console.log('我喜欢的数字是:' + MY_FAV);

// 发生错误 - Uncaught SyntaxError: Missing initializer in const declaration
const FOO;

// 常数的值可以是一个物件
const MY_OBJECT = {'key': 'value'};

// 尝试覆写该物件将会发生错误 - Uncaught TypeError: Assignment to constant variable.
MY_OBJECT = {'OTHER_KEY': 'value'};

// 然而, 物件的属性并没有被保护,
// 所以,以下叙述式没有问题。
MY_OBJECT.key = 'otherValue'; // Use Object.freeze() to make object immutable

// 对阵列来说也是一样
const MY_ARRAY = [];
// 可以把项目加到阵列中。
MY_ARRAY.push('A'); // ["A"]
// 然而,对这个变数指定新阵列,将会发生错误 - Uncaught TypeError: Assignment to constant variable.
MY_ARRAY = ['B'];

参考资料

mdn let
mdn var
mdn const
JavaScript基本功修练:Day5 - 宣告变数 - let、const、var


<<:  Day1 启蒙

>>:  Day1 风生水起,观元辰宫-1

【设计+切版30天实作】|Day5 - 做出3栏式「痛点」设计

设计大纲 早安!今天来设计痛点,这边我想要做三栏式,列出三个他们目前主要可能会遇到的问题,再加上图片...

Day 15. 模板语法Template Syntax – 指令

前两天讲了模板语法中的插值,今天来讲指令的部分吧۹(ÒہÓ)۶ Directives 指令 Vue的...

16. STM32-I²C EEPROM DataSheet

上一篇介绍过了I2C的基本原理以及相关的函数,这一篇会介绍EEPROM来做为I2C实作的示范。 什麽...

离职倒数24天:说出内心烦恼、学到新知识、得到新的角度,三个愿望一次满足

最近看了很多好书,看完的共通感想是,为什麽以前没人推荐我看这些?如果早点看,不知道可以少走多少弯路。...

Ubuntu巡航记(2) -- 在 Ubuntu 作业系统内安装 TensorFlow

前言 前一篇搞定 Ubuntu 作业系统的安装,接下来我们继续安装『机器学习』的相关软件及工具,包括...