關於let、var和const

前言

letconst命令是ES6新增的命令,用來聲明變量,這兩個變量跟ES5中的var有許多不同,同時letconst也有不一樣的地方。並且在ES6中也添加了塊級作用域來解決ES5中作用域存在的問題。

作用域

官方解釋是:“一段程序代碼中所用到的名字並不總是有效/可用的,而限定這個名字的可用性的代碼範圍就是這個名字的作用域。”
 

舉個例子來形象的解析下上面的定義:

function fn () {
   // 聲明變量
   var name = 'marry'
   
   // 定義內部函數
   function innerFn () {
       console.log(name) // 可以訪問到name變量
   }
}
console.log(name) // undefined

ES5作用域分爲全局作用域和函數作用域。

  1. 全局作用域
var a = 0;

if (true) {
  var b = 1;
}

console.log(b); // 輸出1

上面代碼中,在全局中定義變量a,稱爲全局變量,在任何一個地方都可以訪問到變量a。

  1. 局部作用域

局部作用域也可以稱之爲函數作用域。

function fn () {
  var c = 2;
}

console.log(c); // c is not defined
  1. 作用域鏈

Function對象有一個僅供 JavaScript 引擎存取的內部屬性。

這個屬性就是[[Scope]][[Scope]]包含了一個函數被創建的作用域中對象的集合。這個集合被稱爲函數的作用域鏈,它決定了哪些數據能被函數訪問。
關於作用域鏈,局部作用域可以訪問到全局作用域中的變量和方法,而全局作用域不能訪問局部作用域的變量和方法。

var a = 0;

function fn () {
  var b = 1;
  console.log(a); // 輸出 1
}

// 全局作用域並不能訪問 fn 函數中定義的 b 變量
console.log(b); // b is not defined

fn();

塊級作用域

ES6新增塊級作用域,塊作用域由 { }包括,函數內部,if語句和 for語句裏面的{ }也屬於塊作用域。

塊級作用域的出現是爲了解決ES5中作用域的問題:

  1. 內層變量可能覆蓋外層變量
  2. 用來計數的循環變量泄露爲全局變量。
var tmp = new Date();
function f() {
  console.log(tmp); // 想打印外層的時間作用域
  if (false) {
    var tmp = 'hello world'; // 這裏聲明的作用域爲整個函數
  }
}
f(); // undefined

var s = 'hello';
for (var i = 0; i < s.length; i++) {
  console.log(s[i]); // i應該爲此次for循環使用的變量
}
console.log(i); // 5 全局範圍都可以讀到

塊級作用域

通過var聲明的變量存在變量提升的特性

// var 的情況
console.log(foo); // 輸出undefined
{ var foo = 2; }
console.log(foo) // 2  

// 運行時,真正運行的是下面的代碼
var = foo
console.log(foo); // 輸出undefined
{ foo = 2; }
console.log(foo) // 2  
  • JavaScript 引擎的工作方式是,先解析代碼,獲取所有被聲明的變量,然後再一行一行地運行。這造成的結果,就是所有的變量的聲明語句,都會被提升到代碼的頭部,這就叫做變量提升。
  • 對於var命令來說,JavaScript的單獨的{}區塊不構成單獨的作用域。但是在JavaScript語言中,單獨使用區塊並不常見。

除此之外,在for循環中:

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10

上面代碼中,變量i是var命令聲明的,在全局範圍內都有效,所以全局只有一個變量i。

let 和 const

let 和 const 命令

ES6 新增了let命令,用來聲明變量。它的用法類似於var,但是所聲明的變量,只在let命令所在的代碼塊內有效。

  1. let在{}中聲明的變量只在代碼塊中有效(形成塊級作用域)
{
  let a = 10;
  var b = 1;
}

a // ReferenceError: a is not defined.
b // 1
  1. 不存在變量提升(let和const)

let命令所聲明的變量一定要在聲明後使用,否則報錯。

// var 的情況
console.log(foo); // 輸出undefined
var foo = 2;

// let 的情況
console.log(bar); // 報錯ReferenceError
let bar = 2;
  1. 重複聲明報錯(let和const)
let value = 2;
let value = 2; //Uncaught SyntaxError: Identifier 'value' has already been declared
  1. 變量不會掛在頂層對象下面(let和const)
瀏覽器環境頂層對象是: window;
node環境頂層對象是: global
var a = 1;
// 如果在 Node環境,可以寫成 global.a
// 或者採用通用方法,寫成 this.a
window.a // 1

let b = 1;
window.b // undefined

const命令需要注意點

  1. 一旦聲明,必須馬上賦值
let p; var p1; // 不報錯
const p3 = 'abc'
const p3; // 報錯 沒有賦值
  1. 一旦聲明值就不能改變
const p = '不能改變';
p = '報錯' //  Assignment to constant variable
  1. const本質是變量指針不能變
const foo = {};

// 爲 foo 添加一個屬性,可以成功
foo.prop = 123;
foo.prop // 123

// 將 foo 指向另一個對象,就會報錯
foo = {}; // TypeError: "foo" is read-only
const所說的一旦聲明值就不能改變,實際上指的是:變量指向的那個內存地址所保存的數據不得改動
  • 對於簡單類型的數據(數值、字符串、布爾值),值就保存在變量指向的那個內存地址,因此等同於常量。
  • 對於複合類型的數據(主要是對象和數組),變量指向的內存地址,地址保存的是一個指針,const只能保證指針是固定的(總是指向同一個地址),它內部的值是可以改變的(不要以爲const就安全了!)

暫時性死區

只要一進入當前作用域,所要使用的變量就已經存在了,但是不可獲取,只有等到聲明變量的那一行代碼出現,纔可以獲取和使用該變量。

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

總之,在代碼塊內,使用let命令聲明變量之前,該變量都是不可用的。這在語法上,稱爲“暫時性死區”(temporal dead zone,簡稱 TDZ)。

暫時性死區和不能變量提升的意義在於:

爲了減少運行時錯誤,防止在變量聲明前就使用這個變量,從而導致意料之外的行爲。

本章內容總結

var 和 let、const的區別

  1. 塊級作用域
  2. 不存在變量提升
  3. 暫時性死區
  4. 不可重複聲明
  5. let、const聲明的全局變量不會掛在頂層對象下面

const需要注意點

  1. let可以先聲明稍後再賦值,而const在 聲明之後必須馬上賦值,否則會報錯。
  2. const 簡單類型一旦聲明就不能再更改,複雜類型(數組、對象等)指針指向的地址不能更改,內部數據可以更改。

參考文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章