3

Let's say I have a class named Human.

function Human(name, gender, age, personality){
    this.name = name;
    this.gender = gender;
    this.age = age;
    this.personality = personality; 
}

and I have some functions for the class such as greeting and introduce. So I create them like this:

Human.prototype.introduce = function() {
    var _gender = {"M": "Boy", "F": "Girl"};
    switch(this.personality)
    {
    case "passionate":
        alert("Hi, how are you? My name is " + this.name + ". I'm " + this.age + " years old " + _gender[this.gender] + ". ");
        break;
    case "aggressive":
        alert("I'm " + this.name + ". What you want? ");
        break;
    }
}

Human.prototype.greeting = function() {
    alert("Hi!");
}

Since introduce and greeting can be grouped by the same category (let's name it speak), how can I simply wrap this two functions with an object? I've tried this:

Human.prototype.speak = {};
Human.prototype.speak.greeting = function(){
    alert("Hi!");
}
Human.prototype.speak.introduce = function(){
var _gender = {"M": "Boy", "F": "Girl"};
        switch(this.personality)
        {
        case "passionate":
            alert("Hi, how are you? My name is " + this.name + ". I'm " + this.age + " years old " + _gender[this.gender] + ". ");
            break;
        case "aggressive":
            alert("I'm " + this.name + ". What you want? ");
            break;
        }
}

Now the question is, when a function is wrapped by an object, this in the introduce function is no longer referring to the instance. So how can I work this out?

I would like to call the function as this:

var eminem = new Human("Marshall Mathers", "M", 45, "aggressive");
eminem.speak.introduce();
1
  • What's the end goal? How do you want to call the functions? Commented Nov 14, 2017 at 7:58

4 Answers 4

5

Make Speak to A Class

Make Speak to a class, because logical grouping of variables and functionality in OOP are classes.

Speak

function Speak(human) {
    this.human = human
}

Speak.prototype.greeting = function () {
    // ...
}

Speak.prototype.introduce = function () {
    // ..
}

Human

function Human(name, gender, age, personality, greetWord) {
    this.name = name;
    this.gender = gender;
    this.age = age;
    this.personality = personality;
    this.speak  = new Speak(this)
}

Example

function Human(name, gender, age, personality, greetWord) {
    this.name = name;
    this.gender = gender;
    this.age = age;
    this.personality = personality;
    this.greetWord = greetWord;
    this.speak  = new Speak(this)
}

function Speak(human) {
    this.human = human
}

Speak.prototype.greeting = function () {
    alert(this.human.greetWord + "!");
}

Speak.prototype.introduce = function () {
    var _gender = { "M": "Boy", "F": "Girl" };
    switch (this.human.personality) {
        case "passionate":
            alert("Hi, how are you? My name is " + this.human.name + ". I'm " + this.human.age + " years old " + _gender[this.human.gender] + ". ");
            break;
        case "aggressive":
            alert("I'm " + this.human.name + ". What you want? ");
            break;
    }
}

var peter = new Human('Peter', 'M', 35, 'aggressive', 'Hi')
peter.speak.greeting()
peter.speak.introduce()


An other way to solve the solution would be to use apply.

Apply

The apply() method calls a function with a given this value, and arguments provided as an array (or an array-like object).

Code

var peter = new Human('Peter', 'M', 35, 'aggressive')
console.log(peter.speak.introduce.apply(peter))

Example

function Human(name, gender, age, personality){
    this.name = name;
    this.gender = gender;
    this.age = age;
    this.personality = personality; 
}

Human.prototype.speak = {};
Human.prototype.speak.greeting = function(){
    alert("Hi!");
}
Human.prototype.speak.introduce = function(){
var _gender = {"M": "Boy", "F": "Girl"};
        switch(this.personality)
        {
        case "passionate":
            alert("Hi, how are you? My name is " + this.name + ". I'm " + this.age + " years old " + _gender[this.gender] + ". ");
            break;
        case "aggressive":
            alert("I'm " + this.name + ". What you want? ");
            break;
        }
}

var peter = new Human('Peter', 'M', 35, 'aggressive')
console.log(peter.speak.introduce.apply(peter))

Sign up to request clarification or add additional context in comments.

8 Comments

Is there any way that I can call it just "peter.speak.introduce" ? Because calling the apply everytime would be kinda pain in the ***.
@JohnnyCheuk I add a more oop solution
What if I cannot actually access and modify the Human constructor?
@Roman ... very nice. The best approach (the class and aggregation based one) one could come up with, in order to solve the OP's problem.
@Roman 'If you call Human.prototype.speak = new Speak(this); outside the constructor, this will referring to Window
|
3

You can bind the context of this in the function in its constructor

function Human(name, gender, age, personality){
    this.name = name;
    this.gender = gender;
    this.age = age;
    this.personality = personality;
    this.speak.introduce = this.speak.introduce.bind(this);
}

This way, this in your introduce function will always be the current instance of Human

2 Comments

Is there any way that I can bind it outside of the class?
I suppose that you could call bind after instantiation, but in order to have the syntax you want in your post, you'll need the this context inside the constructor
1
function Human(name, gender, age, personality){

    this.name = name;
    this.gender = gender;
    this.age = age;
    this.personality = personality; 

    var self = this; //this is how

    this.speak = {

        introduce: function() {

            var _gender = {"M": "Boy", "F": "Girl"};
            switch(self.personality)
            {
            case "passionate":
                alert("Hi, how are you? My name is " + self.name + ". I'm " + self.age + " years old " + _gender[self.gender] + ". ");
                break;
            case "aggressive":
                alert("I'm " + self.name + ". What you want? ");
                break;
            }
        },

        greeting: function() {

            alert("Hi!");
        }
    };
}

var hulk = new Human("Hulk", "M", 33, "aggressive");
hulk.speak.introduce();

9 Comments

I like your idea but I don't lke the self. So your idea without self but with bind .. jsfiddle.net/kbwy8wuf
This works, but if all functions are defined inside the constructor, it would be kinda messy.
@JohnnyCheuk why don't you use Classes?
@caramba ... what will it be good for? It does not provide any additional benefit in order to solve the OP's original problem.
@PeterSeliger from what I understand with a class you don't need to do Human.prototype.speak... stuff cause the methods defined in the class are prototype methods => developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… or am I wrong?
|
1

Based on the accepted answer you could write a few lines of code to automatically rebind all your functions using Object.keys and forEach.

function Human(name, gender, age, personality){
    this.name = name;
    this.gender = gender;
    this.age = age;
    this.personality = personality; 

    Object.keys(this.speak).filter(function(key) {
        return typeof this.speak[key] === 'function';
    }, this).forEach(function(key) {
        this.speak[key] = this.speak[key].bind(this);
    }, this);
}

Also you could easily improve this code to not only iterate the functions of the 'speak' object.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.