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));
Related
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'
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;
}
});
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.
I am trying to make a general case getter/setter for any given object. for example:
var obj = {
a: 1,
b: "dog",
c: false
}
Object.defineProperty(obj, 'general_case', {
get: function(attr) { return "unknown attribute "+attr }
})
console.log(obj.a) // should print 1
console.log(obj.d) // should print "unknown attribute d"
How would I be able to accomplish this in JavaScript?
You can use a Proxy.
var obj = new Proxy({
a: 1,
b: "dog",
c: false
}, {
get(target, prop, receiver) {
return prop in target ? target[prop] : "unknown attribute " + prop;
}
});
console.log(obj.a);
console.log(obj.d);
I tried to loop through a huge list of properties in object, but failed to extract properties that has the same prefix, I can't use object deletefunction because the list is huge, where is my mistake?
const a = {
obj_abc: true,
obj_def: false,
hello_123: true,
hello_456: 'another value'
};
let b = {};
for(k in a){
const [key] = k.split('_');
if(key === 'hello') {
b = {...b[key], [key]:a[k]} //the problem is here, it gave me only hello_456: 'another value'
}
}
console.log(b);
Try using bracket notation
const a = {
obj_abc: true,
obj_def: false,
hello_123: true,
hello_456: 'another value'
};
let b = {};
for (k in a) {
const [key] = k.split('_');
if (key === 'hello') {
b[k] = a[k];
}
}
console.log(b);
Using startsWith()
const a = {
obj_abc: true,
obj_def: false,
hello_123: true,
hello_456: 'another value'
};
let b = {};
for (k in a) {
if (k.startsWith('hello_')) {
b[k] = a[k];
}
}
console.log(b);
Your key is hello for both hello_123 and hello_456, hence its overriding the old entry for hello key. you need unique keys. eg below.
const a = {
obj_abc: true,
obj_def: false,
hello_123: true,
hello_456: 'another value'
};
let b = {};
for(k in a){
const [key] = k.split('_');
if(key === 'hello') {
//key is hello for both hello_123 and hello_456, hence its overriding
b[k] = a[k] //the problem is here, it gave me only hello_456: 'another value'
}
}
console.log(b);
Try this
const a = {
obj_abc: 123,
obj_def: 456,
hello_123: 123,
hello_456: 456
};
// filter out the keys that start with hello
var keys = Object.keys(a).filter(function(k) {
return k.indexOf("hello") === 0;
});
//to convert an array of filtered keys into an object of key-value pairs
var res = keys.reduce(function(matched, k) {
matched[k] = a[k];
return matched;
}, {});
console.log(res);
You can use entries, reduce for clean code. Same time you can create map of all key, later good to extract. See the example 2.
// Example 1
const a = {
obj_abc: true,
obj_def: false,
hello_123: true,
hello_456: 'another value'
};
const result = Object.entries(a).reduce((map, [key, value]) => {
if (key.indexOf("hello_") === 0) map[key] = value
return map
}, {})
console.log(result);
// To collect all in once
// Example 2
const result2 = Object.entries(a).reduce((map, [key, value]) => {
const [k] = key.split("_")
if(!map[k]) map[k] = {}
map[k][key] = value
return map
}, {})
console.log(result2); // { obj: { obj_abc: true, obj_def: false }, hello: { hello_123: true, hello_456: 'another value' } }
console.log(result2["hello"]); // { hello_123: true, hello_456: 'another value' }
console.log(result2["obj"]); // { obj_abc: true, obj_def: false }
Please find my answer.
const a = {
obj_abc: true,
obj_def: false,
hello_123: true,
hello_456: "another value"
};
let b = {};
for (key in a) {
let [text] = key.split("_");
if (!(text in b)) {
b[text] = { [key]: a[key] };
}
else {
Object.assign(b[text], { [key]: a[key] });
}
}
console.log(b);
OUTPUT
{
"obj": {
"obj_abc": true,
"obj_def": false
},
"hello": {
"hello_123": true,
"hello_456": "another value"
}
}