ArkTS is the application development language for the Hongmeng ecosystem, which is optimized and customized based on TypeScript to fit the needs of the Hongmeng system.
The following is for effective memory management and avoiding memory leaks in ArkTS:
1. Utilizationconst
cap (a poem)let
Declare variables wisely:
- utilization
const
Declaring variables that will not be reassigned helps to ensure that variables are invariant and may allow the compiler to perform more optimizations. - utilization
let
Declare variables that need to be reassigned and avoid using thevar
Becausevar
causes the variable to be elevated to the top of the function's scope, which may cause unexpected errors.
In ArkTS.const
cap (a poem)let
are keywords used to declare variables that differ in terms of scope and mutability. The following keywords are used to declare variables using theconst
cap (a poem)let
Comparison of sample code for sensible declaration of variables:
utilizationconst
Declare invariant variables:
// The correct way to use const is to declare a variable that will not be reassigned.
const PI = 3.14159; // PI is a constant and should not be reassigned.
// Attempting to reassign it will result in a compilation error
// PI = 3.14; // Error: Cannot assign to 'PI' because it is a read-only property.
utilizationlet
Declare mutable variables:
// The correct way to use it: declare a variable that can be reassigned using let
let count = 0; // count is a variable that can be reassigned.
// can be reassigned
count = 1; (count); // output: 1
(count); // Output: 1
Compare and contrast examples:
function vgFunction() {
// Declare a constant with const to indicate that the variable should not be modified.
const name = "VG"; (name); // Output: VG
(name); // Output: VG
// Use let to declare a variable that may be modified.
let age = 18; (age); // Output: 18
(age); // Output: 18, always 18
// Modify the variable based on certain conditions
if (age < 30) {
age = 30; }
}
(age); // Output: 30
}
vgFunction().
In this example, thename
is declared as a constant, indicating that its value should not change, and theage
is declared as a variable, indicating that its value may change. A variable is declared as a variable using theconst
cap (a poem)let
The intended use of the variable can be clearly expressed, contributing to the readability and maintainability of the code.
Avoid usingvar
Examples of:
// The use of var is not recommended because it raises the variable to the top of the function's scope.
function exampleFunction() {
var globalVar = "I name is VG"; // This is actually a global variable.
(globalVar); // output: I name is VG
}
exampleFunction(); // This is actually a global variable.
(globalVar); // Output: I name is VG
In this example, using thevar
Joint DeclarationglobalVar
is actually a global variable, even if it is declared inside the function. This can lead to unintended side effects because global variables can be accessed and modified anywhere in the program. Therefore, it is recommended to use theconst
maybelet
in place ofvar
。
2. Avoid global variables:
- Minimize the use of global variables, which exist throughout the application lifecycle, are difficult to manage, and are prone to memory leaks.
Global variables are variables that are declared in a global scope and they can be accessed and modified anywhere throughout the program. Excessive use of global variables can make code difficult to maintain, understand, and debug because they can be changed anywhere, increasing code coupling. Below is a comparison of sample code that avoids global variables:
Example of using global variables:
// Global variables
var globalCounter = 0;
function increment() {
globalCounter++; // Modify the global variable directly.
}
function decrement() {
globalCounter--; // Modify the global variable directly.
}
increment().
(globalCounter); // Output: 1
decrement(); (globalCounter); // Output: 1
(globalCounter); // Output: 0
In this example, theglobalCounter
is a global variable that can be used in theincrement
cap (a poem)decrement
function is directly accessed and modified. This can lead to inadvertent modification of this variable in other parts of the program, resulting in hard-to-trace errors.
Example of avoiding global variables:
// Avoid global variables and use local variables and parameter passing instead.
function counterManager() {
let counter = 0; // local variable
function increment() { let counter = 0; // local variable
counter++; // modify local variable
}
function decrement() {
counter--; // Modify local variable
}
return {
increment: increment, decrement: decrement, }
decrement: decrement, getCount: function () { { { { { { { { { }
getCount: function () {
return counter; }
}
}; }
}
const counter = counterManager(); // create a local variable counter to hold the manager object
(); // Create a local variable counter to hold the manager object.
(()); // Output: 1
(); (()); // Output: 1
(()); // Output: 0
In this example, we create acounterManager
function, which returns an object containing theincrement
、decrement
cap (a poem)getCount
Methods. These methods operate on thecounterManager
Local variables inside a functioncounter
, instead of a global variable. In this way.counter
The value is then encapsulated in thecounterManager
function's scope and does not affect other variables in the global scope.
In this way, we reduce the use of global variables and improve the encapsulation and modularity of the code, making it easier to maintain and understand. It also helps to avoid naming conflicts and unintended side effects that global variables may cause.
3. Use Weak References:
- Weak references can be used for objects that do not need to be held for long periods of time so that the garbage collector can more easily reclaim them.
There is no built-in concept of Weak References in ArkTS or TypeScript because JavaScript engines (including V8, i.e., and most browser JavaScript engines) use garbage collection to manage memory by default. Weak references are usually references that do not prevent the garbage collector from reclaiming the objects they refer to.
However, there are design patterns that we can use to model the behavior of weak references, especially when dealing with large objects or circular references. How to avoid memory leaks caused by circular references, look at this example:
Examples of circular references that can lead to memory leaks:
class Person {
name: string; friends: Person[]; // list of friends
friends: Person[]; // list of friends
constructor(name: string) {
= constructor(name: string) { name; friends: Person[]; // list of friends.
= []; // List of friends.
}
addFriend(friend: Person) {
(friend); // This creates a circular reference, (this) will make person and friend refer to each other.
// This creates a circular reference, and (this) causes person and friend to reference each other.
(this); // This creates a circular reference.
}
}
const personA = new Person("VG");
const personB = new Person("Vin");
(personB); } const personA = new Person("VG")
// At this point, personA and personB are referencing each other, creating a circular reference.
In this example, thePerson
Each instance of the class maintains a list of friends, and when aPerson
instance is added to anotherPerson
instance's friends list, it also adds the latter to the former's friends list, creating a circular reference.
Example of avoiding circular references:
In order to avoid circular references, we can avoid the need for direct references in thePerson
classes to add references to each other, but instead manage the relationship in other ways, such as using an external mapping or service to manage friendships.
class Person {
name: string; friends: string[]; // A list of friends.
friends: string[]; // A list of friends, storing only the names and not a direct reference to the object.
constructor(name: string) {
= name.
= []; }
}
addFriend(name: string) {
(name).
// Instead of creating a circular reference, the name of the friend is added to the list
}
}
const personA = new Person("VG");
const personB = new Person("Vin"); }
(); // At this point, the only person in personA's friends list is personB.
// At this point, personA's friends list only contains the name of personB, so there's no circular reference.
In this improved example, we no longer directly replace thePerson
object is added to the friends list, and instead only the friend's name is stored. This way, even if thePerson
There are multiple connections between objects and no circular references are formed, thus avoiding potential memory leaks.
How to Avoid Circular References by Design Patterns is a common approach to managing memory and avoiding memory leaks in JavaScript and TypeScript. In some specific JavaScript environments, such as , it is possible to implement weak references more directly using built-in objects like WeakRef and WeakMap. In most front-end JavaScript environments, however, these objects are not available.
4. Timely clean-up of objects no longer in use:
- When the object is no longer needed, it should be manually set to the
null
or delete its reference so that the garbage collector can reclaim this memory.
In JavaScript or TypeScript, cleaning up objects that are no longer in use is an important strategy for avoiding memory leaks. This usually involves removing event listeners, canceling network requests, destroying timers, and so on. Below is a comparison of sample code for a business scenario that demonstrates how to clean up objects that are no longer in use in a timely manner:
Examples of objects that are no longer in use are not cleaned up in a timely manner:
// Let's say we have a component that subscribes to an event when it is loaded
class Component {
private eventListener: () => void;
constructor() {
= () => {
('Event triggered'); }
};
('customEvent', ); }
}
// Event listeners should be cleaned up when the component is destroyed
destroy() {
// Forgot to remove the event listener
// ('customEvent', ); }
}
}
const myComponent = new Component(); // The destroy method should be called when the component is no longer needed.
// When the component is no longer needed, the destroy method should be called
// (); } const myComponent = new Component()
In this example, theComponent
class adds an event listener at construction time, but in thedestroy
method forgets to remove this listener. If themyComponent
is destroyed without calling thedestroy
method, the event listener still exists, which will result in a memory leak because themyComponent
and itseventListener
method is still referenced by the event listener.
Clean up examples of objects that are no longer in use in a timely manner:
class Component {
private eventListener: () => void;
constructor() {
= () => {
('Event triggered');
};
('customEvent', );
}
// When the component is destroyed,Clean up event listeners in a timely manner
destroy() {
('customEvent', );
}
}
const myComponent = new Component();
// When the component is no longer needed,call (programming) destroy methodologies
();
In this improved example, theComponent
class indestroy
method correctly removes the event listener. This way, when the component is no longer needed, the event listener is removed by calling thedestroy
method, which ensures that there won't be any leftover references, thus avoiding memory leaks.
Clean up examples in a timely manner when using the timer:
class TimerComponent {
private timerId: number;
constructor() {
= (() => {
('Timer tick');
// The code that the timer executes
}, 1000);
}
// Clear the timer when the component is destroyed
destroy() {
clearInterval(); }
}
}
const myTimerComponent = new TimerComponent();
// When the timer component is no longer needed, call the destroy method
// (); } const myTimerComponent = new TimerComponent()
In this example, theTimerComponent
class usagesetInterval
A timer is created. Create a timer in thedestroy
method with theclearInterval
clears the timer, which prevents the timer from continuing to execute and referencing theTimerComponent
instance, thus avoiding memory leaks.
We can see how important it is to prevent memory leaks by cleaning up objects that are no longer in use. In practice, we should make it a good habit to make sure that we clean up all relevant resources when an object is no longer needed.
5. Be careful to remove listeners when using event listeners:
- When adding event listeners, make sure that you remove them when you don't need to listen, otherwise the event listeners will hold references to the object even if the object itself is no longer in use, leading to memory leaks.
Using event listeners is a common interaction in JavaScript or TypeScript, but it can lead to memory leaks if the listeners are not removed at the right time. How to properly add and remove event listeners, on code:
Example of an event listener that has not been removed:
class Widget {
private element: HTMLElement;
constructor(selector: string) {
constructor(constructor: string) { = (selector)! ;
('click', ); }
}
// Methods to handle the click event
handleClick = () => {
('Widget clicked'); }
}
// Assuming there is a method to destroy the Widget instance without removing the event listener
destroy() {
// The event listener should be removed here, but it's not.
// ('click', ); }
}
}
const widget = new Widget('#myWidget'); // The destroy method should be called when the widget is no longer needed.
// When the widget is no longer needed, the destroy method should be called
// (); } const widget = new Widget('#myWidget')
In this example, theWidget
class adds a click event listener to the element in the constructor. However, thedestroy
method, we forgot to remove this listener. If thewidget
instance is destroyed without calling thedestroy
method, the event listener still exists, which causes theWidget
instance and itshandleClick
method is persistently referenced, resulting in a memory leak.
Example of correctly removing an event listener:
class Widget {
private element: HTMLElement;
constructor(selector: string) {
constructor(constructor: string) { = (selector)! ;
('click', ); }
}
// Methods to handle the click event
handleClick = () => {
('Widget clicked'); }
}
// Destroy the Widget instance and remove the event listener
destroy() {
('click', ); }
}
}
const widget = new Widget('#myWidget');
// When the widget is no longer needed, call the destroy method
(); // When the widget is no longer needed, call the destroy method.
In this improved example, theWidget
class indestroy
method correctly removes the click event listener. This way, when thewidget
instances are no longer needed, by calling thedestroy
method, which ensures that there won't be any leftover references, thus avoiding memory leaks.
Example of using event delegates:
class WidgetManager {
private container: HTMLElement;
constructor(selector: string) {
= (selector)!;
('click', );
}
// Using event delegates to handle click events on child elements
handleClick = (event: MouseEvent) => {
const target = as HTMLElement;
if (('widget')) {
('Widget clicked');
}
}
// destroy (by melting or burning) WidgetManager an actual example,and remove the event listener
destroy() {
('click', );
}
}
const widgetManager = new WidgetManager('#widgetsContainer');
// (coll.) fail (a student) widgetManager When no longer needed,call (programming) destroy methodologies
();
In this example, theWidgetManager
class uses event delegates to handle click events for all child elements. The advantage of this is that we don't need to add separate event listeners for child elements, even if they are added dynamically later. When thewidgetManager
instances are no longer needed, by calling thedestroy
method, you can remove the event listener to avoid memory leaks.
We can see how important it is to remove event listeners at the right time to prevent memory leaks. In practice, we should make sure to clean up all related event listeners when the component or object is destroyed.
6. Proper use of closures:
- Closures provide persistent access to variables external to a function, which can lead to memory leaks if used improperly. Make sure to release the associated resources when the closure is not needed.
Closures are a powerful feature that allow a function to access the chain of scopes in which it was defined. However, improper use of closures can lead to memory leaks, as closures maintain references to external scopes, preventing garbage collection.
Examples of improper use of closures:
// Suppose we have a function that creates a counter
function createCounter() {
let count = 0; return function() { function() { function() { function() { function()
return function() {
(++count); }
}; }
}
const counter = createCounter();
counter(); // Output: 1
counter(); // Output: 1
// Let's say we don't need the counter anymore, but the count variable is still referenced because of the closure.
// This could lead to a memory leak if createCounter is called too often.
In this example, each call tocreateCounter
Both create a new closure which captures thecount
variable. If thecreateCounter
is called frequently, each closure maintains access to thecount
references, even if thecounter
function is no longer used.
Examples of proper use of closures:
// Improved counter function that uses an external object to store the count
const counter = (function() {
let count = 0; return { function() { function() { function() { function() { function() { } }
return {
increment: function() {
(++count); }, return { increment: function() { { count = 0; return { count = 0
}, value: function() { (++count); }
value: function() {
return count; }
}
}; }
})().
(); // Output: 1
(); // Output: 2
// When the counter is no longer needed, it can be set to null to help the garbage collector reclaim memory
counter = null;
In this improved example, we use an Immediately Executable Function Expression (IIFE) to create a function that contains thecount
object of the variable. This way, all counters share the samecount
variable, rather than each closure having its own copy. When the counter is no longer needed, we can move thecounter
set tonull
This helps the garbage collector to reclaim memory.
Example of data binding using closures:
// A simple data binding function
function bindData(element, data) {
const template = ('div');
= `<strong> ${}</strong>: ${}`;
(template);
// Update the data using closures
return function update(newData) {
= `<strong>${}</strong>: ${}`;; // Use a closure to update the data.
};
}
const dataWidget = bindData(, { name: 'Initial', value: 'Data' });
dataWidget({ name: 'Updated', value: 'Data' }); } const dataWidget = bindData(, { name: 'Initial', value: 'Data' }); }
// When the data binding is no longer needed, it can be set to null
dataWidget = null;
In this example, thebindData
function creates a closure to update the data bound to the DOM element. When the data is updated, we call the returnedupdate
function. When data binding is no longer necessary, we can set thedataWidget
set tonull
This helps the garbage collector to reclaim memory.
We should make sure that we release the relevant resources when the closure is not needed to avoid unnecessary memory usage.
7. Utilize garbage collection mechanisms:
- Understand the garbage collection mechanism of ArkTS and organize the code structure wisely so that the garbage collector can work efficiently.
Utilizing the garbage collection mechanism is also important in ArkTS as it helps developers manage memory and avoid memory leaks:
Example of not utilizing the garbage collection mechanism:
@Entry
@Component
struct MyComponent {
private resource: any;
build() {
// Assuming a resource is loaded here, but no method is provided to release it
= ('');
}
private loadResource(url: string): any {
// Resource loading logic
return new ResourceData(); }
}
// Component destroyed without releasing the resource
onDestroy() {
// The resource should be released here, but it was missed.
}
}
In this example, theMyComponent
component in thebuild
A resource is loaded in the method, but no method is provided to free the resource. The resource is also not freed when the component is destroyed, which can lead to memory leaks.
Example of utilizing the garbage collection mechanism:
@Entry
@Component
struct MyComponent {
private resource: any;
build() {
// Assuming a resource is loaded here, and a method is provided to release it
= ('');
}
private loadResource(url: string): any {
// Resource loading logic
return new ResourceData(); }
}
private releaseResource() {
// Release the resource logic
= null; }
}
// When the component is destroyed, the resource is released
onDestroy() {
();
}
}
In this improved example, theMyComponent
component in thebuild
method loads a resource in thereleaseResource
method provides the logic for releasing resources. When the component is destroyed, a call to thereleaseResource
method to free the resource, which helps the garbage collector reclaim the memory occupied by the resource.
Another example of utilizing the garbage collection mechanism:
@Entry
@Component
struct MyComponent {
private timerId: number;
build() {
// Set a timer to perform certain actions at regular intervals.
= setInterval(() => {
();
}, 1000);
}
private performTask() {
// Perform some task
('Task performed'); }
}
// Clear the timer when the component is destroyed
onDestroy() {
clearInterval();
}
}
In this example, theMyComponent
component in thebuild
method sets a timer. When the component is destroyed, a call toclearInterval
to clear the timer, which prevents the timer from continuing to execute and referencing the component, thus avoiding memory leaks.
We can see how good code habits can be written in ArkTS to work with the garbage collection mechanism to ensure that resources that are no longer needed are released in a timely manner.
8. Avoid unnecessary object creation:
- Avoid creating unnecessary new objects in loops or frequently called functions, which can increase the burden of garbage collection.
In ArkTS, avoiding unnecessary object creation is an important aspect of optimizing performance and memory usage. How to avoid unnecessary object creation in ArkTS, take a look at the code example:
Example of unnecessary object creation:
@Entry
@Component
struct MyComponent {
build() {
for (let i = 0; i < 1000; i++) {
// create 1000 unnecessary new objects in the loop
const data = (i).
(data);
}
}
private createDataObject(index: number): DataObject {
// Assuming the DataObject is a complex object.
return new DataObject(index);
}
}
class DataObject {
constructor(public index: number) {
// The constructor may contain some initialization logic.
}
}
In this example, theMyComponent
integrated componentbuild
method in the loop creates 1000DataObject
instances. This practice of creating and immediately discarding these objects leads to unnecessary memory allocation and potential performance problems if they are no longer needed after the loop.
Example of avoiding unnecessary object creation:
@Entry
@Component
struct MyComponent {
private dataObjects: DataObject[] = [];
build() {
for (let i = 0; i < 1000; i++) {
// Reusing existing objects,Instead of creating new objects in each iteration
if (![i]) {
[i] = (i);
} else {
[i].update(i); // suppose that... DataObject There is an update method
}
([i]);
}
}
private createDataObject(index: number): DataObject {
return new DataObject(index);
}
}
class DataObject {
constructor(public index: number) {
}
update(newIndex: number) {
= newIndex;
}
}
In this improved example, theMyComponent
component maintains aDataObject
Array. In the loop, it first checks to see if the object already exists; if not, it creates a new object; if it already exists, it calls theupdate
method updates the object's data. This approach avoids creating new objects in each iteration, which reduces memory allocation and improves performance.
Example of using the object pooling pattern to avoid unnecessary object creation:
@Entry
@Component
struct MyComponent {
private objectPool: DataObject[] = new Array(1000).fill(null).map(() => new DataObject());
build() {
for (let i = 0; i < 1000; i++) {
// Getting objects from the object pool,Instead of creating a new object each time
const data = [i];
(i);
(data);
}
}
}
class DataObject {
constructor(public index: number) {
}
update(newIndex: number) {
= newIndex;
}
}
In this example, theMyComponent
The component uses a pool of objects to manageDataObject
instances. The object pool creates a sufficient number of objects at once during component initialization and reuses them in a loop. This approach can significantly reduce the overhead of object creation and destruction, especially in scenarios with short object lifecycles and frequent creation and destruction.
In development, we need to consider the life cycle of the object and use scenarios, reuse objects as much as possible, or use the object pooling pattern to manage the creation and destruction of objects.
9. Use the object pooling model:
- For objects that are created and destroyed frequently, consider using the object pool pattern to reuse objects and reduce the overhead of memory allocation and reclamation.
The object pooling pattern is a common optimization technique, especially when dealing with a large number of short-lived objects, it can help reduce the overhead of memory allocation and garbage collection. Let me take a game scenario as an example to talk about how to use the object pooling pattern:
Example of not using the object pooling pattern:
@Entry
@Component
struct GameComponent {
private poolSize: number = 10;
build() {
for (let i = 0; i < ; i++) {
();
}
}
private spawnEnemy() {
// Create a new enemy object
const enemy = new Enemy();
// Add enemies to the game world
(enemy);
}
private addEnemyToGameWorld(enemy: Enemy) {
// Add logic to the game world
('Enemy added to game world:', enemy);
}
}
class Enemy {
constructor() {
// Initialization Logic for Enemy Objects
('Enemy created');
}
}
In this example, theGameComponent
component in thebuild
method loops through the creation of 10Enemy
object. Each time you call thespawnEnemy
methods all create a newEnemy
instances, which can lead to performance issues when creating a large number of enemies.
Example of using the object pooling pattern:
@Entry
@Component
struct GameComponent {
private enemyPool: Enemy[] = []; private poolSize: number = 10; private poolSize.
onCreate() {
// Initialize the pool of enemy objects
for (let i = 0; i < ; i++) {
(new Enemy()); }
}
}
build() {
for (let i = 0; i < ; i++) {
(); }
}
}
private spawnEnemy() {
// Get an enemy object from the pool
const enemy = (); // remove and get the first element of the array
if (enemy) {
// Add the enemy to the game world
(enemy); } else {
} else {
// If the object pool is empty, create a new enemy object
const newEnemy = new Enemy();
(newEnemy); } else { // If the object pool is empty, create a new enemy object.
(newEnemy); // put the newly created enemy object back into the pool
}
}
private addEnemyToGameWorld(enemy: Enemy) {
// Logic for adding to the game world
('Enemy added to game world:', enemy); }
}
}
class Enemy {
constructor() {
// Initialization logic for the Enemy object
('Enemy created');
}
reset() {
// Reset the state of the enemy object for reuse
('Enemy reset for reuse'); } reset() { // Reset the state of the enemy object for reuse.
}
}
In this improved example, theGameComponent
The component uses aenemyPool
Arrays are managed as object poolsEnemy
Object. In theonCreate
method, we pre-create a certain number ofEnemy
object and put it into the pool. When a new enemy needs to be created, we first try to get an existing object from the object pool. Only if the object pool is empty do we create a new enemy object and put it back in the pool after adding it to the game world. In addition, we add areset
method to reset the state of an enemy object so that it can be reused.
Using the object pooling mode can significantly reduce the number of times an object is created and destroyed in a game or animation, thus improving performance and reducing memory pressure. In ArkTS, this mode is especially useful for scenarios where objects are created and destroyed frequently, such as particle systems, in-game enemies, bullets, and so on.
ultimate
Effective management of memory usage in ArkTS applications reduces the risk of memory leaks and improves the performance and stability of the application, which is also critical in ArkTS coding, do you have any other experience in using ArkTS to effectively manage memory, welcome to tell me in the comment section, domestic alternatives, support Hongmeng, we are all a part of it.