var/let/const、塊級作用域、TDZ、變量提升

概覽

ES6 新增了兩個定義變量的關鍵字:letconst,它們幾乎取代了 ES5 定義變量的方式:varlet是新的var,const簡單的常量聲明。

function f() {
  {
    let x;
    {
      // okay, block scoped name
      const x = "sneaky";
      // error, const
      x = "foo";
    }
    // error, already declared in block
    let x = "inner";
  }
}

letconst實現塊級作用域

let,const創建的變量都是塊級作用域:它們只存在包圍它們的最深代碼塊中。

function func() {
    if (true) {
        let tmp = 123;
        // const tmp = 123;
    }
    console.log(tmp); // ReferenceError: tmp is not defined
}
console.log(tmp);// ReferenceError: tmp is not defined

相比之下,var聲明的是函數域。

function func() {
    if (true) {
        var tmp = 123;
    }
    console.log(tmp); // 123
}
func()
console.log(tmp); // tmp is not defined

面試題:循環中定時器閉包

for(var i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i) //5, 5, 5, 5, 5
  }, 0)
}
console.log(i) //5 i跳出循環體污染外部函數

//將var改成let之後
for(let i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i) // 0,1,2,3,4
  }, 0)
}
console.log(i)//i is not defined i無法污染外部函數

在for循環中使用var聲明的循環變量,會跳出循環體污染當前的函數。

letconst暫時性死區(temporal dead zone)

let,const聲明的變量擁有暫時性死區:當進入它的作用域,它不能被訪問(獲取或設置)直到執行到達聲明。
簡單描述:

if (true) {
  //這塊區域是TDZ
  console.log(a) // Uncaught ReferenceError: Cannot access 'a' before initialization
  let a = 1
  // const a = 1
}
if (true) { // enter new scope, TDZ starts
    // Uninitialized binding for `tmp` is created

    tmp = 'abc'; // ReferenceError
    console.log(tmp); // ReferenceError

    let tmp; // TDZ ends, `tmp` is initialized with `undefined`
    console.log(tmp); // undefined

    tmp = 123;
    console.log(tmp); // 123
}

下面示例將演示死區(dead zone)是真正短暫時間的(基於時間)和不受空間條件限制(基於位置)

if (true) { // enter new scope, TDZ starts
    const func = function () {
        console.log(myVar); // OK!
    };

    // Here we are within the TDZ and
    // accessing `myVar` would cause a `ReferenceError`

    let myVar = 3; // TDZ ends
    func(); // called outside TDZ
}

var變量提升

JavaScript中,我們通常說的作用域是函數作用域,使用var聲明的變量,無論是在代碼的哪個地方聲明的,都會提升到當前作用域的最頂部,這種行爲叫做變量提升(Hoisting)

下面代碼,演示了函數的變量提升:

{ // Enter a new scope

    console.log(foo()); // hello, due to hoisting
    function foo() {
        return 'hello';
    }
}

也就是說,如果在函數內部聲明的變量,都會被提升到函數開頭,而在全局的聲明,就會提升到全局作用域的頂部。

function test() {
    console.log('1: ', a) //undefined
    if (false) {
      var a = 1
    }
    console.log('3: ', a) //undefined
}

test()

實際執行時,上面的代碼中的變量a會提升到函數頂部聲明,即使if語句的條件是false,也一樣不影響a的提升。

function test() {
    var a
    //a聲明沒有賦值
    console.log('1: ', a) //undefined
    if (false) {
      a = 1
    }
    //a聲明沒有賦值
    console.log('3: ', a) //undefined
}

在嵌套函數的情況,變量只會提升到最近一個函數的頂部,而不會到外部函數。

//b提升到函數a頂部,但不會提升到函數test。
function test() {
    function a() {
      if (false) {
        var b = 2
      }
    }
    console.log('b: ', b)
}

test() //b is not defined

let不允許重複聲明

let不允許在相同作用域內,重複聲明同一個變量。

// 報錯
function func() {
  let a = 10;
  var a = 1;
}

// 報錯
function func() {
  let a = 10;
  let a = 1;
}

因此在函數內部不能重新聲明函數

function func(arg) {
  let arg;
}
func() // 報錯 Identifier 'arg' has already been declared

function func(arg) {
  {
    let arg;
  }
}
func() // 不報錯

const命令

一般使用場景:

const start = 'hi all';

const getName = () => {
  return 'jelly';
};

const conf = {
  fav: 'Coding'
};

// 模板
const msg = `${start}, my name is ${getName()}, ${conf.fav} is my favourite`;

你可能不知道的事:

// 1. 與引號混用
const wantToSay = `I'm a "tbfed"`;

// 2. 支持多行文本
const slogan = 
`
I have a dream today!
`;

// 比較適合寫HTML
const resultTpl = 
`
  <section>
    <div>...</div>
  </section>
`;

varletconst有什麼區別

  • 相同點var,let,const聲明的變量,是不能被delete的;
  • 區別

變量提升var聲明的變量存在變量提升,即變量可以在聲明之前調用,值爲undefined;
let,const不存在變量提升,即它們聲明的變量一定要在聲明後使用,否則會報錯。

暫時性死區var不存在暫時性死區;letconst存在暫時性死區,只有等聲明變量後,纔可以獲取和使用該變量。

重複聲明var允許重複聲明;latconst在同一作用域不允許重複聲明。

修改聲明的變量varlet可以修改聲明的變量;const聲明一個只讀常量,一旦聲明,常量的值就不能改變。

var/let/const、塊級作用域、TDZ、變量提升

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