Javascript中使用一個變量以前須要先聲明,咱們可使用var、let、const來聲明一個變量。若是在給聲明的變量指定初始值,就是初始化。如:javascript
var a = 1; let b =1; const c = '123'
初始化後,這個變量就在內存中分配了空間,後面的程序就能夠調用了。var、let、const均可以聲明變量,可是各自又不相同。在ES6中,const 定義的變量是必需要初始化賦值,並且之後不能變動, 是一個固定值。而像var,let是能夠只聲明,可是不進行初始化。java
var a; let b; console.log(a) // => undefined console.log(b) // => undefined
而在咱們熟知java代碼中,這樣只聲明,可是未賦值是會報錯的面試
int a; System.out.print(a); // The local variable a may not have been initialized
這是由於javascript的執行引擎(例如V8)會在運行這段代碼以前檢查會這段代碼, 也被稱爲預編譯。發現有var和let聲明的變量,會給這個變量提供一個默認的初始化值undefined。也正是由於要檢查代碼,因此像const所聲明的變量必須初始化、let,const所聲明的變量不能重複聲明。都是在檢查代碼時候,拋出錯誤。瀏覽器
const 必須初始化函數
console.log('hello world') const a // a 必須初始化,若是不初始化將會報 Missing initializer in const declaration
這段代碼在執行的時候,沒有先打印"hello world", 而是直接報錯。我這裏理解是代碼檢查的時候,就已經把錯誤拋出來,程序沒有運行.lua
let聲明後的變量不能重複聲明spa
let b = 1; console.log('hello world'); let b =2;
這段代碼在執行的時候,沒有先打印"hello world", 而是直接報錯。我這裏理解是代碼檢查的時候,就已經把錯誤拋出來,程序沒有運行.code
一個變量的做用域是源程序代碼中定義這個變量的區域。blog
全局做用域:在最頂層聲明的變量成爲全局變量,全局變量擁有全局做用域,它們在程序的任何地方都是可以被訪問到。ip
函數做用域:在函數中聲明的變量只能在函數中被訪問到,函數外面是訪問不到的。
塊級做用域:任何一對花括號({和})中的語句集都屬於一個塊,在這之中定義的全部變量在代碼塊外都是不可見的,咱們稱之爲塊級做用域。
做用域的主要做用是可以控制變量是使用範圍。
在Javascript中,var聲明的變量使用的是函數做用域,而let聲明的變量使用的塊級做用域。ES5中已經存在var來聲明變量,那爲啥到ES6還要使用let來聲明變量?我我的以爲是可以更好的規範咱們程序代碼,避免出現出現一些咱們所沒法預料的錯誤。
經典例子以下:
var arr = []; for(var i = 0; i < 10; i++) { var j = i; arr[i] = function() { console.log(j) } } arr[0](); // =>9 而咱們預想的是0
而使用let就能避免出現這樣的問題
var arr = []; for(var i = 0; i < 10; i++) { let j = i; arr[i] = function() { console.log(j) } } arr[0]() // =>0
這是由於let聲明的變量屬於塊級做用域,上面let所聲明的變量j是隻能在for循環每個{}中能被訪問。大體流程以下
當 i = 0,大體生成這樣一個塊
{ let j = 0; arr[i] = function() { console.log(j) } }
當i = 1
{ let j = 1; arr[i] = function() { console.log(j) } }
. . . . . .
當i = 9
{ let j = 10; arr[i] = function() { console.log(j) } }
當後面去調用arr[0]()這個方法, 這是第一個塊定義的方法, 由於let聲明的變量j是塊{}做用域, 這個方法就只能訪問第一個塊{}中j的值,也就是0;
而當咱們使用var的時候
當 i = 0
{ var j = 0; arr[i] = function() { console.log(j) } }
當 i = 1
{ var j = 1; arr[i] = function() { console.log(j) } }
當咱們調用arr[0]()這個方法, 是第一個塊定義的方法,由於var聲明的變量是函數做用域,
因此上面的程序,能夠理解爲
var j; // var 聲明的變量具備聲明提早的做用 var arr = []; { j = 0; arr[i] = function() { console.log(j) } } { j = 1; arr[i] = function() { console.log(j) } } **. . . . . .** { j = 9; arr[i] = function() { console.log(j) } } arr[0](); // =>9
arr[0]()運行的時候,因爲沒有做用域的限制,j已經被賦值爲9。因此輸出結果就是9
有人會疑問,爲啥不能直接使用i,而是使用中間變量j去替換?
這裏是爲了讓你們更好的理解,下面直接使用i
var arr = []; for(var i = 0 ; i < 10; i++) { arr[i] = function() { console.log(i); } } arr[0](); // => 10
var arr = []; for(let i = 0 ; i < 10; i++) { arr[i] = function() { console.log(i); } } arr[0](); // => 0 這是正常的
這樣也是正常的符合預期,可是當我把代碼作了修改後
var arr = []; let i; for(i = 0 ; i < 10; i++) { arr[i] = function() { console.log(i); } } arr[0](); // => 10
這裏也是let聲明的變量i,可是輸出的結果是10,這裏我看完後很不理解。因而去閱讀了ES的規範 http://www.ecma-international...
這裏是被認爲第一種狀況。let聲明的i和var聲明i在上面的代碼執行的邏輯是同樣的,就致使上面的情形。這裏小夥伴們須要特別注意。
先從一個簡單的示例開始吧
執行代碼:
console.log(a);
瀏覽器運行結果以下:
執行代碼:
if (false) {var b = 1}; console.log(b);
瀏覽器運行結果以下:
執行代碼:
if (false) {let c = 1}; console.log(c);
瀏覽器運行結果以下:
第一種情形下: 當咱們直接在瀏覽器輸入未聲明的變量a, 有報 a is not defined的錯誤。
第二種情形下: b => undefined
第三種情形下: c is not defined
第二種情形下,變量在聲明以前就已經可以被使用,這種特性被稱非官方的稱爲聲明提早。變量可以提早到哪裏呢?全部使用var聲明的變量被提早至函數頂部。
因而面試中經常使用這樣的問題出現
var global = 'global' function test () { console.log(global); var global = 'local'; console.log(global); } test() // undefined, local
這是由於在test函數中,var聲明的global被提早到test函數頂部,注意聲明提早是在預編譯時候執行的,而且給global初始化賦值undeifined。因此實際執行的邏輯,應該是這樣
var global = 'global'; function test() { var global = undefined; console.log(global); global = 'local'; console.log(global); } test(); //undefined, local
變量聲明提早好很差,很明顯,這樣很很差。一個變量尚未聲明就可以被使用,太奇怪了,也太危險了。尤爲是那些習慣了java、c的人看起來,這就是一個異類的特性。
因此,在ES6中,就推薦使用了let來聲明變量。
做用域內 let 聲明的變量不能在聲明以前被使用,若是發如今let聲明的變量在聲明以前被使用,將會拋出 is not defined的錯誤。程序將會被中斷,這個特性被稱爲 暫存死區。
在 let 變量聲明的做用域內,javaScript引擎會把let聲明的變量名收集起來。當程序開始執行的時候,代碼一行行的往下執行,引擎獲取變量以前,首先會判斷此變量的變量名是不是在let聲明的變量集合中。不存在,程序就繼續往下執行。若是存在,就判斷變量名的上一個字符串是否是let。若是是let,就把此變量名從let聲明的集合中去掉,若是不是就拋出錯誤,程序被中斷。
(純屬於我的猜測,方便理解,你們不做爲參考)
console.log('hello world'); c = 1; let c;
這裏是先打印"hello world",而後再進行報錯,說明是程序在執行到 c = 1的時候報錯。
是運行的錯誤。而你們能夠對比前面的let聲明同一個變量屢次,是程序尚未執行就報錯。因此這裏是運行中的錯誤。能夠推斷出變量暫存死區是發生程序運行中產生的,let聲明的變量不能在聲明以前使用,發現有聲明以前使用的,就會拋出錯誤,中斷程序。
另一個程序
let x = x; // x is not defined x // x is not defined let x // Uncaught SyntaxError: Identifier 'x' has already been declared
let x = x, 賦值從右往左執行,因此先獲取變量 x, 因而x陷入暫存死區。拋出x is not defined的錯誤,程序中斷。後面再獲取x,仍是拋出x is not defined,x仍是陷入暫存死區。後面的let x,拋出Uncaught SyntaxError: Identifier 'x' has already been declared 錯誤是由於檢查這段代碼的時候,發現let x 已經被聲明,因而拋出錯誤。不能被重複聲明瞭。