DMarlon Studio

Lesson 6 · 6 of 20

Static members and Symbol · JavaScript

Use static in JavaScript for class-wide counters and factory methods, and Symbol for unique keys and custom object conversion.

JavaScript · Static members and Symbol in DMarlon Studio: the example's code and its output in the terminal

Anything marked `static` belongs to the class, not to each object. Order.TAX is a constant shared by every order, and static #nextId is a private class-level counter: each new Order reads and bumps it with Order.#nextId++, so the orders get 1, 2 and 3 with no repeats.

A static method is called on the class itself: Order.fromLine("Ana;100"). This is a very common pattern, the factory: it turns a line of text (from a CSV, say) into an order, splitting customer and amount with split.

A `Symbol` is a unique value that can be used as a property key. JavaScript defines a few well-known symbols that change how the language behaves: Symbol.toPrimitive decides what the object turns into (text inside a template literal, or the amount with tax in Number(o), which the sum uses for the total), and Symbol.toStringTag changes what Object.prototype.toString shows: [object Order].

You can also create your own symbols: orders[2][internal] stores a value that does not show up in Object.keys and cannot clash with any other property, because every symbol is different (Symbol("id") === Symbol("id") is false). Symbol.for("id"), on the other hand, uses a global registry and always returns the same one.

Code · main.js

class Order {  static #nextId = 1; // belongs to the class, shared by all orders  static TAX = 0.21;  static fromLine(line) { // static factory: Order.fromLine("Ana;100")    const [customer, amount] = line.split(";");    return new Order(customer, Number(amount));  }  constructor(customer, amount) {    this.id = Order.#nextId++;    this.customer = customer;    this.amount = amount;  }  // Well-known symbols change how the language treats the object.  get [Symbol.toStringTag]() {    return "Order";  }  [Symbol.toPrimitive](hint) {    if (hint === "number") return this.amount * (1 + Order.TAX);    return `Order #${this.id} for ${this.customer}`;  }}const orders = ["Ana;100", "Luis;40", "Marta;250"].map((line) => Order.fromLine(line));console.log(`${orders[0]} | ${orders[2]}`);console.log("Total with tax:", orders.reduce((sum, o) => sum + Number(o), 0).toFixed(2));console.log(Object.prototype.toString.call(orders[1]), "| TAX:", Order.TAX);// A Symbol is a unique key: it never clashes with other properties.const internal = Symbol("internal");orders[2][internal] = "priority";console.log("Keys:", Object.keys(orders[2]).join(", "));console.log("Symbol value:", orders[2][internal]);console.log("Unique:", Symbol("id") === Symbol("id"), "| registry:", Symbol.for("id") === Symbol.for("id"));

Output · node main.js

Order #1 for Ana | Order #3 for Marta
Total with tax: 471.90
[object Order] | TAX: 0.21
Keys: id, customer, amount
Symbol value: priority
Unique: false | registry: true

Key points

  • static members belong to the class and are used as Class.member.
  • A static factory method builds objects from other data.
  • Every Symbol() is unique, so it makes a key that never clashes.
  • Symbol.toPrimitive and Symbol.toStringTag change how an object converts and displays.

Exercise

Add a static count() method to Order that returns how many orders have been created (based on #nextId), and use your own Symbol("notes") to attach a note to Luis's order.