整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          今天來聊聊JavaScript的垃圾回收機制

          今天來聊聊JavaScript的垃圾回收機制

          圾回收

            JavaScript是使用垃圾回收的語言,也就是說執行環境負責在代碼執行時管理內存。在C和C++等語言中,跟蹤內存使用對開發者來說是很大的負擔,也是很多問題的來源。JavaScript為開發者卸下了這個負擔,通過自動內存管理實現內存分配和閑置資源回收。基本思路很簡單:確定哪個變量不會再使用,然后釋放它占用的內存。這個過程是周期性的,即垃圾回收程序每隔一定時間(或者說在代碼執行過程中某個預定的收集時間)就會自動運行。垃圾回收過程是一個近似且不完美的方案,因為某塊內存是否還有用,屬于不可判定的問題。意味著算法是解決不了的。

            我們以函數中局部變量的正常生命周期為例。函數中的局部變量會在函數執行時存在。此時,棧(或者堆)內存會分配空間以保存相應的值。函數在內部使用了變量,然后退出。此時,就不再需要那個局部變量了,它占用的內存可以釋放,供以后使用。這種情況下顯然不再需要局部變量了,但并不是所有時候都會這么明顯。垃圾回收程序必須跟蹤記錄哪個變量還會使用,以及哪個變量不會再使用,以便回收內存。如何標記未使用的變量也許有不同的實現方式。不過,在瀏覽器的發展史上,用到過兩種主要的標記策略:標記清理引用計數

          節選自JavaScript高級程序設計(第四版)第四章

          1、引用計數(reference counting)

            另一種不太常見的垃圾回收策略是引用計數。引用計數的含義是跟蹤記錄每個值被引用的次數。當聲明了一個變量并將一個引用類型賦值給該變量時,則這個值的引用次數就是1。相反,如果包含對這個值引用的變量又取得了另外一個值,則這個值的引用次數就減1。當這個引用次數變成0時,則說明沒有辦法再訪問這個值了,因而就可以將其所占的內存空間給收回來。這樣,垃圾收集器下次再運行時,它就會釋放那些引用次數為0的值所占的內存。

          引用計數有個最大的問題: 循環引用。

          舉個 A:

          比如對象A有一個屬性指向對象B,而對象B也有有一個屬性指向對象A,這樣相互引用.

          function func() {
              let obj1={};
              let obj2={};
              obj1.a=obj2; // obj1 引用 obj2
              obj2.a=obj1; // obj2 引用 obj1
          }
          

            在這個例子中,objA和objB通過各自的屬性相互引用;也就是說這兩個對象的引用次數都是2。在采用引用計數的策略中,由于函數執行之后,這兩個對象都離開了作用域,函數執行完成之后,objA和objB還將會繼續存在,因為他們的引用次數永遠不會是0。這樣的相互引用如果說很大量的存在就會導致大量的內存泄露。

          解決:手動解除引用

          obj1.a=null;
          obj2.a=null;
          


          舉個 B:涉及COM對象的循環引用問題:

          let element=document.getElementById('some_element')
          let myObject=new Object()
          myObject.element=element
          element.someObject=myObject
          

            這個例子在一個DOM對象(element)和一個原生JavaScript對象(myObject)之間制造了循環引用。myObject變量有一個名為element的屬性指向DOM對象element,而element對象有一個someObject屬性指回myObject對象.由于存在循環引用,因此DOM元素的內存永遠不會被回收,即使它已經被從頁面上刪除了也是如此。

            為避免類似的循環引用問題,應該在確保不使用的情況下切斷原生JavaScript對象DOM元素之間的連接。比如,通過以下代碼可以清除前面例子中建立的循環引用:

          myObject.element=null
          element.someObject=null
          

          把變量設置為null實際上會切段變量與其之前引用值之間的關系。當下次垃圾回收程序運行時,這些值就會被刪除,內存也會被回收。

          注意??:為了補救這點,IE9把BOM和DOM對象都改成了JavaScript對象,這同時也避免了由于存在兩套垃圾回收算法而導致的問題,還消除了常見的內存泄漏現象。

          2、標記清除(mark-and-sweep)

            這是javascript中最常用的垃圾回收方式。當變量進入執行環境是,就標記這個變量為“進入環境”。從邏輯上講,永遠不能釋放進入環境的變量所占用的內存,因為只要執行流進入相應的環境,就可能會用到他們。當變量離開環境時,則將其標記為“離開環境”。

            垃圾收集器在運行的時候會給存儲在內存中的所有變量都加上標記。然后,它會去掉環境中的變量以及被環境中的變量引用的標記。而在此之后再被加上標記的變量將被視為準備刪除的變量,原因是環境中的變量已經無法訪問到這些變量了。最后。垃圾收集器完成內存清除工作,銷毀那些帶標記的值,并回收他們所占用的內存空間。

            標記清除也會遇到循環引用的問題。IE中有一部分對象并不是原生JavaScript對象。例如,其BOM和DOM中的對象就是使用C++以COM(Component Object Model,組件對象)對象的形式實現的,而COM對象的垃圾回收器就是采用的引用計數的策略。因此,即使IE的Javascript引擎使用標記清除的策略來實現的,但JavaScript訪問的COM對象依然是基于引用計數的策略的。說白了,只要IE中涉及COM對象,就會存在循環引用的問題。

            解決:手工斷開js對象和DOM之間的鏈接。賦值為null。IE9把DOM和BOM轉換成真正的JS對象了,所以避免了這個問題。

          3、避免垃圾回收

          通過上面內容了解了,瀏覽器雖然可以自動化執行垃圾回收,但如果項目比較大代碼復雜,回收執行代價較大,某些情況甚至不能識別回收

          1.數組array優化

          將[]賦值給一個數組對象,是清空數組的捷徑(例如:arr=[];),但是需要注意的是,這種方式又創建了一個新的空對象,并且將原來的數組對象變成了一小片內存垃圾!實際上,將數組長度賦值為0(arr.length=0)也能達到清空數組的目的,并且同時能實現數組重用,減少內存垃圾的產生。

          2. 對象盡量復用

          對象盡量復用,尤其是在循環等地方出現創建新對象,能復用就復用。不用的對象,盡可能設置為null,盡快被垃圾回收掉。

          3.循環優化

          在循環中的函數表達式,能復用最好放到循環外面。

          4、避免內存泄漏

          1.意外的全局變量

          function fn(arg) {
              m="this is a hidden global variable"
          }
          

          m沒被聲明,會變成一個全局變量,在頁面關閉之前不會被釋放。

          另一種意外的全局變量可能由 this 創建:

          function fn() {
              this.variable="potential accidental global"
          }
          // fn 調用自己,this 指向了全局對象(window)
          fn()
          

          在 JavaScript 文件頭部加上 'use strict',可以避免此類錯誤發生。啟用嚴格模式解析 JavaScript ,避免意外的全局變量

          2.被遺忘的計時器或回調函數

          let someResource=getData()
          setInterval(function() {
              let node=document.getElementById('Node')
              if(node) {
                  // 處理 node 和 someResource
                  node.innerHTML=JSON.stringify(someResource))
              }
          }, 1000)
          

          這樣的代碼很常見,如果id為Node的元素DOM中移除,該定時器仍會存在,同時,因為回調函數中包含對someResource的引用,定時器外面的someResource也不會被釋放。

          所以要用完記住清除定時器鴨,也盡量別在定時器里引用dom對象。

          3.閉包

          function fn() {
              let m=document.createElement('xx')
              m.onClick=()=> {
                  // Even if it a empty function
              }
          }
          

          閉包可以維持函數內局部變量,使其得不到釋放。

          上例定義事件回調時,由于是函數內定義函數,并且內部函數 -> 事件回調引用外部函數,形成了閉包

          解決辦法:

          // 1. 將事件處理函數定義在外面
          function fn() {
              let m=document.createElement('xx')
              m.onClick=onClickFn()
          }
          // 2. 定義事件處理函數的外部函數中,刪除對dom對象的引用
          function fn() {
              let m=document.createElement('xx')
              m.onClick=()=> {
                  // Even if it a empty function
              }
              m=null
          }
          

          將事件處理函數定義在外部,解除閉包,或者在定義事件處理函數的外部函數中,刪除對dom的引用。

          4.沒有清理的DOM元素引用

          有時,保存 DOM 節點內部數據結構很有用。假如你想快速更新表格的幾行內容,把每一行 DOM 存成字典(JSON 鍵值對)或者數組很有意義。此時,同樣的 DOM 元素存在兩個引用:一個在 DOM 樹中另一個在字典中。將來你決定刪除這些行時,需要把兩個引用都清除

          let elements={
              button: document.getElementById('button'),
              image: document.getElementById('image'),
              text: document.getElementById('text')
          }
          function doStuff() {
              image.src='http://some.url/image'
              button.click()
              console.log(text.innerHTML)
          }
          function removeButton() {
              document.body.removeChild(document.getElementById('button'))
              // 此時,仍舊存在一個全局的 #button 的引用
              // elements 字典。button 元素仍舊在內存中,不能被 GC 回收。
          }
          

          雖然我們用removeChild移除了button,但是還在elements對象里保存著#button的引用,換言之,DOM元素還在內存里面

          最后最后:

          明天港性能,有空可以來看看蛙,下班10點半,到家11點半,大半夜寫的文章呢。

          公眾號:小何成長,佛系更文,都是自己曾經踩過的坑或者是學到的東西

          有興趣的小伙伴歡迎關注我哦,我是:何小玍。 大家一起進步鴨

          、垃圾回收機制-GC

          javascript具有自動垃圾回收機制,也就是說,執行環境會負責管理代碼執行過程中使用的內存。

          原理:垃圾收集器會定期(周期性)找出那些不在繼續使用的變量垃圾,然后釋放期內存。

          javascript垃圾回收的機制很簡單:找出不在使用的變量,然后釋放其占用的內存,但是這個過程不是實時的,應為其開銷比較大,所以垃圾回收器會按照固定時間間隔性的執行。

          不在使用的變量也就是什么周期結束的變量,單人只可能是局部變量,全局變量的生命周期直至瀏覽器卸載頁面才會結束。局部變量只在函數的執行過程中存在,而在這個過程中會為局部變量在棧或堆上分配相應的空間,以儲存他們的值,然后在函數中使用這些變量,直至函數結束,而閉包中由于內部的函數原因,外部函數并不能算是結束。

          還是上代碼說明吧:

          function fn1(){

          var obj={name:'司徒分享',age:'10' }

          }

          function fn2(){

          var obj={name:'司徒分享',age:'20'}

          return obj;

          }

          var a=fn1();
          var b=fn2();

          我們在看代碼是如何執行的。首先定義倆個function,分別叫做fn1和fn2,當fn1被調用時,進入fn1的環境,會開辟一塊內存存放對象。而當調用結束后,出了fn1的環境,那么該塊內存會被js引擎中的垃圾回收機制自動釋放;以備將來回收其內存,通常情況下有倆種實現方式,標記清除和引用計數。引用計數不太常用,標記清除較為常用。

          二、引用計數

          三、標記清除

          四、內存管理

          猶豫時間原因、這三點后期補充!

          用過Windows系統的朋友都知道,在Windows中有回收站功能。如果有文件不小心刪除的話,可以通過回收站找回,但是安卓系統中卻沒有提供這個功能。如果我們不小心將文件刪除,只能通過第三方還原軟件來恢復文件,非常麻煩。其實我們可以安裝一款名字為Dumpster的應用,利用它就可以給自己的安卓手機安裝上一個回收站,如果不小心刪除某個文件,可以輕松將它找回,非常方便。

          首先將Dumpster安裝到自己的安卓手機上,然后進行簡單的設置,設置哪些類型的文件啟動回收站功能,可以根據自己的實際情況來進行選擇。當選擇好后,就不用管理,讓Dumpster在后臺運行即可。

          當我們將文件不小心刪除后,就可以啟動Dumpster,它會自動顯示攔截到的被刪除了的文件,并通過文件夾的形式顯示出來。你可以設置多種顯示方式,讓自己更輕松地找到想要恢復的文件,找到后選擇恢復,即可將誤刪掉的文件恢復到原來的位置。

          Dumpster的原理跟Windows中的回收站原理一樣。我們將受保護的文件刪除,其實并不是真正意義上的物理刪除,只是將文件轉移到Dumpster指定的位置,所以隨時都可以進行還原。如果要是覺得文件真的沒有用,在Dumpster中將文件進行徹底刪除,從而釋放安卓手機的空間。


          主站蜘蛛池模板: 久久久精品日本一区二区三区| 亚洲第一区视频在线观看| 无码人妻精品一区二区三区在线| 精品一区二区三区四区| 亚洲一区二区三区在线观看蜜桃| 亚洲日本久久一区二区va| 色婷婷香蕉在线一区二区| 在线观看精品视频一区二区三区| 亚洲国产精品一区二区三区久久| 成人区精品人妻一区二区不卡 | 日韩精品一区二区三区中文精品| 天堂一区人妻无码| 国产一区二区视频在线观看| 水蜜桃av无码一区二区| 爱爱帝国亚洲一区二区三区| 中文字幕在线观看一区二区| 一区二区无码免费视频网站| 亚洲一区二区视频在线观看| 日韩国产免费一区二区三区| 亚洲av无码天堂一区二区三区 | 国产拳头交一区二区| 国产亚洲一区二区精品| 精品国产一区二区三区香蕉事| 国产裸体歌舞一区二区| 日本香蕉一区二区三区| 91福利视频一区| 久久久久女教师免费一区| 亚洲av永久无码一区二区三区| 国产午夜精品一区二区| 日韩一区二区免费视频| 国产激情无码一区二区app| 国产产一区二区三区久久毛片国语| 国产一区二区三区久久精品| 国产在线精品一区二区在线看| 日韩高清国产一区在线| 国产成人精品一区二区秒拍| 制服丝袜一区二区三区| 国产vr一区二区在线观看| 欧美激情一区二区三区成人| 国产激情一区二区三区四区| 亚洲免费一区二区|