ArkTS內(nèi)存管理:避免內(nèi)存泄漏的9大策略

2024-12-04 09:49 更新

ArkTS 是鴻蒙生態(tài)的應(yīng)用開發(fā)語(yǔ)言,它在 TypeScript 的基礎(chǔ)上進(jìn)行了優(yōu)化和定制,以適應(yīng)鴻蒙系統(tǒng)的需求。

以下是在 ArkTS 中進(jìn)行有效的內(nèi)存管理和避免內(nèi)存泄漏:

1. 使用 constlet 合理聲明變量:

  • 使用 const 聲明那些不會(huì)重新賦值的變量,這有助于確保變量的不變性,并可能讓編譯器進(jìn)行更多的優(yōu)化。
  • 使用 let 聲明那些需要重新賦值的變量,避免使用 var,因?yàn)?var 會(huì)導(dǎo)致變量提升到函數(shù)作用域的頂部,可能會(huì)引起意外的錯(cuò)誤。

    在 ArkTS 中,constlet 是用來(lái)聲明變量的關(guān)鍵字,它們?cè)谧饔糜蚝涂勺冃苑矫嬗兴煌R韵率鞘褂?constlet 合理聲明變量的示例代碼對(duì)比:

使用 const 聲明不變的變量:

// 正確的使用方式:使用 const 聲明一個(gè)不會(huì)被重新賦值的變量
const PI = 3.14159; // PI 是一個(gè)常量,不應(yīng)該被重新賦值


// 嘗試重新賦值將會(huì)導(dǎo)致編譯錯(cuò)誤
// PI = 3.14; // Error: Cannot assign to 'PI' because it is a read-only property.

使用 let 聲明可變的變量:

// 正確的使用方式:使用 let 聲明一個(gè)可能會(huì)被重新賦值的變量
let count = 0; // count 是一個(gè)變量,可以被重新賦值


// 可以重新賦值
count = 1;
console.log(count); // 輸出:1

對(duì)比示例:

function vgFunction() {
  // 使用 const 聲明一個(gè)常量,表示這個(gè)變量不應(yīng)該被修改
  const name = "VG";
  console.log(name); // 輸出:VG


  // 使用 let 聲明一個(gè)變量,表示這個(gè)變量可能會(huì)被修改
  let age = 18;
  console.log(age); // 輸出:18,永遠(yuǎn)18


  // 根據(jù)某些條件修改變量
  if (age < 30) {
    age = 30;
  }
  console.log(age); // 輸出:30
}


vgFunction();

在這個(gè)例子中,name 被聲明為常量,表示它的值不應(yīng)該改變,而 age 被聲明為變量,表示它的值可能會(huì)改變。使用 constlet 可以清晰地表達(dá)出變量的預(yù)期用途,有助于代碼的可讀性和維護(hù)性。

避免使用 var 的示例:

// 不推薦使用 var,因?yàn)樗鼤?huì)提升變量到函數(shù)作用域的頂部
function exampleFunction() {
  var globalVar = "I name is VG"; // 這實(shí)際上是一個(gè)全局變量
  console.log(globalVar); // 輸出:I name is VG
}


exampleFunction();
console.log(globalVar); // 輸出:I name is VG

在這個(gè)例子中,使用 var 聲明的 globalVar 實(shí)際上是一個(gè)全局變量,即使它在函數(shù)內(nèi)部聲明。這可能會(huì)導(dǎo)致意外的副作用,因?yàn)槿肿兞靠梢栽诔绦虻娜魏蔚胤奖辉L問(wèn)和修改。因此,推薦使用 constlet 來(lái)替代 var。

2. 避免全局變量:

  • 盡量減少全局變量的使用,因?yàn)槿肿兞吭谡麄€(gè)應(yīng)用生命周期中都存在,難以管理,容易造成內(nèi)存泄漏。

全局變量是指在全局作用域中聲明的變量,它們可以在整個(gè)程序的任何地方被訪問(wèn)和修改。過(guò)度使用全局變量可能會(huì)導(dǎo)致代碼難以維護(hù)、理解和調(diào)試,因?yàn)樗鼈兛梢栽谌魏蔚胤奖桓淖儯黾恿舜a的耦合性。以下是避免全局變量的示例代碼對(duì)比:

使用全局變量的示例:

// 全局變量
var globalCounter = 0;


function increment() {
  globalCounter++; // 直接修改全局變量
}


function decrement() {
  globalCounter--; // 直接修改全局變量
}


