知識梳理
1活箕、變量提升
變量還沒有被聲明段标,但是我們卻可以使用這個(gè)未被聲明的變量涯冠,這種情況就叫做提升,并且提升的是聲明逼庞。不僅變量可以被提升蛇更,函數(shù)也可以被提升,并且函數(shù)的提升要優(yōu)于變量的提升赛糟,函數(shù)提升會把整個(gè)函數(shù)挪到作用域頂部派任。
foo()
var foo = 5
function foo() {
console.log('3')
}
// 輸出3
foo() // 輸出4
function foo() {
console.log('3')
}
function foo() {
console.log('4')
}
getName() // 輸出 1
function getName() {
console.log('1')
}
var getName = function() {
console.log('2')
}
getName() // 輸出2
var 命令會發(fā)生變量提升的的現(xiàn)象,即變量可在聲明之前使用璧南,而一般邏輯是先聲明變量再去使用變量掌逛。故為了糾正此現(xiàn)象,let 命令改變了語法行為司倚,即它所聲明的變量一定要在聲明后使用豆混。
// var
console.log(a); // 輸出undefined,沒有值但不會報(bào)錯
var a = 1;
// let
console.log(b); // 引用錯誤ReferenceError: b is not defined.
let b = 2;
2动知、作用域
在 ES5 中皿伺,只有全局作用域和函數(shù)作用域,沒有塊級作用域盒粮,這帶來了很多不合理的場景
場景一鸵鸥、內(nèi)層變量可能覆蓋外層變量:
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
場景二、用來計(jì)數(shù)的循環(huán)變量泄露為全局變量:
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
2.1 全局作用域
全局作用域顧名思義丹皱,就是在任何地方都能訪問到它妒穴,在瀏覽器中能通過 window 對象拿到的變量就是全局作用域下聲明的變量。
var name = 'test';
console.log(window.name) // 輸出test
// 這里的 name 就是全局作用域下的變量
2.2 函數(shù)作用域 / 局部作用域
函數(shù)作用域就是在函數(shù)內(nèi)部定義的變量种呐,也稱局部作用域宰翅,在函數(shù)的外部不能使用這個(gè)變量
function bar() {
var name = 'test';
}
console.log(name); // undefined
2.3 塊級作用域
塊級作用域是 ES6 的概念,它的產(chǎn)生需要有一定的條件的爽室,在大括號 {} 中使用 let 或 const 聲明的變量汁讼,才會產(chǎn)生塊級作用域淆攻。塊級作用域的產(chǎn)生是 let 或 const 帶來的,而不是大括號嘿架,大括號的作用是限制 let 或 const 的作用域范圍瓶珊。當(dāng)不在大括號中聲明時(shí), let 或 const 的作用域范圍是全局耸彪。
if (true) let x = 1; // 報(bào)錯
if (true) { let x = 1;} // 不報(bào)錯
// let 方式聲明的變量在 window 下是取不到的
let a = "test";
console.log(window.a) // undefined
// var 聲明的情況下伞芹,外層的 num 會被 {} 中的 num 覆蓋,所以沒有塊級作用域的概念
var b = 10;
{
var b = 20;
console.log(b) // 20
}
console.log(b) // 20
// let 方式聲明蝉娜,{} 內(nèi)外是互不干涉和影響的
let c = 10;
{
// c 處于暫時(shí)性死區(qū)唱较,是不能被使用的,會報(bào)錯
console.log(c); // Uncaught ReferenceError: Cannot access 'c' before initialization
let c = 20;
console.log(c) // 20
}
console.log(c) // 10
塊級作用域可以任意嵌套召川,每一層都是一個(gè)單獨(dú)的作用域南缓,內(nèi)層作用域可以讀取外層的變量,外層無法讀取內(nèi)層的變量會報(bào)錯
{{
let x = 'Hello HaHa'
{
console.log(x); // Hello HaHa
let y = 'Hello World'
}
console.log(y); // 引用錯誤 ReferenceError: y is not defined.
}};
3荧呐、暫時(shí)性死區(qū)
暫時(shí)性死區(qū)主要是針對 let 和 const 而言的汉形,因?yàn)樗鼈儾淮嬖谧兞刻嵘栽谒鼈兟暶髯兞恐笆遣荒苁褂玫谋恫@個(gè)時(shí)候如果使用了就會報(bào)錯概疆,這時(shí)候就形成了暫時(shí)性的死區(qū),也就是不能被引用峰搪。這在語法上岔冀,稱為 “暫時(shí)性死區(qū)”(temporal dead zone,簡稱 TDZ)
{
console.log(name); // ReferenceError
let name = "test";
// 暫時(shí)性死區(qū) typeof 也會報(bào)錯
typeof x; // ReferenceError
let x;
//如果一個(gè)變量根本沒有被聲明罢艾,使用typeof反而不會報(bào)錯楣颠。
typeof undeclared_variable // 輸出 "undefined"
}
有些“死區(qū)”比較隱蔽,不太容易發(fā)現(xiàn)咐蚯。比如:
// 報(bào)錯
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // Uncaught ReferenceError: Cannot access 'y' before initialization
// 不報(bào)錯
function bar(x = 2, y = x) {
return [x, y];
}
bar(); //[2, 2]
var x = x; // 不報(bào)錯
let x = x; // 報(bào)錯 ReferenceError: y is not defined
4童漩、let命令
let 允許你聲明一個(gè)作用域被限制在塊級中的變量、語句或者表達(dá)式春锋。var 聲明的變量只能在全局或者整個(gè)函數(shù)塊中矫膨。 var 和 let 的不同之處在于 let 是在編譯時(shí)才初始化。
let 不會在全局聲明時(shí)創(chuàng)建 window 對象的屬性期奔,但是 var 會侧馅。
4.1 不能變量提升
// var
{
console.log(bar); // 輸出undefined,沒有值但不會報(bào)錯
var bar = 1;
}
// let
{
console.log(name); // 引用錯誤 ReferenceError: name is not defined.
let name = 'test';
}
4.2 暫時(shí)性死區(qū)
在代碼塊內(nèi)呐萌,使用 let 命令聲明變量之前馁痴,該變量都是不可用的
{
console.log(name); // ReferenceError
let name = 'test';
}
4.2 重復(fù)聲明報(bào)錯
let 不允許在同一個(gè)函數(shù)或塊作用域中重復(fù)聲明同一個(gè)變量,否則會引起語法錯誤(SyntaxError)肺孤,即使用 var 去聲明也是不可以的
{
let x = 10 // 10
let x = 18 // Uncaught SyntaxError: Identifier 'x' has already been declared
}
{
let y = 10; // 10
var y = 1; //Uncaught SyntaxError: Identifier 'y' has already been declared
}
注意在 switch 語句中只有一個(gè)塊級作用域罗晕,所以下面這種情況也是會報(bào)錯的
// 報(bào)錯
let x = 1;
switch(x) {
case 0:
let num;
break;
case 1:
let num; //重復(fù)聲明了
break;
}
// 不報(bào)錯
let x = 1;
switch(x) {
case 0: {// 塊
let num;
break;
}
case 1: {// 塊
let num;// 這里可以正常聲明
break;
}
}
5济欢、const 命令
const 的使用類似于 let:
- let 和 const 都只作用于塊級作用域內(nèi);
- 不能進(jìn)行變量提升小渊,在聲明前不能被使用法褥,否則會拋出異常;
- 存在暫時(shí)性死區(qū)酬屉,在塊中不能被重復(fù)聲明半等,重復(fù)用var聲明也不行;
不同的是 const 在聲明時(shí)必須初始化一個(gè)值呐萨,而且這個(gè)值不能被改變杀饵。
// 在聲明時(shí)必須初始化一個(gè)值
const PI; // Uncaught SyntaxError: Missing initializer in const declaration
// 不能被修改
const PI = 3.1415; // 定義一個(gè)圓周率常量 PI
PI = 12 // Uncaught TypeError: Assignment to constant variable.
const 實(shí)際上保證的,并不是變量的值不得改動谬擦,而是變量指向的那個(gè)內(nèi)存地址所保存的數(shù)據(jù)不得改動凹髓。
對于簡單類型的數(shù)據(jù)(數(shù)值、字符串怯屉、布爾值),值就保存在變量指向的那個(gè)內(nèi)存地址饵沧,因此等同于常量锨络。
但對于復(fù)合類型的數(shù)據(jù)(主要是對象和數(shù)組),變量指向的內(nèi)存地址狼牺,保存的只是一個(gè)指向?qū)嶋H數(shù)據(jù)的指針羡儿,const只能保證這個(gè)指針是固定的(即總是指向另一個(gè)固定的地址)
const obj = {};
obj.a = 12 // 12
const arr = [];
arr.push(12); // 12
arr = {}; // Uncaught TypeError: Assignment to constant variable.
6、var是钥、let 及 const 區(qū)別
聲明方式 | var | let | const |
---|---|---|---|
作用域 | 非塊級 | 塊級 | 塊級 |
變量提升 | 允許 | 不允許 | 不允許 |
暫時(shí)性死區(qū) | 不存在 | 存在 | 存在 |
重復(fù)聲明 | 允許 | 不允許 | 不允許 |
初始值 | 不需要 | 不需要 | 需要 |
代碼練習(xí)
Q1:下面的代碼輸出什么?
function sayHi() {
console.log(name)
console.log(age)
var name = 'Lydia'
let age = 21
}
sayHi(); // 輸出 undefined 和 ReferenceError
Q2:下面的代碼輸出什么?
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000*i);
}
//依次輸出 3 3 3
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000 * i);
}
//依次輸出 0 1 2
Q3:下面的代碼輸出什么?
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// 依次輸出 abc abc abc
// for 循環(huán)掠归,設(shè)置循環(huán)變量的那部分是一個(gè)父作用域,而循環(huán)體內(nèi)部是一個(gè)單獨(dú)的子作用域
Q4:下面的代碼輸出什么?
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 輸出 10
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 輸出 6