76

I'm wondering when I should use

Object.defineProperty

to create new properties for an object. I'm aware that I'm able to set things like

enumerable: false

but when do you need this really? If you just set a property like

myObject.myprop = 5;

its descriptors are all set to true, right? I'm actually more curious when you guys use that rather verbose call to .defineProperty() and for what reasons.

jAndy
  • 231,737
  • 57
  • 305
  • 359
Andre Meinhold
  • 5,087
  • 3
  • 21
  • 29

10 Answers10

51

Object.defineProperty is mainly used to set properties with specific property descriptors (e.g. read-only (constants), enumerability (to not show a property in a for (.. in ..) loop, getters, setters).

"use strict";
var myObj = {}; // Create object
// Set property (+descriptor)
Object.defineProperty(myObj, 'myprop', {
    value: 5,
    writable: false
});
console.log(myObj.myprop);// 5
myObj.myprop = 1;         // In strict mode: TypeError: myObj.myprop is read-only

Example

This method extends the Object prototype with a property. Only the getter is defined, and the enumerability is set to false.

Object.defineProperty(Object.prototype, '__CLASS__', {
    get: function() {
        return Object.prototype.toString.call(this);
    },
    enumerable: false // = Default
});
Object.keys({});           // []
console.log([].__CLASS__); // "[object Array]"
Rob W
  • 341,306
  • 83
  • 791
  • 678
  • ok I understand that. But does this makes sense? Most of the time I want all descriptors set to true, so I can just setup properties the old fashion way right? Is there any other advantage for .defineProperty? – Andre Meinhold Apr 11 '12 at 12:29
  • @RobW: you can also create `getters` and `setters` without using `.defineProperty`. – jAndy Apr 11 '12 at 12:40
  • @jAndy But the property will be exposed in a `for (.. in ..)` loop: `Object.prototype.__defineGetter__('lol',function(){return 3});for(var i in [])alert(i);` shows `"lol"`. `Object.defineProperty` can be used to define a getter/setter which *also* does **not** show up in `for (.. in ..)` loops. – Rob W Apr 11 '12 at 12:45
  • @RobW: I was more thinking about using `var foo = { get lol() { return 5;} };`, but the issue remains the same. – jAndy Apr 11 '12 at 12:48
  • 15
    @Andre: If you don't see a use case for them, then you don't need them... when you need them, you will know ;) – Felix Kling Apr 11 '12 at 12:56
  • can anyone tell me how do you use 'set' how does it work like this or this. Assume x={}, and i have used defineproperty and set prop b and am using 'set'SOOOOOO.... 1st: x.b=2; 2 goes to 'set' then it does something to it and then store it or 2nd: 2 goes to value but also get passed to set function. Is there a way to access/redefine/define value that is being stored before it gets stored just using set. – Muhammad Umer Jul 26 '13 at 22:25
  • @MuhammadUmer Have a look at this example: https://gist.github.com/eligrey/384583 – Rob W Jul 27 '13 at 08:23
  • `writable: false` is sort of like `Object.freeze()`, but for a specific , no? – CodeFinity Dec 29 '19 at 16:56
30

Features like 'enumerable' are rarely used in my experience. The major use case is computed properties:

var myObj = {};

myObj.width = 20;
myObj.height = 20;

