成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

前端進(jìn)擊的巨人(三):從作用域走進(jìn)閉包

Vicky / 584人閱讀

摘要:進(jìn)擊的巨人第三篇,本篇就作用域作用域鏈閉包等知識(shí)點(diǎn),一一擊破。在此我們遵照的方式,暫且稱是閉包。所以,一名合格的前端,除了會(huì)用閉包,還要正確的解除閉包引用。

進(jìn)擊的巨人第三篇,本篇就作用域、作用域鏈、閉包等知識(shí)點(diǎn),一一擊破。

作用域
作用域:負(fù)責(zé)收集并維護(hù)由所有聲明的標(biāo)識(shí)符(變量)組成的一系列查詢,并實(shí)施一套非常嚴(yán)格的規(guī)則,確定當(dāng)前執(zhí)行的代碼對(duì)這些標(biāo)識(shí)符(變量)的訪問(wèn)權(quán)限

——《你不知道的JavaScript上卷》

作用域有點(diǎn)像圈地盤,大家劃好區(qū)域,然后各自經(jīng)營(yíng)管理,井水不犯河水。

var globaValue = "我是全局作用域";
function foo() {
    var fooValue = "我是foo作用域";
    function bar() {
        var barValue = "我是bar作用域";
    }
}

function other() {
    var otherValue = "我是other作用域";
}

作用域的變量聲明

不同作用域下命名相同的變量不會(huì)發(fā)生沖突,"就近原則"選取。

var name = "任何名字";
function getName() {
    var name = "以樂(lè)之名";
    console.log(name);    // "以樂(lè)之名"
}
console.log(name);        // "任何名字"
作用域的類型

執(zhí)行上下文環(huán)境有:全局、函數(shù)、eval。那么作用域也有三種,ES6新增了塊級(jí)作用域。

全局作用域

函數(shù)作用域

eval作用域(不推薦使用eval,暫時(shí)忽略)

塊級(jí)作用域(ES6新增)

全局作用域

JavaScript中全局環(huán)境只有一個(gè),對(duì)應(yīng)的全局作用域也只有一個(gè)。局部環(huán)境沒(méi)有使用var/let/const聲明的變量默認(rèn)都會(huì)成為全局變量。

function foo() {
    a = 10;
};
foo();
console.log(a);    // 10 變?nèi)肿兞浚ㄒ馔庥纱税l(fā)生)
函數(shù)作用域

ES6之前,想要實(shí)現(xiàn)局部作用域的方式,都是是通過(guò)在函數(shù)中聲明變量來(lái)實(shí)現(xiàn)的,所以也稱函數(shù)作用域,支持嵌套多個(gè)。

var a = 20;
function foo() {
    var a = 10;
    console.log(a);    // 10;
}
foo();

函數(shù)中聲明變量時(shí),建議在函數(shù)起始部分聲明所有變量,方便查看,切記要用var/let/const聲明,防止手抖將局部變量變成全局變量。

function getClient() {
    var name;
    var phone;
    var sex;
}
塊級(jí)作用域

我們先來(lái)理解什么是塊?所謂塊,其實(shí)就是被大括號(hào){}包裹的代碼部分。

if (true) {
    // 這里就是塊了,也可稱代碼塊
}

ES6前沒(méi)有塊級(jí)作用域的概念,所以{}中并沒(méi)有自己的作用域。如果我們想在ES5的環(huán)境下構(gòu)建塊級(jí)作用域,一般都是是通過(guò)立即執(zhí)行函數(shù)來(lái)實(shí)現(xiàn)的。

var name = "任何名字";
(function(window) {
    var name = "以樂(lè)之名";
    console.log(name);    // "以樂(lè)之名"
}(window));
console.log(name);        // "任何名字"

ES5借助函數(shù)作用域來(lái)實(shí)現(xiàn)塊級(jí)作用域的方式,會(huì)讓我們的代碼充斥大量的立即執(zhí)行函數(shù)(IIFE),不便于代碼的閱讀。好的代碼的就跟好的文章一樣,讓閱讀者讀起來(lái)舒暢明了。

為此,ES6新增塊級(jí)作用域的概念,使用let/const聲明變量的方式,即可將其作用域指定在代碼塊中,跟函數(shù)作用域一樣支持嵌套。

let i = 0;
for (let i = 0; i < 10; i++){
????console.log(i);
}
i;    // 0

let/const不允許變量提升,必須"先聲明再使用"。這種限制,稱為"暫時(shí)性死區(qū)"。這也能讓我們?cè)诖a編寫(xiě)階段變得更加規(guī)范化,執(zhí)行跟書(shū)寫(xiě)順序保持一致。

作用域鏈(變量查詢規(guī)則)

