Como usar o javascript Object.defineProperty

183

Olhei em volta para saber como usar o Object.definePropertymétodo, mas não consegui encontrar nada decente.

Alguém me deu esse trecho de código :

Object.defineProperty(player, "health", {
    get: function () {
        return 10 + ( player.level * 15 );
    }
})

Mas eu não entendo isso. Principalmente, geté isso que eu não consigo (trocadilho). Como funciona?

Resfriador de matemática
fonte
1
developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/… este é um excelente tutorial aqui.
Martian2049

Respostas:

499

Como você fez uma pergunta semelhante , vamos passo a passo. É um pouco mais longo, mas você pode economizar muito mais tempo do que gastei escrevendo isso:

A propriedade é um recurso de POO projetado para uma separação limpa do código do cliente. Por exemplo, em algumas lojas eletrônicas, você pode ter objetos como este:

function Product(name,price) {
  this.name = name;
  this.price = price;
  this.discount = 0;
}

var sneakers = new Product("Sneakers",20); // {name:"Sneakers",price:20,discount:0}
var tshirt = new Product("T-shirt",10);  // {name:"T-shirt",price:10,discount:0}

Em seguida, no código do seu cliente (a e-shop), você pode adicionar descontos aos seus produtos:

function badProduct(obj) { obj.discount+= 20; ... }
function generalDiscount(obj) { obj.discount+= 10; ... }
function distributorDiscount(obj) { obj.discount+= 15; ... }

Mais tarde, o proprietário da loja virtual pode perceber que o desconto não pode ser maior do que 80%. Agora você precisa encontrar TODAS as ocorrências da modificação do desconto no código do cliente e adicionar uma linha

if(obj.discount>80) obj.discount = 80;

Em seguida, o proprietário da loja virtual poderá mudar ainda mais sua estratégia, como "se o cliente for revendedor, o desconto máximo pode ser de 90%" . E você precisa fazer a alteração em vários locais novamente, além de lembrar de alterar essas linhas sempre que a estratégia for alterada. Este é um design ruim. É por isso que o encapsulamento é o princípio básico da OOP. Se o construtor fosse assim:

function Product(name,price) {
  var _name=name, _price=price, _discount=0;
  this.getName = function() { return _name; }
  this.setName = function(value) { _name = value; }
  this.getPrice = function() { return _price; }
  this.setPrice = function(value) { _price = value; }
  this.getDiscount = function() { return _discount; }
  this.setDiscount = function(value) { _discount = value; } 
}

Então você pode apenas alterar os métodos getDiscount( acessador ) e setDiscount( mutador ). O problema é que a maioria dos membros se comporta como variáveis ​​comuns, apenas o desconto precisa de cuidados especiais aqui. Mas um bom design exige o encapsulamento de todos os membros de dados para manter o código extensível. Então você precisa adicionar muito código que não faz nada. Esse também é um design ruim, um antipadrão padrão . Às vezes, você não pode refatorar os campos para os métodos posteriormente (o código da eshop pode crescer grande ou algum código de terceiros pode depender da versão antiga); portanto, o clichê é menos ruim aqui. Mas ainda assim, é mau. É por isso que as propriedades foram introduzidas em vários idiomas. Você pode manter o código original, basta transformar o membro de desconto em uma propriedade comgete setblocos:

function Product(name,price) {
  this.name = name;
  this.price = price;
//this.discount = 0; // <- remove this line and refactor with the code below
  var _discount; // private member
  Object.defineProperty(this,"discount",{
    get: function() { return _discount; },
    set: function(value) { _discount = value; if(_discount>80) _discount = 80; }
  });
}

// the client code
var sneakers = new Product("Sneakers",20);
sneakers.discount = 50; // 50, setter is called
sneakers.discount+= 20; // 70, setter is called
sneakers.discount+= 20; // 80, not 90!
alert(sneakers.discount); // getter is called

Observe a última, mas uma linha: a responsabilidade pelo valor correto do desconto foi movida do código do cliente (definição da loja virtual) para a definição do produto. O produto é responsável por manter seus membros de dados consistentes. Um bom design é (grosso modo) se o código funcionar da mesma maneira que nossos pensamentos.

Muito sobre propriedades. Mas o javascript é diferente das linguagens orientadas a objetos puras como C # e codifica os recursos de maneira diferente:

Em C # , transformar campos em propriedades é uma mudança radical ; portanto, os campos públicos devem ser codificados como Propriedades Auto-Implementadas se o seu código puder ser usado no cliente compilado separadamente.

