let命令
let與var之間的不同
聲明變量的作用域
作用:聲明變量 類似var
與var的區(qū)別:var聲明的變量將作為全局變量 而let命令聲明的變量只在let命令所在代碼塊內(nèi)有效(局部變量)
例子:
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);//指向全局的i
};
}
a[6](); // 10
var聲明的i在全局范圍內(nèi)都有效,所以全局只有一個(gè)變量i,i在for循環(huán)內(nèi)不斷變化,而a[6]則是for循環(huán)過(guò)程中聲明過(guò)的函數(shù),函數(shù)內(nèi)部的i指向全局的i
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
let聲明的i只在本輪循環(huán)有效,每一次循環(huán)的i都是一個(gè)新的變量
注意: JavaScript 引擎內(nèi)部會(huì)記住上一輪循環(huán)的值,初始化本輪的變量i時(shí),就在上一輪循環(huán)的基礎(chǔ)上進(jìn)行計(jì)算
變量提升現(xiàn)象
var命令存在變量提升現(xiàn)象:
變量可以在聲明之前使用,值為undefined
// var 的情況
console.log(foo); // 輸出undefined
var foo = 2;
但按照一般的邏輯,變量應(yīng)該在生命語(yǔ)句之后才可以使用,因此為了糾正這種現(xiàn)象,出現(xiàn)let命令
let命令聲明的變量一定要在聲明后使用,否則報(bào)錯(cuò),<strong>不存在變量提升現(xiàn)象<strong>
// let 的情況
console.log(bar); // 報(bào)錯(cuò)ReferenceError
let bar = 2;
for循環(huán)的特別之處
設(shè)置循環(huán)變量的那部分為一個(gè)父作用域,而循環(huán)體內(nèi)部是一個(gè)單獨(dú)的子作用域
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
循環(huán)變量i和函數(shù)內(nèi)部的變量i有各自單獨(dú)的作用域
暫時(shí)性死區(qū)
只要塊級(jí)作用域內(nèi)存在let命令,它所聲明的變量就“綁定”(binding)這個(gè)區(qū)域,不再受外部的影響,即在當(dāng)前作用域內(nèi),不會(huì)受到其他作用域同名變量的影響
原因:let命令聲明的變量一定要在聲明后使用
結(jié)果:在代碼塊內(nèi),使用let命令聲明變量之前,該變量都是不可用的。這在語(yǔ)法上被稱為<strong>“暫時(shí)性死區(qū)"<strong>
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
typeof在暫時(shí)性死區(qū)中會(huì)報(bào)錯(cuò)
//在暫時(shí)性死區(qū)中報(bào)錯(cuò)
typeof x; // ReferenceError
let x;
//變量未被聲明
typeof undeclared_variable // "undefined"
本質(zhì)
暫時(shí)性死區(qū)的本質(zhì)就是,只要一進(jìn)入當(dāng)前作用域,所要使用的變量就已經(jīng)存在了,但是不可獲取,只有等到聲明變量的那一行代碼出現(xiàn),才可以獲取和使用該變量。
不允許重復(fù)聲明
let不允許在相同作用域內(nèi),重復(fù)聲明同一個(gè)變量,因此不能在函數(shù)內(nèi)部重新聲明參數(shù)
// 報(bào)錯(cuò)
function func() {
let a = 10;
var a = 1;
}
// 報(bào)錯(cuò)
function func() {
let a = 10;
let a = 1;
}
function func(arg) {
let arg;
}
func() // 報(bào)錯(cuò)
function func(arg) {
{
let arg;
}
}
func() // 不報(bào)錯(cuò)
塊級(jí)作用域
重要性
ES5只有全局作用域和函數(shù)作用域,沒(méi)有塊級(jí)作用域,這帶來(lái)很多不合理的場(chǎng)景:
- 內(nèi)層變量可能會(huì)覆蓋外層變量
- 用來(lái)計(jì)數(shù)的循環(huán)遍歷泄露為全局變量
ES6的塊級(jí)作用域
let實(shí)際上為JavaScript新增了塊級(jí)作用域
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
//若使用var聲明,則最后結(jié)果為10
}
表示外層代碼塊不受內(nèi)層代碼塊的影響(let聲明的變量只在自己的作用域內(nèi)生效)
ES6運(yùn)行塊級(jí)作用域的任意嵌套,但內(nèi)外層作用域無(wú)法讀取互相讀取變量,可以定義同名變量
{{{{
let insane = 'Hello World';
{let insane = 'Hello World'}
}}}};
塊級(jí)作用域與函數(shù)聲明
ES5規(guī)定,函數(shù)只能在頂層作用域和函數(shù)作用域之中聲明,不能在塊級(jí)作用域聲明
ES6引入了塊級(jí)作用域,并明確允許在塊級(jí)作用域中聲明函數(shù)
ES6 規(guī)定,塊級(jí)作用域之中,函數(shù)聲明語(yǔ)句的行為類似于let,在塊級(jí)作用域之外不可引用
ES6 的塊級(jí)作用域允許聲明函數(shù)的規(guī)則,只在使用大括號(hào)的情況下成立,如果沒(méi)有使用大括號(hào),就會(huì)報(bào)錯(cuò)
// 不報(bào)錯(cuò)
'use strict';
if (true) {
function f() {}
}
// 報(bào)錯(cuò)
'use strict';
if (true)
function f() {}
const命令
基本用法
const聲明一個(gè)只讀的常量,一旦聲明,就必須立即初始化,常量的值就不能改變,改變則報(bào)錯(cuò),不初始化也會(huì)報(bào)錯(cuò)
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
const foo;
// SyntaxError: Missing initializer in const declaration
const作用域與let相同:只在聲明所在的塊級(jí)作用域內(nèi)有效,且聲明的變量不提升,同樣存在暫時(shí)性死區(qū),只能在聲明的位置后面使用,不可重復(fù)聲明
本質(zhì)
const實(shí)際上保證的不是變量的值不得改動(dòng),而是變量指向的那個(gè)內(nèi)存地址所保存的數(shù)據(jù)不得改動(dòng):
- 簡(jiǎn)單類型的數(shù)據(jù) 值必須保存在變量所指向的那個(gè)內(nèi)存地址,因此等同于常量
- 復(fù)合類型的數(shù)據(jù)(對(duì)象、數(shù)組等) 變量指向的內(nèi)存地址保存的只是一個(gè)指向?qū)嶋H數(shù)據(jù)的指針,const只能保證這個(gè)指針是固定的,至于它指向的數(shù)據(jù)結(jié)構(gòu)是不是可變的就完全不能控制了
const foo = {};
// 為 foo 添加一個(gè)屬性,可以成功
foo.prop = 123;
foo.prop // 123
// 將 foo 指向另一個(gè)對(duì)象,就會(huì)報(bào)錯(cuò)
foo = {}; // TypeError: "foo" is read-only
不可變的為地址,對(duì)象本身是可變的,依然可以為其添加新屬性
const a = [];
a.push('Hello'); // 可執(zhí)行
a.length = 0; // 可執(zhí)行
a = ['Dave']; // 報(bào)錯(cuò)
數(shù)組本身是可寫(xiě)的,但不能將另一個(gè)數(shù)組賦值給const聲明的數(shù)組變量,會(huì)報(bào)錯(cuò)
凍結(jié)對(duì)象的方法
object.freeze
const foo = Object.freeze({});
// 常規(guī)模式時(shí),下面一行不起作用;
// 嚴(yán)格模式時(shí),該行會(huì)報(bào)錯(cuò)
foo.prop = 123;
常量foo指向一個(gè)凍結(jié)的對(duì)象,所以添加新屬性不起作用
ES6聲明變量的方法
ES6聲明變量總共有6種方法:var,function,let,const,import,class
頂層對(duì)象的屬性
頂層對(duì)象,在瀏覽器環(huán)境指的是window對(duì)象,在Node指的是global對(duì)象
ES5之中,頂層對(duì)象的屬性和全局變量是等價(jià)的
頂層對(duì)象的屬性與全局變量掛鉤帶來(lái)的問(wèn)題:
- 沒(méi)法在編譯時(shí)就報(bào)出變量未聲明的錯(cuò)誤,只有運(yùn)行時(shí)才能知道(因?yàn)槿肿兞靠赡苁琼攲訉?duì)象的屬性創(chuàng)造的,而屬性的創(chuàng)造是動(dòng)態(tài)的)
- 程序員很容易不知不覺(jué)地就創(chuàng)建了全局變量(比如打字出錯(cuò))
- 頂層對(duì)象的屬性是到處可以讀寫(xiě)的,這非常不利于模塊化編程
- window對(duì)象有實(shí)體含義,指的是瀏覽器的窗口對(duì)象,頂層對(duì)象是一個(gè)有實(shí)體含義的對(duì)象,也是不合適的
ES6為了改變這一點(diǎn)并保持兼容性,規(guī)定——var和function命令聲明的全局變量,依舊是頂層對(duì)象的屬性;let、const、class命令聲明的全局變量,不屬于頂層對(duì)象的屬性
var a = 1;
// 如果在 Node 的 REPL 環(huán)境,可以寫(xiě)成 global.a
// 或者采用通用方法,寫(xiě)成 this.a
window.a // 1
let b = 1;
window.b // undefined
global對(duì)象
ES5 的頂層對(duì)象由于它在各種實(shí)現(xiàn)里面是不統(tǒng)一的,所以也導(dǎo)致了很多問(wèn)題:
- 瀏覽器里面,頂層對(duì)象是window,但 Node 和 Web Worker 沒(méi)有window
- 瀏覽器和 Web Worker 里面,self也指向頂層對(duì)象,但是 Node 沒(méi)有self
- Node 里面,頂層對(duì)象是global,但其他環(huán)境都不支持
同一段代碼為了能夠在各種環(huán)境,都能取到頂層對(duì)象,現(xiàn)在一般是使用this變量,但是有局限性:
a. 全局環(huán)境中,this會(huì)返回頂層對(duì)象。但是,Node 模塊和 ES6 模塊中,this返回的是當(dāng)前模塊
b. 函數(shù)里面的this,如果函數(shù)不是作為對(duì)象的方法運(yùn)行,而是單純作為函數(shù)運(yùn)行,this會(huì)指向頂層對(duì)象。但是,嚴(yán)格模式下,這時(shí)this會(huì)返回undefined
c. 不管是嚴(yán)格模式,還是普通模式,new Function('return this')(),總是會(huì)返回全局對(duì)象。但是,如果瀏覽器用了 CSP(Content Security Policy,內(nèi)容安全策略),那么eval、new Function這些方法都可能無(wú)法使用