javascript中的var、let、const最佳實踐
簡介
var
, let
and const
是JavaScript中三種定義變量的方式,它們之間有什么區(qū)別呢?這是前端面試中常見的一道題,今天我們來一文說透它。let
和const
區(qū)別不大,主要是const
聲明的是常量,不可修改,而let
聲明的變量是可修改的。所以我們重點放在var
和let
上。
變量初始化
聲明變量的同時為其賦值叫做初始化。
var
和let
聲明的變量都可以不賦值,此時變量的值為undefined
。const
聲明的變量必須賦值,否則會報錯。
// `var`和`let`聲明的變量可以不賦值,此時變量的值為`undefined`。 var num; // num的值是undefined num = 1; // num的值是1 let str; // str的值是undefined str = 'hello'; // str的值是'hello'
// `const`聲明的變量必須賦值,否則會報錯。 const a; // SyntaxError: Missing initializer in const declaration
變量提升 - Hoisting
Hoisting
這個詞中文譯為提升,就是將變量的聲明提升到其作用域的頂部,注意提升的是聲明,而不是賦值。
var
聲明的變量會被提升至其作用域頂部。let
和const
聲明的變量不會被提升。(注意這個說法有爭議,詳見MDN)- 提升只針對變量聲明,不包括賦值。
如果var是在全局作用域聲明的,那么它會被提升到全局作用域的頂部。
console.log(name); // undefined var name = 'Philip';
以上代碼等價于:
var name; // `var`聲明的變量會被提升到其作用域頂部。 console.log(name); // undefined name = 'Philip';
如果var是在函數(shù)作用域聲明的,那么它會被提升到函數(shù)作用域的頂部。
function printName() { console.log(name); // undefined var name = 'Philip'; } printName();
以上代碼等價于:
function printName() { var name; // `var`聲明的變量會被提升到其作用域頂部。 console.log(name); // undefined name = 'Philip'; } printName();
let和const聲明的變量不會被提升。
對于let
和const
,它們不會被提升,所以下面代碼會報錯。
console.log(num); // ReferenceError: Cannot access 'num' before initialization const num = 1;
前面說過,關(guān)于let
和const
是否被提升有爭議。
- 一種說法是
let
和const
不會被提升,所以在聲明之前訪問會報錯。 - 另一種說法是
let
和const
會被提升,但是在聲明之前訪問會拋出Temporal Dead Zone
錯誤。
比如下面的代碼:
const x = 1; { console.log(x); // ReferenceError: Cannot access 'x' before initialization const x = 2; }
這段代碼會報錯,但是如果我們把{}
內(nèi)的const x = 2;
注釋掉,那么代碼就不會報錯。如果const x = 2
沒有被提升的話,那么console.log(x)
應(yīng)該可以訪問到全局的const x = 1
,而不會報錯。換句話說:因為const x = 2
被提升了,所以console.log(x)
訪問的是提升后的x
,而此時x
還沒有被初始化,所以報錯。
提升只針對變量聲明,不包括賦值。
下面的代碼會報錯,因為x = 1是賦值,并不是聲明,所以不會提升。(注意:如果變量聲明前沒有加var
, let
或const
,那么其實產(chǎn)生的是一個意外的全局變量。)
console.log(x); // ReferenceError: x is not defined x = 1;
如果有同名函數(shù)和變量,那么提升后,變量位于函數(shù)之前(或者說函數(shù)會覆蓋變量)。
以下代碼中有一個同名的函數(shù)和變量。
console.log(foo); // [Function: foo], not undefined. function foo() { console.log('function foo'); } var foo = 1;
提升后代碼如下:
var foo; function foo() { console.log('function foo'); } console.log(foo); foo = 1;
面試題
看幾道面試題,以下幾段代碼輸出什么?
- 第一題
a = 2; var a; console.log(a); // 2
解決var提升的問題很簡單,就是按照提升規(guī)則將代碼重寫一下,上面的代碼等價于如下代碼,結(jié)果一目了然。
var a; a = 2; console.log(a); // 2
- 第二題
var a = true; foo(); function foo() { if (a) { var a = 10; } console.log(a); }
只要函數(shù)內(nèi)部有var
聲明的變量,那么所有全局聲明的var
變量都會被忽略,以上代碼提升后等價于如下代碼(注意function
也有提升),函數(shù)內(nèi)部的var永遠(yuǎn)會覆蓋全局的var
。
var a = true; function foo() { var a; // value of a is `undefined` if (a) { a = 10; // never executed. } console.log(a); } foo();
- 第三題
function fn() { console.log(typeof foo); var foo = 'variable'; function foo() { return 'function'; } console.log(typeof foo); } fn();
還是那句話,此類題目的解法就是按照提升規(guī)則把代碼重新寫一遍,以上代碼提升后等價于如下代碼:
function fn() { var foo; function foo() { return 'function'; } console.log(typeof foo); foo = 'variable'; console.log(typeof foo); } fn();
所以輸出結(jié)果是function
和string
。
變量的作用域
var
聲明的變量有只兩種作用域:全局作用域和函數(shù)作用域。(沒有塊級作用域)let
和const
聲明的變量有三種作用域:全局作用域,函數(shù)作用域和塊級作用域。var
聲明的全局變量會掛載到window
對象上,而let
和const
不會。let
和const
有臨時性死區(qū),而var
沒有。
面試題
第一題
以下代碼輸出什么?
let x = 1; { let x = 2; } console.log(x);
答案:1,因為let
有塊級作用域,所以let x = 2
只在{}
內(nèi)有效。
第二題
以下代碼輸出什么?
var x = 1; { var x = 2; } console.log(x);
答案:2,因為var
沒有塊級作用域,所以var x = 2
會覆蓋外部的var x = 1
。
第三題
以下代碼輸出什么?
let name = 'zdd'; { console.log(name); let name = 'Philip'; }
答案:ReferenceError: Cannot access 'name' before initialization。因為let
有塊級作用域,所以console.log(name);
訪問的是let name = 'Philip';
之前的name
,而此時name
還沒有被初始化,處于暫時性死區(qū)中,所以報錯。
第四題
以下代碼輸出什么?
'use strict'; { function foo() { console.log('foo'); } } foo();
答案:ReferenceError: foo is not defined。因為foo
是在塊級作用域內(nèi)聲明的,所以在外部無法訪問。但是如果我們把'use strict';
去掉,那么代碼就可以正常運行。因為在非嚴(yán)格模式下,函數(shù)聲明會被提升到全局作用域。
第五題
以下代碼輸出什么?
(() => { let x; let y; try { throw new Error(); } catch (x) { x = 1; y = 2; console.log(x); } console.log(x); console.log(y); })();
答案:1 undefined
2。因為catch
中的x
是一個新的變量,不是外部的x
,所以x = 1
只會改變catch
中的x
,而不會改變外部的x
。而y = 2
不是catch
的參數(shù),只是在catch
中賦值的,所以會改變外部的y
。
暫時性死區(qū) - Temporal Dead Zone
TDZ即Temporal Dead Zone
- 中文名暫時性死區(qū),是指let
和const
聲明的變量在其作用域開始到變量聲明之間的這段區(qū)域。在暫時性死區(qū)內(nèi)無法訪問變量,訪問會報錯。
function foo() { console.log(b); // ReferenceError: Cannot access 'b' before initialization let a = 1; const b = 2; } foo();
對于以上代碼,常量b
的暫時性死區(qū)開始于函數(shù)的第一行,終止于b
的聲明,而console.log(b);
這句恰恰在暫時性死區(qū)內(nèi)訪問了b
,所以會報錯。
面試題
以下代碼輸出什么?
function foo() { console.log(typeof bar); const bar = 1; } foo();
答案:ReferenceError: Cannot access 'bar' before initialization
因為console.log(typeof bar);
這句在bar
的暫時性死區(qū)內(nèi)訪問了bar
,所以會報錯。可以看到,即使強如typeof
這種幾乎不會報錯的操作符也無法規(guī)避暫時性死區(qū)。
如果我們把const bar = 1;
去掉,那么代碼就不會報錯。typeof
操作符對于沒有聲明的變量不會報錯,而是返回undefined
。
function foo() { console.log(typeof bar); // 輸出undefined }
重新聲明- Redeclaration
var
聲明的變量可以被重復(fù)聲明,后聲明的覆蓋先聲明的。let
和const
聲明的變量不可以被重復(fù)聲明。
面試題
看幾道面試題,以下幾段代碼輸出什么?
- 第一題
var a = 1; function foo() { var a = 2; { var a = 3; console.log(a); } console.log(a); } foo(); console.log(a);
答案:3 3 1, 這個題主要考察兩個知識點:
var
聲明的變量沒有塊級作用域。var
聲明的變量可以被重復(fù)聲明,后聲明的會覆蓋先聲明的。
所以var a = 3
會覆蓋外部的var a = 2
,但是var a = 2
不會覆蓋最外面的var a = 1
。因為var
有函數(shù)作用域。
以上代碼提升后等價于如下代碼:
var a; a = 1; function foo() { var a; var a; // redeclaration a = 2; { a = 3; console.log(a); } console.log(a); } foo(); console.log(a);
注意:面試題中凡事用{}
包裹var
的都是障眼法,var
沒有塊級作用域。
第二題
這道題比較簡單,考察的是let
的塊級作用域,代碼輸出2, 1。因為let
有塊級作用域。let a = 2
只在{}
內(nèi)有效。
function foo() { let a = 1; { let a = 2; console.log(a); } console.log(a); } foo();
意外的全局變量
如果我們聲明變量的時候忘記了寫var
, let
或者const
,那么這個變量就是所謂的Accidental Global Variables
,意思是意外的全局變量
。
function f1() { b = 2; // accident global variable } f1(); console.log(b); // 2
面試題
以下代碼輸出什么?
for (var i = 0; i < 10; i++) { setTimeout(() => { console.log(i); }) }
答案:3 3 3
因為var
沒有塊級作用域,所以setTimeout
內(nèi)的i
都是指向同一個i
,而setTimeout
是異步的,其回調(diào)函數(shù)代碼需要先進(jìn)入宏任務(wù)隊列,待for
循環(huán)結(jié)束后才能執(zhí)行,此時i
已經(jīng)是3了。關(guān)于這道題的詳細(xì)解釋,請看這篇。
最佳實踐
如今ES6已經(jīng)普及,對于業(yè)務(wù)代碼來說,基本不需要使用
var
了,var
目前只有JS框架或者底層工具庫才會使用。對于
let
和const
,優(yōu)先使用const
,只有在需要修改變量的情況下才使用let
。經(jīng)典for循環(huán)使用
let
,因為循環(huán)變量會被修改。for (let i = 0; i < 5; i++) { console.log(i); }
for...in
和for...of
使用const
,因為循環(huán)變量不會被修改。const arr = [1, 2, 3]; for (const item of arr) { console.log(item); }
const obj = {a: 1, b: 2}; for (const key in obj) { console.log(key); }
到此這篇關(guān)于javascript中的var、let、const的文章就介紹到這了,更多相關(guān)js var let const內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- JavaScript中關(guān)鍵字?var、let、const的區(qū)別詳解
- js中var、let、const之間的區(qū)別
- Js中var,let,const的區(qū)別你知道嗎
- Javascript?中?var?和?let?、const?的區(qū)別及使用方法
- javascript中var與let、const的區(qū)別詳解
- JavaScript變量聲明的var、let、const詳解
- 面試官常問之說說js中var、let、const的區(qū)別
- JavaScript?ES6語法中l(wèi)et,const?,var?的區(qū)別
- javascript的var與let,const之間的區(qū)別詳解
- JavaScript中var let const的用法有哪些區(qū)別
- JavaScript es6中var、let以及const三者區(qū)別案例詳解
相關(guān)文章
JavaScript實現(xiàn)in-place思想的快速排序方法
這篇文章主要介紹了JavaScript實現(xiàn)in-place思想的快速排序方法的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-08-08Javascript將圖片的絕對路徑轉(zhuǎn)換為base64編碼的方法
這篇文章主要介紹了Javascript將圖片的絕對路徑轉(zhuǎn)換為base64編碼的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-01-01微信小程序數(shù)據(jù)統(tǒng)計和錯誤統(tǒng)計的實現(xiàn)方法
這篇文章主要介紹了微信小程序數(shù)據(jù)統(tǒng)計和錯誤統(tǒng)計的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06javascript html5 canvas實現(xiàn)可拖動省份的中國地圖
這篇文章主要介紹了javascript html5 canvas實現(xiàn)可拖動省份的中國地圖的相關(guān)資料,需要的朋友可以參考下2016-03-03