Pular para o conteúdo principal

Verificação de marca privada, também conhecido como `#foo in obj`

· Leitura de 3 minutos
Marja Hölttä ([@marjakh](https://twitter.com/marjakh))

O operador in pode ser usado para testar se o objeto dado (ou qualquer objeto em sua cadeia de protótipos) possui a propriedade dada:

const o1 = {'foo': 0};
console.log('foo' in o1); // true
const o2 = {};
console.log('foo' in o2); // false
const o3 = Object.create(o1);
console.log('foo' in o3); // true

O recurso de verificações de marca privada estende o operador in para suportar campos de classe privados:

class A {
  static test(obj) {
    console.log(#foo in obj);
  }
  #foo = 0;
}

A.test(new A()); // true
A.test({}); // false

class B {
 #foo = 0;
}

A.test(new B()); // false; não é o mesmo #foo

Como os nomes privados estão disponíveis apenas dentro da classe que os define, o teste também deve ocorrer dentro da classe, por exemplo, em um método como static test acima.

As instâncias de subclasses recebem campos privados da classe pai como propriedades próprias:

class SubA extends A {};
A.test(new SubA()); // true

Mas objetos criados com Object.create (ou que têm o protótipo definido posteriormente via o setter __proto__ ou Object.setPrototypeOf) não recebem os campos privados como propriedades próprias. Como a busca de campos privados funciona apenas em propriedades próprias, o operador in não encontra esses campos herdados:

const a = new A();
const o = Object.create(a);
A.test(o); // false, campo privado é herdado e não próprio
A.test(o.__proto__); // true

const o2 = {};
Object.setPrototypeOf(o2, a);
A.test(o2); // false, campo privado é herdado e não próprio
A.test(o2.__proto__); // true

Acessar um campo privado inexistente gera um erro - ao contrário das propriedades normais, onde acessar uma propriedade inexistente retorna undefined, mas não gera erro. Antes das verificações de marca privada, os desenvolvedores eram forçados a usar um try-catch para implementar comportamento de fallback para casos em que um objeto não possuía o campo privado necessário:

class D {
  use(obj) {
    try {
      obj.#foo;
    } catch {
      // Fallback para o caso de obj não possuir #foo
    }
  }
  #foo = 0;
}

Agora a existência do campo privado pode ser testada usando uma verificação de marca privada:

class E {
  use(obj) {
    if (#foo in obj) {
      obj.#foo;
    } else {
     // Fallback para o caso de obj não possuir #foo
    }
  }
  #foo = 0;
}

Mas tenha cuidado - a existência de um campo privado não garante que o objeto tenha todos os campos privados declarados em uma classe! O exemplo a seguir mostra um objeto meio construído que possui apenas um dos dois campos privados declarados em sua classe:

let halfConstructed;
class F {
  m() {
    console.log(#x in this); // true
    console.log(#y in this); // false
  }
  #x = 0;
  #y = (() => {
halfConstructed = this;
throw 'error';
})();
}

try {
  new F();
} catch {}

halfConstructed.m();

Suporte para verificação de marca privada