Why after "freezing" the document.body object, I can modify its properties? - javascript

Object.freeze()
Values cannot be changed for data properties. Accessor properties (getters and setters) work the same (and still give the illusion that you are changing the value). Note that values that are objects can still be modified, unless they are also frozen. As an object, an array can be frozen whereafter its elements cannot be altered. No elements can be added or removed from it as well.
'use strict'
document.body.innerHTML = "Hello#1";
var frozen = Object.freeze(document.body);
document.body = frozen;
console.log("Same frozen body (First):", document.body === frozen)
console.log(document.body['innerHTML']);
// Why I can modify this property?
document.body.innerHTML = "Hello#2";
console.log("Same frozen body (Second):", document.body === frozen);
console.log(document.body['innerHTML']);
This code snippet is using getter and setter (This approach fails)
'use strict'
var myObj = {
a: "Ele#1",
get innerHTML() {
return this.a;
},
set innerHTML(html) {
this.a = html;
}
}
myObj = Object.freeze(myObj);
myObj.innerHTML = "Ele";
console.log(myObj);
Why after "freezing" the body object, I can modify its properties? for example, innerHTML?
Probably I have a lack of information, I don't know!

innerHTML is an accessor property. If you debug document.body to the console and drill down the prototype chain, you can see it's defined in Element as get innerHTML() and set innerHTML().
Here's a barebones example of how you can accomplish the same:
var myObj = (function() {
var html = null;
return {
get innerHTML() { return html; },
set innerHTML(value) { html = value; }
};
})();

Accessor properties (getters and setters) work the same (and still give the illusion that you are changing the value).
innerHTML is indeed an accessor property as you can see below, and in the specification:
function getPropertyDescriptor(object, property) {
while (typeof object === 'object' && object !== null) {
const descriptor = Object.getOwnPropertyDescriptor(object, property)
if (descriptor) {
return descriptor
}
object = Object.getPrototypeOf(object)
}
}
const desc = getPropertyDescriptor(document.body, 'innerHTML')
console.log(desc)
Modifying your simplified example to use a WeakMap instead of a property, you can make it work:
'use strict'
const Element = (() => {
const weakPrivate = new WeakMap()
return class Element {
constructor () {
weakPrivate.set(this, { innerHTML: 'initial' })
}
get innerHTML () {
return weakPrivate.get(this).innerHTML
}
set innerHTML (value) {
return weakPrivate.get(this).innerHTML = value
}
}
})()
let object = new Element()
Object.freeze(object)
console.log(object.innerHTML)
object.innerHTML = 'new value'
console.log(object.innerHTML)

Related

JS ES6 IIFE + Symbol & Prototype - Adding to Instance?

I realize my question's title may be worded weirdly, so I apologize up front.
To be clear, I am referring to this:
var IIFE = (function () {
var a = Symbol("a");
function IIFE() {
this["a"] = null;
}
IIFE.prototype = {
get a() { return this[a]; },
set a(n) { this[a] = n; }
}
return IIFE;
}());
var iife = new IIFE;
I want to dynamically add 'b' & 'c' using an array:
var arrProps = ['b','c'];
to an instance of IIFE. I don't care how it gets done, so as long as the values specified within arrProps can be accessed & assigned the same as you would with 'a' inside the instance, outside the instance, and within prototype get/set. An example of using 'b' would be:
inside the instance: this["b"] = value;
outside of the instance: iife.b = value;
prototype set/get: this[b] = value;
As far as the get/set internals go, there is nothing more than just getting the value and setting value.
Any help would be very much appreciated.
Instead of using a Symbol, have the function return a Proxy instead, and you can use its get and set traps to check for accesses/assignments to arbitrary properties:
const proxy = new Proxy({}, {
get(obj, prop) {
console.log('getting');
return obj[prop];
},
set(obj, prop, newVal) {
console.log('setting');
return obj[prop] = newVal;
},
});
proxy.a = 'aVal';
console.log(proxy.a);
proxy.b= 'bVal';
proxy.c = 'cVal';
proxy.a = 'anotherAVal';

How do I set the default property of an object (object)?

I need to add new objects to the list (Filters.list)
* Filters.prop - is a default prop
* list items also have prop - list[name].prop (equal default prop)
Chage default Filters.prop -> [not] change item list[name].prop
Where is the mistake?
function Filters() {
this.prop = 'some';
this.list = { };
this.add = function (name) {
this.list[name] = {
prop: this.prop,
};
}
}
let attempt = new Filters();
attempt.add('first');
attempt.prop = 'other';
document.writeln(attempt.prop);
document.writeln(attempt.list['first'].prop);
After run snippet output: other some
But I need: other other
I thought the property would be saved by reference. But this is not so, it does not change. When I change Filters.prop I expected that too it will change list[name].prop
The problem with this is that, as you noticed yourself, the value is passed the way you're doing it instead of the reference.
Thanks to JavaScript get, you can return the value of prop of the surrounding object within a function which behaves like an attribute.
function Filters() {
this.prop = 'some';
this.list = { };
this.add = function(name) {
let self = this;
this.list[name] = {
get prop() {
return self.prop;
},
};
}
}
let attempt = new Filters();
attempt.add('first');
attempt.prop = 'other';
document.writeln(attempt.prop)
document.writeln(attempt.list['first'].prop)
Side note: I use the variable self here because using this.prop within get prop() would reference the wrong object and hence cause a recursion.