increment();
console.log(globalCounter); // 輸出:1
decrement();
console.log(globalCounter); // 輸出:0

在這個(gè)例子中,globalCounter 是一個(gè)全局變量,可以在 incrementdecrement 函數(shù)中被直接訪問(wèn)和修改。這可能會(huì)導(dǎo)致在程序的其他部分不小心修改了這個(gè)變量,從而產(chǎn)生難以追蹤的錯(cuò)誤。

避免使用全局變量的示例:

// 避免使用全局變量,改為使用局部變量和參數(shù)傳遞
function counterManager() {
  let counter = 0; // 局部變量


  function increment() {
    counter++; // 修改局部變量
  }


  function decrement() {
    counter--; // 修改局部變量
  }


  return {
    increment: increment,
    decrement: decrement,
    getCount: function () {
      return counter;
    }
  };
}


const counter = counterManager(); // 創(chuàng)建一個(gè)局部變量 counter 來(lái)持有管理器對(duì)象
counter.increment();
console.log(counter.getCount()); // 輸出:1
counter.decrement();
console.log(counter.getCount()); // 輸出:0

在這個(gè)例子中,我們創(chuàng)建了一個(gè) counterManager 函數(shù),它返回一個(gè)對(duì)象,包含 increment、decrementgetCount 方法。這些方法操作的是 counterManager 函數(shù)內(nèi)部的局部變量 counter,而不是全局變量。這樣,counter 的值就被封裝在 counterManager 函數(shù)的作用域內(nèi),不會(huì)影響到全局作用域中的其他變量。

通過(guò)這種方式,我們減少了全局變量的使用,提高了代碼的封裝性和模塊化,使得代碼更易于維護(hù)和理解。同時(shí),這也有助于避免全局變量可能引起的命名沖突和意外的副作用。

3. 使用弱引用(Weak References):

  • 對(duì)于不需要長(zhǎng)期持有的對(duì)象,可以使用弱引用,這樣垃圾回收器可以更容易地回收這些對(duì)象。

在 ArkTS 或 TypeScript 中,并沒有內(nèi)置的弱引用(Weak References)概念,這是因?yàn)?JavaScript 引擎(包括 V8,即 Node.js 和大多數(shù)瀏覽器的 JavaScript 引擎)默認(rèn)就是使用垃圾回收來(lái)管理內(nèi)存的。弱引用通常是指那些不阻止垃圾回收器回收其所引用對(duì)象的引用。

然而,我們可以通過(guò)一些設(shè)計(jì)模式來(lái)模擬弱引用的行為,尤其是在處理大型對(duì)象或者循環(huán)引用時(shí)。如何避免循環(huán)引用導(dǎo)致的內(nèi)存泄漏,來(lái)看這個(gè)例子:

可能導(dǎo)致內(nèi)存泄漏的循環(huán)引用示例:

class Person {
  name: string;
  friends: Person[]; // 朋友列表


  constructor(name: string) {
    this.name = name;
    this.friends = [];
  }


  addFriend(friend: Person) {
    this.friends.push(friend);
    // 這里創(chuàng)建了一個(gè)循環(huán)引用,friend.friends.push(this) 會(huì)使得 person 和 friend 互相引用
    friend.friends.push(this);
  }
}


const personA = new Person("VG");
const personB = new Person("Vin");
personA.addFriend(personB);
// 此時(shí),personA 和 personB 互相引用,形成了循環(huán)引用

在這個(gè)例子中,Person 類的每個(gè)實(shí)例都維護(hù)了一個(gè)朋友列表,當(dāng)一個(gè) Person 實(shí)例被添加到另一個(gè) Person 實(shí)例的朋友列表時(shí),同時(shí)也將后者添加到前者的朋友列表中,形成了循環(huán)引用。

避免循環(huán)引用的示例:

為了避免循環(huán)引用,我們可以不直接在 Person 類中添加彼此的引用,而是通過(guò)其他方式來(lái)管理這種關(guān)系,比如使用一個(gè)外部的映射或者服務(wù)來(lái)管理朋友關(guān)系。

class Person {
  name: string;
  friends: string[]; // 朋友列表,只存儲(chǔ)名字而不是直接引用對(duì)象


  constructor(name: string) {
    this.name = name;
    this.friends = [];
  }


  addFriend(name: string) {
    this.friends.push(name);
    // 這里不再創(chuàng)建循環(huán)引用,而是將朋友的名字添加到列表中
  }
}


