<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 7789字,需瀏覽 16分鐘

           ·

          2021-04-26 18:27



          一、作用域&上下文

          1、 作用域

          作用域就是JS函數(shù)和變量的可訪問范圍,分為全局作用域、局部作用域和塊級作用域。全局作用域是整個程序都能訪問到的區(qū)域,web環(huán)境下為window對象,node環(huán)境下為Global對象。局部作用域也就是函數(shù)作用域,在函數(shù)內(nèi)部形成一個獨(dú)立的作用域,函數(shù)執(zhí)行結(jié)束就銷毀,函數(shù)內(nèi)部的變量只能在函數(shù)內(nèi)部訪問。塊級作用域,使用let或const關(guān)鍵字聲明變量之后,會生成塊級作用域,聲明的變量只在這個塊中有效,并且在這個塊中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引擎檢測到有塊級作用域產(chǎn)生時,系統(tǒng)會生成一個暫時性死區(qū),存儲所有l(wèi)et或const聲明的變量名。當(dāng)訪問暫時性死區(qū)中保存的變量時,系統(tǒng)會拋出錯誤,提示需要先聲明再使用,當(dāng)碰到變量聲明語句時,聲明變量,并從暫時性死區(qū)中刪除該變量,后面就能正常訪問了。

          2、上下文

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

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

          1. 執(zhí)行期上下文執(zhí)行期上下文是在函數(shù)執(zhí)行的時候生成的,定義了函數(shù)在執(zhí)行時,函數(shù)內(nèi)部生成的代表當(dāng)前執(zhí)行函數(shù)的具體信息。產(chǎn)生執(zhí)行期上下文第一步是創(chuàng)建激活對象AO(Activation Object)將AO保存到作用域鏈的頂端設(shè)置上下文 this 的值在AO創(chuàng)建之后,在函數(shù)開始執(zhí)行之前,需要將函數(shù)內(nèi)部可訪問的變量在AO中進(jìn)行聲明和必要的初始化將函數(shù)內(nèi)部定義的變量以及函數(shù)參數(shù)放入AO中,初始值為undefined。將函數(shù)的實(shí)際參數(shù)賦值給AO中的變量。將函數(shù)內(nèi)部聲明的函數(shù)放入到AO中,初始值為 函數(shù)本身。看一個例子:
          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ù)開始執(zhí)行時,函數(shù)的實(shí)際參數(shù)會提前賦值給對應(yīng)的變量,但是函數(shù)內(nèi)部聲明的變量的值則被初始化為undefined 。

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

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

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

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

          三、閉包

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

          正常情況下,函數(shù)執(zhí)行結(jié)束之后,所產(chǎn)生的所有變臉都會被內(nèi)存回收,但是有例外情況,就是,如果所產(chǎn)生的內(nèi)存空間仍然被其他地方的變量所引用,那么,這些空間不會被內(nèi)存回收,成為隱藏在內(nèi)存空間里的黑戶,只會被引用這片空間的變量訪問,如果這種情況存在很多,那么勢必會造成內(nèi)存不會釋放,造成內(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í)行時,會定義el元素的點(diǎn)擊事件函數(shù)clickHandle,clickHandle的[[scope]]中會保存add函數(shù)產(chǎn)生的AO。clickHandle 函數(shù)會被綁定在el元素上,只要el元素存在并且綁定了clickHandle事件響應(yīng)函數(shù),那么clickHandle函數(shù)也會一直存在,導(dǎo)致clickHandle函數(shù)對象中[[scope]]中保存的add函數(shù)的AO對象也會一直存在,不會被內(nèi)存釋放,就像有一個小黑屋,把a(bǔ)dd函數(shù)的AO對象關(guān)起來了,垃圾回收機(jī)制會忽略這塊內(nèi)存。閉包本質(zhì)上是保存了其他函數(shù)執(zhí)行時產(chǎn)生的激活對象AO。

          四、后續(xù)

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

          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ù)沒有使用到外部AO中的變量,那么在函數(shù)的[[scope]]作用域鏈中不會包含該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引擎會根據(jù)引用到的變量,做一波優(yōu)化,只保存用到的變量,并且會把這部分變量從JS執(zhí)行棧中轉(zhuǎn)移出去,減少執(zhí)行棧內(nèi)存占用。函數(shù)內(nèi)部不會被用到的函數(shù)不會聲明,而普通變量的聲明則不受影響validateNum 函數(shù)不會被調(diào)用的情況下:validateNum 函數(shù)會被調(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)聽函數(shù)執(zhí)行過程中,發(fā)現(xiàn)無法訪問到最新的 value 數(shù)據(jù)原因是因?yàn)樵诮M件第一次渲染時,綁定了事件監(jiān)聽函數(shù),此時聲明的函數(shù)的作用域鏈中保存了當(dāng)時的數(shù)據(jù)狀態(tài)(value)的初始值,當(dāng)頁面狀態(tài)發(fā)生變化時,函數(shù)組件會重新渲染執(zhí)行,但是事件監(jiān)聽函數(shù)仍然還是第一次生成的,[[scope]]中保存了初始的value值,所以在函數(shù)執(zhí)行過程中,從作用域鏈中訪問到的value始終是初始值。在setTimeout以及其他延時回調(diào)中也存在類似的情況。

          針對這種情況有兩種解決辦法:

          • 第一種:類似事件監(jiān)聽的場景,在useEffect中,添加需要用到的依賴,當(dāng)依賴發(fā)生變化時,重新注冊監(jiān)聽事件。
          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)部訪問的時候?qū)嶋H上是都是在訪問同一個引用里面的屬性,都能確保拿到的是最新數(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);
          }


          ?? 謝謝支持

          1. 喜歡的話別忘了 分享、點(diǎn)贊、在看 三連哦~。

          2. 點(diǎn)擊下方名片,關(guān)注 前端Sharing ,持續(xù)更新 前端技術(shù)文章





          瀏覽 36
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  福利看B网 | 亚洲一区二区三区 | 国产传媒久久久久 | 日韩手机在线视频 | 97成人网站 |