How can I stop an object property access chain under certain conditions? - javascript

Object A has an array of instances of Object B.
Object A also has a getter method that takes in an index and returns the relevant Object B.
Object B has a set of methods that can be called.
I'm trying to call the object B method using an object chain ( a.getB().method() )like below:
class ObjectB{
foo(){return 'bar'}
}
class ObjectA{
constructor(){
this.list = []
}
addB(){this.list.push(new ObjectB)}
getB(index) { if(index < this.list.length) return this.list[index]}
}
a = new ObjectA();
a.addB();
a.getB(0).foo(); // Works
a.getB(4).foo(); // Returns error, since getB(4) is undefined and has no foo()
I tried different else statements in getB() but I can't stop the script from trying to access 'output'.foo().
I tried a try catch in getB(), but I can't force it to give an error at getB(4). Is there a clean way from me to handle this potential issue without try/catching every instance of the last line?

Use the optional chaining operator:
a.getB(4)?.foo()
You can also add it for function calls:
a.getB(4)?.foo?.(). This last solution works if getB(4) returns an object that is defined, but has no foo function.

Related

Cannot read private member from an object whose class did not declare it...?

In this program:
class Example {
#privateMember = 123;
// these are fine
addNumber (n) { return this.#privateMember + n; }
doAddNumber (n) { return this.addNumber(n); }
// "cannot read private member #privateMember from an
// object whose class did not declare it"
#operations = { add: this.addNumber };
operate (n) { return this.#operations.add(n); }
}
const ex = new Example();
console.log(ex.addNumber(77));
console.log(ex.doAddNumber(77));
console.log(ex.operate(77));
Calling addNumber works fine, so does doAddNumber, but calling operate yields the error:
TypeError: Cannot read private member #privateMember from an object whose class did not declare it
at Object.addNumber [as add] (<anonymous>:11:17)
at Example.operate (<anonymous>:20:29)
at <anonymous>:27:16
at dn (<anonymous>:16:5449)
I can't make any sense of this error because:
addNumber works fine, so its not a syntax error or a typo at least.
doAddNumber works fine, so its not a problem calling functions from other functions.
operate just calls addNumber which, from (1), works fine.
this is an object whose class declares #privateMember... I mean, this is an Example and I can see that it's declared in Example. It's right there... I typed it myself...
I found TypeError: Cannot read private member from an object whose class did not declare it but I can't understand how it applies, if it applies.
I can't figure out what's going on here. Why doesn't operate work even though addNumber and doAddNumber do?
In my real code (this is just a minimal example), I am trying to use a dictionary like #operations to hold implementations of a number of various algorithms for performing a task, indexed by a string ID, where the string algorithm ID is specified to the constructor. This is also convenient because I can get the keys from this dictionary to provide a list of valid algorithm IDs without having to duplicate that list anywhere. Now, I can just switch it to an if statement and make sure I keep the queryable list up to date as well, but I can't understand why this doesn't work.
When you set #operations.addNumber and then call it, this === #operations, not ex, which fails because ex.#operarions.#privateMember does not exist.
If I change your Example class with
#operations = { add: this.addNumber.bind(this)};
then ex.#operations.add runs with this === ex and your code returns
> 200
> 200
> 200
Here's a working example of what I believe you're after...
class Example {
#privateMember = 123;
// these are fine
addNumber (n) { return this.#privateMember + n; }
doAddNumber (n) { return this.addNumber(n); }
// "cannot read private member #privateMember from an
// object whose class did not declare it"
#operations = { add: this.addNumber };
operate (n) { return this.#operations.add.call(this, n); }
}
const ex = new Example();
console.log(ex.addNumber(77));
console.log(ex.doAddNumber(77));
console.log(ex.operate(77));
In short, in your code the #operations method is simply creating an object with a reference to the Example class method of addNumber, with no association to any object. So, when attempting to make use of this reference, you need to pass an object...
Ie, you're essentially defining...
#operations = { add: Example.prototype.addNumber }
In the proposed solution, the operate method invokes the reference to the addNumber method in the #operations object, but of course has to pass an object to addNumber, and does so by performing a call( this, ...).

Why Object and Array constructor don't need to be prefixed with new for constructing an object?

Consider the following snippet :-
function Custom(){}
// prints {}
console.log(Object());
// prints []
console.log(Array());
// prints undefined
console.log(Custom())
// prints {}
console.log(new Custom());
I know that the Custom function constructor needs a new keyword prefixed to bind this. Why isn't that necessary for Array and Object constructors ?
The array constructor says:
also creates and initializes a new Array object when called as a function rather than as a constructor. Thus the function call Array(…) is equivalent to the object creation expression new Array(…) with the same arguments.
The object constructor says:
If NewTarget is neither undefined nor the active function, then
a. Return ? OrdinaryCreateFromConstructor(NewTarget, "%Object.prototype%").
If value is undefined or null, return ! OrdinaryObjectCreate(%Object.prototype%).
Return ! ToObject(value).
So it's explicitly noted that new isn't required for either of those.
(though, you should probably never be using either Object or Array as constructors, whether with or without new - better to use object and array literals)
It'll be trivial to implement this sort of thing yourself in Custom, if you want - check if the function was called with new, and if not, explicitly return something:
function Custom(){
if (!new.target) {
return new Custom();
}
}
console.log(Custom())
console.log(new Custom());
Custom, Object, and Array all are functions. When you call them, i.e. Object(), the value printed is the return value of the function. Object and Array returns a new empty object and array respectively. While the Custom function returns nothing and thus undefined is printed.
When you call a function with new is creates an object by calling the function as a constructor. As one of the comments mentioned This gives more details about the new keyword.

JavaScript Object and Primitive

Please I want someone to explain the code below for me:
var f = new Number(44);
f.name = "Yusuf";
f.hello = function() {
console.log("Hello");
};
console.log(typeof f);
f.hello();
console.log(f.name);
console.log(f.toString() + "good");
console.log(Object.prototype.hasOwnProperty(name));
console.log(f.hasOwnProperty(hello));
console.log(f.length);
When I check the variable type. Object gets return and I am sure this is because of the Number object constructor call function. I added two properties, one a member and a method and when I call them , it work but when I used hasOwnProperty(), false is return for the member key and undefined for the method key.
Why is it so?
where are the methods going to if the hasOwnProperty doesn't work as usual when it is supposed to when I am actually checking the property on the containing object.?
I checked Number and object object and they all return false.
The hasOwnProperty method takes the property key as a string:
console.log(Number.prototype.hasOwnProperty("name"));
console.log(Object.prototype.hasOwnProperty.call(f, "name"));
console.log(f.hasOwnProperty("name"));
console.log(f.hasOwnProperty("hello"));
I recommend to always "use strict" mode so that you get exceptions when you try to use undeclared variables.

Looking for a good clear explanation for why this doesn't work

So I am learning Javascript and testing this in the console.
Can anybody come up with a good explanation why something like this does not work?
var students = ['foo', 'bar', 'baz'];
function forEach2(arr1) {
for (i = 0; i < arr1.length; i++) {
console.log(arr1[i]);
}
}
students.forEach2(students)
I get : Uncaught TypeError: students.forEach2 is not a function
For me it would seem logical to work but it doesn't
why?
forEach2 is not a function of students. students is an array containing 3 string values. just use forEach2 without students. before it.
var students = ['foo', 'bar', 'baz'];
function forEach2(arr1) {
for (i = 0; i < arr1.length; i++) {
console.log(`arr1[${i}]:`, arr1[i]);
}
}
console.log("students:", students);
console.log("students has .forEach2 function ?", typeof students.forEach2 == "function");
console.log("forEach2 is a function?", typeof forEach2 == "function");
console.log("forEach2(arr1)...");
forEach2(students);
console.log("students.forEach(student)...");
//forEach already has a native implementation
students.forEach((student)=> {
return console.log("student:", student);
});
JavaScript is an object-oriented language with prototypal inheritance.
Object-oriented means you have objects with members (called properties in JavaScript) which hold values. In JavaScript, functions are “first-class citizens”, meaning you can assign them to variables just like you would other values.
When you write write a function such as ‘function x(y) { return y +1; }’, what’s really happening is 1) you are declaring a variable named “x”, and 2) you are creating a function “value” which is then assigned to that variable. If you have access to that variable (it’s within scope), you can invoke the function like ‘x(5)’. This evaluates to a new value which you could assign to another variable, and so on.
Ok, so now we have a problem. If functions are values, and values take up space (memory), then what happens when you need a bunch of objects with the same function? That’s where prototypal inheritance comes in. When we try to access a value on an object, via either the member access operator ‘.’, like ‘myObj.someValue’, or via an indexing operator ‘[]’ like ‘myObj[“someValue”] (both of which are equivalent in JavaScript, for the most part), the following occurs:
The runtime checks to see if a ‘myObj’ variable exists in the current scope. If it doesn’t? Exception!
The runtime looks at the object referenced by the variable and checks to see if it has a property with the key “someValue”.
If the object has that property, the member access expression (‘myObj.someValue’) evaluates to that property’s value, and we’re done.
If the object does not have that property, we start doing prototypal inheritance stuff. In JavaScript, all this means is that when we try to access a property that doesn’t exist, the runtime says “hey, what if this objects *prototype has a property with that key?” If it does, we use the prototype’s property’s value. If it doesn’t, we return ‘undefined’.
Notice that because prototypes are just objects, and therefore can themselves have a prototype, step 4 is recursive until we run out of prototypes on which to attempt member access.
Ok, so at this point you may be thinking “what’s a prototype and what does it have to do with my question?” It’s just an object. If any object has a property with the key “prototype”, then that property’s value IS a prototype. Specifically, it’s that object’s prototype. And so this is where your problem arises.
There is no property on your object with the key “forEach2”. Why? Because you didn’t put it there, and you didn’t put it on the object’s prototype (or any of the prototypes “up stream”.
The ‘forEach’ function of an Array exists as a property on the Array’s prototype: ‘Array.prototype.forEach = function (...) {...}’. Your function does not, and therefore you cannot use member access on an array to get that value (the function), and that’s why your code is borked.
Fortunately for you, a variable ‘forEach2’ exists in your current scope, and you can just use it without needing to do any member access! You just write ‘forEach2(students);’ and that’s all.
But what if you want to access that function anywhere you have an array? You have two options: put it on every instance of your arrays, or put it on Array’s prototype. ‘Array.prototype.forEach2 = forEach2;’ however, if you do this you will need to change your function a bit. Right now it expects the array as its first argument (‘arr1’), but its redundant to write ‘students.forEach2(students)’ because when a function is invoked immediately following member access, the function will be provided with a special variable ‘this’ which will have the value of the object for which you are accessing its member. So, in this case, you would omit the ‘arr1’ argument and instead just use the the special ‘this’ variable which is magically in scope within your function.
Array.prototype.forEach2 = function ()
{
for (var i = 0; i < this.length; i++)
{
console.log(this[i]);
}
}
I hope this clarifies some things for you, and I hope it raises a bunch more questions.
P.S: Adding things to prototypes is both powerful and considered harmful unless you know what you’re doing and have a good reason to do it (like writing polyfills)... so do it at your own peril and use responsibly.
For your example to work, simply add the following statement after definition of forEach2:
// Here you're redefining the built-in forEach function with your
// custom function
students.forEach = forEach2;
// Now this must work
students.forEach(students);
This is an explanation why is that:
An array is a particular implementation of the Object. Object is an aggregate of the property:value pairs, such as
{'some_property': '1',
'some_function': function(),
'forEach': function() {<function logic>},
...
}
Your error 'Uncaught TypeError: students.forEach2 is not a function' tells, that there is no property forEach2 (which supposed to be a function) in between the Object's properties. So, there are two methods to correct this situation: - add a property (method function) to the object, or alter the existing property with similar functionality (not necessarily though).
[1] The first method assumes you add a new property function using Array.prototype.forEach2 = function() {...}.
[2] The second - you may redefine or alter any property in the Object by just assigning a new value to that property: Object.forEach = forEach2
Because the function forEach2 is not available either within the Array.prototype or is not a direct property of that array, so, you have two alternatives to use that custom forEach:
Use a decorator to add that function to a specific array
var students = ['foo', 'bar', 'baz'];
function forEach2() {
for (i = 0; i < this.length; i++) {
console.log(this[i]);
}
}
function decorate(arr) {
arr['forEach2'] = forEach2;
return arr;
}
decorate(students).forEach2()
Add that function to the Array.prototype
var students = ['foo', 'bar', 'baz'];
Array.prototype.forEach2 = function() {
for (i = 0; i < this.length; i++) {
console.log(this[i]);
}
}
students.forEach2();
Both alternatives use the context this to get the current array.

airnb/javascript: What does this mean: "These methods may be shadowed by properties ..."?

I try to understand the following sentence at github airnb/javascript
https://github.com/airbnb/javascript#objects--prototype-builtins
Why? These methods may be shadowed by properties on the object in
question
What is meant with "shadowed" in this case?
For easier reference here the full section:
3.7 Do not call Object.prototype methods directly, such as hasOwnProperty, propertyIsEnumerable, and isPrototypeOf.
Why? These methods may be shadowed by properties on the object in
question - consider { hasOwnProperty: false } - or, the object may be
a null object (Object.create(null)).
// bad
console.log(object.hasOwnProperty(key));
// good
console.log(Object.prototype.hasOwnProperty.call(object,key));
// best
const has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope.
/* or */
import has from 'has';
// ...
console.log(has.call(object, key));
When you creating an object
const anObject = {};
it almost always has the Object in prototype chain. It allow the created object to have access to functions defined in Object like hasOwnProperty.
Shadowed means a method or a property that defined in the created object has the same name as those functions or properties that in prototype chain.
Example of shadowing:
const obj = {};
obj.hasOwnProperty // => ƒ hasOwnProperty() { [native code] }
obj.hasOwnProperty = 'don\'t do this!';
obj.hasOwnProperty // => "don't do this!"
Consider some example below:
const object = { hasOwnProperty: false }
console.log(object.hasOwnProperty(key)) // Error: hasOwnProperty is not a function
or
const object = null
console.log(object.hasOwnProperty(key)) // Error: Can not read property of null
So you can understand shallowed in this case is your object methods in prototype is shallowed by an object property (has the same name)
See an example. Here I have created an function with name hasOwnProperty directly on the my object. So it hides the parent version of the hasOwnProperty. In it I write a logic which will return everytime true. So anybody who will use my object and tries to detect if it has some property in it (he/she doesn't know I have shadowed the base on) he can have a logic errors in his/her code. Because JS will try to find the function first in the object and call that version which actually does another work.
But when you call this method from the Object.prototype, which is the correct version of the method, and pass the context to it, it will work correctly, because it will call the Object.prototypes method with name hasOwnProperty and just pass this object as the context of the method. So from here is the warning that you must use this methods from the prototype.
Actually you can also change the Object.prototype version of the method, but there is a rule, Not change the prototypes of the built in objects.
const object = {
hasOwnProperty: function() {
return true;
}
};
console.log(object.hasOwnProperty('name'));
console.log(Object.prototype.hasOwnProperty.call(object, 'name'));
The first log says that there is a property name in the object, but it isn't. This may cause a logic error. The second one uses the correct version and gives a correct result.

Categories

Resources