變量被作用域所管理,那么變量在作用域中的查找規(guī)則,就是所謂的作用域鏈。

作用域鏈的用途,是保證對(duì)執(zhí)行環(huán)境有權(quán)訪問(wèn)的所有變量和函數(shù)的有序訪問(wèn)

——《JavaScript高級(jí)程序設(shè)計(jì)》

"在當(dāng)前執(zhí)行環(huán)境開(kāi)始查找使用到的變量,如果找到,則返回其值。如果找不到,會(huì)逐層往上級(jí)(父作用域)查找,直到全局作用域"。

var money = 100;
function foo() {
    function bar() {
        console.log(money);
    }
    bar();
}
foo();

自由變量

變量我們見(jiàn)的不少,但"自由變量"聽(tīng)著是不是挺唬人的。其實(shí)對(duì)它,我們并不陌生。

"自由變量:當(dāng)前執(zhí)行環(huán)境使用到,但并未在當(dāng)前執(zhí)行環(huán)境聲明的變量(函數(shù)參數(shù)arguments排除)"

函數(shù)調(diào)用時(shí),進(jìn)入執(zhí)行上下文創(chuàng)建階段,會(huì)對(duì)arguments進(jìn)行隱式的變量聲明。

var outer = "我是外面變量";
function foo() {
    var inner = "我是里面變量,不是自由變量";
    console.log(outer);   
    // 這里用到了outer,但outer并不在函數(shù)foo中聲明,所以outer就是foo中的自由變量
}

"自由變量的作用域由詞法環(huán)境決定,也就是它的作用域在代碼書(shū)寫(xiě)階段就已經(jīng)確定了,而不是在代碼執(zhí)行階段確定。"

"自由變量的值是在代碼執(zhí)行時(shí)確定的,變量變量變量,值肯定要變,所以自由變量的值只有在程序運(yùn)行階段才能確定。"

閉包

開(kāi)篇第一文我們就執(zhí)行環(huán)境,執(zhí)行棧做出了詳解,有所遺忘的可再溫習(xí)。執(zhí)行棧是我們理解閉包原理基礎(chǔ)中的基礎(chǔ)。

函數(shù)調(diào)用棧過(guò)程的圖再曬出來(lái),順便溫習(xí)下。

function foo () {
    function bar () {
        return "I am bar";
    }
    return bar();
}
foo();

函數(shù)調(diào)用時(shí)入棧,調(diào)用結(jié)束出棧。執(zhí)行函數(shù)時(shí),會(huì)創(chuàng)建一個(gè)變量對(duì)象去存儲(chǔ)函數(shù)中的變量,方法,參數(shù)arguments等,結(jié)束調(diào)用時(shí),該變量對(duì)象就會(huì)被銷毀。(理想的情況下,不理想的情況就是出現(xiàn)"閉包"調(diào)用了)。

什么是閉包?
閉包是指有權(quán)訪問(wèn)另外一個(gè)函數(shù)作用域的變量的函數(shù)。

——《JavaScript高級(jí)程序設(shè)計(jì)》

閉包是指那些能夠訪問(wèn)自由變量的函數(shù)。

——MDN

閉包的特點(diǎn)首先是函數(shù),其次是它可以訪問(wèn)到父級(jí)作用域的變量對(duì)象,即使父級(jí)函數(shù)完成調(diào)用后"理應(yīng)出棧銷毀"。

判定閉包出現(xiàn)

函數(shù)作為參數(shù)傳遞

函數(shù)作為返回值傳遞

function foo() {
    var fooVal = "2019";
    var bar = function() {
        console.log(fooVal);    // bar中使用到了自由變量fooVal
    }
    return bar;                 // 函數(shù)作為參數(shù)返回
}

var getValue = foo();
getValue();                     // 2019

對(duì)函數(shù)中誰(shuí)是閉包,各文檔解釋不一。在此我們遵照Chrome的方式,暫且稱foo是閉包。

因?yàn)樽饔糜蚝妥饔糜蜴溡?guī)則的限定,子環(huán)境的自由變量只能逐層向上到父環(huán)境查找。

但是通過(guò)閉包,我們?cè)谕獠凯h(huán)境也可以獲取到變量fooVal,雖然foo()函數(shù)執(zhí)行完成了,但它并沒(méi)從函數(shù)調(diào)用棧中銷毀,其變量對(duì)象存儲(chǔ)仍然能被訪問(wèn)到。

實(shí)際執(zhí)行過(guò)程請(qǐng)看圖:

把上述代碼改以下,接著看:

function foo() {
 var fooVal = "2019";
 var bar = function() {
 console.log(fooVal);     // bar中使用到了自由變量fooVal
 }
 return bar;              // 函數(shù)作為參數(shù)返回
}
var getValue = foo();
var fooVal = "2018";      // 這里的fooVal是全局作用域的變量
getValue();               // 2019

答案與結(jié)果不符的小伙伴要回頭理解下自由變量了。"自由變量的作用域在代碼書(shū)寫(xiě)時(shí)(函數(shù)創(chuàng)建時(shí))就確定了",所以函數(shù)中getValue()使用的fooValfoo的作用域下,而不是在全局作用域下。

答對(duì)的小伙伴們?cè)賮?lái)一道題,加深你的記憶

function fn() {
    var max = 10;
    function bar(x) {
        if (x > max) {    
            console.log(x)
        }
    }
    return bar;
}
var f1 = fn();
var max = 100;

f1(20);    // 輸出20

題目解析:max作為函數(shù)bar中的自由變量,它的作用域在函數(shù)bar創(chuàng)建的時(shí)候就確定了,就是函數(shù)fn中的max,所以它的作用域鏈查找到fn中已經(jīng)結(jié)束并返回了,不會(huì)再向上找到全局作用域。

注意:棧中存儲(chǔ)的不只是閉包中使用到的自由變量,而是父級(jí)函數(shù)的整個(gè)變量對(duì)象(父級(jí)函數(shù)作用域中聲明的方法,變量,參數(shù)等)

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

上文中已經(jīng)闡述了閉包的特點(diǎn),就是能夠讓我們跨作用域取值(不局限于父子作用域)。列舉兩個(gè)實(shí)際開(kāi)發(fā)中常用的栗子:

封裝回調(diào)保存作用域

for(var i = 1; i < 5; i++) {
    setTimeout((function(i){
       return function() {
           console.log(i);        
       } 
    })(i), i * 1000)
}
// 原理:通過(guò)自執(zhí)行函數(shù)傳參i,然后返回一個(gè)函數(shù)(閉包)中使用i,使父函數(shù)的變量對(duì)象一直存在

私有變量和方法實(shí)現(xiàn)模塊化

var makePeople = function () {
    var _name = "以樂(lè)之名";
    return {
        getName: function () {
            console.log(_name);
        },
        setName: function (name) {
            if (name != "Hello world") {
                _name = name;
            }
        }
    }
}

var me = makePeople();
me.getName();                   // "以樂(lè)之名"
me.setName("KenTsang");         
me.getName();                   // "KenTsang"

// 原理:私有變量_name沒(méi)有對(duì)外訪問(wèn)權(quán)限,但通過(guò)閉包使其一直保留在內(nèi)存中,可以被外部調(diào)用

閉包的應(yīng)用場(chǎng)景還有很多,具體實(shí)際情況還需具體分析。

閉包造成的內(nèi)存泄露

閉包的使用,破壞了函數(shù)的出棧過(guò)程。解釋執(zhí)行棧的時(shí)候,講到同個(gè)函數(shù)即使調(diào)用自身,創(chuàng)建的變量對(duì)象也并非同一個(gè),其內(nèi)存存儲(chǔ)是各自獨(dú)立的。

棧中只入不出,函數(shù)的變量對(duì)象沒(méi)有被有效回收,就會(huì)造成瀏覽器內(nèi)存占用逐步增加,內(nèi)存占用過(guò)高的情況下,就會(huì)導(dǎo)致頁(yè)面卡頓,甚至瀏覽器崩潰。這就是我們常說(shuō)閉包過(guò)度使用造成的"內(nèi)存泄露"。

所以,一名合格的前端,除了會(huì)用閉包,還要正確的解除閉包引用。
垃圾回收機(jī)制講解時(shí),通過(guò)設(shè)置變量值為null時(shí)可以解除變量的引用,以便下一次垃圾回收銷毀它。

function foo() {
 var fooVal = "2019";
 var bar = function() {
 console.log(fooVal);     
 }
 return bar;              
}
var getValue = foo();
var fooVal = "2018";     
getValue();
getValue = null;         // 解除引用,下一次垃圾回收就會(huì)回收了
寫(xiě)在結(jié)尾

閉包算是前端初學(xué)者的一個(gè)難點(diǎn),能解釋清楚并不容易,涉及到作用域,執(zhí)行上下文環(huán)境、變量對(duì)象等等。

零散知識(shí)的內(nèi)聚匯總,正是是系列更文的初衷所在。

知識(shí)不是小段子,聽(tīng)完笑過(guò)就忘,唯有形成體系,達(dá)成閉環(huán),才能深植入記憶中。

參考文檔:

深入理解javascript原型和閉包

本文首發(fā)Github,期待Star!
https://github.com/ZengLingYong/blog

作者:以樂(lè)之名
本文原創(chuàng),有不當(dāng)?shù)牡胤綒g迎指出。轉(zhuǎn)載請(qǐng)指明出處。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/101184.html

