how to use javascript Object.defineProperty

前端 未结 10 1104
不知归路
不知归路 2020-11-29 14:21

I looked around for how to use the Object.defineProperty method, but couldn\'t find anything decent.

Someone gave me this snippet of code:

Object.def         


        
相关标签:
10条回答
  • 2020-11-29 14:45

    Since you asked a similar question, let's take it to step by step. It's a bit longer, but it may save you much more time than I have spent on writing this:

    Property is an OOP feature designed for clean separation of client code. For example, in some e-shop you might have objects like this:

    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}
    

    Then in your client code (the e-shop), you can add discounts to your products:

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

    Later, the e-shop owner might realize that the discount can't be greater than say 80%. Now you need to find EVERY occurrence of the discount modification in the client code and add a line

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

    Then the e-shop owner may further change his strategy, like "if the customer is reseller, the maximal discount can be 90%". And you need to do the change on multiple places again plus you need to remember to alter these lines anytime the strategy is changed. This is a bad design. That's why encapsulation is the basic principle of OOP. If the constructor was like this:

    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; } 
    }
    

    Then you can just alter the getDiscount (accessor) and setDiscount (mutator) methods. The problem is that most of the members behave like common variables, just the discount needs special care here. But good design requires encapsulation of every data member to keep the code extensible. So you need to add lots of code that does nothing. This is also a bad design, a boilerplate antipattern. Sometimes you can't just refactor the fields to methods later (the eshop code may grow large or some third-party code may depend on the old version), so the boilerplate is lesser evil here. But still, it is evil. That's why properties were introduced into many languages. You could keep the original code, just transform the discount member into a property with get and set blocks:

    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
    

    Note the last but one line: the responsibility for correct discount value was moved from the client code (e-shop definition) to the product definition. The product is responsible for keeping its data members consistent. Good design is (roughly said) if the code works the same way as our thoughts.

    So much about properties. But javascript is different from pure Object-oriented languages like C# and codes the features differently:

    In C#, transforming fields into properties is a breaking change, so public fields should be coded as Auto-Implemented Properties if your code might be used in the separately compiled client.

    In Javascript, the standard properties (data member with getter and setter described above) are defined by accessor descriptor (in the link you have in your question). Exclusively, you can use data descriptor (so you can't use i.e. value and set on the same property):

    • accessor descriptor = get + set (see the example above)
      • get must be a function; its return value is used in reading the property; if not specified, the default is undefined, which behaves like a function that returns undefined
      • set must be a function; its parameter is filled with RHS in assigning a value to property; if not specified, the default is undefined, which behaves like an empty function
    • data descriptor = value + writable (see the example below)
      • value default undefined; if writable, configurable and enumerable (see below) are true, the property behaves like an ordinary data field
      • writable - default false; if not true, the property is read only; attempt to write is ignored without error*!

    Both descriptors can have these members:

    • configurable - default false; if not true, the property can't be deleted; attempt to delete is ignored without error*!
    • enumerable - default false; if true, it will be iterated in for(var i in theObject); if false, it will not be iterated, but it is still accessible as public

    * unless in strict mode - in that case JS stops execution with TypeError unless it is caught in try-catch block

    To read these settings, use Object.getOwnPropertyDescriptor().

    Learn by example:

    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
    

    If you don't wish to allow the client code such cheats, you can restrict the object by three levels of confinement:

    • Object.preventExtensions(yourObject) prevents new properties to be added to yourObject. Use Object.isExtensible(<yourObject>) to check if the method was used on the object. The prevention is shallow (read below).
    • Object.seal(yourObject) same as above and properties can not be removed (effectively sets configurable: false to all properties). Use Object.isSealed(<yourObject>) to detect this feature on the object. The seal is shallow (read below).
    • Object.freeze(yourObject) same as above and properties can not be changed (effectively sets writable: false to all properties with data descriptor). Setter's writable property is not affected (since it doesn't have one). The freeze is shallow: it means that if the property is Object, its properties ARE NOT frozen (if you wish to, you should perform something like "deep freeze", similar to deep copy - cloning). Use Object.isFrozen(<yourObject>) to detect it.

    You don't need to bother with this if you write just a few lines fun. But if you want to code a game (as you mentioned in the linked question), you should care about good design. Try to google something about antipatterns and code smell. It will help you to avoid situations like "Oh, I need to completely rewrite my code again!", it can save you months of despair if you want to code a lot. Good luck.

    0 讨论(0)
  • 2020-11-29 14:45

    defineProperty is a method on Object which allow you to configure the properties to meet some criterias. Here is a simple example with an employee object with two properties firstName & lastName and append the two properties by overriding the toString method on the object.

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

    You will get Output as : Jameel Moideen

    I am going to change the same code by using defineProperty on the object

    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());
    

    The first parameter is the name of the object and then second parameter is name of the property we are adding , in our case it’s toString and then the last parameter is json object which have a value going to be a function and three parameters writable,enumerable and configurable.Right now I just declared everything as true.

    If u run the example you will get Output as : Jameel Moideen

    Let’s understand why we need the three properties such as writable,enumerable and configurable.

    writable

    One of the very annoying part of the javascript is , if you change the toString property to something else for example

    if you run this again , everything gets breaks. Let’s change writable to false. If run the same again you will get the correct output as ‘Jameel Moideen’ . This property will prevent overwrite this property later.

    enumerable

    if you print all the keys inside the object , you can see all the properties including toString.

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

    if you set enumerable to false , you can hide toString property from everybody else. If run this again you will get firstName,lastName

    configurable

    if someone later redefined the object on later for example enumerable to true and run it. You can see toString property came again.

    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));
    

    you can restrict this behavior by set configurable to false.

    Orginal reference of this information is from my personal Blog

    0 讨论(0)
  • 2020-11-29 14:56

    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

    0 讨论(0)
  • 2020-11-29 14:56

    Defines a new property directly on an object, or modifies an existing property on an object, and return the object.

    Note: You call this method directly on the Object constructor rather than on an instance of type 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
       });
    

    Simple explanation about define Property.

    Example code: https://jsfiddle.net/manoj_antony32/pu5n61fs/

    0 讨论(0)
  • 2020-11-29 14:56

    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

    0 讨论(0)
  • 2020-11-29 14:57

    get is a function that is called when you try to read the value player.health, like in:

    console.log(player.health);
    

    It's effectively not much different than:

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

    The opposite of get is set, which would be used when you assign to the value. Since there is no setter, it seems that assigning to the player's health is not intended:

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

    A very simple example:

    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

    0 讨论(0)
提交回复
热议问题