const personA = new Person("VG");
const personB = new Person("Vin");
personA.addFriend(personB.name);
// 此時(shí),personA 的 friends 列表中只有 personB 的名字,不會(huì)造成循環(huán)引用

在這個(gè)改進(jìn)的例子中,我們不再直接將 Person 對(duì)象添加到朋友列表中,而是只存儲(chǔ)朋友的名字。這樣,即使 Person 對(duì)象之間有多個(gè)連接,也不會(huì)形成循環(huán)引用,從而避免了潛在的內(nèi)存泄漏問(wèn)題。

如何通過(guò)設(shè)計(jì)模式來(lái)避免循環(huán)引用,這是在 JavaScript 和 TypeScript 中管理內(nèi)存和避免內(nèi)存泄漏的一種常用方法。在某些特定的 JavaScript 環(huán)境中,如 Node.js,可以使用弱引用(WeakRef)和弱映射(WeakMap)這樣的內(nèi)置對(duì)象來(lái)更直接地實(shí)現(xiàn)弱引用。但在大多數(shù)前端 JavaScript 環(huán)境中,這些對(duì)象并不可用。

4. 及時(shí)清理不再使用的對(duì)象:

  • 當(dāng)對(duì)象不再需要時(shí),應(yīng)該手動(dòng)將其設(shè)置為 null 或刪除其引用,這樣垃圾回收器可以回收這部分內(nèi)存。

在 JavaScript 或 TypeScript 中,及時(shí)清理不再使用的對(duì)象是避免內(nèi)存泄漏的重要策略。這通常涉及到移除事件監(jiān)聽器、取消網(wǎng)絡(luò)請(qǐng)求、銷毀定時(shí)器等操作。以下是一個(gè)業(yè)務(wù)場(chǎng)景的示例代碼對(duì)比,展示如何及時(shí)清理不再使用的對(duì)象:

未及時(shí)清理不再使用的對(duì)象示例:

// 假設(shè)我們有一個(gè)組件,它在加載時(shí)訂閱了一個(gè)事件
class Component {
  private eventListener: () => void;


  constructor() {
    this.eventListener = () => {
      console.log('Event triggered');
    };
    document.addEventListener('customEvent', this.eventListener);
  }


  // 組件被銷毀時(shí),應(yīng)該清理事件監(jiān)聽器
  destroy() {
    // 忘記移除事件監(jiān)聽器
    // document.removeEventListener('customEvent', this.eventListener);
  }
}


const myComponent = new Component();
// 當(dāng)組件不再需要時(shí),應(yīng)該調(diào)用 destroy 方法
// myComponent.destroy();

在這個(gè)例子中,Component 類在構(gòu)造時(shí)添加了一個(gè)事件監(jiān)聽器,但在 destroy 方法中忘記移除這個(gè)監(jiān)聽器。如果 myComponent 被銷毀而沒有調(diào)用 destroy 方法,事件監(jiān)聽器仍然存在,這將導(dǎo)致內(nèi)存泄漏,因?yàn)?myComponent 和它的 eventListener 方法仍然被事件監(jiān)聽器引用。

及時(shí)清理不再使用的對(duì)象示例:

class Component {
  private eventListener: () => void;


  constructor() {
    this.eventListener = () => {
      console.log('Event triggered');
    };
    document.addEventListener('customEvent', this.eventListener);
  }


  // 組件被銷毀時(shí),及時(shí)清理事件監(jiān)聽器
  destroy() {
    document.removeEventListener('customEvent', this.eventListener);
  }
}


const myComponent = new Component();
// 當(dāng)組件不再需要時(shí),調(diào)用 destroy 方法
myComponent.destroy();

在這個(gè)改進(jìn)的例子中,Component 類在 destroy 方法中正確地移除了事件監(jiān)聽器。這樣,當(dāng)組件不再需要時(shí),通過(guò)調(diào)用 destroy 方法,可以確保不會(huì)有任何遺留的引用,從而避免內(nèi)存泄漏。

使用定時(shí)器時(shí)及時(shí)清理示例:

class TimerComponent {
  private timerId: number;


  constructor() {
    this.timerId = window.setInterval(() => {
      console.log('Timer tick');
      // 定時(shí)器執(zhí)行的代碼
    }, 1000);
  }


  // 組件被銷毀時(shí),清除定時(shí)器
  destroy() {
    clearInterval(this.timerId);
  }
}