相關(guān)文章

  • 前端進(jìn)擊巨人(四):略知函數(shù)式編程

    摘要:自執(zhí)行函數(shù)閉包實(shí)現(xiàn)模塊化以樂(lè)之名程序員產(chǎn)品經(jīng)理對(duì)作用域,以及閉包知識(shí)還沒(méi)掌握的小伙伴,可回閱前端進(jìn)擊的巨人三從作用域走進(jìn)閉包。參考文檔利用閉包實(shí)現(xiàn)模塊化翻譯淺談中的高階函數(shù)系列更文請(qǐng)關(guān)注專欄前端進(jìn)擊的巨人,不斷更新中。。。 系列更文前三篇文章,圍繞了一個(gè)重要的知識(shí)點(diǎn):函數(shù)。函數(shù)調(diào)用棧、函數(shù)執(zhí)行上下文、函數(shù)作用域到閉包??梢?jiàn)不理解函數(shù)式編程,代碼都擼不好。 showImg(https:/...

    omgdog 評(píng)論0 收藏0
  • 前端進(jìn)擊巨人(五):學(xué)會(huì)函數(shù)柯里化(curry)

    摘要:函數(shù)柯里化是把支持多個(gè)參數(shù)的函數(shù)變成接收單一參數(shù)的函數(shù),并返回一個(gè)函數(shù)能接收處理剩余參數(shù),而反柯里化就是把參數(shù)全部釋放出來(lái)。但在一些復(fù)雜的業(yè)務(wù)邏輯封裝中,函數(shù)柯里化能夠?yàn)槲覀兲峁└玫膽?yīng)對(duì)方案,讓我們的函數(shù)更具自由度和靈活性。 showImg(https://segmentfault.com/img/bVburN1?w=800&h=600); 柯里化(Curring, 以邏輯學(xué)家Has...

    chengtao1633 評(píng)論0 收藏0
  • 前端進(jìn)擊巨人(一):執(zhí)行上下文與執(zhí)行棧,變量對(duì)象

    摘要:在中,通過(guò)棧的存取方式來(lái)管理執(zhí)行上下文,我們可稱其為執(zhí)行棧,或函數(shù)調(diào)用棧。而處于棧頂?shù)氖钱?dāng)前正在執(zhí)行函數(shù)的執(zhí)行上下文,當(dāng)函數(shù)調(diào)用完成后,它就會(huì)從棧頂被推出理想的情況下,閉包會(huì)阻止該操作,閉包后續(xù)文章深入詳解。 寫(xiě)在開(kāi)篇 已經(jīng)不敢自稱前端小白,曾經(jīng)吹過(guò)的牛逼總要一點(diǎn)點(diǎn)去實(shí)現(xiàn)。 正如前領(lǐng)導(dǎo)說(shuō)的,自己喝酒吹過(guò)的牛皮,跪著都得含著淚去實(shí)現(xiàn)。 那么沒(méi)有年終完美總結(jié),來(lái)個(gè)新年莽撞開(kāi)始可好。 進(jìn)擊巨...

    _Suqin 評(píng)論0 收藏0
  • 前端進(jìn)擊巨人(六):知否知否,須知this

    摘要:有關(guān)函數(shù)柯里化的詳解,請(qǐng)回閱前端進(jìn)擊的巨人五學(xué)會(huì)函數(shù)柯里化。構(gòu)造函數(shù)中的通過(guò)操作符可以實(shí)現(xiàn)對(duì)函數(shù)的構(gòu)造調(diào)用。在了解構(gòu)造函數(shù)中的前,有必要先了解下實(shí)例化對(duì)象的過(guò)程。 showImg(https://segmentfault.com/img/bVburMp?w=800&h=600); 常見(jiàn)this的誤解 指向函數(shù)自身(源于this英文意思的誤解) 指向函數(shù)的詞法作用域(部分情況) th...

    Andrman 評(píng)論0 收藏0
  • 前端進(jìn)擊巨人(七):走進(jìn)面向?qū)ο?,原型與原型鏈,繼承方式

    摘要:除了以上介紹的幾種對(duì)象創(chuàng)建方式,此外還有寄生構(gòu)造函數(shù)模式穩(wěn)妥構(gòu)造函數(shù)模式。 showImg(https://segmentfault.com/img/remote/1460000018196128); 面向?qū)ο?是以 對(duì)象 為中心的編程思想,它的思維方式是構(gòu)造。 面向?qū)ο?編程的三大特點(diǎn):封裝、繼承、多態(tài): 封裝:屬性方法的抽象 繼承:一個(gè)類繼承(復(fù)制)另一個(gè)類的屬性/方法 多態(tài):方...

    wums 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<