configure an object property descriptor - javascript

Using Object.defineProperty() You can define a property on an object.
It isn't like defining an object property using the literals' syntax, ex:
const obj = {}
// then,
obj.x = "Hello"
It gives you more options where you can configure the property descriptor of an object, ex:
const obj = {}
Object.defineProperty(obj, "y", {
writable: false,
value: "You can't overwrite me"
})
console.log(obj) // { y: "You can't overwrite me" }
obj.y = "Hallo again"
console.log(obj) // { y: "You can't overwrite me" }
// -- (would throw an error in strict mode)
This is cool.
Say for example I have the example above, and I have defined the property "y" and set its property descriptor property writable: false, and I wanted to change that.
Is there a way JavaScript provides a method to do so?
for example, a static method such as:
// of course this doesn't work, but just to point to the idea.
Object.updatePropertyDescriptor(x, "y", { writable: true })

This is only possible if the object is configurable (MDN: Object.defineProperty()).
const obj = {}
Object.defineProperty(obj, "y", {
writable: false,
configurable: true,
value: "You can't overwrite me"
})
console.log(1, obj.y) // { y: "You can't overwrite me" }
obj.y = "Hallo again"
console.log(2, obj.y) // { y: "You can't overwrite me" }
Object.defineProperty(obj, "y", {
writable: true,
});
obj.y = "Hallo again"
console.log(3, obj.y) // { y: "Hallo again" }
Object.defineProperty(obj, "y", {
writable: false,
});
obj.y = "You can't overwrite me"
console.log(4, obj.y) // { y: "Hallo again" }

You can't when the property configurable is set to false which is the default value.
Besides the writable property you may also set configurable and enumerable on an object. When configurable is set to true you can use Object.defineProperty() again and change the property back to being writable if you like.
Let me illustrate this with an example:
const obj = {}
Object.defineProperty(obj, "y", {
writable: false,
configurable: true,
value: "You can't overwrite me"
})
console.log(obj.y) // "You can't overwrite me"
obj.y = "Hallo again"
console.log(obj.y) // "You can't overwrite me"
// since it's configurable we can define the property again
// otherwise we would get an error saying we can't
// redefine the property
Object.defineProperty(obj, "y", {
writable: true,
configurable: true,
value: "I can override you since you're configurable"
})
console.log(obj.y); // "I can override you since you're configurable"
Object.defineProperty(obj, "y", {
writable: false,
configurable: false,
value: "I cannot be overridden or redefined"
})
console.log(obj.y); // "I cannot be overridden or redefined"
// throws error
Object.defineProperty(obj, "y", {
writable: true,
configurable: true,
value: "Trying to override non-configurable property throws error"
})
More details on those properties can be found in this blog post.

You can use the configurable attributes to do this. It is mentioned in the documentation that-
When configurable is set to false,
other attributes of its descriptor cannot be changed (however if it's
a data descriptor with writable: true, the value can be changed, and
writable can be changed to false).
This means if configurable is set to true, the writable can be updated. Here is the demo-
const obj = {}
Object.defineProperty(obj, "y", {
writable: false,
value: "You can't overwrite me",
configurable: true,
})
console.log(obj.y) // { y: "You can't overwrite me" }
obj.y = "Hallo again"
console.log(obj.y) // { y: "You can't overwrite me" }
// -- (would throw an error in strict mode)
Object.defineProperty(obj, 'y', {
value: 'Now you can overwrite me',
writable: true,
});
console.log(obj.y);
// Expected output: 'Now you can overwrite me'

Related

How do i read only value attr?

I want make value attribute to read-only and i do these code but not work ??
Need help ?
const obj = {
name: "karl"
}
const origName = obj.name;
Object.defineProperty(obj, 'name', {
enumerable: false,
configurable: false,
get() {
return origName + 2;
}
});
You must add "writebale" key;
Like this;
Object.defineProperty(obj, "name", {
value: "karl",
writable: false
});
You could instead use the writable property of the property descriptor, which prevents the need for a get accessor:
Object.defineProperty(obj, 'name', {
value: "karl",
writable: false
get() {
return origName + 2;
}
});

How does changing metadata in JavaScript work?

