Post Top Ad

Your Ad Spot

domingo, 28 de junio de 2020

Atributos de las propiedades del objeto en JavaScript

En esta publicación de blog, observamos más de cerca cómo la especificación ECMAScript ve los objetos JavaScript. En particular, las propiedades no son atómicas en la especificación, sino que se componen de múltiples atributos (campos de pensamiento en un registro). ¡Incluso el valor de una propiedad de datos se almacena en un atributo!

Tabla de contenido:

La estructura de los objetos   

En la especificación ECMAScript , un objeto consta de:
  • Las ranuras internas , que son ubicaciones de almacenamiento a las que no se puede acceder desde JavaScript, solo para operaciones en la especificación.
  • Una colección de propiedades . Cada propiedad asocia una clave con atributos (campos de pensamiento en un registro).

Ranuras internas   

Así es como la especificación describe las ranuras internas (el énfasis es mío):
  • Las ranuras internas corresponden al estado interno asociado con los objetos y utilizado por varios algoritmos de especificación ECMAScript.
  • Las ranuras internas no son propiedades de objeto y no se heredan.
  • Dependiendo de la especificación de ranura interna específica, dicho estado puede consistir en valores:
    • de cualquier tipo de lenguaje ECMAScript o
    • de valores específicos del tipo de especificación ECMAScript.
  • A menos que se especifique explícitamente lo contrario, las ranuras internas se asignan como parte del proceso de creación de un objeto y no se pueden agregar dinámicamente a un objeto.
  • A menos que se especifique lo contrario, el valor inicial de una ranura interna es el valor undefined.
  • Varios algoritmos dentro de esta especificación crean objetos que tienen ranuras internas. Sin embargo, el lenguaje ECMAScript no proporciona una forma directa de asociar ranuras internas con un objeto .
  • Los métodos internos y las ranuras internas se identifican dentro de esta especificación utilizando nombres entre corchetes dobles [[ ]].
Hay dos tipos de ranuras internas:
  • Ranuras de método para manipular objetos (obtener propiedades, establecer propiedades, etc.)
  • Ranuras de datos con almacenamiento (enumerados en la tabla a continuación)
Ranura de datos internaTipo
[[Prototype]]null | object
[[Extensible]]boolean
[[PrivateFieldValues]]Listado de entradas
Descripciones para estos espacios de datos:
  • [[Prototype]] almacena el prototipo de un objeto.
    • Se puede cambiar a través de Object.getPrototypeOf()yObject.setPrototypeOf()
  • [[Extensible]] indica si es posible agregar propiedades a un objeto.
    • Se puede configurar a falsetravés de Object.preventExtensions().
  • [[PrivateFieldValues]]se utiliza para administrar campos de clases privadas .

Claves de propiedad   

La clave de una propiedad es:
  • Una cuerda
  • Un símbolo

Atributos de propiedad   

Hay dos tipos de propiedades y tienen atributos diferentes:
  • Una propiedad de datos almacena datos. Sus atributos valuecontienen cualquier valor de JavaScript.
  • Una propiedad de acceso tiene una función getter y / o una función setter. El primero se almacena en el atributo get, el último en el atributo set.
La siguiente tabla enumera todos los atributos de propiedad.
Tipo de propiedadNombre y tipo de atributoValor por defecto
Propiedad de datosvalue: anyundefined
writable: booleanfalse
Propiedad accesoriaget(): anyundefined
set(v: any): voidundefined
Todas las propiedadesconfigurable: booleanfalse
enumerable: booleanfalse
Ya hemos encontrado los atributos valuegetsetLos otros atributos funcionan de la siguiente manera:
  • writable determina si se puede cambiar el valor de una propiedad de datos.
  • configurabledetermina si los atributos de una propiedad se pueden cambiar. Si es así false, entonces:
    • No puede eliminar la propiedad.
    • No puede cambiar una propiedad de una propiedad de datos a una propiedad de acceso o viceversa.
    • No puede cambiar ningún atributo que no sea value.
    • Sin embargo, se permite un cambio de atributo más: puede cambiar writablede truefalseLa razón detrás de esta anomalía es histórica : la propiedad .lengthde matrices siempre ha sido grabable y no configurable. Permitir writableque se cambie su atributo nos permite congelar matrices.
  • enumerableinfluye en algunas operaciones (como Object.assign()). Si es así false, esas operaciones ignoran la propiedad.

Descriptores de propiedad   

Un descriptor de propiedad codifica los atributos de una propiedad como un objeto JavaScript. Sus interfaces TypeScript tienen el siguiente aspecto.
interface DataPropertyDescriptor {
  value?: any;
  writable?: boolean;
  configurable?: boolean;
  enumerable?: boolean;
}
interface AccessorPropertyDescriptor {
  get?(): any;
  set?(v: any): void;
  configurable?: boolean;
  enumerable?: boolean;
}
type PropertyDescriptor = DataPropertyDescriptor | AccessorPropertyDescriptor;
Los signos de interrogación indican que cada propiedad es opcional. Si omite una propiedad al pasar un descriptor a una operación, se utiliza su valor predeterminado.