Em Javascript , as propriedades padrão (membro de dados com getter e setter descritas acima) são definidas pelo descritor do acessador (no link que você tem na sua pergunta). Exclusivamente, você pode usar o descritor de dados (para que você não possa usar, por exemplo, valor e definir na mesma propriedade):

  • descritor do acessador = get + set (veja o exemplo acima)
    • get deve ser uma função; seu valor de retorno é usado na leitura da propriedade; se não especificado, o padrão é indefinido , que se comporta como uma função que retorna indefinida
    • conjunto deve ser uma função; seu parâmetro é preenchido com RHS ao atribuir um valor à propriedade; se não especificado, o padrão é indefinido , que se comporta como uma função vazia
  • descritor de dados = valor + gravável (veja o exemplo abaixo)
    • valor padrão indefinido ; se gravável , configurável e enumerável (veja abaixo) forem verdadeiros, a propriedade se comportará como um campo de dados comum
    • gravável - padrão falso ; se não for verdade , a propriedade é somente leitura; tentativa de gravação é ignorada sem erro *!

Ambos os descritores podem ter estes membros:

  • configurável - padrão false ; se não for verdade, a propriedade não pode ser excluída; tentativa de excluir é ignorada sem erro *!
  • enumerável - padrão falso ; se for verdade, será iteradofor(var i in theObject); se falso, não será iterado, mas ainda estará acessível como público

* a menos que no modo estrito - nesse caso, o JS interrompe a execução com TypeError, a menos que seja capturado no bloco try-catch

Para ler essas configurações, use Object.getOwnPropertyDescriptor().

Aprenda pelo exemplo:

var o = {};
Object.defineProperty(o,"test",{
  value: "a",
  configurable: true
});
console.log(Object.getOwnPropertyDescriptor(o,"test")); // check the settings    

for(var i in o) console.log(o[i]); // nothing, o.test is not enumerable
console.log(o.test); // "a"
o.test = "b"; // o.test is still "a", (is not writable, no error)
delete(o.test); // bye bye, o.test (was configurable)
o.test = "b"; // o.test is "b"
for(var i in o) console.log(o[i]); // "b", default fields are enumerable

Se você não deseja permitir que o cliente codifique tais truques, pode restringir o objeto em três níveis de confinamento:

  • Object.preventExtensions (yourObject) impede que novas propriedades sejam adicionadas ao yourObject . UseObject.isExtensible(<yourObject>)para verificar se o método foi usado no objeto. A prevenção é superficial (leia abaixo).
  • Object.seal (yourObject) é o mesmo que acima e as propriedades não podem ser removidas (configuradas efetivamenteconfigurable: falsepara todas as propriedades). UseObject.isSealed(<yourObject>)para detectar esse recurso no objeto. O selo é raso (leia abaixo).
  • O Object.freeze (yourObject) é o mesmo que acima e as propriedades não podem ser alteradas (efetivamente configuradaswritable: falsepara todas as propriedades com o descritor de dados). A propriedade gravável do setter não é afetada (pois não possui uma). O congelamento é raso : significa que, se a propriedade for Objeto, suas propriedades NÃO serão congeladas (se desejar, você deverá executar algo como "congelamento profundo", semelhante à clonagem profunda ). UseObject.isFrozen(<yourObject>)para detectá-lo.

Você não precisa se preocupar com isso se escrever apenas algumas linhas divertidas. Mas se você deseja codificar um jogo (como você mencionou na pergunta vinculada), realmente deve se preocupar com o bom design. Tente pesquisar algo no Google sobre antipadrões e cheiro de código . Isso ajudará você a evitar situações como "Ah, preciso reescrever completamente meu código novamente!" , você pode economizar meses de desespero se quiser codificar muito. Boa sorte.