const myTimerComponent = new TimerComponent();
// 當(dāng)定時(shí)器組件不再需要時(shí),調(diào)用 destroy 方法
// myTimerComponent.destroy();

在這個(gè)例子中,TimerComponent 類使用 setInterval 創(chuàng)建了一個(gè)定時(shí)器。在 destroy 方法中,使用 clearInterval 清除了定時(shí)器,這樣可以避免定時(shí)器繼續(xù)執(zhí)行并引用 TimerComponent 實(shí)例,從而避免內(nèi)存泄漏。

我們可以看到及時(shí)清理不再使用的對(duì)象對(duì)于防止內(nèi)存泄漏是多么重要。在實(shí)際開發(fā)中,我們應(yīng)該養(yǎng)成良好的習(xí)慣,確保在對(duì)象不再需要時(shí),清理所有相關(guān)的資源。

5. 使用事件監(jiān)聽時(shí)注意移除監(jiān)聽器:

  • 在添加事件監(jiān)聽器時(shí),確保在不需要監(jiān)聽時(shí)移除它們,否則即使對(duì)象本身不再被使用,事件監(jiān)聽器也會(huì)保持對(duì)象的引用,導(dǎo)致內(nèi)存泄漏。

在 JavaScript 或 TypeScript 中,使用事件監(jiān)聽是常見的交互方式,但如果沒有在適當(dāng)?shù)臅r(shí)候移除監(jiān)聽器,可能會(huì)導(dǎo)致內(nèi)存泄漏。如何正確地添加和移除事件監(jiān)聽器,上代碼:

未移除事件監(jiān)聽器的示例:

class Widget {
  private element: HTMLElement;


  constructor(selector: string) {
    this.element = document.querySelector(selector)!;
    this.element.addEventListener('click', this.handleClick);
  }


  // 處理點(diǎn)擊事件的方法
  handleClick = () => {
    console.log('Widget clicked');
  }


  // 假設(shè)有一個(gè)方法來(lái)銷毀 Widget 實(shí)例,但沒有移除事件監(jiān)聽器
  destroy() {
    // 應(yīng)該在這里移除事件監(jiān)聽器,但被遺漏了
    // this.element.removeEventListener('click', this.handleClick);
  }
}


const widget = new Widget('#myWidget');
// 當(dāng) widget 不再需要時(shí),應(yīng)該調(diào)用 destroy 方法
// widget.destroy();

在這個(gè)例子中,Widget 類在構(gòu)造函數(shù)中為元素添加了一個(gè)點(diǎn)擊事件監(jiān)聽器。然而,在 destroy 方法中,我們忘記了移除這個(gè)監(jiān)聽器。如果 widget 實(shí)例被銷毀而沒有調(diào)用 destroy 方法,事件監(jiān)聽器仍然存在,這將導(dǎo)致 Widget 實(shí)例和它的 handleClick 方法被持續(xù)引用,從而造成內(nèi)存泄漏。

正確移除事件監(jiān)聽器的示例:

class Widget {
  private element: HTMLElement;


  constructor(selector: string) {
    this.element = document.querySelector(selector)!;
    this.element.addEventListener('click', this.handleClick);
  }


  // 處理點(diǎn)擊事件的方法
  handleClick = () => {
    console.log('Widget clicked');
  }


  // 銷毀 Widget 實(shí)例,并移除事件監(jiān)聽器
  destroy() {
    this.element.removeEventListener('click', this.handleClick);
  }
}


const widget = new Widget('#myWidget');
// 當(dāng) widget 不再需要時(shí),調(diào)用 destroy 方法
widget.destroy();

在這個(gè)改進(jìn)的例子中,Widget 類在 destroy 方法中正確地移除了點(diǎn)擊事件監(jiān)聽器。這樣,當(dāng) widget 實(shí)例不再需要時(shí),通過(guò)調(diào)用 destroy 方法,可以確保不會(huì)有任何遺留的引用,從而避免內(nèi)存泄漏。

使用事件委托的示例:

class WidgetManager {
  private container: HTMLElement;


  constructor(selector: string) {
    this.container = document.querySelector(selector)!;
    this.container.addEventListener('click', this.handleClick);
  }


  // 使用事件委托來(lái)處理子元素的點(diǎn)擊事件
  handleClick = (event: MouseEvent) => {
    const target = event.target as HTMLElement;
    if (target.classList.contains('widget')) {
      console.log('Widget clicked');
    }
  }


