一、LHS和RHS查詢
當(dāng)變量出現(xiàn)在賦值操作的左側(cè)時(shí),進(jìn)行LSH查詢;當(dāng)變量出現(xiàn)在賦值操作的右側(cè),即非左側(cè)時(shí),進(jìn)行RSH查詢。
或者說(shuō)
LSH查詢是在找變量容器的本身,從而對(duì)其賦值,RHS是查找某個(gè)變量的值
現(xiàn)在我們就要舉很多例子來(lái)看看
console.log(b)
a = 2
① console.log(...)需要一個(gè)引用才能執(zhí)行,查是否有console,一次RSH,再?gòu)腸onsole對(duì)象中查是否有l(wèi)og方法,一次RHS。要打印出b,那就要查找b的值,這是一個(gè)RSH;為a賦值的時(shí)候,則要先找到a這個(gè)容器然后再賦值操作,這是一個(gè)LSH。
function foo(a) {
console.log(a);
}
foo(1)
② 對(duì)foo調(diào)用,就要去找foo的值,一次RSH查詢;
實(shí)參到形參,a=2的操作,一次LSH查詢;
打印a的時(shí)候,查詢?nèi)纰僦兴觯?/p>
function foo(a) {
var b = a;
return a + b;
}
var c = foo(1)
③ 查詢foo,一次RHS
為c賦值操作c = ...,一次LHS
傳參賦值操作a=...,一次LHS
查詢a的值,一次RHS
為b進(jìn)行賦值b=...,一次LHS
查詢a和b的值,兩次RHS
二、作用域
作用域是根據(jù)名稱查找變量的一套規(guī)則,從當(dāng)前的范圍逐層往上查找,如下圖所示,LSH和RSH引用都會(huì)在當(dāng)前作用域往上查。

