project中的堆疊記憶體,記憶體地址引用,gc相關問題

weixin_34148340發表於2018-10-31

專案中遇到了關於js記憶體,引用型別,gc機制相關的問題,記錄下來。
首先復現一下程式碼:

const option = {
  yAxis: [{
    type: 'value',
    ......  
  }]
};
option.yAxis.push(option.yAxis[0]);
option.yAxis[0].min = 1;
option.yAxis[1].min = 2;

發現option.yAxis下面的min的值都是2;第一反應是以為之前的程式碼寫的有問題,所以打斷點,打日誌,發現都正常,然後想到了有可能是記憶體指標導致的,然後可以做以下兩種修改。

const option = {
  yAxis: [{
    type: 'value',
    ......  
  }]
};
option.yAxis.push(JSON.parse(JSON.stringify(option.yAxis[0])));
option.yAxis[0].min = 1;
option.yAxis[1].min = 2;

或者

const option = {
  yAxis: [{
    type: 'value',
    ......  
  }]
};
option.yAxis.push(option.yAxis[0]);
option.yAxis[0] = {min: 1};
option.yAxis[1] = {min: 2};

以上兩種寫法都可以規避記憶體指標引用導致的資料問題。

剖析以下問題的本質吧:

  1. JS基本資料型別:Null,Undefined,Number,Boolean,Symbol,String
  2. JS複雜資料型別:Object Array (也算是object)

當我們在程式碼裡面:

var num = 123;
var obj = { name: 'obj' };
var obj_copy = obj;

此時會開闢兩塊記憶體空間,一個儲存123,一個儲存 { name: 'obj' }; 其中是num的指標會直接指向棧記憶體中的123,obj的指標會指向堆記憶體中的 { name: 'obj' },obj_copy的引用指標會存放在棧記憶體中;
如果我們編輯以下程式碼:

var num = 123;
var obj = { name: 'obj' };
var obj_copy = obj;
obj.name = 'xxx';

那麼我們會發現obj_copy的name也會變成xxx,其實就是因為產生了指標以引用,指向的是同一塊記憶體空間;
如果編輯以下程式碼:

var num = 123;
var obj = { name: 'obj' };
var obj_copy = obj;
obj = {name: 'xxx'};

那麼我們會發現obj_copy的name還是obj,這是因為obj = {name: 'xxx'};會產生一塊新的記憶體空間,然後obj會產生一次引用,obj_copy的引用跟該引用沒有任何毛線關係。那麼我們專案中的問題就迎刃而解了。

寫到這裡,突然想到了es6 的const 變數申明:

const  object = { name: 'object'};
const num = 5;
object.name = 'new_object';
num = 4;

我們會發現object的name確實變成了字串new_object,但是num=4會報錯,為什麼呢?其實這個問題跟堆疊記憶體沒有關係,還是跟引用資料型別有關;
看下面的程式碼:

let numA = 1;
let numB = numA;
numA = 2;
console.log(numB );

我們發現numB的值仍然是1,其實基本資料型別也是存在引用的,只是基本資料型別無法像object一樣去更改某個key的值而已,就比如一座房子和一把板凳,如果你改變了房子或者凳子,那麼他就是實實在在地改變了,如果你只是改變了一座房子內部的一部分,它仍然是房子。

說到這裡,不得不提一下es6的 WeekMap了(也是借用了阮大的例子吧,哈哈哈!)

const wm = new WeakMap();
const element = document.getElementById('example');
wm.set(element, 'some information');
wm.get(element) // "some information"

WeekMap 是一種弱引用,也就是說,該節點的引用計數是1,如果element設定為null,Weakmap 儲存的這個鍵值對,也會自動消失。

說到這裡,就要說一下javascript的垃圾回收機制了,分為兩種型別吧,一種是標記清除 ,一種是引用計數。

let fn = function (){
  let a = 1;
  return a;
}
fn();

在執行fn函式時,變數a被標記為進入環境,在函式沒有被執行結束之前,是不能釋放該變數所指向的記憶體的,當函式執行完之後,變數會被標記為離開環境,則會被gc回收

let fn = function (){
  let a = 1;
  return a;
}
let back = fn();

如果程式碼寫成這樣的話,變數a所佔用的記憶體是不會被gc的,因為在外部存在了引用,雖然a已經離開了fn的執行環境,但是a的引用計數是2,所以不會被gc回收清除。

function problem(){     
    var objectA = new Object();
    var objectB = new Object(); 

    objectA.someOtherObject = objectB;
    objectB.anotherObject = objectA; 
} 

上述情況也是objectA 和B都離開了函式環境,但是因為存在迴圈引用,所以引用計數都不為0,所以記憶體就不會得到回收,在個別情況下,需要手動回收。
另外,gc時,會阻塞主執行緒,所以平常寫程式碼的時候一定要注意相關問題,務必規避記憶體洩漏的相關問題。

相關文章