Recuperando descriptores para propiedades   

El siguiente código recupera el descriptor de objeto para la propiedad de datos first:
const obj = {
  first: 'Jane',
};
assert.deepEqual(
  Object.getOwnPropertyDescriptor(obj, 'first'),
  {
    value: 'Jane',
    writable: true,
    enumerable: true,
    configurable: true,
  });
En el siguiente ejemplo, recuperamos el descriptor de propiedad para el captador fullName:
const desc = Object.getOwnPropertyDescriptor.bind(Object);

const jane = {
  first: 'Jane',
  last: 'Doe',
  get fullName() {
    return this.first + ' ' + this.last;
  },
};
assert.deepEqual(
  Object.getOwnPropertyDescriptor(jane, 'fullName'),
  {
    get: desc(jane, 'fullName').get, // (A)
    set: undefined,
    enumerable: true,
    configurable: true
  });
Usar desc()en la línea A es una solución alternativa para que .deepEqual()funcione.

Crear nuevas propiedades a través de descriptores   

También puede crear nuevas propiedades a través de descriptores de propiedades:
const car = {};

Object.defineProperty(car, 'color', {
  value: 'blue',
  writable: true,
  enumerable: true,
  configurable: true,
});

assert.deepEqual(
  car,
  {
    color: 'blue',
  });

Cambio de propiedades existentes mediante descriptores   

Si ya existe una propiedad propia, la definición a través de un descriptor cambia esa propiedad. Por un lado, eso nos permite usar Object.defineProperty()como asignación:
const car = {
  color: 'blue',
};
Object.defineProperty(car, 'color', {
  value: 'green',
  writable: true,
  enumerable: true,
  configurable: true,
});

assert.deepEqual(
  car,
  {
    color: 'green',
  });
Por otro lado, también podemos usar Object.defineProperty()para convertir una propiedad de datos en un captador (y viceversa):
const car = {
  color: 'blue',
};

let getterCallCount = 0;
Object.defineProperty(car, 'color', {
  get() {
    getterCallCount++;
    return 'red';
  },
});

assert.equal(car.color, 'red');
assert.equal(getterCallCount, 1);

Error: las propiedades de solo lectura heredadas no se pueden asignar a   

Si una propiedad heredada es de solo lectura, entonces no podemos usar la asignación para cambiarla. La razón es que anular una propiedad heredada creando una propiedad propia puede verse como un cambio no destructivo de la propiedad heredada. Posiblemente, si una propiedad no se puede escribir, no deberíamos poder hacerlo.
Veamos un ejemplo:
const proto = Object.defineProperties({}, {
  prop: {
    value: 1,
    writable: false,
  }
});
const obj = Object.create(proto);

assert.throws(
  () => obj.prop = 2,
  /^TypeError: Cannot assign to read only property 'prop'/);
No podemos cambiar la propiedad mediante asignación. Pero aún podemos crear una propiedad propia definiéndola:
Object.defineProperty(obj, 'prop', {value: 2});
assert.equal(obj.prop, 2);
Las propiedades de acceso que no tienen un setter también se consideran de solo lectura:
const proto = Object.defineProperties({}, {
  prop: {
    get() {
      return 1;
    }
  }
});
const obj = Object.create(proto);
assert.throws(
  () => obj.prop = 2,
  'TypeError: Cannot set property prop of #<Object> which has only a getter');

API: descriptores de propiedad   