In the console, I get abc despite setting {writable:false}. Could you explain how changing metadata works?
let portfolio = {
myFirstName: "Bob",
myLastName: "Alice",
myAge: 26,
aboutMe: function() {
return ("First Name: " + this.myFirstName + "; Last Name: " + this.myLastName + "; Age: " + this.myAge + ";");
}
};
Object.defineProperty(portfolio, "myFirstName", { writable: false });
Object.defineProperty(portfolio, "myFirstName", { value: "abc" });
console.log(portfolio.myFirstName);
in your 2nd line Object.defineProperty(portfolio, "myFirstName", { value: "abc" }); you are defining the property again.
You are not assigning a value. You are tossing out the old property and replacing it with a brand spanking new one. (Technically incorrect, it goes through a lot of steps to evaluate and apply values to property properties, but for simple understanding, I believe this suffices as understanding, as it feels like a new one in this scenario. Please read the link if you wish to have the complex truth)
To assign a new value use portfolio.myFirstName = "value here" and you see it's write protected.
let portfolio = {
myFirstName: "Bob",
myLastName: "Alice",
myAge: 26,
aboutMe: function() {
return ("First Name: " + this.myFirstName + "; Last Name: " + this.myLastName + "; Age: " + this.myAge + ";");
}
};
Object.defineProperty(portfolio, "myFirstName", { writable: false });
portfolio.myFirstName = "Alice";
console.log(portfolio.myFirstName);
to prevent the workaround, call Object.freeze() on the object after modifying its property. This will also have other side effects like not being able to edit the values of other properties.
let portfolio = {
myFirstName: "Bob",
myLastName: "Alice",
myAge: 26,
aboutMe: function() {
return ("First Name: " + this.myFirstName + "; Last Name: " + this.myLastName + "; Age: " + this.myAge + ";");
}
};
Object.defineProperty(portfolio, "myFirstName", { writable: false });
Object.freeze(portfolio);
Object.defineProperty(portfolio, "myFirstName", {value:"abc"});
console.log(portfolio.myFirstName);
writable: false only has an effect on Object.defineProperty if configurable is also set to false. See step 7 of the ValidateAndApplyPropertyDescriptor algorithm:
Else if IsDataDescriptor(current) and IsDataDescriptor(Desc) are both true, then
If current.[[Configurable]] is false and current.[[Writable]] is false, then
If Desc.[[Writable]] is present and Desc.[[Writable]] is true, return false.
If Desc.[[Value]] is present and SameValue(Desc.[[Value]], current.[[Value]]) is false, return false.
Return true.
That's likely because as long as a property is configurable, nothing stops you from changing the value of writable back to true, e.g.
Object.defineProperty(
portfolio,
"myFirstName",
{value: "abc", writable: true}
);
Note that any property declared as part of an object literal automatically has {writable: true, configurable: true, enumerable: true}.
Examples
Can't assign because writable and configurable are both false:
var obj = {};
Object.defineProperty(obj, 'test', {
value: 42,
configurable: false,
writable: false,
enumerable: true,
});
console.log(obj);
Object.defineProperty(obj, 'test', {value: 21});
console.log(obj);
Can assign a value because writable or configurable are true:
var obj = {};
Object.defineProperty(obj, 'test', {
value: 42,
configurable: true,
writable: false,
enumerable: true
});
console.log(obj);
Object.defineProperty(obj, 'test', {value: 21});
console.log(obj);
var obj = {};
Object.defineProperty(obj, 'test', {
value: 42,
configurable: false,
writable: true,
enumerable: true
});
console.log(obj);
Object.defineProperty(obj, 'test', {value: 21});
console.log(obj);
Lastly, if writable and configurable are both false but if the new value is the same as the current value, no error will be thrown since no change is actually being made to the property:
var obj = {};
Object.defineProperty(obj, 'test', {
value: 42,
configurable: false,
writable: false,
enumerable: true,
});
console.log(obj);
Object.defineProperty(obj, 'test', {value: 42});
console.log(obj);
Setting writable: false will work as expected for normal assignments (foo.bar = 42) because such assignments go through OrdinarySetWithOwnDescriptor which check the writable value of an existing property descriptor first.
You're working around that access restriction to hard-set the property. That only impacts the portfolio.myFirstName mutator.
You really can't block access to defineProperty like that. It's too low-level. That's probably a good thing, though, since it is dependable.

Why {...window}.Math is undefined?

const a = {x:1};
console.log({...a}.x);
// 1
console.log(window.Math);
// Math {abs: ƒ, acos: ƒ, acosh: ƒ, asin: ƒ, asinh: ƒ, …}
console.log({...window}.Math);
// undefined
I don't understand why {...a}.x evaluates 1, but {...window}.Math evaluates undefined.
That's because Math is not enumerable.
The ECMA-2018 (ES9) specs is a little hard to read. MDN and a proposal page stated: {...obj} creates a new object for all of obj's (1) own and (2) enumerable properties. Math is window's own property, but not enumerable:
console.log(window.hasOwnProperty("Math"));
console.log(Object.getOwnPropertyDescriptor(window, "Math"));
You can reproduce the situation with an object:
obj = {};
Object.defineProperty(obj, 'x', {
enumerable: false,
value: 123
});
console.log(obj); // Google Chrome prints { x: 123 } but StackOverflow prints {} and Node.js prints {} too
console.log(obj.x);
console.log(({...obj}).x);
{...Window} is using the spread operator to make a copy into a new object. Math is not enumerable so it won't be copied to the new object.
You can test it by your self:
const a = {}
Object.defineProperty(a, "prop1", { value: "enumerable", enumerable: true })
Object.defineProperty(a, "prop2", { value: "not enumerable", enumerable: false })
Then copy your object:
{...a}.prop1 //"enumerable"
{...a}.prop2 // undefined

Throw an error when trying to delete non-allowed property

