When I create a class in JavaScript like this one:
class Person {
_firstName = "";
_lastName = "";
constructor(firstName, lastName) {
this._firstName = firstName;
this._lastName = lastName;
}
get firstName() {
return this._firstName;
}
get lastName() {
return this._lastName;
}
fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
let me = new Person("John", "Doe");
It creates a variable me that is an instance of Person. Its prototype has some functions whose names are “fullName” and “get firstName” (plus other functions)
I can call the function fullName by its name using:
me[“fullName”]();
John Doe
However, calling the getter function by its name throws an error:
me[“get firstName”]();
Uncaught TypeError: me.get firstName is not a function
Why does this behavior happen?
EDIT: Thanks for the replies. I know I can and should access it as a property. But I'd like to understand why accessing it by its name with the brackets notation does not work. Does that mean JavaScript has different kinds of functions since a behavior that works for one function does not work for another function (when it has a space in its name / is a getter or setter)?
When a function is assigned to a variable or as a property value (maybe a getter), there are two names involved:
The name of the function
The name of the variable/property
These names are different entities and don't necessarily have to be the same.
Here is a simple object with property "a" that has as value a function whose name is "b":
let obj = {
a: function b() { }
};
console.log("property: ", Object.keys(obj)[0]); // "a"
console.log("function: ", obj.a.name); // "b"
Now when the function does not get an explicit name, it is given one:
let obj = {
a() { }
};
console.log("property: ", Object.keys(obj)[0]); // "a"
console.log("function: ", obj.a.name); // "a"
This may give the false impression that both names are one and the same string, but this cannot be true as the same function object might be assigned to different properties:
let obj = {
a() { }
};
obj.c = obj.a;
console.log("property: ", Object.keys(obj)[1]); // "c"
console.log("function: ", obj.c.name); // "a"
Getters
The situation with a getter can be confusing, because the expression obj.a.name will not retrieve the name of the function, but will invoke the getter and then try to access the name property of the returned value.
To still get the name of the getter function, you can use Object.getOwnPropertyDescriptor as follows:
let obj = {
get a() { }
};
console.log("property: ", Object.keys(obj)[0]); // "a"
console.log("function: ", Object.getOwnPropertyDescriptor(obj, "a").get.name); // "get a"
This is how a getter's function name is set by default. You can however set it explicitly to your desire using Object.defineProperty as follows:
let obj = {};
Object.defineProperty(obj, "a", {
get: function b() {},
enumerable: true
});
console.log("property: ", Object.keys(obj)[0]); // "a"
console.log("function: ", Object.getOwnPropertyDescriptor(obj, "a").get.name); // "b"
Conclusions
A property name does not necessarily match with the name of the function that might be the property's value.
A property is identified by property name, not by the name of the function that might be associated with it.
The function object, that serves as a getter property, can be accessed with Object.getOwnPropertyDescriptor(object, propertyName).get
You should access it as a normal property like this:
me["firstName"];
But, if you know already the name, you could access the firstName value like this:
me.firstName
Typically, you use the first form if you want to access a property of an object using a variable like this:
const person = new Person("John", "Doe");
const propertyName = "firstName";
const firstName = person[propertyName];
If you want to know more about getter and setters check out this and this.
Follows a fully working example:
class Person {
_firstName = "";
_lastName = "";
constructor(firstName, lastName) {
this._firstName = firstName;
this._lastName = lastName;
}
get firstName() {
return this._firstName;
}
get lastName() {
return this._lastName;
}
fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
const me = new Person("John", "Doe");
const propertyName = "firstName";
console.log(me["firstName"])
console.log(me.firstName)
console.log(me[propertyName])
When doing this:
get firstName() {
return this._firstName;
}
it´s defining a property and the you have to call it over:
me.firstName;
doing it like this:
getFirstName() {
return this._firstName;
}
is really defining a getter function and you can call it like this:
me.getFirstName();
Maybe this is helpfull: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Functions/get
Related
I wanted to experiment a bit with the Proxy object, and got some unexpected results, as follows:
Test script
function Person(first, last, age) {
this.first = first;
this.last = last;
this.age = age;
}
Person.prototype.greeting = function () {
return `Hello my name is ${this.first} and I am ${this.age} years old`;
};
So in tracking how the prototype object is being modified, I added this wrapper:
let validator = {
set: function(target, key, value) {
console.log(`The property ${key} has been updated with ${value}`);
target[key] = value;
return true;
}
};
Person.prototype = new Proxy(Person.prototype, validator);
let george = new Person('George', 'Clooney', 55);
Person.prototype.farewell = function () {
return `Hello my name is ${this.first} and I will see you later`;
};
What I expected
The property: "farewell" has been updated with: "function () {
return `Hello my name is ${this.first} and I will see you later`;
}"
and nothing else.
And of course, each time I added or removed something from the prototype, i.e Person.prototype or instance.constructor.prototype I expected to see the console.log() message.
However I did not expect to see anything when setting something on the instance, like:
george.someProp = 'another value'; // did NOT expect to see the console.log()
Output
The property: "first" has been updated with: "george"
The property: "last" has been updated with: "clooney"
The property: "age" has been updated with: "55"
The property: "farewell" has been updated with: "function () {
return `Hello my name is ${this.first} and I will see you later`;
}"
Person.prototype
Proxy {greeting: ƒ, first: "George", last: "Clooney", age: 55, farewell: ƒ, constructor: ƒ}
It set all the properties on the prototype and nothing on the instance, and each time I set something on the instance it sets it straight on the prototype.
Evidently this is not the default behaviour, as if I remove that Proxy, every property set with this will be set on the instance itself and the prototype will start up empty (or in our case with just the greeting function).
What am I missing here ? A point in the right direction would be appreciated.
What you are missing is the fact that when you have a Proxy object in the prototype chain, the set handler will be called when you modify the child object.
In your example, when you set a property on the new instance, the set trap will be executed, the target will be the wrapped Person.prototype object, but there's a fourth argument, the receiver. This argument points to the object that the property has been accessed on.
To properly do the property assignment, you can use the Reflect.set API to set it:
Reflect.set(target, key, value, receiver);
That's why the Reflect API matches the proxy traps arguments.
So, in your example, we could use the Reflect API and you will see that Person.prototype doesn't get "polluted".
function Person(first, last, age) {
this.first = first;
this.last = last;
this.age = age;
}
Person.prototype.greeting = function () {
return `Hello my name is ${this.first} and I am ${this.age} years old`;
};
const validator = {
set: function(target, key, value, receiver) {
console.log(`The property ${key} has been updated with ${value}`);
Reflect.set(target, key, value, receiver)
return true;
}
};
Person.prototype = new Proxy(Person.prototype, validator);
const george = new Person('George', 'Clooney', 55);
Person.prototype.farewell = function () {
return `Hello my name is ${this.first} and I will see you later`;
};
console.log(george.hasOwnProperty('first')); // true
console.log(Person.prototype.hasOwnProperty('first')); // false
In the following snippet, the spread syntax works in a manner that I don't quite understand:
let obj = {
set setName(name){
obj.name = name
},
get myName() {
return obj.name
}
}
obj.setName = 'Jon Doe'
let spread_obj = {...obj}
spread_obj.setName = 'Marion Luke'
console.log('spread_obj name', spread_obj.myName) // spread_obj name Jon Doe
let create_obj = Object.create(obj)
create_obj.setName = 'Marion Luke'
console.log('create_obj name', create_obj.myName) // create_obj name Marion Luke
Can you explain why the reassignment of the name does not work in such a case?
Spreading an object does not copy getter and setter methods - rather, the spread operation only gets the property (that is, it calls the getter, if there is one), and assigns the result to the resulting object (the spread_obj, here), just like a normal object, without any getters or setters. You can see this if you put log statements inside the setter and getter. The resulting spread_obj has a setName property of undefined because setName is only a setter method, which doesn't return anything.
let obj = {
set setName(name){
console.log('setting ' + name);
this.name = name
},
get myName() {
console.log('getting');
return this.name
}
}
obj.setName = 'Jon Doe'
console.log('About to spread');
let spread_obj = {...obj}
console.log('Done spreading. spread_obj contents is:');
console.log(spread_obj);
spread_obj.setName = 'Marion Luke' // No "setting" log statement
Also note that if you want to use the second part of your code, you should probably change the setter and getter methods to refer to this rather than to obj, otherwise the results could be unintuitive; your getter and setter methods area always referring to the .name property on obj, rather than the .name property on the standard calling context (that is, to spread_obj or to create_obj). When you try to assign to spread_obj.setName, you actually change the original obj's .name property:
let obj = {
set setName(name){
obj.name = name
},
get myName() {
return obj.name
}
}
obj.setName = 'Jon Doe'
let create_obj1 = Object.create(obj)
create_obj1.setName = 'Marion Luke1'
let create_obj2 = Object.create(obj)
create_obj2.setName = 'Marion Luke2'
// "Marion Luke2", but we're logging the property from the "first" object!
console.log('create_obj name', create_obj1.name)
Fix by referring to this instead of obj:
let obj = {
set setName(name){
this.name = name
},
get myName() {
return this.name
}
}
obj.setName = 'Jon Doe'
let create_obj1 = Object.create(obj)
create_obj1.setName = 'Marion Luke1'
let create_obj2 = Object.create(obj)
create_obj2.setName = 'Marion Luke2'
console.log(create_obj1.name)
In addition to #CertainPerformances explanation, I would add that the behavior seems more sensible when using the getters/setters a more traditionally by having one name for both get and set.
For example when your object looks like this, everything works a little better (at least on the surface):
let obj = {
set name(name){ // setter and getter are accessed with the same property
this._name = name
},
get name() {
return this._name
}
}
obj.name = 'Jon Doe'
let spread_obj = {...obj}
spread_obj.name = 'Marion Luke'
// this seems more expected
console.log('spread_obj name', spread_obj.name)
// but this is still a little strange because
// Jon Doe is still there
console.log(spread_obj)
It's seems like a lot of work, but since object spread only takes enumerable properties, you can make _name non-enumerable. Then everything seems a little more sensible:
let obj = {
set name(name){
this._name = name
},
get name() {
return this._name
}
}
Object.defineProperty(obj, '_name', {
enumerable: false,
writable: true,
configurable: true
});
obj.name = 'Jon Doe'
let spread_obj = {...obj}
spread_obj.name = 'Marion Luke'
// now spread_obj only has a name prop:
console.log(spread_obj)
// and obj too:
console.log(obj)
the select answer is wrong.
getter/setter is not method, it is special properties.
...spread and object.assign will not work, just because they treat getter/setter like normal enumerable property, they
copy the 'value' of it, so getter/setter will fail.
if you want to copy it, you can refer undercore.js the extend method:
this is my assign code:
const assign = (ob, ...o) => {
o.forEach(obj=>{if (typeof obj !== 'undefined')
Object.defineProperties(ob, Object.getOwnPropertyDescriptors(obj));})
return ob;
};
function User() {
this.firstname = null;
get getFirst() {
return this.firstname;
}
}
JavaScript console gives me an error saying "Unexpected Identifier" on line 12
var Jake = new User();
Jake.firstname = "Jake";
document.write(Jake.getFirst);
That's just not the syntax you use to define a getter. You'd use it in an object literal, like this:
var foo = {
get bar() {
return 42;
}
};
foo.bar; // 42
...but that's not where your get is.
To define it where your get is, you'd use defineProperty:
function User() {
this.firstname = null;
Object.defineProperty(this, "first", {
get: function() {
return this.firstname;
}
});
}
Note I called it first, not getFirst, because it's an accessor function, which looks like a direct property access, and so is traditionally not given a name in a verb form:
var u = new User();
u.firstname = "Paul";
u.first; // "Paul"
If you wanted to create a function called getFirst, just get rid of the get keyword:
this.getFirst = function() {
return firstname;
};
// ...
var u = new User();
u.firstname = "Paul";
u.getFirst(); // "Paul"
I believe the issue is that you are using get with a function rather than the object literal as outlined in the documentation.
var User = {
firstName: 'Darren',
get getFirst() { return this.firstName; }
}
alert(User.getFirst);
https://jsfiddle.net/ecao51n0/
get is intended to be called within an object, not a function constructor. If you want to declare getFirst as a function on User, then here's one way you could do it:
function User() {
this.firstname = null;
this.getFirst = function() {
return this.firstname;
}
}
Then you would also need to call getFirst as a function:
var Jake = new User();
Jake.firstname = "Jake";
document.write(Jake.getFirst());
I tried to bind a function from an object to some variable without external calling bind():
var man = {
age: "22",
getAge: function(){
return "My age is "+this.age;
},
test: function(){
return this.getAge.bind(this);
}
}
This works:
var a = man.test();
a();
// "My age is 22"
But when I try to change some things in my code:
var man = {
age: "22",
getAge: function(){
return "My age is "+this.age;
},
test: function(){
return this.getAge.bind(this);
}()//there it's, that do not do "var a = man.test()", but "var a = man.test"
}
JavaScript gives me an Error:
Uncaught TypeError: Cannot read property 'bind' of undefined(…)
What am I doing wrong?
this in your second version is not referring to what you think it is, it's referring to the window and so does not have the property available...
NB: Adding the () to the end calls the anonymous function you created
In your example this is referring to the context the Object literal is written in, not the Object literal.
You can't actually refer to yourself in an Object literal's construction time because even it's identifier hasn't been properly set yet. Instead, separate it into two steps
// 1, set up with a literal
var man = {
age: "22",
getAge: function () {
return "My age is " + this.age;
}
}
// 2, set up things needing references to the object we just made
man.test = man.getAge.bind(man);
By your specific example it looks like you may repeat this pattern many times, are you sure that it wouldn't be better to use a Constructor? This also means you can use inheritance and prototyping
For example, you could have Man set up as inheriting from Human, and also create a Woman later with shared code
// Common category
function Human(age) {
this.age = age;
this.test = this.getAge.bind(this);
}
Human.prototype = Object.create(null);
Human.prototype.getAge = function () {
return 'My age is ' + this.age;
};
// specific category
function Man(age) {
Human.call(this, age);
}
Man.prototype = Object.create(Human.prototype);
Man.prototype.gender = function () {
return 'I am male.';
};
// then
var man = new Man('22'); // as you used a string age
var a = man.test;
a(); // "My age is 22"
Then later
// another specific category
function Woman(age) {
Human.call(this, age);
}
Woman.prototype = Object.create(Human.prototype);
Woman.prototype.gender = function () {
return 'I am female.';
};
// then usage
var woman = new Woman('22'); // as you used a string age
woman.getAge(); // "22", because getAge was common to humans
I am experimenting imitating OOP like behavior in JS. I am trying to have (private) variables: id and name in function Person. To this function I am passing arguments which are used to initialize (private) variables. Then I am returning object having getter and setter for name and only a getter for id, thus effectively making id read-only.
So id can be set only through constructor whereas name can be set and get anytime.
This is the code:
var Person = function (_id,_nm) {
var id, name;
this.id = _id;
this.name = _nm;
return {
setName: function (nm) {
name = nm;
},
getName: function () {
return name;
},
getId: function () {
return id;
},
print: function () {
document.writeln("Id: "+id+"<br />Name: "+name);
}
}
}
var person = new Person(123, "Mahesh");
person.print();
However when new Person(123,"Mahesh") executes, I dont understand it is actually setting id and name or not, since while debugging I can see values set appropriately when hovered over them but Locals panel does not show them initialized:
Or either while in print() is is not referring to the desired id and name variables:
Whats wrong here?
Working fiddle
The reason is because you are using public members for the Person.prototype. You don't need to add this references to these two. So delete:
this.id = _id;
this.name = _nm;
and simply use:
var id = _id,
name = _nm;
Now everything will work fine. The whole idea is to use var, and not this, otherwise a closure will not be created. Now you will not be able to access name and id directly, instead you will have to use setName(), getName(), setId(), getId() etc.
The two members, id and name, will now become closures as you want them to be.
Update
If you used this.id, then it wouldn't have been private and you could just do var p = new Person(1, "Mahesha"); and access p.name or p.id directly. They are supposed to be private so this is not what you want.
With the closure pattern, p.name and p.id are undefined and can only be accessed through p.getName(); and p.getId();. Read on how closures work. The idea is that because you are using that var name, a closure will be created to remember it's value.
Your getName and setName are using that closure to access the name property. There is no this.name, there is a value remembered through a higher - order closure.
this.id and var id are not the same. this.id is a property of the object. var id belongs to the local scope.
Either use new or return a value. Not both.
The problem is that you're creating a new instance of Person using the new keyword, but your constructor function is returning another object instead.
When you return something from a constructor function it returns that value, and not the instance of the function.
You see when you execute new Person(123, "Mahesh") a new instance of Person is created. This is accessible within the constructor function as this.
If you don't return anything from your constructor then JavaScript automatically returns this. However you're explicitly returning another object.
No wonder var person doesn't have id and name properties (which you only defined on this).
In addition print doesn't display the id and name because although you declared them (var id, name) you didn't give them any values. Hence they are undefined.
This is how I would rewrite your Person constructor:
function Person(id, name) {
this.getId = function () {
return id;
};
this.getName = function () {
return name;
};
this.setName = function (new_name) {
name = new_name;
};
this.print = function () {
document.writeln("Id: " + id + "<br/>Name: " + name);
};
}
I didn't set the id and name properties on this because it makes no sense to include them.
You've mixed up using locally scoped ("private") variables for _id and _nm and "public" instance properties (this.id and this.nm).
In this case you need the former, but you created both and only initialised the latter.
Note that since id is read-only you don't really need a separate local variable at all, you can just use the lexically scoped first parameter to the constructor:
var Person = function (id, _nm) {
var name = _nm;
...
};
Let me try to explain using the following:
// Scope 1
var Person = function (_id,_nm) {
// Scope 2
var id, name;
this.id = _id;
this.name = _nm;
return {
// Scope 3
setName: function (nm) {
name = nm;
},
getName: function () {
return name;
},
getId: function () {
return id;
},
print: function () {
$("#output").append("<p>Id: "+id+"; Name: "+name + "<p/>");
}
}
}
The print method will return undefined because you are referring to the var id, name; that is never set in your code. You set the _id and _name to the property id and name but you fail to return the object that you just created. Instead, you return a dictionary, that references the name and id variable you created in Scope 2 that you never set, hence the undefined output in the print() call.
Here is what you should have done:
var NewPerson = function (_id,_nm) {
var self = this;
this.id = _id;
this.name = _nm;
this.print = function () {
$("#output").append("<p>New Person Id: "+this.id+"; Name: "+ this.name + "<p/>");
};
return this;
};
var nperson = new NewPerson(456, "Marcos");
nperson.print();
This will output:
New Person Id: 456; Name: Marcos
In essence, new is creating a new object represented by this, and you must return this to have a reference to the object created. In fact, if you do not use the new before creating an object, you end up referencing a global instance of this. Try the following:
var nperson = new NewPerson(456, "Marcos");
this.name = "Hello";
nperson.print();
var nperson1 = NewPerson(456, "Marcos");
this.name = "Hello";
nperson1.print();
You will see that the first output will be as expected:
New Person Id: 456; Name: Marcos
But the second will pick up the change made to this.name:
New Person Id: 456; Name: Hello
Here is the JSFiddle.