Las siguientes funciones le permiten trabajar con descriptores de propiedad:
  • Object.defineProperty(obj: object, key: string|symbol, propDesc: PropertyDescriptor): object
    Crea o cambia una propiedad en objcuya clave se encuentra keyy cuyos atributos se especifican mediante propDescDevuelve el objeto modificado.
    const obj = {};
    const result = Object.defineProperty(
      obj, 'happy', {
        value: 'yes',
        writable: true,
        enumerable: true,
        configurable: true,
      });
    
    // obj was returned and modified:
    assert.equal(result, obj);
    assert.deepEqual(obj, {
      happy: 'yes',
    });
    
  • Object.defineProperties(obj: object, properties: {[k: string|symbol]: PropertyDescriptor}): object
    La versión por lotes de Object.defineProperty()Cada propiedad de propertiestiene un descriptor de propiedad. Las claves de las propiedades y sus valores indican Object.definePropertiesqué propiedades crear o cambiar obj.
    const address1 = Object.defineProperties({}, {
      street: { value: 'Evergreen Terrace', enumerable: true },
      number: { value: 742, enumerable: true },
    });
    
  • Object.create(proto: null|object, properties?: {[k: string|symbol]: PropertyDescriptor}): object
    Primero, crea un objeto cuyo prototipo es protoLuego, si propertiesse ha proporcionado el parámetro opcional , le agrega propiedades, de la misma manera que Object.defineProperties()Finalmente, devuelve el resultado. Por ejemplo, el siguiente fragmento de código produce el mismo resultado que el fragmento anterior:
    const address2 = Object.create(Object.prototype, {
      street: { value: 'Evergreen Terrace', enumerable: true },
      number: { value: 742, enumerable: true },
    });
    assert.deepEqual(address1, address2);
    
  • Object.getOwnPropertyDescriptor(obj: object, key: string|symbol): undefined|PropertyDescriptor
    Devuelve el descriptor de la propiedad propia (no heredada) de objcuya clave es keySi no existe tal propiedad, undefinedse devuelve.
    assert.deepEqual(
      Object.getOwnPropertyDescriptor(Object.prototype, 'toString'),
      {
        value: {}.toString,
        writable: true,
        enumerable: false,
        configurable: true,
      });
    assert.equal(
      Object.getOwnPropertyDescriptor({}, 'toString'),
      undefined);
    
  • Object.getOwnPropertyDescriptors(obj: object):
    Devuelve un objeto donde cada clave 'k'de propiedad de objse asigna al descriptor de propiedad para obj.kEl resultado puede usarse como entrada para Object.defineProperties()Object.create().
    const desc = Object.getOwnPropertyDescriptor.bind(Object);
    
    const propertyKey = Symbol('propertyKey');
    const obj = {
      [propertyKey]: 'abc',
      get count() { return 123 },
    };
    assert.deepEqual(
      Object.getOwnPropertyDescriptors(obj),
      {
        [propertyKey]: {
          value: 'abc',
          writable: true,
          enumerable: true,
          configurable: true
        },
        count: {
          get: desc(obj, 'count').get, // (A)
          set: undefined,
          enumerable: true,
          configurable: true
        }
      });
    
    Usar desc()en la línea A es una solución alternativa para que .deepEqual()funcione.

Casos de uso para Object.getOwnPropertyDescriptors()  

Object.getOwnPropertyDescriptors(): copiando propiedades en un objeto   

Desde ES6, JavaScript ha tenido ya un método de herramienta para copiar propiedades: Object.assign()Sin embargo, este método utiliza operaciones simples de obtención y configuración para copiar una propiedad cuya clave es key:
target[key] = source[key];
Eso significa que solo crea una copia fiel de una propiedad si:
  • Su atributo writablees truey su atributo enumerablees true(porque así es como la asignación crea propiedades).
  • Es una propiedad de datos.
El siguiente ejemplo ilustra esta limitación. El objeto sourcetiene un setter cuya clave es data.
const source = {
  set data(value) {
    this._data = value;
  }
};

const desc = Object.getOwnPropertyDescriptor.bind(Object);
assert.deepEqual(
  Object.getOwnPropertyDescriptor(source, 'data'),
  {
    get: undefined,
    set: desc(source, 'data').set,
    enumerable: true,
    configurable: true,
  });

// Because there is only a setter, property `data` exists,
// but has the value `undefined`.
assert.equal('data' in source, true);
assert.equal(source.data, undefined);
Si usamos Object.assign()para copiar propiedades data, la propiedad de acceso datase convierte en una propiedad de datos:
const target1 = {};
Object.assign(target1, source);
assert.deepEqual(
  Object.getOwnPropertyDescriptor(target1, 'data'),
  {
    value: undefined,
    writable: true,
    enumerable: true,
    configurable: true,
  });
Afortunadamente, el uso Object.getOwnPropertyDescriptors()junto con Object.defineProperties()copia fielmente la propiedad data:
const target2 = {};
Object.defineProperties(
  target2, Object.getOwnPropertyDescriptors(source));

assert.deepEqual(
  Object.getOwnPropertyDescriptor(target2, 'data'),
  {
    get: undefined,
    set: desc(source, 'data').set,
    enumerable: true,
    configurable: true,
  });

Dificultad: copiar métodos que usan super  

Un método que utiliza superestá firmemente conectado con su objeto de inicio (el objeto en el que está almacenado). Actualmente no hay forma de copiar o mover dicho método a un objeto diferente.

Object.getOwnPropertyDescriptors(): clonando objetos   

La clonación superficial es similar a las propiedades de copia, por lo que también Object.getOwnPropertyDescriptors()es una buena opción aquí.
Para crear el clon, usamos Object.create():
const original = {
  set data(value) {
    this._data = value;
  }
};

const clone = Object.create(
  Object.getPrototypeOf(original),
  Object.getOwnPropertyDescriptors(original));

assert.deepEqual(original, clone);

No hay comentarios.:

Publicar un comentario

Dejanos tu comentario para seguir mejorando!

outbrain

Páginas