Jan Turoň
fonte
Esta parte é clara. function Product(name,price) { this.name = name; this.price = price; var _discount; // private member Object.defineProperty(this,"discount",{ get: function() { return _discount; }, set: function(value) { _discount = value; if(_discount>80) _discount = 80; } }); } var sneakers = new Product("Sneakers",20); sneakers.discount = 50; // 50, setter is called sneakers.discount+= 20; // 70, setter is called sneakers.discount+= 20; // 80, not 90! alert(sneakers.discount); // getter is called
precisa saber é
27

geté uma função que é chamada quando você tenta ler o valor player.health, como em:

console.log(player.health);

Efetivamente, não é muito diferente de:

player.getHealth = function(){
  return 10 + this.level*15;
}
console.log(player.getHealth());

O oposto de get é definido, o que seria usado quando você atribuir ao valor. Como não há levantador, parece que a atribuição à saúde do jogador não é intencional:

player.health = 5; // Doesn't do anything, since there is no set function defined

Um exemplo muito simples:

var player = {
  level: 5
};

Object.defineProperty(player, "health", {
  get: function() {
    return 10 + (player.level * 15);
  }
});

console.log(player.health); // 85
player.level++;
console.log(player.health); // 100

player.health = 5; // Does nothing
console.log(player.health); // 100

Paulo
fonte
é como uma função que você não precisa usar ()para ligar ... Eu não entendo qual foi a ideia quando eles inventaram essa coisa. As funções são totalmente o mesmo: jsbin.com/bugipi/edit?js,console,output
vsync
15

defineProperty é um método no Object que permite configurar as propriedades para atender a alguns critérios. Aqui está um exemplo simples com um objeto empregado com duas propriedades firstName e lastName e acrescente as duas propriedades substituindo o método toString no objeto.

var employee = {
    firstName: "Jameel",
    lastName: "Moideen"
};
employee.toString=function () {
    return this.firstName + " " + this.lastName;
};
console.log(employee.toString());

Você obterá saída como: Jameel Moideen

Vou alterar o mesmo código usando defineProperty no objeto

var employee = {
    firstName: "Jameel",
    lastName: "Moideen"
};
Object.defineProperty(employee, 'toString', {
    value: function () {
        return this.firstName + " " + this.lastName;
    },
    writable: true,
    enumerable: true,
    configurable: true
});
console.log(employee.toString());

O primeiro parâmetro é o nome do objeto e, em seguida, o segundo parâmetro é o nome da propriedade que estamos adicionando, no nosso caso, é toString e, em seguida, o último parâmetro é o objeto json, cujo valor será uma função e três parâmetros graváveis, enumeráveis. e configurável. Agora, acabei de declarar tudo como verdadeiro.

Se você executar o exemplo, obterá Saída como: Jameel Moideen

Vamos entender por que precisamos das três propriedades, como gravável, enumerável e configurável.

gravável

Uma parte muito irritante do javascript é, se você alterar a propriedade toString para outra coisa, por exemplo

insira a descrição da imagem aqui

se você executar isso novamente, tudo será interrompido. Vamos mudar gravável para falso. Se executar o mesmo novamente, você obterá a saída correta como 'Jameel Moideen'. Essa propriedade impedirá a substituição posterior dessa propriedade.

enumerável

se você imprimir todas as chaves dentro do objeto, poderá ver todas as propriedades, incluindo toString.

console.log(Object.keys(employee));

insira a descrição da imagem aqui

se você definir enumerável como false, poderá ocultar a propriedade toString de todos os outros. Se executar novamente, você receberá firstName, lastName

configurável

se alguém redefiniu o objeto posteriormente mais tarde, por exemplo, enumerável para true e executá-lo. Você pode ver a propriedade toString veio novamente.

var employee = {
    firstName: "Jameel",
    lastName: "Moideen"
};
Object.defineProperty(employee, 'toString', {
    value: function () {
        return this.firstName + " " + this.lastName;
    },
    writable: false,
    enumerable: false,
    configurable: true
});

//change enumerable to false
Object.defineProperty(employee, 'toString', {

    enumerable: true
});
employee.toString="changed";
console.log(Object.keys(employee));

insira a descrição da imagem aqui

você pode restringir esse comportamento definindo configurável como false.

A referência original dessas informações é do meu blog pessoal

Code-EZ
fonte
1
Entendi que você tinha isso no seu blog e colou aqui, mas pelo menos sabe disso no futuro: as screencaps não são populares no SO. Você não pode copypaste código para experimentá-lo e o código não será visto pelos mecanismos de pesquisa ou pela tecnologia assistiva.
Domino
@JacqueGoupil Você está right.I irá atualizar por código add vez de captura de tela
Code-EZ
3

Basicamente, definePropertyé um método que utiliza três parâmetros - um objeto, uma propriedade e um descritor. O que está acontecendo nessa chamada em particular é que a "health"propriedade do playerobjeto está sendo atribuída a 10 mais 15 vezes o nível do objeto do jogador.

Cole Pilegard
fonte
0

Sim, não há mais função estendida para o setup setter & getter, este é o meu exemplo Object.defineProperty (obj, name, func)

var obj = {};
['data', 'name'].forEach(function(name) {
    Object.defineProperty(obj, name, {
        get : function() {
            return 'setter & getter';
        }
    });
});


console.log(obj.data);
console.log(obj.name);
Faizal Pribadi
fonte
0

Object.defineProperty () é uma função global. Não está disponível dentro da função que declara o objeto de outra forma. Você precisará usá-lo estaticamente ...

ISONecroMAn
fonte
0

Resumo:

Object.defineProperty(player, "health", {
    get: function () {
        return 10 + ( player.level * 15 );
    }
});

Object.definePropertyé usado para criar uma nova propriedade no objeto player. Object.definePropertyé uma função que está presente nativamente no ambiente de tempo de execução JS e usa os seguintes argumentos:

Object.defineProperty(obj, prop, descriptor)

  1. O objeto no qual queremos definir uma nova propriedade
  2. O nome da nova propriedade que queremos definir
  3. objeto descritor

O objeto descritor é a parte interessante. Aqui podemos definir o seguinte:

  1. configurável <boolean> : se true o descritor de propriedade puder ser alterado e a propriedade puder ser excluída do objeto. Se configurável, falseas propriedades do descritor transmitidas Object.definePropertynão podem ser alteradas.
  2. Gravável <boolean> : setrue a propriedade puder ser substituída usando o operador de atribuição.
  3. Enumerável <boolean> : se true a propriedade puder ser iterada em um for...inloop. Além disso, ao usar a Object.keysfunção, a tecla estará presente. Se a propriedade for, falseelas não serão iteradas usando um for..inloop e não serão exibidas ao usarObject.keys .
  4. get <function> : Uma função chamada sempre que a propriedade é necessária. Em vez de fornecer o valor direto, essa função é chamada e o valor retornado é dado como o valor da propriedade
  5. set <function> : Uma função que é chamada sempre que a propriedade é atribuída. Em vez de definir o valor direto, essa função é chamada e o valor retornado é usado para definir o valor da propriedade.

Exemplo:

const player = {
  level: 10
};

Object.defineProperty(player, "health", {
  configurable: true,
  enumerable: false,
  get: function() {
    console.log('Inside the get function');
    return 10 + (player.level * 15);
  }
});

console.log(player.health);
// the get function is called and the return value is returned as a value

for (let prop in player) {
  console.log(prop);
  // only prop is logged here, health is not logged because is not an iterable property.
  // This is because we set the enumerable to false when defining the property
}

Willem van der Veen
fonte
0

import { CSSProperties } from 'react'
import { BLACK, BLUE, GREY_DARK, WHITE } from '../colours'

export const COLOR_ACCENT = BLUE
export const COLOR_DEFAULT = BLACK
export const FAMILY = "'Segoe UI', sans-serif"
export const SIZE_LARGE = '26px'
export const SIZE_MEDIUM = '20px'
export const WEIGHT = 400

type Font = {
  color: string,
  size: string,
  accent: Font,
  default: Font,
  light: Font,
  neutral: Font,
  xsmall: Font,
  small: Font,
  medium: Font,
  large: Font,
  xlarge: Font,
  xxlarge: Font
} & (() => CSSProperties)

function font (this: Font): CSSProperties {
  const css = {
    color: this.color,
    fontFamily: FAMILY,
    fontSize: this.size,
    fontWeight: WEIGHT
  }
  delete this.color
  delete this.size
  return css
}

const dp = (type: 'color' | 'size', name: string, value: string) => {
  Object.defineProperty(font, name, { get () {
    this[type] = value
    return this
  }})
}

dp('color', 'accent', COLOR_ACCENT)
dp('color', 'default', COLOR_DEFAULT)
dp('color', 'light', COLOR_LIGHT)
dp('color', 'neutral', COLOR_NEUTRAL)
dp('size', 'xsmall', SIZE_XSMALL)
dp('size', 'small', SIZE_SMALL)
dp('size', 'medium', SIZE_MEDIUM)

export default font as Font

Alvin Smith
fonte
0

Define uma nova propriedade diretamente em um objeto ou modifica uma propriedade existente em um objeto e retorna o objeto.

Nota: Você chama esse método diretamente no construtor Object, em vez de em uma instância do tipo Object.

   const object1 = {};
   Object.defineProperty(object1, 'property1', {
      value: 42,
      writable: false, //If its false can't modify value using equal symbol
      enumerable: false, // If its false can't able to get value in Object.keys and for in loop
      configurable: false //if its false, can't able to modify value using defineproperty while writable in false
   });

insira a descrição da imagem aqui

Explicação simples sobre definir Propriedade.

Código de exemplo: https://jsfiddle.net/manoj_antony32/pu5n61fs/

Mano
fonte
0

Object.defineProperty(Array.prototype, "last", {
  get: function() {
    if (this[this.length -1] == undefined) { return [] }
    else { return this[this.length -1] }
  }
});

console.log([1,2,3,4].last) //returns 4

Jaeyson Anthony Y.
fonte