  // 銷毀 WidgetManager 實(shí)例,并移除事件監(jiān)聽器
  destroy() {
    this.container.removeEventListener('click', this.handleClick);
  }
}


const widgetManager = new WidgetManager('#widgetsContainer');
// 當(dāng) widgetManager 不再需要時(shí),調(diào)用 destroy 方法
widgetManager.destroy();

在這個(gè)例子中,WidgetManager 類使用事件委托來(lái)處理所有子元素的點(diǎn)擊事件。這樣做的好處是,即使子元素是后來(lái)動(dòng)態(tài)添加的,我們也不需要為它們單獨(dú)添加事件監(jiān)聽器。當(dāng) widgetManager 實(shí)例不再需要時(shí),通過(guò)調(diào)用 destroy 方法,可以移除事件監(jiān)聽器,避免內(nèi)存泄漏。

我們可以看到在適當(dāng)?shù)臅r(shí)候移除事件監(jiān)聽器對(duì)于防止內(nèi)存泄漏是多么重要。在實(shí)際開發(fā)中,我們應(yīng)該確保在組件或?qū)ο箐N毀時(shí),清理所有相關(guān)的事件監(jiān)聽器。

6. 合理使用閉包:

  • 閉包可以持續(xù)訪問(wèn)函數(shù)外部的變量,如果不當(dāng)使用,可能會(huì)導(dǎo)致內(nèi)存泄漏。確保在不需要閉包時(shí)釋放相關(guān)資源。

    閉包是一個(gè)強(qiáng)大的特性,它允許一個(gè)函數(shù)訪問(wèn)其定義時(shí)的作用域鏈。然而,不當(dāng)使用閉包可能會(huì)導(dǎo)致內(nèi)存泄漏,因?yàn)殚]包會(huì)保持對(duì)外部作用域的引用,從而阻止垃圾回收。

不當(dāng)使用閉包的示例:

// 假設(shè)我們有一個(gè)函數(shù),用于創(chuàng)建計(jì)數(shù)器
function createCounter() {
  let count = 0;
  return function() {
    console.log(++count);
  };
}


const counter = createCounter();
counter(); // 輸出:1
counter(); // 輸出:2


// 假設(shè)我們不再需要這個(gè)計(jì)數(shù)器,但是由于閉包,count 變量仍然被引用
// 這可能會(huì)導(dǎo)致內(nèi)存泄漏,如果 createCounter 被頻繁調(diào)用

在這個(gè)例子中,每次調(diào)用 createCounter 都會(huì)創(chuàng)建一個(gè)新的閉包,它捕獲了 count 變量。如果 createCounter 被頻繁調(diào)用,每個(gè)閉包都會(huì)保持對(duì) count 的引用,即使 counter 函數(shù)不再被使用。

合理使用閉包的示例:

// 改進(jìn)后的計(jì)數(shù)器函數(shù),使用一個(gè)外部對(duì)象來(lái)存儲(chǔ)計(jì)數(shù)
const counter = (function() {
  let count = 0;
  return {
    increment: function() {
      console.log(++count);
    },
    value: function() {
      return count;
    }
  };
})();


counter.increment(); // 輸出:1
counter.increment(); // 輸出:2


// 當(dāng)計(jì)數(shù)器不再需要時(shí),可以將其設(shè)置為 null,幫助垃圾回收器回收內(nèi)存
counter = null;

在這個(gè)改進(jìn)的例子中,我們使用了一個(gè)立即執(zhí)行的函數(shù)表達(dá)式(IIFE)來(lái)創(chuàng)建一個(gè)包含 count 變量的對(duì)象。這樣,所有的計(jì)數(shù)器都共享同一個(gè) count 變量,而不是每個(gè)閉包都有自己的副本。當(dāng)計(jì)數(shù)器不再需要時(shí),我們可以將 counter 設(shè)置為 null,這有助于垃圾回收器回收內(nèi)存。

使用閉包進(jìn)行數(shù)據(jù)綁定的示例:

// 一個(gè)簡(jiǎn)單的數(shù)據(jù)綁定函數(shù)
function bindData(element, data) {
  const template = document.createElement('div');
  template.innerHTML = `<strong>${data.name}</strong>: ${data.value}`;
  element.appendChild(template);


  // 使用閉包來(lái)更新數(shù)據(jù)
  return function update(newData) {
    template.innerHTML = `<strong>${newData.name}</strong>: ${newData.value}`;
  };
}


