javascript中的var、let、const最佳實(shí)踐
簡(jiǎn)介
var, let and const是JavaScript中三種定義變量的方式,它們之間有什么區(qū)別呢?這是前端面試中常見的一道題,今天我們來(lái)一文說(shuō)透它。let和const區(qū)別不大,主要是const聲明的是常量,不可修改,而let聲明的變量是可修改的。所以我們重點(diǎn)放在var和let上。
變量初始化
聲明變量的同時(shí)為其賦值叫做初始化。
var和let聲明的變量都可以不賦值,此時(shí)變量的值為undefined。const聲明的變量必須賦值,否則會(huì)報(bào)錯(cuò)。
// `var`和`let`聲明的變量可以不賦值,此時(shí)變量的值為`undefined`。 var num; // num的值是undefined num = 1; // num的值是1 let str; // str的值是undefined str = 'hello'; // str的值是'hello'
// `const`聲明的變量必須賦值,否則會(huì)報(bào)錯(cuò)。 const a; // SyntaxError: Missing initializer in const declaration
變量提升 - Hoisting
Hoisting這個(gè)詞中文譯為提升,就是將變量的聲明提升到其作用域的頂部,注意提升的是聲明,而不是賦值。
var聲明的變量會(huì)被提升至其作用域頂部。let和const聲明的變量不會(huì)被提升。(注意這個(gè)說(shuō)法有爭(zhēng)議,詳見MDN)- 提升只針對(duì)變量聲明,不包括賦值。
如果var是在全局作用域聲明的,那么它會(huì)被提升到全局作用域的頂部。
console.log(name); // undefined var name = 'Philip';
以上代碼等價(jià)于:
var name; // `var`聲明的變量會(huì)被提升到其作用域頂部。 console.log(name); // undefined name = 'Philip';
如果var是在函數(shù)作用域聲明的,那么它會(huì)被提升到函數(shù)作用域的頂部。
function printName() {
console.log(name); // undefined
var name = 'Philip';
}
printName();以上代碼等價(jià)于:
function printName() {
var name; // `var`聲明的變量會(huì)被提升到其作用域頂部。
console.log(name); // undefined
name = 'Philip';
}
printName();let和const聲明的變量不會(huì)被提升。
對(duì)于let和const,它們不會(huì)被提升,所以下面代碼會(huì)報(bào)錯(cuò)。
console.log(num); // ReferenceError: Cannot access 'num' before initialization const num = 1;
前面說(shuō)過,關(guān)于let和const是否被提升有爭(zhēng)議。
- 一種說(shuō)法是
let和const不會(huì)被提升,所以在聲明之前訪問會(huì)報(bào)錯(cuò)。 - 另一種說(shuō)法是
let和const會(huì)被提升,但是在聲明之前訪問會(huì)拋出Temporal Dead Zone錯(cuò)誤。
比如下面的代碼:
const x = 1;
{
console.log(x); // ReferenceError: Cannot access 'x' before initialization
const x = 2;
}這段代碼會(huì)報(bào)錯(cuò),但是如果我們把{}內(nèi)的const x = 2;注釋掉,那么代碼就不會(huì)報(bào)錯(cuò)。如果const x = 2沒有被提升的話,那么console.log(x)應(yīng)該可以訪問到全局的const x = 1,而不會(huì)報(bào)錯(cuò)。換句話說(shuō):因?yàn)?code>const x = 2被提升了,所以console.log(x)訪問的是提升后的x,而此時(shí)x還沒有被初始化,所以報(bào)錯(cuò)。
提升只針對(duì)變量聲明,不包括賦值。
下面的代碼會(huì)報(bào)錯(cuò),因?yàn)閤 = 1是賦值,并不是聲明,所以不會(huì)提升。(注意:如果變量聲明前沒有加var, let或const,那么其實(shí)產(chǎn)生的是一個(gè)意外的全局變量。)
console.log(x); // ReferenceError: x is not defined x = 1;
如果有同名函數(shù)和變量,那么提升后,變量位于函數(shù)之前(或者說(shuō)函數(shù)會(huì)覆蓋變量)。
以下代碼中有一個(gè)同名的函數(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提升的問題很簡(jiǎn)單,就是按照提升規(guī)則將代碼重寫一下,上面的代碼等價(jià)于如下代碼,結(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變量都會(huì)被忽略,以上代碼提升后等價(jià)于如下代碼(注意function也有提升),函數(shù)內(nèi)部的var永遠(yuǎn)會(huì)覆蓋全局的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ī)則把代碼重新寫一遍,以上代碼提升后等價(jià)于如下代碼:
function fn() {
var foo;
function foo() {
return 'function';
}
console.log(typeof foo);
foo = 'variable';
console.log(typeof foo);
}
fn();所以輸出結(jié)果是function和string。
變量的作用域
var聲明的變量有只兩種作用域:全局作用域和函數(shù)作用域。(沒有塊級(jí)作用域)let和const聲明的變量有三種作用域:全局作用域,函數(shù)作用域和塊級(jí)作用域。var聲明的全局變量會(huì)掛載到window對(duì)象上,而let和const不會(huì)。let和const有臨時(shí)性死區(qū),而var沒有。
面試題
第一題
以下代碼輸出什么?
let x = 1;
{
let x = 2;
}
console.log(x);答案:1,因?yàn)?code>let有塊級(jí)作用域,所以let x = 2只在{}內(nèi)有效。
第二題
以下代碼輸出什么?
var x = 1;
{
var x = 2;
}
console.log(x);答案:2,因?yàn)?code>var沒有塊級(jí)作用域,所以var x = 2會(huì)覆蓋外部的var x = 1。
第三題
以下代碼輸出什么?
let name = 'zdd';
{
console.log(name);
let name = 'Philip';
}答案:ReferenceError: Cannot access 'name' before initialization。因?yàn)?code>let有塊級(jí)作用域,所以console.log(name);訪問的是let name = 'Philip';之前的name,而此時(shí)name還沒有被初始化,處于暫時(shí)性死區(qū)中,所以報(bào)錯(cuò)。
第四題
以下代碼輸出什么?
'use strict';
{
function foo() {
console.log('foo');
}
}
foo();答案:ReferenceError: foo is not defined。因?yàn)?code>foo是在塊級(jí)作用域內(nèi)聲明的,所以在外部無(wú)法訪問。但是如果我們把'use strict';去掉,那么代碼就可以正常運(yùn)行。因?yàn)樵诜菄?yán)格模式下,函數(shù)聲明會(huì)被提升到全局作用域。
第五題
以下代碼輸出什么?
(() => {
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。因?yàn)?code>catch中的x是一個(gè)新的變量,不是外部的x,所以x = 1只會(huì)改變catch中的x,而不會(huì)改變外部的x。而y = 2不是catch的參數(shù),只是在catch中賦值的,所以會(huì)改變外部的y。
暫時(shí)性死區(qū) - Temporal Dead Zone
TDZ即Temporal Dead Zone - 中文名暫時(shí)性死區(qū),是指let和const聲明的變量在其作用域開始到變量聲明之間的這段區(qū)域。在暫時(shí)性死區(qū)內(nèi)無(wú)法訪問變量,訪問會(huì)報(bào)錯(cuò)。
function foo() {
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let a = 1;
const b = 2;
}
foo();對(duì)于以上代碼,常量b的暫時(shí)性死區(qū)開始于函數(shù)的第一行,終止于b的聲明,而console.log(b);這句恰恰在暫時(shí)性死區(qū)內(nèi)訪問了b,所以會(huì)報(bào)錯(cuò)。
面試題
以下代碼輸出什么?
function foo() {
console.log(typeof bar);
const bar = 1;
}
foo();答案:ReferenceError: Cannot access 'bar' before initialization因?yàn)?code>console.log(typeof bar);這句在bar的暫時(shí)性死區(qū)內(nèi)訪問了bar,所以會(huì)報(bào)錯(cuò)??梢钥吹?,即使強(qiáng)如typeof這種幾乎不會(huì)報(bào)錯(cuò)的操作符也無(wú)法規(guī)避暫時(shí)性死區(qū)。
如果我們把const bar = 1;去掉,那么代碼就不會(huì)報(bào)錯(cuò)。typeof操作符對(duì)于沒有聲明的變量不會(huì)報(bào)錯(cuò),而是返回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, 這個(gè)題主要考察兩個(gè)知識(shí)點(diǎn):
var聲明的變量沒有塊級(jí)作用域。var聲明的變量可以被重復(fù)聲明,后聲明的會(huì)覆蓋先聲明的。
所以var a = 3會(huì)覆蓋外部的var a = 2,但是var a = 2不會(huì)覆蓋最外面的var a = 1。因?yàn)?code>var有函數(shù)作用域。
以上代碼提升后等價(jià)于如下代碼:
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沒有塊級(jí)作用域。
第二題
這道題比較簡(jiǎn)單,考察的是let的塊級(jí)作用域,代碼輸出2, 1。因?yàn)?code>let有塊級(jí)作用域。let a = 2只在{}內(nèi)有效。
function foo() {
let a = 1;
{
let a = 2;
console.log(a);
}
console.log(a);
}
foo();意外的全局變量
如果我們聲明變量的時(shí)候忘記了寫var, let或者const,那么這個(gè)變量就是所謂的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
因?yàn)?code>var沒有塊級(jí)作用域,所以setTimeout內(nèi)的i都是指向同一個(gè)i,而setTimeout是異步的,其回調(diào)函數(shù)代碼需要先進(jìn)入宏任務(wù)隊(duì)列,待for循環(huán)結(jié)束后才能執(zhí)行,此時(shí)i已經(jīng)是3了。關(guān)于這道題的詳細(xì)解釋,請(qǐng)看這篇。
最佳實(shí)踐
如今ES6已經(jīng)普及,對(duì)于業(yè)務(wù)代碼來(lái)說(shuō),基本不需要使用
var了,var目前只有JS框架或者底層工具庫(kù)才會(huì)使用。對(duì)于
let和const,優(yōu)先使用const,只有在需要修改變量的情況下才使用let。經(jīng)典for循環(huán)使用
let,因?yàn)檠h(huán)變量會(huì)被修改。for (let i = 0; i < 5; i++) { console.log(i); }for...in和for...of使用const,因?yàn)檠h(huán)變量不會(huì)被修改。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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- js中var,let,const的區(qū)別及相關(guān)面試題講解
- JavaScript中var、let和const的用法及區(qū)別詳解
- javascript中定義變量const和var有什么區(qū)別詳解
- js中var、let、const之間的區(qū)別
- Js中var,let,const的區(qū)別你知道嗎
- JavaScript變量中var,let和const的區(qū)別
- 淺談JS中var,let和const的區(qū)別
- javascript?變量聲明?var,let,const?的區(qū)別
- 面試官常問之說(shuō)說(shuō)js中var、let、const的區(qū)別
- JavaScript?ES6語(yǔ)法中l(wèi)et,const?,var?的區(qū)別
- javascript的var與let,const之間的區(qū)別詳解
- JavaScript中const和var的區(qū)別小結(jié)
相關(guān)文章
用javascript實(shí)現(xiàn)檢測(cè)指定目錄是否存在的方法
今天看到一篇關(guān)于onegreen被掛馬的代碼發(fā)現(xiàn)這個(gè)函數(shù),它用js就可以檢測(cè),制定的目錄或指定的文件是否存在,一般用來(lái)讀chm文件中的圖片來(lái)檢測(cè),目錄的存在。高手就是不學(xué)好。2008-01-01
JS 封裝父頁(yè)面子頁(yè)面交互接口的實(shí)例代碼
這篇文章主要介紹了js 封裝父頁(yè)面子頁(yè)面交互接口的實(shí)例代碼,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-06-06
原生Js Canvas去除視頻綠幕背景的方法實(shí)現(xiàn)
本文主要介紹了原生Js Canvas去除視頻綠幕背景的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-09-09
JavaScript實(shí)現(xiàn)當(dāng)網(wǎng)頁(yè)加載完成后執(zhí)行指定函數(shù)的方法
這篇文章主要介紹了JavaScript實(shí)現(xiàn)當(dāng)網(wǎng)頁(yè)加載完成后執(zhí)行指定函數(shù)的方法,實(shí)例分析了javascript加載頁(yè)面及執(zhí)行函數(shù)的技巧,需要的朋友可以參考下2015-03-03
js實(shí)現(xiàn)鼠標(biāo)經(jīng)過時(shí)圖片滾動(dòng)停止的方法
這篇文章主要介紹了js實(shí)現(xiàn)鼠標(biāo)經(jīng)過時(shí)圖片滾動(dòng)停止的方法,可實(shí)現(xiàn)js滾動(dòng)特效中的鼠標(biāo)懸停停止圖片滾動(dòng)的功能,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-02-02
詳解webpack打包第三方類庫(kù)的正確姿勢(shì)
這篇文章主要介紹了詳解webpack打包第三方類庫(kù)的正確姿,我們主要介紹了webpack.optimize.CommonsChunkPlu,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10
firefox火狐瀏覽器與與ie兼容的2個(gè)問題總結(jié)
這幾天遇到幾個(gè)頭疼的火狐與ie兼容問題整理下來(lái),希望對(duì)需要的朋友有所幫助。2010-07-07