Getter/Setter as function

Is it possible to create property getter/setter as a function?
Standard getter/setter work like following:
class Test {
something: any;
get prop() {
return something;
}
set prop(value) {
something = value;
}
}
let instance = new Test();
instance.prop = 'Foo';
console.log(instance.prop); // = Foo
I would need following:
let instance = new Test();
instance.prop('Bar') = 'Foo'; // access setter as a function of prop
console.log(instance.prop('Bar')); // = Foo
Yes, I know it's unorthodox usage and yes, I know that I could implement this functionality bit differently. I'm just interested if this is possible in JS/TS/ES6.
Update
This is the closest I got:
class Test {
something: any;
prop(area /* my custom symbol type */) {
const obj: any = {};
Object.defineProperty(obj, 'value', {
// get child object of my complex object
get: () => this.something[area];
// replace part of my complex object
set: (value) => {
this.something = {...this.something, [area]: value}
}
});
}
}
let instance = new Test();
instance.prop('Bar').value = 'Foo';
console.log(instance.prop('Bar').value); // = Foo
So in short, I'd like to get rid of value suffix if possible.
As #deceze mentioned in the first comment, it is not possible to assign to function call, so solution in the update is as best as it gets.

Unsettable & Unwritable properties are still mutable

I am trying to create a property within a constructor function which is immutable except through a prototype function. I am trying to go off MDN documentation of this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties. But there does not seem to be a way to make a property completely immutable. Consider a simple example:
function Test(){
Object.defineProperties(this,{
elems : { value : [] }
})
}
Test.prototype.addElem = function(newElem){
if (this.elems.indexOf(newElem) == -1){
this.elems.push(newElem);
}
};
which works fine in most cases (not assignable):
>a = new Test()
Object { , 1 moreā€¦ }
>a.elems
Array [ ]
>a.elems = 10
10
>a.elems
Array [ ]
Unfortunately, it is still mutable. Consider:
>a.elems.push(10)
1
>a.elems
Array [ 10 ]
I am sure they are other functions (array or object methods?) that will change the value of a non-writeable & non-settable property. Push was just the one I ran into. Is there a way to accomplish this? I know that one possible solution is :
function Test(){
var elems = [];
this.addElem = function(newElem){
if (elems.indexOf(newElem) == -1){
elems.push(newElem);
}
}
}
But I have read this is memory-inefficient especially when there are many instances of the "class". Also, what I am working on may have many methods like this, so I am even more worried about memory considerations.
Any ideas? I am not super knowledgeable about all the intricacies of JS prototyping.
In JavaScript, objects are extensible by default, but if you're able to take advantage of ES5, you should be able to use the Object.seal() or Object.freeze() methods to get immutable properties.
The MDN docs for Object.freeze() have an example that shows how to recursively freeze ("deepFreeze") all of the properties of an object, effectively making it completely immutable.
Here's a proof of concept that combines the code in the question with the code from the docs:
function Test() {
Object.defineProperties(this, {
elems : { value : [] }
})
}
Test.prototype.addElem = function(newElem) {
if (this.elems.indexOf(newElem) == -1) {
this.elems.push(newElem);
}
};
function deepFreeze(obj) {
// Retrieve the property names defined on obj
var propNames = Object.getOwnPropertyNames(obj);
// Freeze properties before freezing self
propNames.forEach(function(name) {
var prop = obj[name];
// Freeze prop if it is an object
if (typeof prop == 'object' && prop !== null)
deepFreeze(prop);
});
// Freeze self (no-op if already frozen)
return Object.freeze(obj);
}
a = new Test();
a.elems.push(1);
console.log(a.elems); // [1]
deepFreeze(a);
a.elems.push(2);
console.log(a.elems); // Error
In FireBug, the a.elems.push() after the object is "deep frozen" returns a TypeError exception, indicating the property is not writable;
TypeError: can't define array index property past the end of an array
with non-writable length
The Safari inspector also returns a TypeError exception:
TypeError: Attempted to assign to readonly property.
You can largely accomplish this with the help of a closure. This is how you achieve privacy in JavaScript.
In a nutshell you create a variable inside of a function and have that function return an object that contains setters/getters.
In my example the foo function contains a _foo variable that can only be set by the methods in the object returned from function foo. You are effectively creating an API to the var held withing the function foo's scope.
var foo = function(config){
if (!config) {
config = {};
}
//enclosed variable
var _foo = {
bar: []
};
if (config.bar) {//All item to be initialized with data
_foo.bar = config.bar;
}
var fooAPI = {
addBarItem: function(val){
_foo.bar.push(val);
return _foo.bar.length - 1;//return idenx of item added
},
removeBarItem: function(index) {
return _foo.bar.slice(index, 1);//return the removed item
},
getBarItem: function(index) {
return _foo.bar[index];//return the removed item
},
emptyBarItems: function() {
return _foo.bar.slice(0, _foo.bar.length);//return all removed
},
getBarItems: function(){
//clone bar do not return reference to it in order to keep private
var newBar = [];
_foo.bar.forEach(function(item){
newBar.push(item);
});
return newBar;
}
};
return fooAPI;
};
var myFoo = new foo({bar: ['alpha', 'beta', 'gamma']});
console.log(myFoo.getBarItems());

