ES6的深入認識(1)
let&const
首先列出我對于let和cons起初粗淺的認識
- let ≈ var,const只是定義一個不可變常量
- 二者均不支持提升
學習總結(jié)后的一些認識:
(1) var在全局創(chuàng)建的變量,全局各處均可以訪問到,let則不是,如果在代碼塊中創(chuàng)建,則只能在代碼塊中訪問。
驗證:
第一點認識起初不是很明朗,于是進行了如下的驗證:
var arr01 = [],
arr02 = [];
for(let i = 0; i < 5; i++) {
arr01.push(function() {
console.log('i in arr01', i);
});
}
for(var i = 0; i < 5; i++) {
arr02.push(function() {
console.log('i in arr02', i);
});
}
arr01.forEach(item => item());
arr02.forEach(item => item());
結(jié)果

結(jié)論:很明顯,var創(chuàng)建的i一輪又一輪被重新賦值,到最后我們執(zhí)行打印函數(shù)的時候,全局的i只等于最后循環(huán)結(jié)束時被賦予的值了,前面的值均已經(jīng)被覆蓋掉,let創(chuàng)建的i則健全的保留了每一次創(chuàng)建時被賦予的新值。
(2) var創(chuàng)建的變量,在循環(huán)中始終保持只有一個變量被循環(huán)覆蓋式賦值,而let在循環(huán)中每一次都會創(chuàng)建一個新的變量,賦予新值(js的內(nèi)部引擎會記住上一輪循環(huán)的值,初始化本輪的變量i時,就在上一輪循環(huán)的基礎(chǔ)上進行計算。)
(3)暫時性死區(qū)
在代碼塊中,如果我們在沒有用let或者const去聲明一個變量之前就使用了該變量,那么就會報引用錯誤。換一句話說,也就是在進入當前作用域時,需要使用的變量就已經(jīng)存在,但是不可獲取,只有在遇到聲明該變量的這一行代碼出現(xiàn)之后,才可以正常的使用。
(4)塊級作用域存在的意義
內(nèi)層變量可能會覆蓋外層變量
先解讀一段代碼
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
起初并不明白為什么會返回Undefined,我最開始認為應當返回new Date(),但是后來發(fā)現(xiàn)var是存在變量提升的,所以上述代碼應當解讀為:
var tmp = new Date();
function f() {
var tmp = undefined;
console.log(tmp);
if (false) {
tmp = 'hello world';
}
}
f(); // undefined
用來計數(shù)的循環(huán)變量泄露為全局變量
同樣先讀一段代碼
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
在假設(shè)不考慮性能的前提下,如果我有兩個以上的循環(huán),
for() {
//...
}
for() {
//...
}
for() {
//...
}
//...
那么我一個頁面要設(shè)置多個計數(shù)的循環(huán)變量。但是如果換做是let,那么情況就發(fā)生變化了。
var s = 'hello';
for (let i = 0; i < s.length; i++) {
console.log(s[i]);
}
for(let i = 5; i < 10; i++) {
console.log('第二個循環(huán)', i);
}
for(let i = 10; i < 15; i++) {
console.log('第三個循環(huán)', i);
}
console.log(i); // 5
結(jié)果

我不用再考慮計數(shù)變量會泄露到全局了。
(5)ES6的塊級作用域
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
起初看這一段代碼的時候,會有一些疑問,前面的學習告訴我let是不能重復對一個變量進行聲明的,這一段代碼必然會報錯,但是運行之后沒有報錯甚至打印了5,這就說明函數(shù)中不僅有函數(shù)自己的局部作用域,還有if代碼塊中的塊級作用域。
此時,就不得不聯(lián)想到es5中常年為了躲避變量泄露二使用到的IIFE(Immediately-invoked-function-expression),哇,很方便了。
// before
(function(){
var temp = 'hello';
})();
// after
{
let temp = ...;
...
}
(6)塊級作用域與函數(shù)聲明
在學習過程中,碰到這么一段代碼,說在es5環(huán)境下運行和es6環(huán)境下運行是不一樣的,前者環(huán)境下會執(zhí)行內(nèi)部函數(shù),后者則會視執(zhí)行環(huán)境的表現(xiàn)而定。
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重復聲明一次函數(shù)f
function f() { console.log('I am inside!'); }
}
f();
}());
果然,在Chrome中執(zhí)行結(jié)果如下:

這么一看,與理論上es6的塊級作用域表現(xiàn)不符,我們期待的是會打印出外層函數(shù)的outside,但是這里的表現(xiàn)說明,我們的函數(shù)沒有做到es5的提升,即
function f() { console.log('I am outside!'); }
(function () {
// 重復聲明一次函數(shù)f
function f() { console.log('I am inside!'); }
}
if (false) {
f();
}());
那具體是怎么回事呢,在查閱資料之后發(fā)現(xiàn),es6的附錄中規(guī)定了瀏覽器的表現(xiàn)可以不遵守理論上的規(guī)定,它可以有自己的表現(xiàn)方式,如下:
- 允許在塊級作用域內(nèi)聲明函數(shù)。
- 函數(shù)聲明類似于var,即會提升到全局作用域或函數(shù)作用域的頭部。
- 同時,函數(shù)聲明還會提升到所在的塊級作用域的頭部。
那么上述代碼的解讀就變成了:
function f() { console.log('I am outside!'); }
(function () {
var f = undefined;
if (false) {
// 重復聲明一次函數(shù)f
function f() { console.log('I am inside!'); }
}
f();
}());
另外,還有一個需要注意的地方。ES6 的塊級作用域允許聲明函數(shù)的規(guī)則,只在使用大括號的情況下成立,如果沒有使用大括號,就會報錯。
(7)const保證不變的并非是值
以前的學習只會粗淺的認為const會保證一個值不變,但是直到我遇到const設(shè)置對象為常量。
const foo = {};
foo.name = 'foo';
console.log('foo', foo);
foo.name = 'boo';
console.log('foo', foo);
foo = {};
結(jié)果

原來const只能保證設(shè)置的值指向的內(nèi)存指針是不變的,而這個內(nèi)存地址縮儲存的值為多少const并不關(guān)心,所以上述代碼中name屬性如何變又或是foo的對象屬性增或減與const無關(guān),因為foo所在的內(nèi)存地址始終沒有變過。
結(jié)論:使用const去聲明一個不變的對象時需要格外的謹慎
那么,我們真的想要一個始終都不會變的對象,又該怎么辦呢?Object有一個方法叫做freeze
const foo = Object.freeze({});
foo.name = 'foo';
console.log('foo', foo);
foo.name = 'boo';
console.log('foo', foo);
foo = {};
結(jié)果

嚴格模式下:

可是,這就結(jié)束了嗎?我們以前復制對象的時候回去考慮深淺拷貝的問題,那么這里修改的時候,會不會也考慮到層級深淺的問題呢?于是就出現(xiàn)了以下的代碼(函數(shù)聲明或是表達式都可以)
var freezeComplete = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
freezeComplete( obj[key] );
}
});
return obj;
};
const foo = freezeComplete({
person: {},
otherKeys: 'otherKeys'
});
foo.person.name = 'foo';
foo.person.height = '1.8';
console.log('foo', foo);