const dataWidget = bindData(document.body, { name: 'Initial', value: 'Data' });
dataWidget({ name: 'Updated', value: 'Data' });
// 當(dāng)數(shù)據(jù)綁定不再需要時(shí),可以將其設(shè)置為 null
dataWidget = null;

在這個(gè)例子中,bindData 函數(shù)創(chuàng)建了一個(gè)閉包,用于更新綁定到 DOM 元素的數(shù)據(jù)。當(dāng)數(shù)據(jù)更新時(shí),我們調(diào)用返回的 update 函數(shù)。當(dāng)數(shù)據(jù)綁定不再需要時(shí),我們可以將 dataWidget 設(shè)置為 null,這有助于垃圾回收器回收內(nèi)存。

我們應(yīng)該確保在不需要閉包時(shí)釋放相關(guān)資源,以避免不必要的內(nèi)存占用。

7. 利用垃圾回收機(jī)制:

  • 理解 ArkTS 的垃圾回收機(jī)制,合理組織代碼結(jié)構(gòu),以便于垃圾回收器高效工作。

在 ArkTS 中,利用垃圾回收機(jī)制同樣重要,因?yàn)樗梢詭椭_發(fā)者管理內(nèi)存,避免內(nèi)存泄漏:

不利用垃圾回收機(jī)制的示例:

@Entry
@Component
struct MyComponent {
  private resource: any;


  build() {
    // 假設(shè)這里加載了一個(gè)資源,但沒有提供釋放資源的方法
    this.resource = this.loadResource('resource.json');
  }


  private loadResource(url: string): any {
    // 資源加載邏輯
    return new ResourceData();
  }


  // 組件銷毀時(shí),沒有釋放資源
  onDestroy() {
    // 應(yīng)該在這里釋放資源,但被遺漏了
  }
}

在這個(gè)例子中,MyComponent 組件在 build 方法中加載了一個(gè)資源,但沒有提供釋放資源的方法。在組件銷毀時(shí),也沒有釋放資源,這可能會(huì)導(dǎo)致內(nèi)存泄漏。

利用垃圾回收機(jī)制的示例:

@Entry
@Component
struct MyComponent {
  private resource: any;


  build() {
    // 假設(shè)這里加載了一個(gè)資源,并提供了釋放資源的方法
    this.resource = this.loadResource('resource.json');
  }


  private loadResource(url: string): any {
    // 資源加載邏輯
    return new ResourceData();
  }


  private releaseResource() {
    // 釋放資源邏輯
    this.resource = null;
  }


  // 組件銷毀時(shí),釋放資源
  onDestroy() {
    this.releaseResource();
  }
}

在這個(gè)改進(jìn)的例子中,MyComponent 組件在 build 方法中加載了一個(gè)資源,并在 releaseResource 方法中提供了釋放資源的邏輯。在組件銷毀時(shí),調(diào)用 releaseResource 方法來(lái)釋放資源,這樣可以幫助垃圾回收器回收資源占用的內(nèi)存。

利用垃圾回收機(jī)制的另一個(gè)示例:

@Entry
@Component
struct MyComponent {
  private timerId: number;


  build() {
    // 設(shè)置一個(gè)定時(shí)器,用于定期執(zhí)行某些操作
    this.timerId = setInterval(() => {
      this.performTask();
    }, 1000);
  }


  private performTask() {
    // 執(zhí)行某些任務(wù)
    console.log('Task performed');
  }


  // 組件銷毀時(shí),清除定時(shí)器
  onDestroy() {
    clearInterval(this.timerId);
  }
}

在這個(gè)例子中,MyComponent 組件在 build 方法中設(shè)置了一個(gè)定時(shí)器。在組件銷毀時(shí),調(diào)用 clearInterval 來(lái)清除定時(shí)器,這樣可以避免定時(shí)器繼續(xù)執(zhí)行并引用組件,從而避免內(nèi)存泄漏。

我們可以看到在 ArkTS 中如何通過(guò)編寫良好的代碼習(xí)慣來(lái)配合垃圾回收機(jī)制,確保及時(shí)釋放不再需要的資源。

8. 避免不必要的對(duì)象創(chuàng)建:

  • 在循環(huán)或頻繁調(diào)用的函數(shù)中,避免創(chuàng)建不必要的新對(duì)象,這會(huì)增加垃圾回收的負(fù)擔(dān)。

在 ArkTS 中,避免不必要的對(duì)象創(chuàng)建是優(yōu)化性能和內(nèi)存使用的一個(gè)重要方面。如何在 ArkTS 中避免不必要的對(duì)象創(chuàng)建呢,來(lái)看一下代碼示例:

不必要的對(duì)象創(chuàng)建示例:

@Entry
@Component
struct MyComponent {
  build() {
    for (let i = 0; i < 1000; i++) {
      // 在循環(huán)中創(chuàng)建了1000個(gè)不必要的新對(duì)象
      const data = this.createDataObject(i);
      console.log(data);
    }
  }


  private createDataObject(index: number): DataObject {
    // 假設(shè) DataObject 是一個(gè)復(fù)雜的對(duì)象
    return new DataObject(index);
  }
}


class DataObject {
  constructor(public index: number) {
    // 構(gòu)造函數(shù)中可能包含一些初始化邏輯
  }
}

在這個(gè)例子中,MyComponent 組件的 build 方法在循環(huán)中創(chuàng)建了1000個(gè) DataObject 實(shí)例。如果這些對(duì)象在循環(huán)之后不再需要,這種創(chuàng)建和立即丟棄的做法會(huì)導(dǎo)致不必要的內(nèi)存分配和潛在的性能問(wèn)題。

避免不必要的對(duì)象創(chuàng)建示例:

@Entry
@Component
struct MyComponent {
  private dataObjects: DataObject[] = [];


  build() {
    for (let i = 0; i < 1000; i++) {
      // 復(fù)用已有的對(duì)象,而不是在每次迭代中創(chuàng)建新對(duì)象
      if (!this.dataObjects[i]) {
        this.dataObjects[i] = this.createDataObject(i);
      } else {
        this.dataObjects[i].update(i); // 假設(shè) DataObject 有一個(gè)更新方法
      }
      console.log(this.dataObjects[i]);
    }
  }


  private createDataObject(index: number): DataObject {
    return new DataObject(index);
  }
}


class DataObject {
  constructor(public index: number) {
  }


  update(newIndex: number) {
    this.index = newIndex;
  }
}

在這個(gè)改進(jìn)的例子中,MyComponent 組件維護(hù)了一個(gè) DataObject 數(shù)組。在循環(huán)中,它首先檢查是否已經(jīng)存在對(duì)象,如果不存在,則創(chuàng)建新對(duì)象;如果已存在,則調(diào)用 update 方法更新對(duì)象的數(shù)據(jù)。這種方式避免了在每次迭代中創(chuàng)建新對(duì)象,從而減少了內(nèi)存分配和提高了性能。

使用對(duì)象池模式避免不必要的對(duì)象創(chuàng)建示例:

@Entry
@Component
struct MyComponent {
  private objectPool: DataObject[] = new Array(1000).fill(null).map(() => new DataObject());


  build() {
    for (let i = 0; i < 1000; i++) {
      // 從對(duì)象池中獲取對(duì)象,而不是每次都創(chuàng)建新對(duì)象
      const data = this.objectPool[i];
      data.update(i);
      console.log(data);
    }
  }
}


class DataObject {
  constructor(public index: number) {
  }


  update(newIndex: number) {
    this.index = newIndex;
  }
}

在這個(gè)例子中,MyComponent 組件使用了一個(gè)對(duì)象池來(lái)管理 DataObject 實(shí)例。對(duì)象池在組件初始化時(shí)一次性創(chuàng)建了足夠數(shù)量的對(duì)象,并在循環(huán)中復(fù)用這些對(duì)象。這種方法可以顯著減少對(duì)象創(chuàng)建和銷毀的開銷,特別是在對(duì)象生命周期短且頻繁創(chuàng)建銷毀的場(chǎng)景中。

開發(fā)中,我們要考慮對(duì)象的生命周期和使用場(chǎng)景,盡可能地復(fù)用對(duì)象,或者使用對(duì)象池模式來(lái)管理對(duì)象的創(chuàng)建和銷毀。

9. 使用對(duì)象池模式:

  • 對(duì)于頻繁創(chuàng)建和銷毀的對(duì)象,可以考慮使用對(duì)象池模式來(lái)重用對(duì)象,減少內(nèi)存分配和回收的開銷。

對(duì)象池模式是一種常用的優(yōu)化技術(shù),特別是在處理大量短生命周期對(duì)象時(shí),它可以幫助減少內(nèi)存分配和垃圾回收的開銷。我以游戲場(chǎng)景為例,來(lái)講一下如何使用對(duì)象池模式:

未使用對(duì)象池模式的示例:

