Not sure if anyone can help me with this issue but I am aware that one can use the bind, apply and call methods to make the this keyword within a function reference a particular object of your choosing. However, I'm still a little confused because in certain instances when I expected a particular outcome I got another. Here is an example of an object I have:
var obj = {
fullName: "John Doe",
person: {
sayHi: function() {
console.log("This person's name is " + this.fullName)
}
}
}
I am aware that calling the obj.person.sayHi() method will result in the person's name being undefined as the implicit object is person.
I am also aware that you can reference the correct object by calling the method using bind or call like so:
obj.person.sayHi.call(obj) or obj.person.sayHi.bind(obj)()
in order for the person's name to display. However, I also expected that one could attach the bind directly to the function expression inside of the object like so:
var obj = {
fullName: "John Doe",
person: {
sayHi: function() {
console.log("This person's name is " + this.fullName)
}.bind(obj)
}
}
and then call the function like so: obj.person.sayHi() and you would get the same result however in this instance the this keyword refers to the global object. However if I do the following:
let sayHi = function() {
console.log("This person's name is " + this.fullName)
}
sayHi.bind(obj)();
then I get the desired result. Can anyone explain why the second approach is not a valid one? Thanks in advance.
This is invalid:
var obj = {
fullName: "Harry Potter",
person: {
sayHi: function() {
console.log("This person's name is " + this.fullName)
}.bind(obj) // obj is not defined here yet, obj is undefined
}
}
Because obj is undefined when bind is called. obj is indeed defined when the declaration is over, but you have a kind of circular reference in your declaration of obj which most likely will have the value of undefined when bind(obj) is called.
Another way to see this explicitly is to do the following:
var obj = {
fullName: "Harry Potter",
person: {
sayHi: function() {
console.log("This person's name is " + this.fullName)
}.bind(obj) // obj is not defined here yet, obj is undefined
},
ref: obj
}
console.log(obj.ref); // undefined
Hi #Nikos thanks for the response. I think I'm on track now. I just wanted to know one more thing. If I do the following:
function dummy(fn) {return fn();}
var obj = {
fullName: "Harry Potter",
person: {
sayHi: function() {
dummy(function() {
console.log("This person's name is " + this.fullName);
console
}.bind(obj))
}
}
}
Then calling the function obj.person.sayHi() will produce the desired effect. Is this somehow taking the function expression out of the object definition? Sorry I tried posting this as a comment however, I don't know how to get it into the correct formatting
Related
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
I have the following JS, interfacing with the Undum interactive fiction library:
undum.game.situations = {
main: new undum.SimpleSituation(
"",
{
enter: function(character, system, from) {
...
},
actions: {
testMethod: function() {
return "hello from test method";
},
'test-action': function(character, system, action) {
console.log("testMethod for main situation says: "+this.testMethod());
...
}
}
}
),
...other situation defs
}
when I get into the test-action method of actions, the console shows the error "Uncaught TypeError: this.testMethod is not a function".
I'm guessing the problem is related to the fact that testMethod and its caller are defined as part of the SimpleSituation constructor? If so, how should I go about setting up event-based code like this where an object needs to hook into its own methods during construction (although they aren't actually called during construction)?
A fiddle with some similar boilerplate that I think demos the same categorical behavior is working fine:
function Obj(optionsObj) {
optionsObj.testMethod();
}
var myObj = new Obj(
{
testMethod: function() {
alert("hello from test method");
}
}
)
The key to understanding this problem was that in JS keyword this is all about the calling context rather than the called context -- I had read the W3 schools blurb about keyword this, but it's kind of misleading. In their example code
var person = {
firstName: "John",
lastName : "Doe",
id : 5566,
fullName : function() {
return this.firstName + " " + this.lastName;
}
};
they call out fullName as a method of person, in which keyword this will refer to the enclosing object. That's only true, however, if the calling context of fullName is on an instance of person, e.g.
var p = new person();
p.fullName();
Being called on an instance of person is what makes fullName a method. On its own, fullName is just a function like any other, however, and can be extracted from person and used as a standalone function:
var fullNameFn = person.fullName;
fullNameFn(); // in this case, keyword this points to global object (usually Window) or undefined in strict mode.
The situation I was looking at in Undum turned out to be such a case. Instead of calling main.actions.test-action(), it was doing the following in response to player clicking an action link:
SimpleSituation.prototype.act = function(character, system, action) {
// my take-action action function is extracted and stored as response
var response = this.actions[action];
try {
response(character, system, action);
} catch (err) {
if (response) system.write(response);
}
if (this._act) this._act(character, system, action);
};
the actual test-action function is extracted from the Situation instance and executed standalone, causing keyword this to point to Window.
function Foo(name, age){
this.name = name;
this.age = age;
this.announce = function(){
alert(this.name + " is " + this.age + " years old");
};
}
var myFoo = new Foo("John", 42);
Lets say I want to add a method to this particular instance of Foo (not to the others).
Should I use this keyword to modify the age property
myFoo.becomeYounger = function(){
this.age--;
};
or should I refer to the object by its name since it already exists?
myFoo.becomeYounger = function(){
myFoo.age--;
};
Which one is better/faster or is there any difference whatsoever?
They both work, but there are some risks about using the object name, look at this:
let user = {
name: "John",
age: 30,
sayHi() {
alert( user.name ); // leads to an error
}
};
let admin = user;
user = null; // overwrite to make things obvious
admin.sayHi(); // Whoops! inside sayHi(), the old name is used! error!
By using this, the code would worked correctly, just take care about this kind of scenarios.
Also if you like to do reusable code, using this fits better:
let user = { name: "John" };
let admin = { name: "Admin" };
function sayHi() {
alert( this.name );
}
// use the same functions in two objects
user.f = sayHi;
admin.f = sayHi;
// these calls have different this
// "this" inside the function is the object "before the dot"
user.f(); // John (this == user)
admin.f(); // Admin (this == admin)
admin['f'](); // Admin (dot or square brackets access the method – doesn't matter)
To learn more, here:
https://javascript.info/object-methods
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
The Object class has both methods and functions meaning they both are accessed through Object.nameOfMethodOrFunction(). The following question What is the difference between a method and a function explains the difference between a method and and a function, but it doesn't explain how to create them within an object. For example, the code below defines the method sayHi. But how do you define a function inside the same object?
var johnDoe =
{
fName : 'John',
lName: 'Doe',
sayHi: function()
{
return 'Hi There';
}
};
The following defines two classes, ClassA and ClassB, with equal functionality but different in nature:
function ClassA(name){
this.name = name;
// Defines method ClassA.say in a particular instance of ClassA
this.say = function(){
return "Hi, I am " + this.name;
}
}
function ClassB(name){
this.name = name;
}
// Defines method ClassB.say in the prototype of ClassB
ClassB.prototype.say = function(){
return "Hi, I am " + this.name;
}
As shown below, they doesn't differ much in usage, and they are both "methods".
var a = new ClassA("Alex");
alert(a.say());
var b = new ClassB("John");
alert(b.say());
So now what you mean for "function", according to the msdn link that you gave as a comment, seems that "function" is just a "static method" like in C# or Java?
// So here is a "static method", or "function"?
ClassA.createWithRandomName = function(){
return new ClassA("RandomName"); // Obviously not random, but just pretend it is.
}
var a2 = ClassA.createWithRandomName(); // Calling a "function"?
alert(a2.say()); // OK here we are still calling a method.
So this is what you have in your question:
var johnDoe =
{
fName : 'John',
lName: 'Doe',
sayHi: function()
{
return 'Hi There';
}
};
OK, this is an Object, but obviously not a class.
Quoting Aaron with "A method is on an object. A function is independent of an object".
Logically a method is useless without a "this" defined.
Consider this example:
var johnDoe =
{
fName: 'John',
lName: 'Doe',
sayHi: function () {
return 'Hi There, my name is ' + this.fName;
}
};
function sayHi2() {
return 'Hi There, my last name is ' + this.lName;
}
//Will print Hi there, my first name is John
alert(johnDoe.sayHi());
//An undefined will be seen since there is no defined "this" in SayHi2.
alert(sayHi2());
//Call it properly now, using the oject johnDoe for the "this"
//Will print Hi there, my last name is Doe.
alert(sayHi2.call(johnDoe));
var johnDoe = {
fName: 'John',
lName: 'Doe',
sayHi: function(){
function message(){ return 'Hi there'; }
return message();
}
};
That's about as good as you're going to get with the object declaration method of creating a 'class' in JavaScript. Just keep in mind that function is only valid within sayHi's scope.
However, if you use a function as a class structure, you have a little more flexibility:
var johnDoe = function(){
this.publicFunction = function(){
};
var privateFunction = function(){
};
};
var jd = new johnDoe();
jd.publicFunction(); // accessible
jd.privateFunction(); // inaccessible
(though both are really considered methods since they have access to the object's scope within).