你真的了解JavaScript的作用域與閉包嗎
一、作用域
1.作用域總體來說就是根據(jù)名稱查找變量的一套規(guī)則。JS查找變量的方式有兩種:LHS和RHS。
LHS(left hand side)大致可以理解為給某個(gè)變量賦值,在賦值符(=)的左邊;RHS(right hand side)是指找到某個(gè)變量的源值,如在賦值符(=)的右邊。
舉個(gè)例子說明,對于 a = 10; a = b; 這兩個(gè)語句。
var a 這里就是LHS查詢,查找當(dāng)前作用域是否有a這個(gè)變量來給a進(jìn)行賦值,如果有則進(jìn)行賦值;如果沒有,在非嚴(yán)格模式下JS引擎會(huì)自動(dòng)創(chuàng)建一個(gè)變量a來進(jìn)行賦值;在嚴(yán)格模式下JS引擎會(huì)拋出ReferenceError錯(cuò)誤。
a = b;這個(gè)語句,對于b這個(gè)變量是進(jìn)行RHS查詢,查找b這個(gè)變量的值。如果成功查詢,則進(jìn)行賦值操作;如果查詢失敗,則拋出ReferenceError錯(cuò)誤。如果查詢成功,但是對這個(gè)變量的操作有問題,就會(huì)拋出TypeError錯(cuò)誤。如b只是一個(gè)普通的變量,卻當(dāng)成函數(shù)使用(b())。
2.JS是基于詞法作用域的。指在你寫代碼時(shí)將變量和函數(shù)寫在哪里決定的,而不是調(diào)用順序決定的。
舉個(gè)例子
var num = 1; function a() { console.log(num); // 1 } function b() { var num = 2; a(); } b();
當(dāng)按照順序執(zhí)行b()時(shí),在b函數(shù)作用域中定義了一個(gè)值為2的num變量,然后在b函數(shù)中調(diào)用a函數(shù),來到a函數(shù)的作用域進(jìn)行調(diào)用(而不是在b函數(shù)作用域),找到全局變量num = 1。
3.不同作用域間是互相不會(huì)影響的,重復(fù)定義變量是不會(huì)發(fā)生錯(cuò)誤的。
舉個(gè)例子
var i = 1; var j = 2; function a(){ var i = 3; var j = 4; console.log(i,j); // 3,4 } function b(){ var i = 5; var j = 6; console.log(i,j); // 5,6 } console.log(i,j);// 1,2 a(); b();
4.for循環(huán)中,var聲明的變量在全局作用域能夠訪問,并且值是退出循環(huán)的那個(gè)值;let聲明的變量會(huì)產(chǎn)生塊作用域,它將值重新綁定到循環(huán)迭代中。
for(var i = 0;i<5;i++){ console.log(i); // 0 1 2 3 4 } console.log(i); // 5
for(let i = 0;i<5;i++){ console.log(i); // 0 1 2 3 4 } console.log(i); // ReferenceError
5.每個(gè)作用域都會(huì)發(fā)生提升操作;函數(shù)聲明會(huì)發(fā)生提升,但是函數(shù)表達(dá)式不會(huì)被提升;
b() // TypeErrorn() // ReferenceError,因?yàn)椴粫?huì)變量提升// 函數(shù)聲明function a(){ console.log(num); // undefined var num = 1;}// 函數(shù)表達(dá)式var b = function n(){ console.log('b');}
6.函數(shù)聲明首先被提升,變量聲明在后;
函數(shù)首先被提升可以認(rèn)為是函數(shù)的優(yōu)先級高于變量,重復(fù)聲明的變量會(huì)被函數(shù)替代。但是其他函數(shù)聲明可以覆蓋之前的函數(shù)聲明。
b() // TypeError n() // ReferenceError,因?yàn)椴粫?huì)變量提升 // 函數(shù)聲明 function a(){ console.log(num); // undefined var num = 1; } // 函數(shù)表達(dá)式 var b = function n(){ console.log('b'); }
二、閉包
1.閉包是基于詞法作用域書寫代碼時(shí)的必然結(jié)果。
不論函數(shù)在哪里執(zhí)行,函數(shù)都會(huì)在代碼書寫的位置(即函數(shù)本身在哪)來進(jìn)行作用域鏈的查找。當(dāng)函數(shù)進(jìn)行作用域查找時(shí),就形成了閉包。
也可以說是當(dāng)函數(shù)被當(dāng)作值進(jìn)行傳遞的時(shí)候(如參數(shù)傳遞、返回值等),就會(huì)形成閉包。
舉個(gè)例子
function foo() { var a = 2; function bar() { console.log(a); } return bar; } var a = 3; var baz = foo(); baz(); // 2
當(dāng)foo函數(shù)執(zhí)行時(shí),會(huì)返回bar函數(shù),當(dāng)bar函數(shù)在全局作用域執(zhí)行時(shí),會(huì)沿著bar函數(shù)定義的地方開始進(jìn)行RHS查找a變量的值。這時(shí)就產(chǎn)生了閉包。閉包的產(chǎn)生準(zhǔn)確來說應(yīng)該是函數(shù)在函數(shù)本身所處作用域之外執(zhí)行,對于IIFE(立即執(zhí)行函數(shù))來說雖然創(chuàng)建了閉包,但是卻沒有真正使用閉包,因?yàn)榱⒓磮?zhí)行函數(shù)的執(zhí)行作用域是函數(shù)定義的作用域,雖然是閉包,但是沒有使用閉包的功能。
閉包的功能:函數(shù)在所處作用域之外執(zhí)行時(shí),會(huì)沿著函數(shù)定義時(shí)的作用域鏈進(jìn)行查找。
var a = 1; // 立即執(zhí)行函數(shù) ( function(){ console.log(a); } )();
2.循環(huán)與閉包
觀察下面的例子
for(var i = 0;i<3;i++){ setTimeout(()=>{ console.log(i); },i*500) }
輸出結(jié)果為 3 3 3,并不是想象中的 0 1 2。因?yàn)樵趂or循環(huán)結(jié)束時(shí),setTimeout開始執(zhí)行,在執(zhí)行的時(shí)候進(jìn)行變量 i 的RHS查詢,但是變量 i 只能在全局作用域中查到,這是變量 i 的值是退出循環(huán)的值 i = 3;
可能與你的想象結(jié)果有些許不同,如果要輸出 0 1 2 該怎么做?
(1).使用IIFE形成閉包是否可行?
for (var i = 0; i < 3; i++) { (function () { setTimeout(() => { console.log(i); }, i*500) })() }
結(jié)果依然是 3 3 3。因?yàn)殡m然使用IIFE形成了閉包,但是在閉包中并沒有 i 的值,還會(huì)沿著作用域鏈查找到全局 i = 3;
(2).在IIFE使用一個(gè)變量臨時(shí)保存 i 值或傳參
for (var i = 0; i < 3; i++) { (function () { var j = i; setTimeout(() => { console.log(j); }, i*500) })() } // 或 for (var i = 0; i < 3; i++) { (function (i) { setTimeout(() => { console.log(i); }, i*500) })(i) }
結(jié)果為 0 1 2,第一個(gè)for循環(huán)閉包中有變量 j ,第二個(gè)for循環(huán)有形參 i ,都不會(huì)到達(dá)全局作用域。
(3).使用 let 生成塊作用域
for(let i = 0;i<3;i++){ setTimeout(()=>{ console.log(i); },i*500) }
結(jié)果為 0 1 2,let聲明的變量會(huì)包含在塊作用域中,并且作為循環(huán)變量時(shí),每一次的迭代都會(huì)聲明一次變量并將變量結(jié)果保存,當(dāng)訪問變量時(shí),由于閉包的作用,會(huì)得到每次迭代的值。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
推薦一些非常不錯(cuò)的javascript學(xué)習(xí)資源站點(diǎn)
推薦一些非常不錯(cuò)的javascript學(xué)習(xí)資源站點(diǎn)...2007-08-08細(xì)說javascript函數(shù)從函數(shù)的構(gòu)成開始
javascript函數(shù)是一個(gè)比較奇怪的東西,接觸一段時(shí)間你就會(huì)犯迷糊,我想從函數(shù)的構(gòu)成來細(xì)說函數(shù),這聽起來像是一句廢話,講任何東西當(dāng)然是從構(gòu)成去談2013-08-08關(guān)于JavaScript限制字?jǐn)?shù)的輸入框的那些事
這篇文章主要介紹了關(guān)于JavaScript限制字?jǐn)?shù)的輸入框在項(xiàng)目過程中容易遇到的各種坑的匯總,非常的詳細(xì),有需要的小伙伴可以參考下2016-08-08