作用域和閉包

詞法作用域和動態(tài)作用域

作用域:

  1. 作用域是指程序源代碼中定義變量的區(qū)域;作用域規(guī)定了如何查找變量,也就是確定當(dāng)前執(zhí)行代碼對變量的訪問權(quán)限窖式;
  2. JavaScript 采用詞法作用域(lexical scoping)港柜,也就是靜態(tài)作用域。

詞法作用域
函數(shù)的作用域在函數(shù)定義的時候就決定了胳赌。JavaScript 采用的是詞法作用域牢撼。

動態(tài)作用域
函數(shù)的作用域是在函數(shù)調(diào)用的時候才決定
例子:

var name ="logic"
function getName(){
return name;
}
function getNameWrap(){
var name = 'wind'
return getName()
}
console.log(getNameWrap()) // 輸出 logic 。因?yàn)?JavaScript 是詞法作用域疑苫,在函數(shù)定義時就確定了變量的訪問熏版。


作用域鏈

定義:當(dāng)查找變量的時候,會先從當(dāng)前上下文的變量查找捍掺,如果沒有找到撼短,就從父級(詞法層面的父級)執(zhí)行上下文的變量對象查找,一直找到全局上下文的變量對象挺勿,也就是全局對象曲横。由多個執(zhí)行上下文的變量對象構(gòu)成的鏈,就是作用域鏈不瓶。
作用:保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問

js執(zhí)行上下文和執(zhí)行上下文棧

執(zhí)行上下文
概念:當(dāng) JavaScript 代碼執(zhí)行一段可執(zhí)行代碼(executable code)時禾嫉,會創(chuàng)建對應(yīng)的執(zhí)行上下文(execution context)
執(zhí)行上下有三個重要屬性(也就是前面 3 題所說的內(nèi)容) :變量對象;作用域鏈蚊丐;this
執(zhí)行上下文棧
每個函數(shù)都會創(chuàng)建執(zhí)行上下文熙参,執(zhí)行上下文棧(Execution context stack,ECS)就是 JavaScript 引擎創(chuàng)建出來管理執(zhí)行上下文的
執(zhí)行上下文棧是有全局上下文初始化麦备,由于執(zhí)行代碼首先是全局代碼孽椰。全局上下文永遠(yuǎn)在執(zhí)行上下文棧中的最底下,只有等程序關(guān)閉才釋放凛篙。

函數(shù)聲明和函數(shù)表達(dá)式的區(qū)別


f1: 聲明式創(chuàng)建的函數(shù) f1 可以在 f1 定義之前就進(jìn)行調(diào)用弄屡;
f2: 函數(shù)表達(dá)式創(chuàng)建的函數(shù) f2 不能在 f2 被賦值之前進(jìn)行調(diào)用
出現(xiàn)這個陷阱的本質(zhì)原因體現(xiàn)在這兩種類型在Javascript function hoisting(函數(shù)提升)和運(yùn)行時機(jī)(解析時/運(yùn)行時)上的差異。
函數(shù)聲明JS解析時進(jìn)行函數(shù)提升鞋诗,因此在同一個作用域內(nèi)膀捷,不管函數(shù)聲明在哪里定義,該函數(shù)都可以進(jìn)行調(diào)用
函數(shù)表達(dá)式的值是在JS運(yùn)行時確定削彬,并且在表達(dá)式賦值完成后全庸,該函數(shù)才能調(diào)用。

this 幾種不同場景的取值

顯示綁定
call融痛,apply壶笼,bind可以顯示的修改this的指向
隱式綁定

  1. 全局上下文:this 指向 window,嚴(yán)格模式下為 undefined
  2. 直接調(diào)用函數(shù):this 指向 window,嚴(yán)格模式下為 undefined
  3. 作為對象的方法調(diào)用:那個對象調(diào)用,this指向那個對象雁刷;obj.foo()覆劈。 this 指向?qū)ο?obj
  4. DOM 事件的綁定:onclick和addEventerListener中 this 默認(rèn)指向綁定事件的元素;
    setTimeout的函數(shù)內(nèi)this是window(非嚴(yán)格模式下)
  5. new 構(gòu)造函數(shù)綁定:構(gòu)造函數(shù)中的 this 指向?qū)嵗龑ο?/li>
  6. 箭頭函數(shù):
    箭頭函數(shù)沒有 this, 因此也不能綁定
    在箭頭函數(shù)里的 this 會指向 外層的非箭頭函數(shù)的 this。

閉包

當(dāng)函數(shù)可以記住并訪問所在的詞法作用域時责语,就產(chǎn)生了閉包炮障,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行(通俗一點(diǎn): 即使創(chuàng)建它的上下文已經(jīng)銷毀,它仍然存在(比如坤候,內(nèi)部函數(shù)從父函數(shù)中返回) 在代碼中引用了自由變量)

通過例子來了解閉包

例子1:

function fn1() {
    var name = 'iceman';
    function fn2() {
        console.log(name);
    }
    return fn2;
}
var fn3 = fn1();
fn3();

正常來說胁赢,當(dāng)fn1函數(shù)執(zhí)行完畢之后,其作用域是會被銷毀的白筹,然后垃圾回收器會釋放那段內(nèi)存空間智末。而閉包卻很神奇的將fn1的作用域存活了下來,fn2的詞法作用域能訪問fn1的作用域徒河。fn2依然持有該作用域的引用系馆,這個引用就是閉包。
總結(jié):某個函數(shù)在定義時的詞法作用域之外的地方被調(diào)用顽照,閉包可以使該函數(shù)極限訪問定義時的詞法作用域

例子2:

function waitSomeTime(msg, time) {
    setTimeout(function () {
        console.log(msg)
    }, time);
}
waitSomeTime('hello', 1000);

