
『ES6腳丫系列』let+const+變量+變量作用域+塊作用域+變量聲明提升
一、let命令
【01】ES6新加。
【02】let用于聲明變量,let聲明的變量,只在let命令所在的代碼塊內(nèi)有效。
所謂的代碼塊就是指花括號內(nèi)的東西。
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1代碼
【03】在for循環(huán)的小括號里,let定義的變量,只在for循環(huán)體內(nèi)有效。其他地方就是未定義了。
例子:
for(let i = 0; i < arr.length; i++){}
console.log(i)//ReferenceError: i is not defined
代碼
例子:
下面的代碼for使用var,最后輸出的是10。因為i在全局作用域內(nèi)有效,。所以每一次循環(huán),新的i值都會覆蓋舊值,導(dǎo)致最后輸出的是最后一輪的i的值。
/*
吃碼小妖解釋:a[i]中的i是每次for循環(huán)的i值,此時function(){console.log(i);})并沒有運行。
a6;運行時,才開始獲取i,此時i是最后for循環(huán)后的值,也就是10。
*/
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
代碼
如果使用let,聲明的變量僅在塊級作用域內(nèi)有效,最后輸出的是6。
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6 代碼
上面代碼中,變量i是let聲明的,當(dāng)前的i只在本輪循環(huán)有效,所以每一次循環(huán)的i其實都是一個新的變量,所以最后輸出的是6。
【04】不存在變量聲明提升,只能先聲明,再使用。否則報錯。
console.log(foo); // ReferenceError
let foo = 2;代碼
這也意味著typeof不再是一個百分之百安全的操作。
由于typeof運行時,x還沒有聲明,所以會拋出一個ReferenceError。
typeof x; // ReferenceError
let x;
代碼
【05】暫時性死區(qū)(temporal dead zone,簡稱TDZ)
意思就是:如果代碼塊內(nèi)使用let聲明了某個變量x,那么在這個聲明之前,這個變量不能被賦值或使用。否則就會報錯。
打個比方,你的朋友胖虎有個變形金剛,在他說給你玩之前,你不能搶過來玩,更不能賣掉。
其實,就是為了規(guī)范,避免先使用后聲明這種奇葩的行為。
【】例子:
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
} 代碼
【】例子:
if (true) {
// TDZ開始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ結(jié)束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
代碼
【】有些“死區(qū)”比較隱蔽,不太容易發(fā)現(xiàn)。
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 報錯
代碼
【】例子:
function bar(x = 2, y = x) {
return [x, y];
}
bar(); // [2, 2]
代碼
【05】不能重復(fù)聲明
用let聲明后,不可以再用let或var聲明同一個變量。
不能在函數(shù)內(nèi)部用let聲明函數(shù)形參。
// 報錯
function () {
let a = 10;
var a = 1;
}
// 報錯
function () {
let a = 10;
let a = 1;
}
代碼
[圖片上傳失敗...(image-2e1c90-1561932387592)]
function func(arg) {
let arg; // 報錯
}
function func(arg) {
{
let arg; // 不報錯
}
}
代碼
~~~~~~~~~~~~~~~~~~~~~ 我是吃碼小妖的分割線 ~~~~~~~~~~~~~~~~~~~~~~~~
二、const命令
【01】聲明的是常量,一旦聲明,不得修改。所以必須聲明的同時并賦值。否則報錯。
const的定義是不可重新賦值的值,與不可變的值(immutable value)不同。重新賦值會報錯。
如果const定義的是引用類型,比如Object,在定義之后仍可以修改屬性。
它的使用場景很廣,包括常量、配置項以及引用的組件、定義的 “大部分” 中間變量等,都應(yīng)該以const做定義。
let多用于在 loop循環(huán)(for,while)和少量必須重賦值的變量上。
const PI = 3.1415;
PI // 3.1415
PI = 3;// TypeError: "PI" is read-only
const foo;// SyntaxError: missing = in const declaration代碼
和let一樣:
【02】只在聲明所在的塊級作用域內(nèi)有效。
if (true) { const MAX = 5;``} MAX // Uncaught ReferenceError: MAX is not defined
【03】const命令聲明的常量也是不提升。
【04】同樣存在暫時性死區(qū),只能在聲明的位置后面使用。
if (true) {
console.log(MAX); // ReferenceError
const MAX = 5;} 代碼
【05】const聲明的常量,也與let一樣不可重復(fù)聲明。
var message = "Hello!";let age = 25;
// 以下兩行都會報錯
const message = "Goodbye!";
const age = 30;代碼
【06】對于引用類型的變量,變量名不指向數(shù)據(jù),而是指向數(shù)據(jù)所在的地址。(指針的概念。)
const命令只是保證變量名指向的地址不變,并不保證該地址的數(shù)據(jù)不變,所以將一個對象聲明為常量必須非常小心。
例子:
const foo = {};
foo.prop = 123;
foo.prop// 123
foo = {} // TypeError: "foo" is read-only不起作用
代碼
例子:
const a = [];
a.push("Hello"); // 可執(zhí)行
a.length = 0; // 可執(zhí)行
a = ["Dave"]; // 報錯代碼
【07】ES5只有兩種聲明變量的方式:var命令和function命令。
ES6除了添加let和const命令,另外兩種聲明變量的方法:import命令和class命令。
所以,ES6一共有6種聲明變量的方法。
【08】ES6規(guī)定,var命令和function命令聲明的全局變量,依舊是全局對象的屬性;
let命令、const命令、class命令聲明的全局變量,不屬于全局對象的屬性。
var a = 1;
// 如果在Node的REPL環(huán)境,可以寫成global.a
// 或者采用通用方法,寫成this.a
window.a // 1
let b = 1;
window.b // undefined
代碼
【09】如果真的想將對象凍結(jié)不再變,應(yīng)該使用Object.freeze()方法。此時添加屬性無效。
const foo = Object.freeze({});
foo.prop = 123; // 不起作用
代碼
除了將對象本身凍結(jié),對象的屬性也應(yīng)該凍結(jié)。
下面是一個將對象徹底凍結(jié)的函數(shù)。
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach((key, value) => {
if (typeof obj[key] === 'object') {
constantize(obj[key]);
}
});
};
代碼
【10】跨模塊常量
const聲明的常量只在當(dāng)前代碼塊有效。
如果想設(shè)置跨模塊的常量,可以將這些變量暴露出去,提供給其他模塊即可。
// constants.js 模塊
export const A = 1;
export const B = 3;
export const C = 4;
// test1.js 模塊
import * as constants from './constants';
console.log(constants.A); // 1
console.log(constants.B); // 3
// test2.js 模塊
import {A, B} from './constants';
console.log(A); // 1
console.log(B); // 3
代碼
【01】變量+標(biāo)識符
06、在函數(shù)內(nèi)部,沒有var聲明的變量,是作為全局變量存在的;
當(dāng)用var聲明一個JS全局變量時,實際上是定義了全局對象window的一個屬性。
~~~~~~~~~~~~~ 我是晚上沒蓋被子的分割線 ~~~~~~~~~~~~~~~~~~~
三、變量+標(biāo)識符
【01】聲明變量
01、變量是用var運算符(variable 的縮寫)加變量名定義的。
例如:
var test = "hi";代碼
02、ES中的變量并不一定要初始化賦值。
如果未在var聲明語句中給變量指定初始值,那么它的初始值就是undefined。var test;代碼
03、可以用一個 var 語句定義兩個或多個變量:
var test1 = "hi", test2 = "hello";代碼
04、變量可以是不同類型。
var test = "hi", age = 25;代碼
05、由于由于ES是弱類型的,所以變量聲明時不用聲明類型。
變量可以存放不同類型的值。這是弱類型變量的優(yōu)勢。
變量是弱類型的?;蛘哒f是“松散類型”。就是可以用來保存任何類型的數(shù)據(jù)。
可以隨時改變變量保存的數(shù)據(jù)的類型。
例如,可以把變量初始化為字符串類型的值,之后把它設(shè)置為數(shù)字值,如下所示:
var test = "hi";
alert(test);
test = 55;
alert(test);代碼
06、在函數(shù)內(nèi)部,沒有var聲明的變量,是作為全局變量存在的;
【02】變量的生命周期
JS變量的生命期從它們被聲明的時間開始。
局部變量會在函數(shù)運行以后被刪除。
全局變量會在頁面關(guān)閉后被刪除。
【03】當(dāng)用var聲明一個JS全局變量時,實際上是定義了全局對象window的一個屬性。
【04】當(dāng)使用var聲明一個變量時,這個變量無法通過delete運算符刪除。
非嚴(yán)格模式,給一個未聲明的變量賦值,JS會自動創(chuàng)建一個全局變量,可以用delete刪除。
例子:
var truevar = 1; // 聲明一個不可刪除的全局變量
fakevar = 2; // 創(chuàng)建全局對象的一個可刪除的屬性
this.fakevar2 = 3; // 同上
delete truevar // => false: 變量并沒有被刪除
delete fakevar // => true: 變量被刪除
delete this.fakevar2 // => true: 變量被刪除
代碼
【05】聲明、初始化、賦值之間的區(qū)別
聲明: 變量將會對一個包含相應(yīng)作用域范圍(比如: 在一個函數(shù)里面)的名字進(jìn)行注冊。
初始化: 當(dāng)你聲明一個變量的時候會自動的進(jìn)行初始化,也就是說由 JavaScript 解釋引擎為變量分配了一塊內(nèi)存。
賦值: 把一個指定的值指派(賦)給一個變量。
使用:使用變量的值 例如alert(myValue)
~~~~~~~~~~~~~ 我是被蚊子咬的分割線 ~~~~~~~~~~~~~~~~~~~
四、變量作用域
【01】全局變量和局部變量
全局變量
在函數(shù)外聲明的變量是全局變量,網(wǎng)頁上的所有腳本和函數(shù)都能訪問它。
局部變量
在JS函數(shù)內(nèi)部聲明的變量(使用 var)是局部變量,只能在函數(shù)內(nèi)部訪問它。(該變量的作用域是局部的)。 可以在不同的函數(shù)中使用名稱相同的局部變量,因為只有聲明過該變量的函數(shù)才能識別出該變量。 只要函數(shù)運行完畢,局部變量就會被刪除。
function a (){ var b=3; }
console.log(b);//b is not defined
【02】一個變量的作用域(scope)是代碼中定義這個變量的區(qū)域。
全局變量擁有全局作用域,在JS代碼中的任何地方都是有定義的。
然而在函數(shù)內(nèi)聲明的變量只在函數(shù)體內(nèi)有定義。
它們是局部變量,作用域是局部性的。
函數(shù)參數(shù)也是局部變量,它們只在函數(shù)體內(nèi)有定義。
【03】在ES5中,JS通過函數(shù)管理作用域。ES6中,有塊作用域。
【04】全局變量會在下列情況下出現(xiàn):
- 在任何地方不使用 var ,let,const聲明變量。
- 直接向未聲明的變量賦值。
- 在函數(shù)外部使用 var 聲明的變量。
- 以 window. variable 形式聲明的變量。
【05】全局變量的問題
所有的JS文件共享這些全局變量,它們在同一個全局命名空間,很容易命名沖突。
比如:
- 第三方的功能庫文件。
- 同事的代碼。
- 第三方的用戶分析代碼。
function sum(x, y) {
// 不推薦寫法: 隱式全局變量
result = x + y;
return result;
}
代碼
a是本地變量但是b確實全局變量。賦值表達(dá)式是從右向左進(jìn)行的。
// 反例,勿使用
function foo() {
var a = b = 0;
// ...
}
代碼
【07】避免全局變量的方式:
方式1:使用ES6的let和const聲明變量。
方式2:使用立即自執(zhí)行函數(shù)。
(val=>val+1)(3); //4
【08】在函數(shù)體內(nèi),局部變量的優(yōu)先級高于同名的全局變量。
如果在函數(shù)內(nèi)聲明的一個局部變量或者函數(shù)參數(shù)中帶有的變量和全局變量重名,那么全局變量就被局部變量所覆蓋。
var scope = "global"; // 聲明一個全局變量
function checkscope() {
var scope = "local"; // 聲明一個同名的局部變量
return scope; // 返回局部變量的值,而不是全局變量的值
}
checkscope() // => "local"
代碼
【09】函數(shù)定義是可以嵌套的。由于每個函數(shù)都有它自己的作用域,因此會出現(xiàn)幾個局部作用域嵌套的情況,例如:
var scope = "global scope"; // 全局變量
function checkscope() {
var scope = "local scope"; //局部變量
function nested() {
var scope = "nested scope"; // 嵌套作用域內(nèi)的局部變量
return scope; // 返回當(dāng)前作用域內(nèi)的值
}
return nested();
}
checkscope() // => "嵌套作用域"代碼
var a = 2;
function test(){
console.log(a);
var a = 10;
}
test();//undefined;
代碼
01、在程序設(shè)計語言中,變量可分為自由變量與約束變量兩種。
簡單來說,局部變量和參數(shù)都被認(rèn)為是約束變量;
而不是約束變量的則是自由變量。
02、在馮·諾依曼計算機體系結(jié)構(gòu)的內(nèi)存中,變量的屬性可以視為一個六元組:(名字,地址,值,類型,生命期,作用域)。
地址屬性具有明顯的馮·諾依曼體系結(jié)構(gòu)的色彩,代表變量所關(guān)聯(lián)的存儲器地址(內(nèi)存)。
類型規(guī)定了變量的取值范圍和可能的操作。(比如數(shù)值有取值范圍)
生命期表示變量與某個存儲區(qū)地址綁定的過程。
根據(jù)生命期的不同,變量可以被分為四類:靜態(tài)、棧動態(tài)、顯式堆動態(tài)和隱式堆動態(tài)。
作用域限制了變量在語句中的適用范圍,分為詞法作用域和動態(tài)作用域兩種。
在詞法作用域的環(huán)境中,變量的作用域與其在代碼中所處的位置有關(guān)。
由于代碼可以靜態(tài)決定(運行前就可以決定),所以變量的作用域也可以被靜態(tài)決定,因此也將該作用域稱為靜態(tài)作用域。
在動態(tài)作用域的環(huán)境中,變量的作用域與代碼的執(zhí)行順序有關(guān)。
JavaScript和C都是詞法作用域語言。
~~~~~~~~~~~~~ 我是晚上沒蓋被子的分割線 ~~~~~~~~~~~~~~~~~~~
五、塊級作用域
【01】為什么要有塊級作用域?
ES5只有全局作用域和函數(shù)作用域,沒有塊級作用域,這帶來很多不合理的場景。
var tmp = new Date();
function f(){
console.log(tmp);
if (false){
var tmp = "hello world";
}
}
f() // undefined
代碼
var s = 'hello';
for (var i = 0; i < s.length; i++){
console.log(s[i]);
}
console.log(i); // 5
代碼
【02】ES6的塊級作用域
let和const為JavaScript新增了塊級作用域。
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
代碼
【03】內(nèi)層作用域可以和外層作用域定義同名的變量。
{{{{let insane = 'Hello World';{let insane = 'Hello World';}}}}};
代碼
【04】立即執(zhí)行匿名函數(shù)(IIFE)不再必要了。
// IIFE寫法
(function () {var tmp = ...;...}());
// 塊級作用域?qū)懛?{let tmp = ...;...}
代碼
【05】函數(shù)的作用域,在其定義的父級代碼塊作用域之內(nèi)。
function f() {
console.log('I am outside!');
}
(function () {
if (false) { // 重復(fù)聲明一次函數(shù)f
function f() {
console.log('I am inside!');
}
}
f();
}());
代碼
上面代碼在ES5中運行,會得到“I am inside!”,但是在ES6中運行,會得到“I am outside!”。
這是因為ES5存在函數(shù)提升,不管會不會進(jìn)入 if代碼塊,函數(shù)聲明都會提升到當(dāng)前作用域的頂部,得到執(zhí)行;
而ES6支持塊級作用域,不管會不會進(jìn)入if代碼塊,其內(nèi)部聲明的函數(shù)皆不會影響到作用域的外部。
【06】塊級作用域外部,無法調(diào)用塊級作用域內(nèi)部定義的函數(shù)。如果確實需要調(diào)用,就要像下面這樣處理。
{
let a = 'secret';
function f() {
return a;
}
}
f() // 報錯
代碼
【】例子:
let f;{let a = 'secret';
f = function () {return a;}}f(); // "secret"
代碼
【07】在嚴(yán)格模式下,函數(shù)只能在頂層作用域和函數(shù)內(nèi)聲明,其他情況(比如if代碼塊、循環(huán)代碼塊)的聲明都會報錯。
【08】ES6允許塊級作用域的任意嵌套。
{{{{{let insane = 'Hello World'}}}}};
代碼
上面代碼使用了一個五層的塊級作用域。外層作用域無法讀取內(nèi)層作用域的變量。
{{{{{let insane = 'Hello World'}
console.log(insane); // 報錯
}}}};代碼
~~~~~~~~~~~~~ 我是晚上沒蓋被子的分割線 ~~~~~~~~~~~~~~~~~~~
六、變量和函數(shù)聲明提升
【01】變量提升(hoisting)是 JavaScript 編譯器的行為,將所有的變量和函數(shù)聲明移至當(dāng)前作用域的最高處。
然而,只有聲明被提升了。任何賦值行為都被留在它們所在的地方。
如果在聲明之前訪問該變量,它的值是undefined。
例子:
(function() {
var foo = 1;
var bar = 2;
var baz = 3;
console.log(foo + " " + bar + " " + baz);//"1 2 3"
})();
代碼
【】例子:
(function() {
var foo = 1;
console.log(foo + " " + bar + " " + baz);// "1 undefined undefined"
var bar = 2;
var baz = 3;
})();
代碼
【】例子:
(function() {
var foo = 1;
console.log(foo + " " + bar + " " + baz);// ReferenceError baz未定義。
var bar = 2;
代碼
【02】函數(shù)變量提升(hoisting)
函數(shù)聲明也是可以被提升的。
函數(shù)聲明表達(dá)式不會聲明提升。
【】例子:
foo();//Hello! 成功。
function foo() {
console.log("Hello!");
}
代碼
【】例子:
foo();//失敗。函數(shù)表達(dá)式聲明不能函數(shù)提升。
var foo = function() {
console.log("Hello!");
};
代碼
【03】聲明命令和變量聲明提升
用var 聲明的變量存在變量聲明提升。
let和const聲明的變量不會變量聲明提升,存在暫時性死區(qū)。
class 聲明的類不會聲明提升。
function double(num) {
console.log(myVariable); // => undefined
var myVariable;
return num * 2;
}
double(3); // => 6