How does changing metadata in JavaScript work? - javascript

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.

Related

configure an object property descriptor

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'

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

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

Freezing the elements of an array

I have an array of certain primitive elements:
const array = [1, 2, 3]
I want to be able to temporarily freeze an element of this array and prevent it from being modified. But the moment the element can be allowed to be modified, there should be a way to unfreeze the value.
Is there a way to do so?
Off-topic for those who flag this question as duplicate:
The question is about freezing the elements of an array, not the entire array. It is not a duplicate of a question regarding freezing the entire array.
You don't want to freeze the value, you want to freeze the property that holds the value (the "1" property of the array, in your case). You'd use Object.defineProperty to redefine the property without the writable flag. To make it writable again, you redefine it with writable: true:
const array = [1, 2, 3];
console.log("A", array.join(", ")); // 1, 2, 3
// Freeze it
Object.defineProperty(array, "1", {
value: array[1],
writable: false, // For emphasis (this is the default)
enumerable: true,
configurable: true
});
console.log("B1", array.join(", ")); // 1, 2, 3
array[1] = 42; // <== Doesn't change it
console.log("B2", array.join(", ")); // 1, 2, 3 (still)
// Thaw it
Object.defineProperty(array, "1", {
value: array[1],
writable: true,
enumerable: true,
configurable: true
});
console.log("C1", array.join(", ")); // 1, 2, 3
array[1] = 42; // <== Changes it
console.log("C2", array.join(", ")); // 1, 42, 3 (changed!)
That assignment would be an exception if the code doing the assignment were running in strict mode:
"use strict";
const array = [1, 2, 3];
console.log("A", array.join(", ")); // 1, 2, 3
// Freeze it
Object.defineProperty(array, "1", {
value: array[1],
writable: false, // For emphasis (this is the default)
enumerable: true,
configurable: true
});
console.log("B1", array.join(", ")); // 1, 2, 3
array[1] = 42; // <== Doesn't change it
console.log("B2", array.join(", ")); // 1, 2, 3 (still)
// Thaw it
Object.defineProperty(array, "1", {
value: array[1],
writable: true,
enumerable: true,
configurable: true
});
console.log("C1", array.join(", ")); // 1, 2, 3
array[1] = 42; // <== Changes it
console.log("C2", array.join(", ")); // 1, 42, 3 (changed!)
But note that if your code can redefine it to make it writable, anyone else's code can, too.
Alternately, give it a getter and setter, make it non-configurable (so no one else can redefie it), and maintain a flag:
const array = [1, 2, 3];
let elementValue = array[1];
let writable = true;
Object.defineProperty(array, "1", {
get: function() {
return elementValue;
},
set: function(newValue) {
if (writable) {
elementValue = newValue;
}
},
enumerable: true,
configurable: false // Again, emphasis
});
console.log("A", array.join(", ")); // 1, 2, 3
array[1] = 42;
console.log("B", array.join(", ")); // 1, 42, 3 -- it changed
writable = false;
array[1] = 67;
console.log("C", array.join(", ")); // 1, 42, 3 -- didn't change
writable = true;
array[1] = 94;
console.log("D", array.join(", ")); // 1, 94, 3 -- changed
Naturally, you'd hide some of that and just expose the array itself.

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