RHS在任何相關(guān)的作用域都取不到變量的值時(shí),則會(huì)拋出ReferenceError異常。如果查到了這個(gè)變量,但是你對(duì)他進(jìn)行了不合理的操作,比如:
① 對(duì)非函數(shù)進(jìn)行調(diào)用
② 引用null或者undefined的屬性
則會(huì)拋出TypeError異常
LHS在頂層的作用域都找不到變量的話,如果是非嚴(yán)格模式,則會(huì)幫我們默認(rèn)創(chuàng)建一個(gè),默認(rèn)值為undefined,如果是嚴(yán)格模式,那就不好意思了,一樣會(huì)拋出ReferenceError異常。
三、提升
(1)變量的提升
a = 1;
var a;
console.log(a) >>> 1
console.log(b) >>> undefined
var b = 2;
var a的聲明被提前了,所以能成功賦值,也能成功打印出來(lái)
var b = 2可以看成是①var b; ② b =2;第一步聲明被提前了,第二步則留在原地,等到執(zhí)行打印時(shí)候,b還未被賦值,所以是undefined。
總結(jié):聲明會(huì)提升,賦值或者其他運(yùn)行邏輯會(huì)留在原地
(2)函數(shù)的提升
foo();
function foo() {
console.log( a );
var a = 2;
}
等價(jià)于:
function foo() {
var a;
console.log( a ); >>> undefined
a = 2;
}
foo();
foo是一個(gè)函數(shù)的聲明,則會(huì)將整個(gè)函數(shù)提升
但是如果是一個(gè)表達(dá)式
foo(); // TypeError!
bar(); // referenceError
var foo = function bar() {
// ...
};
因?yàn)?code>var foo會(huì)被先執(zhí)行,則foo未定義,而被當(dāng)作函數(shù)調(diào)用,則會(huì)出現(xiàn)TypeError異常,而且此時(shí)bar在賦值給foo之前,也不能在之前使用。
等價(jià)于:
var foo;
foo(); // TypeError
bar(); // ReferenceError
foo = function() {
var bar = ...
// ...
}
舉個(gè)例子:
foo(); // ->>> 1
var foo;
function foo() {
console.log( 1 );
}
foo = function() {
console.log( 2 );
};
foo(); // ->>>3
function foo() {
console.log( 1 );
}
var foo = function() {
console.log( 2 );
};
function foo() {
console.log( 3 );
}
第一段代碼:對(duì) foo函數(shù)的定義會(huì)被提前聲明,而后面的var foo 重復(fù)定義則會(huì)被忽略,所以打印的是1
第二段代碼:同樣是函數(shù)提升,但是后面定義的會(huì)覆蓋前面的,所以是打印出3
四、作用域閉包
函數(shù)與對(duì)其狀態(tài)即詞法環(huán)境(lexical environment)的引用共同構(gòu)成閉包(closure)。也就是說(shuō),閉包可以讓你從內(nèi)部函數(shù)訪問(wèn)外部函數(shù)作用域。在JavaScript,函數(shù)在每次創(chuàng)建時(shí)生成閉包。
來(lái)自MDN文檔
那我們就來(lái)看看:
① 變量間接傳遞
function foo() {
var a = 2;
function bar() {
console.log( a );
}
return bar;
}
var baz = foo();
baz(); // >>> 2
首先調(diào)用了foo,可以看到這個(gè)函數(shù)的返回值也是一個(gè)函數(shù)bar,bar函數(shù)打印了foo作用域內(nèi)的a的值。調(diào)用baz時(shí)候,可以正常打印出a的值。這樣就間接訪問(wèn)到a了。
② 函數(shù)間接傳遞
var fn;
function foo() {
var a = 2;
function baz() {
console.log( a );
}
fn = baz;
}
function bar() {
fn();
}
foo();
bar(); // 2
foo被調(diào)用時(shí),給fn全局變量賦值了,是名為baz的foo內(nèi)部函數(shù)。調(diào)用bar,間接調(diào)用了fn,由于fn又指向了baz函數(shù),間接調(diào)用了baz,而他又能訪問(wèn)到a,即可成功打印。
可能引發(fā)的問(wèn)題:
-
引用變量問(wèn)題
我們用得最多的,當(dāng)然是for循環(huán)了,那么請(qǐng)看這個(gè)經(jīng)典的例子
function test() {
var result = [];
for (var i = 0; i < 10; i++) {
result[i] = function () {
console.info(i)
}
}
return result
}
test()[0]() // >>> 10
result是一個(gè)包含十個(gè)函數(shù)的數(shù)組,按照預(yù)要分別打印0-9,但是我們?nèi)〉谝粋€(gè)函數(shù)執(zhí)行,結(jié)果卻是10。那是因?yàn)槊總€(gè)result里面的閉包函數(shù),訪問(wèn)的i都是test函數(shù)作用域內(nèi)的i,循環(huán)結(jié)束后,a=10,所以每個(gè)閉包函數(shù)都是訪問(wèn)到a=10時(shí)候的值。
解決方法:
function test () {
var result = []
for (var i = 0; i < 10; i++) {
result[i] = (function (j) {
return function () {
console.log(j)
}
}(i))
}
return result
},
this.test()[0]() // >>> 0
我們可以把i的值賦值給內(nèi)層的j,這樣打印的j就是每個(gè)函數(shù)所形成閉包中自己的j,就不會(huì)造成指向同一個(gè)變量的問(wèn)題。
this指向問(wèn)題
test () {
var people = {
id: 1,
name: '小飛',
age: 18,
getAge: function () {
return function () {
console.log(this.age)
}
}
}
people.getAge()() // TypeError: Cannot read property 'age' of undefined
},
當(dāng)使用people調(diào)用getAge函數(shù)時(shí),返回的是打印閉包函數(shù)下的age,而此時(shí)是在全局作用域window下調(diào)用的,沒(méi)有age這個(gè)變量,所以會(huì)找不到。
解決方法:
test () {
var people = {
id: 1,
name: '小飛',
age: 18,
getAge: function () {
return (function (that) { // 閉包函數(shù)
console.log(that.age)
})(this)
}
}
people.getAge() // 18
},
可以在getAge函數(shù)內(nèi)將this指向people作用域,傳遞給閉包函數(shù),給予作用域權(quán)限訪問(wèn)age
③ 內(nèi)存泄漏問(wèn)題
由于閉包會(huì)攜帶包含它的函數(shù)的作用域,因?yàn)闀?huì)比其他函數(shù)占用更多內(nèi)容,過(guò)度使用閉包,會(huì)導(dǎo)致內(nèi)存占用過(guò)多。 內(nèi)存泄漏是指,一塊被分配的內(nèi)存既不能使用,也不能回收,影響程序性能。
JS對(duì)象和DOM對(duì)象的引用造成的問(wèn)題:
function test() {
var e = document.getElementByID("container");
e.onclick = function() {
console.log(e.name)
}
}
先創(chuàng)建了DOM對(duì)象,為該對(duì)象設(shè)置點(diǎn)擊事件,引用匿名閉包函數(shù),該函數(shù)都可以訪問(wèn)test作用域內(nèi)的所有屬性,產(chǎn)生了一種關(guān)聯(lián),但是執(zhí)行完之后,由于這種關(guān)聯(lián),DOM對(duì)象無(wú)法被回收,形成了內(nèi)存占用
解決方法:
function test() {
var e = document.getElementByID("container");
var name = e.name
e.onclick = function() {
console.log(name)
}
e = null
}
手動(dòng)釋放DOM對(duì)象,保留有用數(shù)據(jù)
參考文章:
徹底搞懂JS閉包各種坑