日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > vue >内容正文

vue

vue中引用js_从JS中的内存管理说起 —— JS中的弱引用

發布時間:2024/10/14 vue 123 豆豆
生活随笔 收集整理的這篇文章主要介紹了 vue中引用js_从JS中的内存管理说起 —— JS中的弱引用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

點擊上方“藍字”關注本公眾號

寫在前面

在所有的編程語言中,我們聲明一個變量時,需要系統為我們分配一塊內存。當我們不再需要這個變量時,需要將內存進行回收(這個過程稱之為垃圾回收)。在C語言中,有malloc和free來協助我們進行內存管理。在JS中,開發者不需要手動進行內存管理,JS引擎會為我們自動做這些事情。但是,這并不意味著我們在使用JS進行編碼時,不需要關心內存問題。

JS中的內存分配與變量

內存聲明周期如下:

  • 分配你所需要的內存

  • 使用分配到的內存(讀、寫)

  • 不需要時將其釋放

  • 在JS中,這三步都是對開發者無感的,不需要我們過多的關心。

    我們需要注意的是,當我們聲明一個變量、得到一塊內存時,需要正確區分一個變量到底是一個基本類型還是引用類型。

    基本類型:String,Number,Boolean,Null,Undefined,Symbol

    引用類型:Object,Array,Function

    對于基本類型變量來說,系統會為其分配一塊內存,這塊內存中保存的,就是變量的內容。

    對于引用類型變量來說,其存儲的只是一個地址而已,這個地址指向的內存塊才是是變量的真正內容。引用變量的賦值,也只是把地址進行傳遞(復制)。舉個例子:

    // a 和 b 指向同一塊內存var a = [1,2,3];var b = a;a.push(4);console.log(b); // [1,2,3,4]

    還有一點需要注意,JS中的函數傳參,其實是按值傳遞(按引用傳遞)。舉個例子:

    // 函數f的入參,其實是把 a 的值復制了一份。注意 a 是一個引用類型變量,其保存的是一個指向內存塊的一個地址。function f(obj) { obj.b = 1;}var a = { a : 1};f(a);console.log(a); // { a: 1, b: 1}

    在平時的開發中,完全理解JS中變量的存儲方式是十分重要的。對于我自己來說,盡量避免把引用類型變量到處傳遞,可能一不小心在某個地方修改了變量,另一個地方邏輯沒有判斷好,很容易出Bug,特別是在項目復雜度較高,且多人開發時。這也是我比較喜歡使用純函數的原因。

    另外,根據我之前的面試經驗,有不少的小伙伴認為下面的代碼會報錯,這也是對JS中變量存儲方式掌握不熟導致的。

    // const 聲明一個不可改變的變量。// a 存儲的只是數組的內存地址而已,a.push 并不會改變 a 的值。const a = [];a.push('1'); console.log(a); // ['1']

    JS中的垃圾回收

    垃圾回收算法主要依賴于引用的概念。在內存管理的環境中,一個對象如果有訪問另一個對象的權限(隱式或者顯式),叫做一個對象引用另一個對象。例如,一個Javascript對象具有對它原型的引用(隱式引用)和對它屬性的引用(顯式引用)。

    在這里,“對象”的概念不僅特指 JavaScript 對象,還包括函數作用域(或者全局詞法作用域)。當變量不再需要時,JS引擎會把變量占用的內存進行回收。但是怎么界定【變量不再需要】呢?主要有兩種方法。

    引用計數法

    把“對象是否不再需要”簡化定義為“對象有沒有其他對象引用到它”。如果沒有引用指向該對象(零引用),對象將被垃圾回收機制回收。MDN上的例子:

    var o = { a: { b:2 }}; // 兩個對象被創建,一個作為另一個的屬性被引用,另一個被分配給變量o// 很顯然,沒有一個可以被垃圾收集var o2 = o; // o2變量是第二個對“這個對象”的引用o = 1; // 現在,“這個對象”只有一個o2變量的引用了,“這個對象”的原始引用o已經沒有var oa = o2.a; // 引用“這個對象”的a屬性 // 現在,“這個對象”有兩個引用了,一個是o2,一個是oao2 = "yo"; // 雖然最初的對象現在已經是零引用了,可以被垃圾回收了 // 但是它的屬性a的對象還在被oa引用,所以還不能回收oa = null; // a屬性的那個對象現在也是零引用了 // 它可以被垃圾回收了

    這種方法有一個局限性,那就是無法處理循環引用。在下面的例子中,兩個對象被創建,并互相引用,形成了一個循環。它們被調用之后會離開函數作用域,所以它們已經沒有用了,可以被回收了。然而,引用計數算法考慮到它們互相都有至少一次引用,所以它們不會被回收。

    //?這種情況下,o和o2都無法被回收function f(){ var o = {}; var o2 = {}; o.a = o2; // o 引用 o2 o2.a = o; // o2 引用 o return "azerty";}f();

    標記-清除 算法

    這個算法假定設置一個叫做根(root)的對象(在Javascript里,根是全局對象)。垃圾回收器將定期從根開始,找所有從根開始引用的對象,然后找這些對象引用的對象……從根開始,垃圾回收器將找到所有可以獲得的對象和收集所有不能獲得的對象。

    關于JS中的垃圾回收算法,網上已經有很多的文章講解,這里不再進行贅述。

    JS中的內存泄露

    盡管JS為我們自動處理內存的分配、回收問題,但是在某些特定的場景下,JS的垃圾回收算法并不能幫我們去除已經不再使用的內存。這種【由于疏忽或錯誤造成程序未能釋放已經不再使用的內存】的現象,被稱作內存泄露。

    內存占用越來越高,輕則影響系統性能,重則導致進程崩潰。

    可能產生內存泄露的場景有不少,包括全局變量,DOM事件,定時器等等。

    下面是一段存在內存泄露的示例代碼:

    class Page1 extends React.Component { events= [] componentDidMount() { window.addEventListener('scroll', this.handleScroll.bind(this)); } render() { return <div> <div><Link to={'/page2'}>前往Page2Link>div> <p>page1p> .... div> } handleScroll(e) { this.events.push(e); }}

    當我們點擊按鈕跳轉到Page2后,在page2不停進行滾動操作,我們會發現內存占用不斷的上漲:

    產生這個內存泄露的原因是:我們在Page1被unmount的時候,盡管Page1被銷毀了,但是Page1的滾動回調函數通過eventListener依然可“觸達”,所以不會被垃圾回收。進入Page2后,滾動事件的邏輯依然生效,內部的變量無法被GC。如果用戶在Page2進行長時間滑動等操作,頁面會逐漸變得卡頓。

    上述的例子,在我們開發的過程中,并不少見。不僅僅是事件綁定,也有可能是定時上報邏輯等等。如何解決呢?記得在unmount的時候,進行相應的取消操作即可。

    在平時的項目開發中,內存泄露還有很多其他的場景。瀏覽器頁面還好,畢竟一直開著某個頁面的用戶不算太多,而且刷新就好。而Node.js發生內存泄露的后果就比較嚴重了,可能服務就直接崩潰了。掌握JS的變量存儲方式、內存管理機制,養成良好的編碼習慣,可以幫助我們減少內存泄露的發生。

    JS中的弱引用

    前面我們講到了JS的垃圾回收機制,如果我們持有對一個對象的引用,那么這個對象就不會被垃圾回收。這里的引用,指的是強引用

    在計算機程序設計中,還有一個弱引用的概念:一個對象若只被弱引用所引用,則被認為是不可訪問(或弱可訪問)的,并因此可能在任何時刻被回收。

    WeakMap、WeakSet

    要說WeakMap,先來說一說Map。Map 對象保存鍵值對,并且能夠記住鍵的原始插入順序。任何值(對象或者原始值) 都可以作為一個鍵或一個值。

    Map對對象是強引用:

    const m = new Map();let obj = { a: 1 };m.set(obj, 'a');obj = null; // 將obj置為null并不會使 { a: 1 } 被垃圾回收,因為還有map引用了 { a: 1 }

    WeakMap是一組鍵/值對的集合,其中的鍵是弱引用的。其鍵必須是對象,而值可以是任意的。WeakMap是對對象的弱引用:

    const wm = new WeakMap();let obj = { b: 2 };wm.set(obj, '2');obj?=?null;?//?將obj置為?null?后,盡管?wm?依然引用了{?b:?2?},但是由于是弱引用,{?b:?2?}?會在某一時刻被GC

    正由于這樣的弱引用,WeakMap 的 key 是不可枚舉的 (沒有方法能給出所有的 key)。如果key 是可枚舉的話,其列表將會受垃圾回收機制的影響,從而得到不確定的結果。

    WeakSet可以視為 WeakMap 中所有值都是布爾值的一個特例,這里就不再贅述了。

    JavaScript 的 WeakMap 并不是真正意義上的弱引用:實際上,只要鍵仍然存活,它就強引用其內容。WeakMap 僅在鍵被垃圾回收之后,才弱引用它的內容。這種關系更準確地稱為 ephemeron 。

    WeakRef

    WeakRef是一個更高級的API,它提供了真正的弱引用。我們直接借助上文的內存泄露的例子來看一看WeakRef的效果:

    import React from 'react';import { Link } from 'react-router-dom';// 使用WeakRef將回調函數“包裹”起來,形成對回調函數的弱引用。function addWeakListener(listener) { const weakRef = new WeakRef(listener); const wrapper = e => { if (weakRef.deref()) { return weakRef.deref()(e); } } window.addEventListener('scroll', wrapper);}class Page1 extends React.Component { events= [] componentDidMount() { addWeakListener(this.handleScroll.bind(this)); } componentWillUnmount() { console.log(this.events); } render() { return <div> <div><Link to={'/page2'}>前往Page2Link>div> <p>page1p> .... div> } handleScroll(e) { this.events.push(e); }}export default Page1;

    我們再來看看點擊按鈕跳轉到page2后的內存表現:

    可以很直觀的看到,在跳轉到page2后,持續滾動一段時間后,內存平穩。這是因為隨著page1被unmount,真正的滾動回調函數( Page1的 handleScroll 函數)被GC掉了。其內部的變量也最終被GC。

    但其實,這里還有一個問題,雖然我們通過weakRef.deref() 拿不到 handleScroll 滾動回調函數了(已被GC),但是我們的包裹函數 wrapper 依然會執行。因為我們沒有執行removeEventListener。理想情況是:我們希望滾動監聽函數也被取消掉。

    可以借助FinalizationRegistry來實現這個功能。看下面的示例代碼:

    // FinalizationRegistry構造函數接受一個回調函數作為參數,返回一個示例。我們把實例注冊到某個對象上,當該對象被GC時,回調函數會觸發。const gListenersRegistry = new FinalizationRegistry(({ window, wrapper }) => { console.log('GC happen!!'); window.removeEventListener('scroll', wrapper);});function addWeakListener(listener) { const weakRef = new WeakRef(listener); const wrapper = e => { console.log('scroll'); if (weakRef.deref()) { return weakRef.deref()(e); } } // 新增這行代碼,當listener被GC時,會觸發回調函數。回調函數傳參由我們自己控制。 gListenersRegistry.register(listener, { window, wrapper }); window.addEventListener('scroll', wrapper);}

    WeakRef 和 FinalizationRegistry 屬于高級Api,在Chrome v84 和 Node.js 13.0.0 后開始支持。一般情況下不建議使用。因為容易用錯,導致更多的問題。

    寫在后面

    本文從JS中的內存管理講起,說到了JS中的弱引用。雖然JS引擎幫我們處理了內存管理問題,但是我們在業務開發中并不能完全忽視內存問題,特別是在Node.js的開發中。

    關于JS內存管理的更多細節,可以移步我之前翻譯的一篇文章:

    V8引擎的內存管理

    參考資料:

    1、https://www.youtube.com/watch?v=TPm-UhWkiq8

    2、https://www.infoq.cn/article/lKsmb2tlGH1EHG0*bbYg

    3、https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Memory_Management

    總結

    以上是生活随笔為你收集整理的vue中引用js_从JS中的内存管理说起 —— JS中的弱引用的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。