從一道詭異的JS面試題說“作用域”與“提升”
“面試造火箭,工作擰螺絲”。面試題目之詭異,常令人匪夷所思。試看一道考察“Hoisting"的面試題目:
一、提升全局變量 var
- var tmp = new Date();
- function f() {
- console.log(tmp);
- if (false) {
- var tmp = "hello";
- }
- }
- f();
JS新手往往會以為將正常打印出日期,而實際輸出的確是`undefined`!
- > var tmp = new Date();
- > function f() {
- ... console.log(tmp);
- ... if (false) {
- ..... var tmp = "hello";
- ..... }
- ... }
- > f();
- undefined
這是因為在函數f()的內部,var被提升到定義域的頂部,實際執行為:
- var tmp = new Date();
- function f() {
- var tmp;// 提升到這里,將全局的tmp覆蓋了。var默認賦值為undefined
- console.log(tmp);
- if (false) {
- var tmp = "hello";
- }
- }
- f();
也就是說var不僅提升,而且將tmp初始化賦值為undefined。
二、如何才能正常輸入日期呢?
解決方案是將global-scope的var替換為block-scope的let:
- var tmp = new Date();
- function f() {
- //var tmp;// 提升到這里,將全局的tmp覆蓋了。var默認賦值為undefined
- console.log(tmp);
- if (false) {
- let tmp = "hello";
- }
- }
- f();
- // 2021-04-02T10:52:30.983Z
這是因為let定義的是local-variable.
三、TDZ臨時DeadZones
更加詭異的案例,來單獨看let:
- var tmp = new Date();
- function f() {
- console.log(tmp);
- let tmp = "hello";
- }
- f();
你原以為將會如常打印出時間,但卻報錯tmp未定義。
- ReferenceError: Cannot access 'tmp' before initialization
這是因為 tmp 被提升,其實際執行為:
- var tmp = new Date();
- function f() {
- let tmp; // 提升在這里
- console.log(tmp);
- let tmp = "hello";
- }
- f();
然而區別于var的是,tmp僅僅被提升,卻不會被自動賦值為undefined,因此會報錯`ReferenceError`.
該問題就是傳說中的TDZ (temporal dead zone)。解決方案也簡單,就是將所有的let或者const等全部都寫到最上面。