1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        這一次,徹底弄懂 JavaScript 函數(shù)執(zhí)行機(jī)制

        共 7743字,需瀏覽 16分鐘

         ·

        2021-05-01 11:39

        大廠技術(shù)  堅(jiān)持周更  精選好文

        一、作用域&上下文

        1、 作用域

        作用域就是JS函數(shù)和變量的可訪問(wèn)范圍,分為全局作用域、局部作用域和塊級(jí)作用域。全局作用域是整個(gè)程序都能訪問(wèn)到的區(qū)域,web環(huán)境下為window對(duì)象,node環(huán)境下為Global對(duì)象。局部作用域也就是函數(shù)作用域,在函數(shù)內(nèi)部形成一個(gè)獨(dú)立的作用域,函數(shù)執(zhí)行結(jié)束就銷毀,函數(shù)內(nèi)部的變量只能在函數(shù)內(nèi)部訪問(wèn)。塊級(jí)作用域,使用let或const關(guān)鍵字聲明變量之后,會(huì)生成塊級(jí)作用域,聲明的變量只在這個(gè)塊中有效,并且在這個(gè)塊中l(wèi)et或const聲明的變量必須先聲明后使用。

        var a = 10// 全局變量
        if (true) {
            console.log(b) // error 必須先定義后使用
            let b = 20// 塊內(nèi)變量
            console.log(b) // 20
            console.log(a) // 10
        }
        console.log(a) // 10
        console.log(b) // error not defined
        function add (a, b{
            var c = 0// 局部變量
            console.log(c) // 0
            return a + b + c;
        }
        console.log(c) // error not defined

        當(dāng)JS引擎檢測(cè)到有塊級(jí)作用域產(chǎn)生時(shí),系統(tǒng)會(huì)生成一個(gè)暫時(shí)性死區(qū),存儲(chǔ)所有l(wèi)et或const聲明的變量名。當(dāng)訪問(wèn)暫時(shí)性死區(qū)中保存的變量時(shí),系統(tǒng)會(huì)拋出錯(cuò)誤,提示需要先聲明再使用,當(dāng)碰到變量聲明語(yǔ)句時(shí),聲明變量,并從暫時(shí)性死區(qū)中刪除該變量,后面就能正常訪問(wèn)了。

        2、上下文

        context上下文代表代碼執(zhí)行中this代表的值,JS函數(shù)中的this總是指向調(diào)用這個(gè)函數(shù)的對(duì)象;使用call,apply,bind等修改this指向的除外。

        二、函數(shù)執(zhí)行

        1. 執(zhí)行期上下文執(zhí)行期上下文是在函數(shù)執(zhí)行的時(shí)候生成的,定義了函數(shù)在執(zhí)行時(shí),函數(shù)內(nèi)部生成的代表當(dāng)前執(zhí)行函數(shù)的具體信息。產(chǎn)生執(zhí)行期上下文第一步是創(chuàng)建激活對(duì)象AO(Activation Object)將AO保存到作用域鏈的頂端設(shè)置上下文 this 的值在AO創(chuàng)建之后,在函數(shù)開(kāi)始執(zhí)行之前,需要將函數(shù)內(nèi)部可訪問(wèn)的變量在AO中進(jìn)行聲明和必要的初始化將函數(shù)內(nèi)部定義的變量以及函數(shù)參數(shù)放入AO中,初始值為undefined。將函數(shù)的實(shí)際參數(shù)賦值給AO中的變量。將函數(shù)內(nèi)部聲明的函數(shù)放入到AO中,初始值為 函數(shù)本身??匆粋€(gè)例子:
        function add (a, b{
            debugger
            var temp1 = 100;
            function validateNum (n{
                return typeof n === "number";
            }
            var validateNum = 100;
            return a + b;
        }
        console.log(add(12))

        可以看到,在函數(shù)開(kāi)始執(zhí)行時(shí),函數(shù)的實(shí)際參數(shù)會(huì)提前賦值給對(duì)應(yīng)的變量,但是函數(shù)內(nèi)部聲明的變量的值則被初始化為undefined 。

        2. 作用域鏈上面說(shuō)到,JS內(nèi)部是分為很多個(gè)作用域的,其中函數(shù)內(nèi)部能訪問(wèn)的變量有很多,那這些變量又是從哪里來(lái)的,其中包含哪些作用域里的變量呢?這個(gè)問(wèn)題需要從作用域鏈著手。在JS中,采用的是詞法作用域,在函數(shù)聲明時(shí),它的作用域就已經(jīng)確定了,不會(huì)再改變,函數(shù)的作用域保存在[[scope]]變量中,僅供JS引擎調(diào)用,我們從最簡(jiǎn)單的例子來(lái)看函數(shù)作用域包含些什么:
        function add (a: number, b: number): number {
            return a + b;
        }

        Add 函數(shù)生成的作用域包含如下:可以看到,函數(shù)的作用域[[scope]]是一個(gè)數(shù)組,里面包含一個(gè)window對(duì)象,即全局對(duì)象。如果函數(shù)不是直接在全局作用域中定義,生成的作用域又是什么樣子呢?

        function add (a, b{
            function validateNum (n{
                console.log(a, b)
                return typeof n === "number";
            }
            debugger
            if (!validateNum(a) || !validateNum(b)) {
            throw new Error('type error');
            }
            return a + b;
        }
        console.log(add(1,2))
        console.log(add(2,3))

        從上圖能看出,函數(shù)的作用域[[scope]]中包含兩個(gè)對(duì)象,一個(gè)是全局對(duì)象,一個(gè)是add函數(shù)內(nèi)部的值。由此可知,函數(shù)作用域的生成是基于函數(shù)定義環(huán)境的,它會(huì)保存定義時(shí)當(dāng)前環(huán)境的數(shù)據(jù)。經(jīng)過(guò)上面的過(guò)程,我們能夠整理出整個(gè)函數(shù)執(zhí)行的過(guò)程:可以看到validateNum函數(shù)的作用域鏈上保存了函數(shù)可以訪問(wèn)的全部變量或函數(shù),首先是自己生成的激活對(duì)象AO內(nèi)的變量,包含函數(shù)內(nèi)部定義的變量和函數(shù)以及實(shí)參變量

        二、函數(shù)執(zhí)行結(jié)束,內(nèi)存釋放

        函數(shù)執(zhí)行結(jié)束之后,函數(shù)釋放自己執(zhí)行時(shí)創(chuàng)建的激活對(duì)象AO,在一段時(shí)間之后AO對(duì)象以及內(nèi)部的變量會(huì)被當(dāng)作垃圾回收掉,釋放內(nèi)存空間。validateNum 函數(shù)執(zhí)行完之后, validateNum AO 被釋放,但是[[scope]]屬性仍然存在validateNum函數(shù)對(duì)象中。Add 函數(shù)執(zhí)行結(jié)束之后,add AO 對(duì)象被釋放,AO對(duì)象中validateNum函數(shù)也被釋放,但是add函數(shù)的仍然存在。最終內(nèi)存中的狀態(tài)是這樣的。

        三、閉包

        閉包是一塊內(nèi)存空間始終被系統(tǒng)中某個(gè)變量引用著,導(dǎo)致這塊內(nèi)存一直不會(huì)被釋放,形成一個(gè)封閉的內(nèi)存空間,尋常不可見(jiàn),只有引用它的變量可訪問(wèn)。

        正常情況下,函數(shù)執(zhí)行結(jié)束之后,所產(chǎn)生的所有變臉都會(huì)被內(nèi)存回收,但是有例外情況,就是,如果所產(chǎn)生的內(nèi)存空間仍然被其他地方的變量所引用,那么,這些空間不會(huì)被內(nèi)存回收,成為隱藏在內(nèi)存空間里的黑戶,只會(huì)被引用這片空間的變量訪問(wèn),如果這種情況存在很多,那么勢(shì)必會(huì)造成內(nèi)存不會(huì)釋放,造成內(nèi)存泄漏。例如:

        var el = document.getElementById('id');
        function add (a, b{
            function validateNum (n{
                return typeof n === "number";
            }
            el.onclick = function clickHandle ({
            console.log(a, b)
            }
            if (!validateNum(a) || !validateNum(b)) {
            throw new Error('type error');
            }
            return a + b;
        }
        console.log(add(12))

        當(dāng)add函數(shù)執(zhí)行時(shí),會(huì)定義el元素的點(diǎn)擊事件函數(shù)clickHandle,clickHandle的[[scope]]中會(huì)保存add函數(shù)產(chǎn)生的AO。clickHandle 函數(shù)會(huì)被綁定在el元素上,只要el元素存在并且綁定了clickHandle事件響應(yīng)函數(shù),那么clickHandle函數(shù)也會(huì)一直存在,導(dǎo)致clickHandle函數(shù)對(duì)象中[[scope]]中保存的add函數(shù)的AO對(duì)象也會(huì)一直存在,不會(huì)被內(nèi)存釋放,就像有一個(gè)小黑屋,把a(bǔ)dd函數(shù)的AO對(duì)象關(guān)起來(lái)了,垃圾回收機(jī)制會(huì)忽略這塊內(nèi)存。閉包本質(zhì)上是保存了其他函數(shù)執(zhí)行時(shí)產(chǎn)生的激活對(duì)象AO。

        四、后續(xù)

        當(dāng)函數(shù)內(nèi)部的函數(shù)不引用外部變量時(shí),不會(huì)形成閉包

        function add (a, b{
            function validateNum (n{
                return typeof n === "number";
            }
            debugger
            if (!validateNum(a) || !validateNum(b)) {
            throw new Error('type error');
            }
            return a + b;
        }
        console.log(add(12))

        生成的作用域鏈如下:可以看到,如果函數(shù)內(nèi)部生命的函數(shù)沒(méi)有使用到外部AO中的變量,那么在函數(shù)的[[scope]]作用域鏈中不會(huì)包含該AO。

        function add (a, b{
            function validateNum (n{
                console.log(a)
                return typeof n === "number";
            }
            debugger
            if (!validateNum(a) || !validateNum(b)) {
            throw new Error('type error');
            }
            return a + b;
        }
        console.log(add(12))

        執(zhí)行階段看到的作用域鏈如下:可以看到在chrome中如果出現(xiàn)閉包,那么JS引擎會(huì)根據(jù)引用到的變量,做一波優(yōu)化,只保存用到的變量,并且會(huì)把這部分變量從JS執(zhí)行棧中轉(zhuǎn)移出去,減少執(zhí)行棧內(nèi)存占用。函數(shù)內(nèi)部不會(huì)被用到的函數(shù)不會(huì)聲明,而普通變量的聲明則不受影響validateNum 函數(shù)不會(huì)被調(diào)用的情況下:validateNum 函數(shù)會(huì)被調(diào)用的情況下:

        五、react 函數(shù)式組件中的閉包

        const [value, setValue] = useState([]);

        useEffect(() => {
            notificationCenter.on(EVENT_NAME, eventListener);
            return () => {
                notificationCenter.off(EVENT_NAME, eventListener);
            };
        }, []);

        function eventListener(chatId?: string{
             console.log(value);
        }

        在事件監(jiān)聽(tīng)函數(shù)執(zhí)行過(guò)程中,發(fā)現(xiàn)無(wú)法訪問(wèn)到最新的 value 數(shù)據(jù)原因是因?yàn)樵诮M件第一次渲染時(shí),綁定了事件監(jiān)聽(tīng)函數(shù),此時(shí)聲明的函數(shù)的作用域鏈中保存了當(dāng)時(shí)的數(shù)據(jù)狀態(tài)(value)的初始值,當(dāng)頁(yè)面狀態(tài)發(fā)生變化時(shí),函數(shù)組件會(huì)重新渲染執(zhí)行,但是事件監(jiān)聽(tīng)函數(shù)仍然還是第一次生成的,[[scope]]中保存了初始的value值,所以在函數(shù)執(zhí)行過(guò)程中,從作用域鏈中訪問(wèn)到的value始終是初始值。在setTimeout以及其他延時(shí)回調(diào)中也存在類似的情況。

        針對(duì)這種情況有兩種解決辦法:

        • 第一種:類似事件監(jiān)聽(tīng)的場(chǎng)景,在useEffect中,添加需要用到的依賴,當(dāng)依賴發(fā)生變化時(shí),重新注冊(cè)監(jiān)聽(tīng)事件。
        const [value, setValue] = useState([]);

        useEffect(() => {
            notificationCenter.on(EVENT_NAME, eventListener);
            return () => {
                notificationCenter.off(EVENT_NAME, eventListener);
            };
        }, [value]);

        • 第二種:使用ref將需要使用到的變量變?yōu)橐妙愋?,?dāng)外部修改以及函數(shù)內(nèi)部訪問(wèn)的時(shí)候?qū)嶋H上是都是在訪問(wèn)同一個(gè)引用里面的屬性,都能確保拿到的是最新數(shù)據(jù)。
        const valueRef = useRef([]);

        useEffect(() => {
            notificationCenter.on(EVENT_NAME, eventListener);
            return () => {
                notificationCenter.off(EVENT_NAME, eventListener);
            };
        }, []);

        function eventListener(chatId?: string{
             console.log(valueRef.curremt);
        }
        瀏覽 49
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            午夜爱爱影院 | 国产精品1页 | 老妇女黄色片 | 人人摸人人操人人摸 | 欧美日韩国产精品 | 宝贝白浆流下来了h | 亚洲午夜久久久久久久久红桃 | 欧美成人伊人 | 成人激情视频网 | BBBBBBBBB成人免费毛片视频 |