JavaScript閉包(內存泄漏、溢出以及內存回收),超直白解析


1 引言

變量作用域

首先我們先鋪墊一個知識點——變量作用域:

變量根據作用域的不同分為兩種:全局變量和局部變量。

  1. 函數內部可以使用全局變量。
  2. 函數外部不可以使用局部變量。
  3. 當函數執行完畢,本作用域內的局部變量會銷毀。

如果我想在函數外部引用這個函數的局部變量呢?

2 閉包

閉包是什么?

閉包(closure)指有權訪問另一個函數作用域中變量的函數。 ----- JavaScript 高級程序設計

閉包有什么用?

1延伸變量作用域范圍,讀取函數內部的變量
2讓這些變量的值始終保持在內存中

簡單理解就是 ,一個作用域可以訪問另外一個函數內部的局部變量。

閉包案例一

我們來看一個簡單的閉包案例。

        function fn1() {
            var num = 10;
            function fn2() {
                console.log(num);
            }
            fn2();
        }
        fn1(); //輸出結果10

這樣的一個函數寫法我們已經見過或者用過很多次了,但其實這就是一個閉包的運用。
我們可以用Chrome的調試工具驗證一下。

如圖(看不清圖片的伙伴們可以把圖片放大)
在 Scope 選項(Scope 作用域的意思)中,有兩個參數(global 全局作用域、local 局部作用域)。我在fn1函數調用的前面(21行)中設置了一個斷點,進行單步調試,當執行到 fn2() 時,Scope 里面會多一個 Closure (閉包)參數 ,這就表明產生了閉包。被訪問的變量是num,包含num的函數為fn1。

fn2的作用域當中訪問到了fn1函數中的num這個局部變量 ,所以此時fn1 就是一個閉包函數(被訪問的變量所在的函數就是一個閉包函數)

也有人說,閉包是一種現象,一個作用域訪問了另外一個函數中的局部變量,如果有這種現象的產生,就有了閉包的發生。 我覺的這樣理解也是沒有什么問題的。

閉包案例二

接下來我們來看一個稍微復雜一點點的閉包

        function fn() {
            var num = 10;
            return function() {
                console.log(num);
            }
        }
        var f = fn();
        // 上面這步類似於
        // var f = function() {
        //         console.log(num);
        //     }
        f();//輸出結果10

在f=fn()這步操作中,執行了num =10 的賦值,並且給f賦值了一個匿名函數,這個函數是fn中return 返回的那個匿名函數,注意此時只是賦值了,並沒有調用。
然后在f()中調用了那個匿名函數,此時我們便做到了在 fn() 函數外面訪問 fn() 中的局部變量 num 。

$閉包延伸了變量作用域范圍,讀取了函數內部的變量$

閉包案例三

首先我們看一下這個閉包案例

        var fn  =function(){
            var sum = 0
            return function(){
                sum++
                console.log(sum);
            }
        }
        fn()() //1
        fn()() //1
        //fn()進行sum變量申明並且返回一個匿名函數,第二個()意思是執行這個匿名函數

這里出現了一個小問題,sum為什么沒有自增?如果想要實現自增怎么操作?
回答這個問題需要先了解一下js中內存回收機制。(詳細內容可以看文章后面的3 Js內存回收機制

我這里直接簡單解釋一下,執行fn()() 后,fn()()已經執行完畢,沒有其他資源在引用fn,此時內存回收機制會認為fn不需要了,就會在內存中釋放它。

那如何不被回收呢?

        var fn  =function(){
            var sum = 0
            return function(){
                sum++
                console.log(sum);
            }
        }
        fn1=fn() 
        fn1()   //1
        fn1()   //2
        fn1()   //3

這種情況下,fn1一直在引用fn(),此時內存就不會被釋放,就能實現值的累加。那么問題又來了,這樣的函數如果太多,就會造成內存泄漏(內存泄漏、內存溢出的知識點在文章后面4 內存溢出、內存泄漏

內存泄漏了怎么辦呢?我們可以手動釋放一下

        var fn  =function(){
            var sum = 0
            return function(){
                sum++
                console.log(sum);
            }
        }
        fn1=fn() 
        fn1()   //1
        fn1()   //2
        fn1()   //3
        fn1 = null // fn1的引用fn被手動釋放了
        fn1=fn()  //num再次歸零
        fn1() //1

3 Js內存回收機制

由於字符串、對象和數組沒有固定大小,當他們的大小已知時,才能對他們進行動態的存儲分配。JavaScript程序每次創建字符串、數組或對象時,解釋器都必須分配內存來存儲那個實體。只要像這樣動態地分配了內存,最終都要釋放這些內存以便他們能夠被再用,否則,JavaScript的解釋器將會消耗完系統中所有可用的內存,造成系統崩潰。

現在各大瀏覽器通常用采用的垃圾回收有兩種方法:標記清除、引用計數。

1標記清除

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

2引用計數

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

4 內存溢出、內存泄漏

內存溢出

內存溢出一般是指執行程序時,程序會向系統申請一定大小的內存,當系統現在的實際內存少於需要的內存時,就會造成內存溢出

內存溢出造成的結果是先前保存的數據會被覆蓋或者后來的數據會沒地方存

內存泄漏

內存泄漏是指程序執行時,一些變量沒有及時釋放,一直占用着內存
而這種占用內存的行為就叫做內存泄漏。

作為一般的用戶,根本感覺不到內存泄漏的存在。真正有危害的是內存泄漏的堆積,這會最終消耗盡系統所有的內存。從這個角度來說,一次性內存泄漏並沒有什么危害,因為它不會堆積。

內存泄漏如果一直堆積,最終會導致內存溢出問題

5 總結

  1. 閉包是什么?

閉包是一個函數 (一個作用域可以訪問另外一個函數的局部變量)

  1. 閉包的作用是什么?

1延伸變量作用域范圍,讀取函數內部的變量
2讓這些變量的值始終保持在內存中


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM