Menu

Modificadores de acceso en TypeScript: public, private y protected

TypeScript tiene tres modificadores de acceso, public, private y protected, además de readonly. Aprende qué permite cada uno, por qué private de TypeScript es una comprobación en tiempo de compilación mientras que los campos #private de JavaScript se aplican en tiempo de ejecución, y cuál elegir.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

TypeScript tiene tres modificadores de acceso para los miembros de una clase: public (el valor por defecto), protected y private. Controlan dónde se puede usar un miembro, y el compilador informa de cualquier acceso desde el sitio equivocado.

La última línea es un error de compilación, pero fíjate en lo que imprimió al ejecutarla de todos modos: 123-45-6789. Es el dato más importante de esta página, y la sección sobre private lo explica.

Qué permite cada modificador

ModificadorDentro de la claseEn una subclaseDesde fueraSe aplica en tiempo de ejecución
public (por defecto)sísísíno hay nada que aplicar
protectedsísínono
privatesínonono
#name (JavaScript)sínonosí
readonlylectura, y escritura en el constructorlecturalecturano

Los modificadores funcionan con campos, métodos, getters y setters, constructores y parameter properties (constructor(private id: string)). Escribir public es opcional, y muchos proyectos lo omiten.

private es una comprobación en tiempo de compilación

TypeScript borra private junto con el resto de los tipos. La clase compilada tiene una propiedad normal, así que todo lo que no pasa por el comprobador de tipos la ve: quien llama desde JavaScript normal, JSON.stringify, Object.keys e incluso la notación con corchetes del propio TypeScript, que se permite como escapatoria deliberada.

Eso está bien para lo que sirve private: decir a otros desarrolladores (y a tu editor) que un miembro es un detalle de implementación. No es una barrera de seguridad, y un campo private acabará en los logs y en las respuestas JSON salvo que lo quites tú.

Campos #private: se aplican en tiempo de ejecución

JavaScript tiene sus propios campos privados, escritos con #. El motor los hace cumplir: fuera de la clase, obj.#field ni siquiera es sintaxis válida, y el campo no aparece en Object.keys, JSON.stringify ni console.log.

Escribir s.#token fuera de la clase es el error TS18013 (Property '#token' is not accessible outside class 'Session' because it has a private identifier), y a diferencia del caso de private, tampoco hay forma de saltárselo en tiempo de ejecución. Consulta campos privados en JavaScript para las reglas en tiempo de ejecución.

private frente a #private: cuál usar

private x#x
Lo compruebael compiladorel motor de JavaScript
Visible para Object.keys / JSON.stringifysíno
Acceso con corchetes obj["x"]permitidoimposible
Una subclase puede declarar su propio xno, chocasí, cada clase tiene su propio #x
Se copia con una expansión { ...obj }síno
Sintaxis en métodosprivate helper()#helper()

Usa #private cuando los datos deban seguir siendo privados en tiempo de ejecución (tokens, estado interno que los usuarios de una librería no deben tocar) o cuando quieras que JSON.stringify los omita. Usa private cuando el objetivo sea solo una API pública limpia, cuando un framework necesite leer el campo por reflexión o para seguir el estilo existente del proyecto. No los combines: private #x es el error TS18010 (An accessibility modifier cannot be used with a private identifier).

protected y las subclases

Un miembro protected está disponible dentro de la clase y en cualquier clase que la extienda, pero no en instancias desde fuera.

Hay una regla que sorprende. Dentro de Polygon, puedes leer sides sobre this o sobre otro Polygon, pero no sobre un Shape cualquiera recibido como parámetro: other.sides con other: Shape es el error TS2446 (Property 'sides' is protected and only accessible through an instance of class 'Polygon'. This is an instance of class 'Shape'.). Una subclase solo puede acceder a miembros protegidos de objetos que pertenecen a su propia rama de la jerarquía.

Una subclase puede hacer público un miembro protegido volviendo a declararlo, pero no puede hacer protegido o privado un miembro público. Vuelve a declararlo con un inicializador (public override sides = 6) o solo como tipo (declare public sides: number). Un simple public sides: number; se rechaza con TS2564 y TS2612, porque con los campos de clase modernos restablecería el valor heredado a undefined después de que super() termine.

readonly

readonly impide reasignar después de la construcción. El campo se puede asignar en su declaración o en el constructor; cualquier asignación posterior es el error TS2540.

Aquí se ven dos límites. readonly es superficial: el array no se puede sustituir, pero su contenido puede cambiar (tipalo como readonly string[] para impedir push). Y, como private, desaparece en tiempo de ejecución, así que la asignación que rechazó el compilador se ejecutó igualmente. Para un valor que no debe cambiar en tiempo de ejecución, usa Object.freeze o un getter sin setter. La página de readonly explica Readonly<T> y los arrays readonly.

readonly se combina con los modificadores de acceso: private readonly cache = new Map<string, number>() es un patrón habitual para un campo interno que nunca se reasigna.

Errores habituales

  • Tratar private como seguridad. Desaparece en tiempo de ejecución. Usa #field para datos que deben quedar ocultos, y nunca pases directamente a JSON.stringify un objeto con secretos.
  • Escribir public en todas partes. Es el valor por defecto; añadirlo no cambia nada.
  • Usar protected para todo lo «interno». Si ninguna subclase lo necesita, private mantiene más pequeña la superficie.
  • Esperar que readonly congele los datos anidados. Solo impide reasignar la propia propiedad.

Preguntas frecuentes

¿Cuáles son los modificadores de acceso de TypeScript?

public (el valor por defecto: accesible en todas partes), protected (dentro de la clase y sus subclases) y private (solo dentro de la clase). readonly es un modificador aparte que impide reasignar después del constructor y se combina con cualquiera de los tres.

¿Qué diferencia hay entre private y #private en TypeScript?

private solo lo comprueba el compilador; el JavaScript generado tiene una propiedad normal, así que obj["secret"], JSON.stringify y Object.keys la siguen viendo. #secret es un campo privado de JavaScript: lo aplica el runtime, y el código de fuera de la clase no puede leerlo de ninguna manera.

¿private en TypeScript es privado de verdad?

Solo en tiempo de compilación. Después de compilar, el campo es una propiedad normal que cualquier código JavaScript puede leer. TypeScript incluso permite el acceso con corchetes (obj["field"]) a miembros privados como escapatoria. Usa #field cuando la privacidad tenga que mantenerse en tiempo de ejecución.

¿Qué diferencia hay entre protected y private en TypeScript?

Un miembro private solo es visible dentro de la clase que lo declara. Un miembro protected también es visible dentro de las subclases. A ninguno de los dos se puede acceder desde una instancia fuera de la jerarquía de clases.

¿Se pueden cambiar las propiedades readonly en TypeScript?

Una propiedad readonly se puede asignar en su declaración o en el constructor, y en ningún otro sitio (si no, TS2540). Es superficial: un readonly tags: string[] no se puede sustituir, pero tags.push() sigue funcionando. Usa readonly string[] para impedir también eso.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR