es6(2)——let和const
let
解釋:
1. 簡單來說,就是類似var,但使用該方法聲明的變量,隻在當前作用域生效;
幾個特點:
1、let和var相比,不存在變量提升(即先使用後聲明會報錯);
{
console.log(a); //Uncaught ReferenceError: a is not defined
let a = 1;
}
2、使用let聲明的變量,當前作用域裏,該變量唯一。
即,假如在當前作用於的父級作用域裏聲明一個var a,在當前作用域裏也聲明一個let a,那麼在當前作用域裏,隻有let聲明的a生效,也就是說,以下代碼是不可行的:
var a = 1;
{
console.log(a); //Uncaught ReferenceError: a is not defined
let a = 2;
}
但若將let a改為var a,那麼console.log的結果就是1了(采用父級作用域的變量的值);
var a = 1;
{
console.log(a); //1
var a = 2;
}
3、同一級作用域裏麵,隻允許對一個變量使用一個let來進行聲明。
具體來說,2個var a是正常的,剩下的2個let a或者先let a後var a又或者先var a再let a是統統都是報錯的。
{
var a = 1;
var a = 2; //ok
}
{
var a = 1; //Uncaught SyntaxError: Identifier 'a' has already been declared
let a = 2;
}
{
let a = 1;
let a = 2; //Uncaught SyntaxError: Identifier 'a' has already been declared
}
{
let a = 1;
var a = 2; //Uncaught SyntaxError: Identifier 'a' has already been declared
}
4、但是不同級作用域裏,是沒有影響的,比如分別在父級作用域和當前作用域裏聲明let a各一次,是沒問題的。
另外,外層和內層作用域都聲明了一個同樣的變量名時,內層作用域該變量的值的修改,對外層作用域的值是沒有影響的。
{
let a = 1;
{
let a = 2; //can do it
}
console.log(a); //1
}
但若內層不適用let聲明,而是直接調用a = 2進行修改,那麼是有影響的
{
let a = 1;
{
a = 2; //1 to 2
}
console.log(a); //2
}
5、let的限製情況
不是十分確認,是實踐後的結果。(部分因為條件不足,沒法調試)
- 變量名和和頁麵裏html標簽的id名重複時,該變量在全局作用域下。變量聲明時不能用let,隻能用var(在safari瀏覽器下會發生此bug);
- 解決方法是比如把變量名放在局部作用於內;
- 有時候let需要在"use strict"條件下才能使用(嚴格模式),我在寫nodejs的服務器端遇見過這種問題;
塊級作用域
1、let相當於新增了塊級作用域。
簡單來說,以前隻有全局作用域和函數作用域,例如以下是函數作用域的體現:
(function () {
var a = 1;
})();
console.log(a); //Uncaught SyntaxError: Unexpected identifier
而在使用var時,是不存在塊級作用域的,即如下代碼視為同一個作用域內,所以console.log可以顯示結果:
{
var a = 1;
}
console.log(a); //1
而使用let時,會相當於創造出了一個塊級作用域,例如將以上代碼改用let進行聲明,則在塊級作用域外無法正常顯示結果:
{
let a = 1;
}
console.log(a); //Uncaught ReferenceError: a is not defined
【Babel處理】另外提一句,使用babel在對以上代碼進行轉換處理時,為了使結果符合預期的運行結果,他會自動進行一些處理。
例如將let a轉換為其他的,例如var _a,而下麵的console.log(a)不變;如果有重名(比如有_a)他會繼續處理,變為_a2這樣。
2、在塊級作用域下進行函數聲明
簡單來說,let製造了塊級作用域(見上麵);
而ES5中(也許還包括更早的),理論上,函數隻能在全局作用域和函數作用域內聲明,不能在塊級作用域內聲明。但實際上,因為要兼容以前版本,所以是可以的(不會出錯,除非在嚴格模式'use strict')。
但也是因為這樣,所以如果在塊級作用域內聲明函數,你很難控製在不同瀏覽器中(包括同一瀏覽器不同版本)的實現是同樣的效果。所以應該 盡量避免在塊級作用域內聲明函數。
3、do表達式(存疑)
按照阮一峰的博客關於do表達式的說明,現在有一個提案,使得塊級作用域可以變為表達式,也就是說可以返回值,辦法就是在塊級作用域之前加上do,使它變為do表達式。
代碼如下:
let x = do { //Uncaught SyntaxError: Unexpected token do
let t = f();
t * t + 1;
};
我實測無效(chrome版本 55.0.2883.87),會報錯,報錯信息見注釋,不知為何。也許是該提案未實現?
const
解釋:
1. 簡單來說,學過c++的可以理解為c++的const,沒學過可以繼續往下看;
2. 如果指向非按引用傳遞類型(比如字符串,布爾值等),那麼該值聲明後無法被修改;
3. 如果指向按引用傳遞,則無法更改其指向的對象,但該對象的值可以被修改;
4. 準確的說,是讓按引用傳遞時,保證該const變量指向的地址不變(而非該地址裏的數據不變)(理解本條需要有指針相關概念);
1、指向非按引用傳遞類型的變量,其變量值不可以被修改
即聲明後不能被修改,修改會報錯;
const a = 1;
a = 2; //Uncaught TypeError: Assignment to constant variable.
2、指向引用類型的變量,其值可以被修改,但是不能讓其指向另外一個對象
對象的值可以被修改:
const a = {test: 1};
a.test = 2;
console.log(a.test); //2
不能修改指向的對象:(報錯這步是因為更改了指向的對象)
var a = {test: 1};
var b = {another: 2};
const c = a;
console.log(c); //{test:1}
c = b; //Uncaught TypeError: Assignment to constant variable.
3、不能聲明const變量時不賦值
會報錯
const a; //Uncaught SyntaxError: Missing initializer in const declaration
4、塊級作用域,相關特性類似let
顯然是塊級的
var a = 1;
{
const a = 2;
console.log(a); //2
}
console.log(a); //1
不存在變量提升,出現暫時性死區,不能先使用後聲明
{
console.log(a); //Uncaught ReferenceError: a is not defined
const a = 1;
}
也不可重複聲明(在同一個塊級作用域內)(使用let和var同樣不可)
{
const a = 1;
const a = 2; //Uncaught SyntaxError: Identifier 'a' has already been declared
}
5、指向一個被凍結的對象
const和Object.freeze不同,後者是凍結對象,而前者隻涉及地址。
所以可以二者結合起來,讓const變量指向一個被凍結的對象。那麼該變量則不可更改指向的目標(因為const)也不可更改其值(因為凍結)。
先從阮一峰的博客拿來一個深度凍結函數(遞歸凍結該對象所有屬性):
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach((key, value) => {
if (typeof obj[key] === 'object') {
constantize(obj[key]);
}
});
};
然後略微修改,讓const變量指向一個被凍結的對象,
會發現既無法更改變量裏對象的值,也無法讓變量指向另外一個對象。
有點像讓const變量成為一個常量。
(下麵代碼沒有體現深度凍結的效果)
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach((key, value) => {
if (typeof obj[key] === 'object') {
constantize(obj[key]);
}
});
return obj; //I add this code
};
const a = constantize({a: 1});
console.log(a); //{a:1}
a.a = 2;
console.log(a.a); //1
a = 10; //Uncaught TypeError: Assignment to constant variable.
頂層對象的屬性
- 所謂頂層對象,在js裏麵指window
- 當一個變量在頂層作用域裏(比如說打開瀏覽器通過F12的console來直接輸入命令),那麼該變量在之前情況下,是屬於window這個頂層對象的屬性的;
- 我們之前一般稱之為全局變量,全局變量在以前會被認為就是window的屬性的值;
- 而ES6中則不是,全局變量和頂層對象的屬性的值將脫鉤;
具體來說:
1、通過var或者function甚至直接寫變量名然後進行賦值創建的對象,其變量名作為key添加到window對象中,而window裏該key的值為被賦值的值。
如代碼:
console.log(window.a); //undefined
console.log(window.b); //undefined
console.log(window.c); //undefined
var a = 1;
console.log(window.a); //1
b = 2;
console.log(window.b); //2
function c(){}
console.log(window.c); //function c(){}
2、而通過let、const,以及之後的class創建的對象,則不會被添加到window裏麵。
如代碼:
console.log(window.a); //undefined
console.log(window.b); //undefined
let a = 1;
console.log(window.a); //undefined
const b = 2;
console.log(window.b); //undefined
頂層對象的獲得
- 簡單來說,頂層對象在瀏覽器裏就是window;但是在Node.js裏麵沒有window(Web Worker也沒有,他是運行在後台的js腳本);
- 瀏覽器和Web Worker裏,self指向頂層對象,但是Node.js裏沒有self;
- Node裏,頂層對象是global,但其他環境不支持(比如chrome裏打global會告訴你未定義);
- 有時候我們需要用同一套代碼,但在各個環境拿到頂層對象(啥時候?),所以得找個通用的辦法;
- 但是沒有非常完美的。
- 阮一峰給了兩個辦法,我直接摘抄了,如下代碼:
// 方法一
(typeof window !== 'undefined'
? window
: (typeof process === 'object' &&
typeof require === 'function' &&
typeof global === 'object')
? global
: this);
// 方法二
var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};
想要了解更多的話,參考阮一峰的博客相關內容。
最後更新:2017-06-12 12:01:58