Object.defineProperty(myObj, 'area', {
    get: function() {
        return this.width*this.height;
    }
});
console.log(myObj.area);
Pascalius
  • 14,024
  • 4
  • 40
  • 38
  • Seems to me like your going out of your way to make that a property all in the name of a very grey area in semantics; – aaaaaa Jan 12 '15 at 14:49
  • @aaaaaa what? grey area? – Pascalius Jan 12 '15 at 14:57
  • 2
    I just don't understand the use case for providing a calculated property as opposed to simply making it a function. e.g. myObj.area = function() { return this.width * this.height; } – aaaaaa Jan 12 '15 at 22:46
  • 8
    The function is completely valid, but there may be use cases where you prefer a property over a function. – Pascalius Jan 13 '15 at 10:26
  • 15
    The primary use would be that, rather than having two separate functions (ex: getFoo(), setFoo()), you can define a single property with a getter / setter that wraps custom logic and then simply use standard assignment operators on a single member (ex: x = myObj.Foo or myObj.Foo = x). This makes your code simpler and also is useful when you want to prevent a value from being emitted when serializing to JSON. (ex: your object has a bunch of UI-specific properties you don't want sent over the wire...) All in all, this helps JS more closely follow standard OO principles – Joshua Barker Jul 29 '15 at 22:28
19

A really good reason for using Object.defineProperty is that it lets you loop through a function in an object as a computed property, which executes the function instead of returning the function's body.

For example:

var myObj = {};

myObj.width = 20;
myObj.height = 20;

Object.defineProperty(myObj, 'area', {
    get: function() {
        return this.width*this.height;
    },
    enumerable: true
});

for (var key in myObj) {
  if (myObj.hasOwnProperty(key)) {
    console.log(key + " -> " + myObj[key]);
  }
}
//width -> 20, height -> 20, area -> 400

Versus adding the function as a property to an object literal:

var myObj = {};

myObj.width = 20;
myObj.height = 20;

myObj.area = function() {
       return this.width*this.height;
    };

for (var key in myObj) {
  if (myObj.hasOwnProperty(key)) {
    console.log(key + " -> " + myObj[key]);
  }
}
// width -> 20, height -> 20, area -> function() { return this.width*this.height;}

Make sure you set the enumerable property to true in order to loop through it.

Gerard Simpson
  • 2,026
  • 2
  • 30
  • 45
6

For example, that's how Vue.js keeps track of changes in the data object:

When you pass a plain JavaScript object to a Vue instance as its data option, Vue will walk through all of its properties and convert them to getter/setters using Object.defineProperty. This is an ES5-only and un-shimmable feature, which is why Vue doesn’t support IE8 and below.

The getter/setters are invisible to the user, but under the hood they enable Vue to perform dependency-tracking and change-notification when properties are accessed or modified.

[...]

Keep in mind that even a super slim and basic version of Vue.js would use something more than just Object.defineProperty, but the main functionality comes from it:

Vue.js's Reactivity Cycle

Here you can see an article where the author implements a minimal PoC version of something like Vue.js: https://medium.com/js-dojo/understand-vue-reactivity-implementation-step-by-step-599c3d51cd6c

And here a talk (in Spanish) where the speaker builds something similar while explaining reactivity in Vue.js: https://www.youtube.com/watch?v=axXwWU-L7RM

tony19
  • 125,647
  • 18
  • 229
  • 307
Danziger
  • 19,628
  • 4
  • 53
  • 83
5

Summary:

In Javascript Objects are collections of key-value pairs. Object.defineProperty() is a function which can define a new property on an object and can set the following attributes of a property:

  • value <any>: The value associated with the key
  • writable <boolean>: if writable is set to true The property can be updated by assigning a new value to it. If set to false you can't change the value.
  • enumerable <boolean>: if enumerable is set to true Property can be accessed via a for..in loop. Furthermore are the only the enumerable property keys returned with Object.keys()
  • configurable <boolean>: If configurable is set to false you cannot change change the property attributes (value/writable/enumerable/configurable), also since you cannot change the value you cannot delete it using the delete operator.

Example:

let obj = {};


Object.defineProperty(obj, 'prop1', {
      value: 1,
      writable: false,
      enumerable: false,
      configurable: false
});   // create a new property (key=prop1, value=1)


Object.defineProperty(obj, 'prop2', {
      value: 2,
      writable: true,
      enumerable: true,
      configurable: true
});  // create a new property (key=prop2, value=2)


console.log(obj.prop1, obj.prop2); // both props exists

for(const props in obj) {
  console.log(props);
  // only logs prop2 because writable is true in prop2 and false in prop1
}


obj.prop1 = 100;
obj.prop2 = 100;
console.log(obj.prop1, obj.prop2);
// only prop2 is changed because prop2 is writable, prop1 is not


delete obj.prop1;
delete obj.prop2;

console.log(obj.prop1, obj.prop2);
// only prop2 is deleted because prop2 is configurable and prop1 is not
Willem van der Veen
  • 33,665
  • 16
  • 190
  • 155
5

Object.defineProperty prevents you from accidentally assigning values to some key in its prototype chain. With this method you assign only to that particular object level(not to any key in prototype chain).

For example: There is an object like {key1: value1, key2: value2} and you don't know exactly its prototype chain or by mistake you miss it and there is some property 'color' somewhere in prototype chain then-

using dot(.) assignment-

this operation will assign value to key 'color' in prototype chain(if key exist somewhere) and you will find the object with no change as . obj.color= 'blue'; // obj remain same as {key1: value1, key2: value2}

using Object.defineProperty method-

Object.defineProperty(obj, 'color', {
  value: 'blue'
});

// now obj looks like {key1: value1, key2: value2, color: 'blue'}. it adds property to the same level.Then you can iterate safely with method Object.hasOwnProperty().

Tim Ogilvy
  • 1,923
  • 1
  • 24
  • 36
  • 1
    Anish congratulations on your first answer. A very detailed explanation. You may find syntax highlighting helpful in your answers: https://meta.stackoverflow.com/questions/274371/what-is-syntax-highlighting-and-how-does-it-work – Tim Ogilvy Nov 10 '18 at 12:20
3

One neat use case I have seen for defineProperty is for libraries to provide an error property to the user which, if it's not accessed within a certain interval you would log the error yourself. For example:

let logErrorTimeoutId = setTimeout(() => {
  if (error) {
    console.error('Unhandled (in <your library>)', error.stack || error);
  }
}, 10);

Object.defineProperty(data, 'error', {
    configurable: true,
    enumerable: true,
    get: () => {
      clearTimeout(logErrorTimeoutId);
      return error;
    },
  });

Source for this code: https://github.com/apollographql/react-apollo/blob/ddd3d8faabf135dca691d20ce8ab0bc24ccc414e/src/graphql.tsx#L510

Alfonso Embid-Desmet
  • 3,561
  • 3
  • 32
  • 45
1

A good use is when you need to do some interception or apply a classical Observer/Observable pattern in a elegant way:

https://www.monterail.com/blog/2016/how-to-build-a-reactive-engine-in-javascript-part-1-observable-objects

David
  • 623
  • 7
  • 16
0

A very useful case is to monitor changes to something and act on them. It's easy because you can have callback functions fire whenever the value gets set. Here's a basic example.

You have an object Player that can be playing or not playing. You want something to happen right when it starts playing, and right when it stops playing.

function Player(){}
Object.defineProperty(Player.prototype, 'is_playing', {
  get(){
    return this.stored_is_playing;  // note: this.is_playing would result in an endless loop
  },
  set(newVal){
    this.stored_is_playing = newVal;
    if (newVal === true) {
      showPauseButton();
    } else {
      showPlayButton();
    }
  }
});
const cdplayer = new Player();
cdplayer.is_playing = true; // showPauseButton fires 

This answer is related to a couple other answers here, which are good stepping points for more information, but with no need to follow external links to read about libraries or programming paradigms.

AFOC
  • 699
  • 6
  • 15
-2

@Gerard Simpson

If 'area' should be enumerable it can be written without Object.defineProperty, too.

var myObj = {
    get area() { return this.width * this.height }
};

myObj.width = 20;
myObj.height = 20;

for (var key in myObj) {
  if (myObj.hasOwnProperty(key)) {
    console.log(key + " -> " + myObj[key]);
  }
}

//area -> 400, width -> 20, height -> 20
SammieFox
  • 554
  • 1
  • 6
  • 4