So I know you can prevent a property from being deleted by setting the configurable property on the object to false. However the only feedback you get from this delete action is a boolean that shows true or false. Is there any way to throw an error immediately when a non-allowed property is being deleted?
const obj = {
deleteMe: "Some text",
dontDeleteMe: "Some other text"
};
Object.defineProperty(obj, 'dontDeleteMe', {
value: "Some text",
writable : true,
enumerable : true,
configurable : false
});
console.log(Object.keys(obj));
delete obj.deleteMe;
console.log(Object.keys(obj));
delete obj.dontDeleteMe;
console.log(Object.keys(obj));
An error would throw if you add use strict:
'use strict';
const obj = {
deleteMe: "Some text",
dontDeleteMe: "Some other text"
};
Object.defineProperty(obj, 'dontDeleteMe', {
value: "Some text",
writable : true,
enumerable : true,
configurable : false
});
console.log(Object.keys(obj));
delete obj.deleteMe;
console.log(Object.keys(obj));
delete obj.dontDeleteMe;
console.log(Object.keys(obj));
With Proxy:
const obj = {
deleteMe: "Some text",
dontDeleteMe: "Some other text"
};
Object.defineProperty(obj, 'dontDeleteMe', {
value: "Some text",
writable : true,
enumerable : true,
configurable : false
});
const handler1 = {
deleteProperty(target, prop) {
delete target[prop];
if (target[prop]) {
throw new Error(`Invalid attempt to delete '${prop}' property`);
}
return true;
}
};
const proxyObj = new Proxy(obj, handler1);
console.log(Object.keys(proxyObj));
delete proxyObj.deleteMe;
console.log(Object.keys(proxyObj));
delete proxyObj.dontDeleteMe;
console.log(Object.keys(proxyObj));
You can use "use strict"; with Object.defineProperty() or Object.freeze()
Visit MDN for details
'use strict';
var obj = Object.freeze({name: 'Elsa', score: 157});
delete obj.score; // TypeError
'use strict';
var obj = {};
Object.defineProperty(obj, 'foo', {value: 2, configurable: false});
delete obj.foo; // TypeError
'use strict';
var frozenArray = Object.freeze([0, 1, 2]);
frozenArray.pop(); // TypeError
With your code:
"use strict";
const obj = {
deleteMe: "Some text",
dontDeleteMe: "Some other text"
};
Object.defineProperty(obj, 'dontDeleteMe', {
value: "Some text",
writable : true,
enumerable : true,
configurable : false
});
console.log(Object.keys(obj));
delete obj.deleteMe;
console.log(Object.keys(obj));
delete obj.dontDeleteMe;
console.log(Object.keys(obj));
If you don't want to use "use strict" (for whatever reasons) this would be the proxy solution:
const obj = {
deleteMe: "Some text",
dontDeleteMe: "Some other text"
};
const deleteHandler = {
deleteProperty(target, prop) {
if (prop in target) {
const deleted = delete target[prop];
if (!deleted) {
console.error(`deletion not allowed: ${prop}`);
}
}
}
};
Object.defineProperty(obj, 'dontDeleteMe', {
value: "Some text",
writable: true,
enumerable: true,
configurable: false
});
const proxyObj = new Proxy(obj, deleteHandler);
console.log(Object.keys(proxyObj));
delete proxyObj.deleteMe;
console.log(Object.keys(proxyObj));
delete proxyObj.dontDeleteMe;
console.log(Object.keys(proxyObj));

define property array of objects in Javascript

I have initialized an object that I wish to add to dynamically. Specifically I want to add array of objects to this object. I have tried the below but neither work.. is there a way to do this correctly? The final output should be object1.property1[0].value = 42 and object1.property1[0].writable = false.
const object1 = {};
Object.defineProperty(object1, 'property1', '' );
object1.property1 = [{value: 42, writable: false}];
const object1 = {};
Object.defineProperty(object1, 'property1', [{value: 42, writable: false}] );
Try using value property from descriptor argument:
const object1 = {};
Object.defineProperty(object1, 'property1', {
value: [{
value: 42,
writable: false
}]
});
console.log(object1.property1[0].value);
console.log(object1.property1[0].writable);
The object descriptor must be declared as follow:
{value: [{value: 42}], writable: false}
const object1 = {};
Object.defineProperty(object1, 'property1', {value: [{value: 42}], writable: false});
console.log(object1.property1[0].value)
Since you specified you wanted to add an array of objects to the object, this solution is similar to other answers, but should demonstrate adding an array with more than one element:
const obj = {};
Object.defineProperty(obj, 'property', {
value: [{
value: 42,
writable: false
},
{
value: 55,
writable: false
}
]
});
console.log(obj.property[0].value);
console.log(obj.property[0].writable);
console.log(obj.property[1].value);
console.log(obj.property[1].writable);
Of course, your writable isn't going to do anything, since it'll be treated as just another property. Due to the fact that your property is an array of objects (e.g., [{...},{...}]), what you may want to do is iterate over that array and freeze those objects:
obj.property.forEach(o=>Object.freeze(o))
obj.property[0].value=33; // when frozen you won't be able to update the value

Categories

Resources