Dive into getter/setters - javascript

I want to create objects which they will have an object named generic_object as their prototype object. generic_object must have a getter/setter property whose role will be to assign different functions to newly created objects. Virtually, that's the same how accessor property .onclick works. We may give different listeners to different DOM elements by just using the setter:
elem.onclick=function(){...};
I 've constructed the following:
var generic_object={
temp:null,
set onair(callback){
callback();
this.temp=callback;
},
get onair(){
return this.temp;
}
};
var obj1=Object.create(generic_object);
var obj2=Object.create(generic_object);
obj1.onair=function(){console.log(1);};
obj2.onair=function(){console.log(2);};
This does my job, however I want to eliminate .temp property because it is being created inside obj1 and obj2 as an own property (.temp inside generic_object is shadowed) and that's something I do not want.So:
Question1: Is it possible to transform the above code and get the same result without using temp?
Question2: Let's take the previous example.Since .onair property never becomes own property of obj1, obj2 (getter/setters are not shadowed), how JS engine tells between obj1.onair and obj2.onair? Are these function objects stored inside the memory spots reserved for each of the objects obj1 and obj2 even, I repeat, these are not their own properties? I am confused about that...
Thank you

Related

Javascript: Why are getters used over methods?

So I'm wondering what the difference is between methods and getters?
Reading the Mozilla documentation:
Sometimes it is desirable to allow access to a property that returns a
dynamically computed value, or you may want to reflect the status of
an internal variable without requiring the use of explicit method
calls
But don't they achieve the same thing? Why is one better than the other?
Why is saying:
var obj = {
log: ['a', 'b', 'c'],
get latest() {
if (this.log.length == 0) {
return undefined;
}
return this.log[this.log.length - 1];
}
}
console.log(obj.latest);
Sometimes preferable to just having a latest() method and calling obj.latest(). If the code is run in both cases, then what's the point? Aren't they both dynamic?
The documentation also reads:
Getters give you a way to define a property of an object, but they do
not calculate the property's value until it is accessed.
What exactly is the difference between 'accessed' and 'called'? A method isn't run until it's called, just as a property isn't accessed until its included? So where does the significance lie?
Note, the following is an opinion.
A getter is for retrieving the value of a specific property, and it allows your object to have dynamic properties based on the value of other properties but otherwise behave just as you would expect properties to behave. The getter allows you to define a method that is called when you access that property, even though you access it with normal object property accessors, myObject.dynamicProp and myObject['dynamicProp'].
Getters are provided for exactly that purpose. If that's the intent of the method you are writing, then use the getter syntax.
Methods exist to do other things that are verbal, action oriented, and not to simply return some property of the object you're writing.
You can always write a method to be a getter, but why?
So it depends on your intent. Getters specify intent, and if your intent is to provide a dynamic property that behaves just like an object property then use a getter over a method.
Hope that helps!
Updated:
While there is technical nuance to be understood here, and though I've leaned toward using the getters above, I'd say, after further reflection, that the approach you wish to take is up to you based on:
(1) Do you want to use normal accessors to access this property? Are you ok with dynamic properties that hide some logic? (They don't really 'hide' the logic per se, but they look just like normal props to the caller). Then use the get().
(2) Do you want to make it obvious and more readable that your property is dynamic? Are you doing things besides just computing a dynamic property based on the current values of existing properties of this object? Are you ok with having to call the method explicitly? Then use a method, e.g. getLatest().
According to the documentation for Object.defineProperty() on MSDN
Property descriptors present in objects come in two main flavors: data
descriptors and accessor descriptors. A data descriptor is a property
that has a value, which may or may not be writable. An accessor
descriptor is a property described by a getter-setter pair of
functions. A descriptor must be one of these two flavors; it cannot be
both.
Both data and accessor descriptors are objects. They share the
following optional keys:
configurable true if and only if the type of this property descriptor may be changed and if the property may be deleted from the corresponding object. Defaults to false.
enumerable true if and only if this property shows up during enumeration of the properties on the corresponding object. Defaults to false.
A data descriptor also has the following optional keys:
value The value associated with the property. Can be any valid JavaScript value (number, object, function, etc). Defaults to undefined.
writable true if and only if the value associated with the property may be changed with an assignment operator. Defaults to false.
An accessor descriptor also has the following optional keys:
get A function which serves as a getter for the property, or undefined if there is no getter. When the property is accessed, this function is called without arguments and with this set to the object through which the property is accessed (this may not be the object on which the property is defined due to inheritance). The return value will be used as the value of the property. Defaults to undefined.
set A function which serves as a setter for the property, or undefined if there is no setter. When the property is assigned to, this function is called with one argument (the value being assigned to the property) and with this set to the object through which the property is assigned. Defaults to undefined.
If a descriptor has neither of value, writable, get and set
keys, it is treated as a data descriptor. If a descriptor has both
value or writable and get or set keys, an exception is thrown.
This indicates that get and set intercept access calls and assignment calls respectively.
A very good example (and description) here points out a clear example of when this is useful.
Take a case where you have the first and last name on a person, with the common need for the full name.
person.setLastName('Smith');
person.setFirstName('Jimmy');
person.getFullName(); // Jimmy Smith
Using the get and set keys, gives you the option of declaring the object like so:
var person = {
firstName: 'Jimmy',
lastName: 'Smith',
get fullName() {
return this.firstName + ' ' + this.lastName;
},
set fullName (name) {
var words = name.toString().split(' ');
this.firstName = words[0] || '';
this.lastName = words[1] || '';
}
}
and assigning it, and accessing it like so:
person.fullName = 'Jack Franklin';
console.log(person.firstName); // Jack
console.log(person.lastName) // Franklin
console.log(person.fullName) // Jack Franklin
This allows the developer to interact with fullname, without accidentally leaving firstname or lastname unassigned or improperly assigned.
Lastly,
The use strict directive will enforce read and write attempts to attributes defined through get or set accordingly. See W3Schools.
"use strict";
var obj = {get x() {return 0} };
obj.x = 3.14; // This will cause an error

Prototypal inheritance with nested objects

I'm trying to get my head around prototype inheritance in Javascript. I think I got the basic concept, but when I was playing around with this I ran into the following which still has me puzzled.
There is a very similar question and answer here but it doesn't fully answer why this is happening, at least not for me.
I create a new object like this:
var User = {
username: "",
name: {
first: "",
last: ""
}
}
Next I create two "instances" of that object:
var user1 = Object.create(User);
var user2 = Object.create(User);
Now I set the name property like so:
user1.name = { first: "John", last: "Jackson"}
user2.name = { first: "James", last: "Jameson"}
Now I do
alert(user1.name.first) \\ -> John
alert(user2.name.first) \\ -> James
All as expected. So far so good.
However, if I set the name.first property like this:
user1.name.first = "John";
user2.name.first = "James";
and I get
alert(user1.name.first) \\ -> James
alert(user2.name.first) \\ -> James
Clearly now the property is being set on the prototype object User (or rather the contained name object) instead of overriding it in the current object user1. Why does that occur?
Further if I do
user1.name.middle = "Mortimer"
I can now do
alert(User.name.middle) // -> Mortimer
which is not what I would expect. Generally, whenever a property is set on a derived object, that object either already has that property as an ownProperty in which case the value is simply assigned, or the property is newly created as an ownProperty on the derived object, overriding the prototype property. Just like happens when I assign to user1.name.
So why does assigning to an object contained in the prototype object cause such (at least to me) unexpected and counter-intuitive behavior?
The way I understand it, when the assignment is made the first check is to see if user1 has an ownProperty called name, which it doesn't. If this were a read operation the prototype property would now be looked up and User checked to see if it has ownProperty name. But since this is a set operation why walk the prototype chain when usually a missing ownProperty is simply created?
But since this is a set operation why walk the prototype chain when usually a missing ownProperty is simply created?
When you say user1.name.first = "John", the user1.name part has to be resolved before the .first property can be retrieved or set. And in your example the user1.name part only exists on the prototype object, so it is that object whose .first property you are setting.
Similarly, when you say user1.name.middle = "Mortimer", again the user1.name part resolves to the nested object from the prototype, so then you create a .middle property on that object, which is why User.name.middle also returns "Mortimer".
If you said user1.name.first and user1.name could not be resolved (on the current object or in its prototype chain) then you'd have a TypeError: Cannot set property 'first' of undefined. (You can try that concept with your existing code by saying user1.address.street = "something" - you'd get the TypeError, because user1.address doesn't exist on user1 or its prototype chain.)
Since you've already read over similar questions and answers and still seem to be having trouble understanding the behavior, I'll try to make my explanation as clear as possible. Here is where I think you are going wrong (emphasis mine):
Generally, whenever a property is set on a derived object, that object either already has that property as an ownProperty in which case the value is simply assigned, or the property is newly created as an ownProperty on the derived object, overriding the prototype property. Just like happens when I assign to user1.name.
The problem here is that you assume that user.name.first counts as "a property . . . set on a derived object" (an instance of User). However, this is simply not the case. In JavaScript, inheritance of properties is only shallow (a single layer deep). user.name is just an object value shared by reference through the prototype, so modifications to it from one place are reflected everywhere.
Think of user1.name and user2.name like firstReference and secondReference in the following example snippet, and hopefully the behavior will seem a bit clearer to you.
var User = {
username: "",
name: {
first: "",
last: ""
}
}
var firstReference = User.name
var secondReference = User.name
firstReference.name.first = 'First!'
console.log(secondReference.name) //=> 'First!' (logical and expected result)
The Object.create method creates an object using the first parameter as a prototype, and the second, optional, parameter is an additional object of own properties.
I think the problem here is that name is, by definition, in the prototype, and so not an own property.
If you want separate properties, then you should use the second parameter. The prototype is where you store methods and shared properties.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
has more details.
The key is that you want both prototype and own properties.

How to remove last key:value pair in JavaScript

I am very new to JavaScript and I am trying to figure out how to set a function to remove the last key:value pair to the right, much like array.pop for an array. This is an assignment I am working on. It seems confusing to me because, from my limited understanding of JS their is no specific order in a list of properties in an object. If anyone has any insight I would appreciate it. Here is the object:
var array = {length:0, size:big, smell:strange};
this is where I have started to go, but just having a hard time completing the function:
array.pop = function() {
//...
};
Ultimately I would like it to turn out like this:
array = {length:0, size:big};
Thanks in advance.
Objects do not have any defined order of properties so there is no "last" property. You have to remove a property by name, not position.
You can, of course, iterate over the properties and inspect them as you iterate and decide whether you want to delete any given property by looking at its name. Some javascript implementations will preserve the order that properties were added, but that is specifically not guaranteed by the ECMAScript specification so it cannot be relied upon.
This will work
const car = {
color: 'blue',
brand: 'Ford'
}
let keys = Object.keys(car)
delete car[keys[keys.length-1]]
console.log(car)
This answer is good for those situtations where the key is dynamically generated numbers like 0,1,2,3,4 etc
const myObject = {
0: 'somestring',
1: 42,
2: false
};
delete myObject[`${Object.keys(myObject).length-1}`]
console.log(myObject);
output:
Object { 0: "somestring", 1: 42 }
this one line logic may not good when key is a string. So, carefully use it.
The snippet below demonstrates that "objects have no order", and an [inefficient] workaround: use an array alongside of the object, to store the order that the properties were added to the object.
Click to add random properties, and note the order that they appear below.
In CodePen (or on my webserver) the properties seem to be stored sorted numerically (even though they're stored as strings).
However, in the snippet below they seem to be ordered randomly.
Neither are the order that the properties are added.
It should be noted:
Unlike what common belief suggests (perhaps due to other programming languages like delete in C++), the delete operator has nothing to do with directly freeing memory. Memory management is done indirectly via breaking references.
More info: delete operator and Memory Management.
var obj={}, // object to store properties (keys) and values
props=[]; // array to store property names
add.onclick=function(){
var prop=rnd(), val=rnd(); // get 2 random numbers
obj[ prop ] = val; // add property & value → object
props.push( prop ); // add property name → array
updateInfo(); // display object
}
del.onclick=function(){
var lastProp=props.pop(); // get/remove last property name in array
delete obj[ lastProp ]; // remove property
updateInfo(); //display object
}
function rnd(){return Math.floor(Math.random()*1E5);} // random 0-99999
function updateInfo(){ // show pretty object 😘
info.innerHTML=JSON.stringify(obj).replace(/[\{\}]+/g,"").replaceAll(',','<br>');
}
<button id='add'>add new property</button>
<button id='del'>delete last added</button>
<div id='info'></div>

using the extend function

this is my very first post! I have a quick question in regarding inheritance in javascript.
Of course the 'extend2' method is used to inherit child objects from the parent object using a for-in loop.
var extend2 = function (child, parent) {
var c = child.prototype;
var p = parent.prototype;
for (var i in p) {
c[i] = p[i];
}
}
I'm currently reading "Object Oriented Javascript" by Stoyan Stefanov. It's an awesome book.
Can anyone give me a good detailed explanation of how the child's prototype object is not entirely overwritten or replaced but it's just augmented?
How is it that when the objects inherit, they copy (primitive data types) instead of being looked up as a reference by using the extend2 function?
This would really help thanks!
Primitive data types in javascript are passed via value, rather than reference. Thus when you copy a value it is actually copying it, not referring to it.
Traditionally this is because a primitive was literally encoded in the memory in such a way (so the primitive int 7 would be encoded in memory as 0x7. When dealing with objects, however, they are encoded as a pointer to the memory location where the actualy object is. Thus, when you copy the value it is merely a copy of the reference pointer, not the object that that pointer referrs to.
As for the fact that the child's prototype is not replaced, that is because a prototype in java is merely another object. So a prototype may look like:
{
someField: 5
}
Which would indicate that instances of the object would initialize with a field called someField whose value is 5. with the above code, each entry in the object is copied to the child prototype, but nothing is deleted. Thus if the child prototype looks like:
{
someField: 10
someOtherField: 3
}
Then performing the above extend2 command will overwrite someField, but not someOtherField, so the resulting prototype would be:
{
someField: 5
someOtherField: 3
}

JS assignment order of properties in objects

Just had a quick question about why a certain order of assignment works and another doesn't.
I wanted to create a simple "inherit" / "copy" function (just for testing it) that copies properties from one object to another:
var cat = { tail:"yes", hairy:"yes, hairy" };
var dog = { sick:"extremely ill"};
function inherit(obj1, obj2) {
for (var p in obj1)
{
obj2[p] = obj1[p]; // this works, but "obj1[p] = obj2[p];" doesn't. Why??
}
}
inherit(cat, dog);
console.log(dog.tail);
You are looping over all the properties of obj1, so all those properties exist on obj1.
If you try to copy from obj2 then you are trying to copy properties that don't exist (on that object), so you cause an error.
You're reading the properties var p in obj1, so those indexes only necessarily exist in obj1. As such, trying to assign obj1[p] = obj2[p]; won't work as expected, since there is no guarantee (and in your particular example this is certainly the case) that obj[p] is defined. This assignment will simply assign undefined to indexes in obj1 that don't exist in obj2 and copy the values whose indexes do exist in obj2.
You need to loop over each object's properties separately (i.e. two loops), although this also isn't a good idea, since any values with the same index on both object will result in one being wiped out. What are you actually trying to achieve by this? It seems a very dangerous/volatile thing to do.

Categories