@Entry
@Component
struct GameComponent {
  private poolSize: number = 10;


  build() {
    for (let i = 0; i < this.poolSize; i++) {
      this.spawnEnemy();
    }
  }


  private spawnEnemy() {
    // 創(chuàng)建一個(gè)新的敵人對(duì)象
    const enemy = new Enemy();
    // 將敵人添加到游戲世界
    this.addEnemyToGameWorld(enemy);
  }


  private addEnemyToGameWorld(enemy: Enemy) {
    // 添加到游戲世界的邏輯
    console.log('Enemy added to game world:', enemy);
  }
}


class Enemy {
  constructor() {
    // 敵人對(duì)象的初始化邏輯
    console.log('Enemy created');
  }
}

在這個(gè)例子中,GameComponent 組件在 build 方法中循環(huán)創(chuàng)建了10個(gè) Enemy 對(duì)象。每次調(diào)用 spawnEnemy 方法都會(huì)創(chuàng)建一個(gè)新的 Enemy 實(shí)例,這在創(chuàng)建大量敵人時(shí)可能會(huì)導(dǎo)致性能問(wèn)題。

使用對(duì)象池模式的示例:

@Entry
@Component
struct GameComponent {
  private enemyPool: Enemy[] = [];
  private poolSize: number = 10;


  onCreate() {
    // 初始化敵人對(duì)象池
    for (let i = 0; i < this.poolSize; i++) {
      this.enemyPool.push(new Enemy());
    }
  }


  build() {
    for (let i = 0; i < this.poolSize; i++) {
      this.spawnEnemy();
    }
  }


  private spawnEnemy() {
    // 從對(duì)象池中獲取一個(gè)敵人對(duì)象
    const enemy = this.enemyPool.shift(); // 移除并獲取數(shù)組的第一個(gè)元素
    if (enemy) {
      // 將敵人添加到游戲世界
      this.addEnemyToGameWorld(enemy);
    } else {
      // 如果對(duì)象池為空,則創(chuàng)建一個(gè)新的敵人對(duì)象
      const newEnemy = new Enemy();
      this.addEnemyToGameWorld(newEnemy);
      this.enemyPool.push(newEnemy); // 將新創(chuàng)建的敵人對(duì)象放回池中
    }
  }


  private addEnemyToGameWorld(enemy: Enemy) {
    // 添加到游戲世界的邏輯
    console.log('Enemy added to game world:', enemy);
  }
}


class Enemy {
  constructor() {
    // 敵人對(duì)象的初始化邏輯
    console.log('Enemy created');
  }


  reset() {
    // 重置敵人對(duì)象的狀態(tài),以便再次使用
    console.log('Enemy reset for reuse');
  }
}

在這個(gè)改進(jìn)的例子中,GameComponent 組件使用了一個(gè) enemyPool 數(shù)組作為對(duì)象池來(lái)管理 Enemy 對(duì)象。在 onCreate 方法中,我們預(yù)先創(chuàng)建了一定數(shù)量的 Enemy 對(duì)象并放入池中。當(dāng)需要?jiǎng)?chuàng)建新的敵人時(shí),我們首先嘗試從對(duì)象池中獲取一個(gè)現(xiàn)有的對(duì)象。如果對(duì)象池為空,我們才創(chuàng)建一個(gè)新的敵人對(duì)象,并在添加到游戲世界后將其放回池中。此外,我們添加了一個(gè) reset 方法來(lái)重置敵人對(duì)象的狀態(tài),以便它可以被重復(fù)使用。

使用對(duì)象池模式可以顯著減少在游戲或動(dòng)畫中創(chuàng)建和銷毀對(duì)象的次數(shù),從而提高性能和減少內(nèi)存壓力。在 ArkTS 中,這種模式尤其適用于那些頻繁創(chuàng)建和銷毀對(duì)象的場(chǎng)景,如粒子系統(tǒng)、游戲中的敵人、子彈等。

最后

有效地管理 ArkTS 應(yīng)用中的內(nèi)存使用,減少內(nèi)存泄漏的風(fēng)險(xiǎn),并提高應(yīng)用的性能和穩(wěn)定性,這在 ArkTS編碼中同樣至關(guān)重要,你在使用 ArkTS的過(guò)程中,還有其它有效管理內(nèi)存的經(jīng)驗(yàn)嗎,歡迎評(píng)論區(qū)告訴我,國(guó)產(chǎn)替代,支持鴻蒙,我們都是一份子。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)