定時器中有一個匿名函數(shù)它呀,該匿名函數(shù)就有涵蓋waitSomeTime函數(shù)作用域的閉包,因此當(dāng)1秒之后棒厘,該匿名函數(shù)能輸出msg

例子3:

for (var i = 1; i <= 10; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000);
}
// 輸出了 10個 11

i是聲明在全局作用中的下隧,定時器中的匿名函數(shù)也是執(zhí)行在全局作用域中,那當(dāng)然是每次都輸出11了淆院。

解決方案:我們可以讓i在每次迭代的時候,都產(chǎn)生一個私有的作用域土辩,在這個私有的作用域中保存當(dāng)前i的值。

for (var i = 1; i <= 10; i++) {
    (function (j) {
        setTimeout(function () {
            console.log(j);
        }, 1000);
    })(i);
}


閉包的應(yīng)用場景

https://juejin.im/post/5d7747185188254dc43a610a
https://juejin.im/post/5d50c6ef6fb9a06b1d21358d

什么是堆棧溢出拷淘?什么是內(nèi)存泄漏?那些操作會造成內(nèi)存泄漏启涯?怎么樣防止內(nèi)存泄漏贬堵?

堆棧溢出:代碼執(zhí)行前都會進(jìn)行編譯和創(chuàng)建執(zhí)行上下文结洼。而管理執(zhí)行上下文的叫做執(zhí)行上下文棧黎做。棧有個特點(diǎn)就是后進(jìn)先出。同時執(zhí)行上下文棧是有大小限制的松忍。當(dāng)執(zhí)行上下文棧大小超過限制就會產(chǎn)生棧溢出錯誤蒸殿。經(jīng)常發(fā)生在遞歸中。

內(nèi)存泄漏:JavaScript是在創(chuàng)建變量(對象,字符串等)時自動進(jìn)行了分配內(nèi)存宏所,并且在不使用它們時“自動”釋放酥艳。 釋放的過程稱為垃圾回收。不在使用楣铁,沒有釋放的內(nèi)存玖雁,稱為內(nèi)存泄漏
造成內(nèi)存泄漏的原因:閉包,意外的全局變量盖腕,循環(huán)(在兩個對象彼此引用且彼此保留時赫冬,就會產(chǎn)生一個循環(huán)),脫離 DOM 的引用
示例:

// 意外全局變量
function foo(arg) {
    bar = "this is a hidden global variable";
}

// 脫離 DOM 的引用
const button = document.getElementById('button');
document.body.removeChild(button);
// 此時溃列,仍舊存在一個全局的 #button 的引用,button 元素仍舊在內(nèi)存中劲厌,不能被 GC 回收。

// 閉包
(function (){
    let num = 1;
    return function add(a,b){
        return a+b;
    }
})()


如何處理循環(huán)的異步操作

先上一段代碼

function getMoney(){
    var money=[100,200,300]  
    for( let i=0; i<money.length; i++){
        compute.exec().then(()=>{
            console.log(money[i])
            //alert(i)
        })
    }
}
//compute.exec()這是個異步方法,在里面處理一些實(shí)際業(yè)務(wù)
//這時候打印出來的很可能就是300,300,300(因?yàn)楫惒絝or循環(huán)還沒有等異步操作返回Promise對象過來i值已經(jīng)改變)

正確處理思路
關(guān)鍵字async/await async告訴getMoney方法里面存在異步的操作听隐,await放在具體異步操作(方法)前面补鼻,意思是等待該異步返回Promise才會繼續(xù)后面的操作

async function getMoney(){
    var money=[100,200,300]  
    for( let i=0; i<money.length; i++){
        await compute.exec().then(()=>{
            console.log(money[i])
            //alert(i)
        })
    }
}

另外一種處理思路:使用遞歸
用遞歸來實(shí)現(xiàn)自我循環(huán)(具體循環(huán)在then里面,可以確保前面的compute.exec()的異步操作完成).then()是返回了Promise對象為resolve后才進(jìn)行的(可以了解一下Promise對象)

function getMoney(i) {
  var money=[100,200,300]
  compute.exec().then(() => {
    if ( i < money.length ) {  
      console.log(money[i]);
      i++;
        getMoney(i);
      }
   });
}
getMoney(0);//開始調(diào)用
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末雅任,一起剝皮案震驚了整個濱河市风范,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌沪么,老刑警劉巖硼婿,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異禽车,居然都是意外死亡寇漫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門殉摔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來州胳,“玉大人,你說我怎么就攤上這事逸月∷ㄗ玻” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵碗硬,是天一觀的道長腐缤。 經(jīng)常有香客問我,道長肛响,這世上最難降的妖魔是什么岭粤? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮特笋,結(jié)果婚禮上巾兆,老公的妹妹穿的比我還像新娘角塑。我一直安慰自己淘讥,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布窒朋。 她就那樣靜靜地躺著蝗岖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪欺劳。 梳的紋絲不亂的頭發(fā)上铅鲤,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天邢享,我揣著相機(jī)與錄音,去河邊找鬼。 笑死袜漩,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的奠货。 我是一名探鬼主播座掘,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼溢陪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了杉编?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤嘶朱,失蹤者是張志新(化名)和其女友劉穎光酣,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體财异,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宝当,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年庆揩,在試婚紗的時候發(fā)現(xiàn)自己被綠了跌穗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡锈拨,死狀恐怖奕枢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缝彬,我是刑警寧澤哺眯,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站一疯,受9級特大地震影響夺姑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜磕蒲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望兔院。 院中可真熱鬧站削,春花似錦、人聲如沸十偶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狮崩。三九已至鹿寻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間坦敌,已是汗流浹背痢法。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蘸炸,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓幻馁,卻偏偏與公主長得像越锈,于是被迫代替她去往敵國和親甘凭。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容