Javascript: initialize object by property

var someObject = function(arg) {
this.property = function() {
// do something with the argument
return arg;
}();
};
var obj = new someObject(some argument);
// object.property instanceof "someObject" should be true
When property of someObject is used, a new instance of newObject should be created. For example, when I use the native DOM Element's nextSibling property, a new DOM Element object instance is returned. I wonder if it is possible to create a similar structure. Or would such cause infinite recursion?
Strictly speaking, this is possible in ES5 (all latest browsers, yes that includes IE).
ES5 specifies getters and setters via the get and set keyword or the Object.defineProperty function so you can make functions behave like properties (think innerHTML). Here's how you can do it:
function Mother () {
this.name = '';
Object.defineproperty(this,'child',{
get: function(){
return new Mother();
}
});
}
So the object can now create new instances of itself simply by reading the child property:
var a = new Mother();
a.name = 'Alice';
b = a.child;
b.name = 'Susan';
alert(a.name) // Alice
alert(b.name) // Susan
a instanceof Mother; // true
b instanceof Mother; // true
Having said that, your observation about DOM elements is wrong. The DOM is simply a tree structure. You can create a similar structure yourself using old-school javascript:
function MyObject () {}
var a = new MyObject();
var b = new MyObject();
var c = new MyObject();
a.children = [b,c];
b.nextSibling = c;
c.prevSibling = b;
// now it works like the DOM:
b.nextSibling; // returns c
a.children[1]; // returns c
b.nextSibling.prevSibling instanceof MyObject; // true
No, that's not possible. You could set function to the property, but anyway, you will need to invoke function somehow (with property() notation or with call/apply), because function it's an object itself, and only () or call/apply say to interpreter that you want to execute code, but not only get access to function's object data.
Your understanding of the nextSibling property in the DOM is incorrect. It does not create a new DOMElement, it simply references an existing DOM Node.
When you create a sibling of an element to which you have a reference (e.g., via jQuery or document.createElement), the browser knows to update sibling and parent/child references.
So, the behavior you're trying to emulate doesn't even exist.
As others have intimated, simply accessing a property on an object is not sufficient to get the Javascript interpreter to "do" anything (other than deference the name you're looking up). You'll need property to be a function.
nextSibling doesn't return a new element, it returns an existing element which is the next sibling of the target element.
You can store an object reference as a property of another object just like you can store primitive values.
function SomeObject(obj) {
this.obj = obj;
}
var someObject = new SomeObject(new SomeObject());
someObject.obj instanceof SomeObject //true
However if you want to create a new instance of SomeObject dynamically when accessing someObject.obj or you want to return an existing object based on conditions that shoul be re-evaluated every time the property is accessed, you will need to use a function or an accessor.
function SomeObject(obj) {
this.obj = obj;
}
SomeObject.prototype.clone = function () {
//using this.constructor is a DRY way of accessing the current object constructor
//instead of writing new SomeObject(...)
return new this.constructor(this.obj);
};
var someObject = new SomeObject(new SomeObject());
var someObjectClone = someObject.clone();
Finally with accessors (be aware that they aren't cross-browser and cannot be shimmed)
function SequentialObj(num) {
this.num = num;
}
Object.defineProperty(SequentialObj.prototype, 'next', {
get: function () {
return new this.constructor(this.num + 1);
},
configurable: false
});
var seq = new SequentialObj(0);
console.log(seq.next); //SequentialObj {num: 1}
console.log(seq.next.next.next); //SequentialObj {num: 3}
If you want this.property() to return a new someObject you can write the class as follows:
var someObject = function(arg) {
this.arg = arg;
};
someObject.prototype.property = function(arg) {
// do something with the argument
return new someObject(arg||this.arg);
}();
var obj = new someObject(/*some argument*/);
// object.property instanceof "someObject" should be true
If you want it to return some already instantiated version you can write the code as follows:
var someObject = (function() {
var previous;
function(arg) {
this.arg = arg;
this.propertyBefore = previous;//refers to the someObject created before this one
if(previous) previous.property = this; //before.property now references this class
//this.property will be undefined until another instance of someObject is created
previous = this;
};
})()
var obj = new someObject(/*some argument*/);// returns someObject already created earlier (similar to nextSibling)
One small note - its best practice in javascript to declare class names with a capitalized name (SomeObject rather than someObject)

Categories