Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

javascript: prototypes with callbacks and 'this'

I've create a prototype based class Person that opens a WebSocket connection and defines callback functions as prototype methods.

Because inside the callback this will refer to the WebSocket object I used another variable to hold on to the Person's this. However when I deal with multiple instances the variable gets overwritten.

Here is a small snipped that shows the issue:

function Person(name){
    self = this
    self.name = name
}

Person.prototype = {
    getName : function(){
        return self.name
    },

    openConnection : function(host, port){
        self.pointCount = 0
        self.ws = new WebSocket("ws://" + host + ":" + port)
        self.ws.onopen = self.onOpenConnection
    },

    onOpenConnection : function()   {
        console.log(this) // prints the websocket
        console.log(self) // prints the person
        self.ws.send(self.name) // works only if one person exists
    }
}

var p1 = new Person("Jonh")
var p2 = new Person("Adam")

console.log(p1.getName()) // Prints Adam
console.log(p2.getName()) // Prints Adam

p1.openConnection("localhost", 7000) // opens connection for p1
p2.openConnection("localhost", 7000) // opens another connection for p1    

If more than one Person is created, then when trying to send a message via the socket I get the following error:

Uncaught Error: INVALID_STATE_ERR: DOM Exception 11

So it seems that self is defined globally and my attempt to get a handle to the Person's this inside the callback fails. Any suggestions on how to achieve that?

like image 620
unexplored Avatar asked Apr 04 '12 15:04

unexplored


3 Answers

When you do:

self = this

You are implicitly creating a global variable which (since it's global) will have the same value for all instances. Local variables, must have var, let or const in front of them like one of these:

var self = this;
const self = this;
let self = this;

But, that isn't your solution here. You need to be using this instead. And, if you're going to supply a callback for the websocket and you want the person associated with that, I would suggest you just put a reference to the Person object on the websocket so you can then retrieve it from there. And, what's with all the missing semicolons to end each statement? Anyway, here is some fixed up code:

function Person(name){
    this.name = name;
}

Person.prototype = {
    getName : function(){
        return this.name;
    },

    openConnection : function(host, port){
        this.pointCount = 0;
        this.ws = new WebSocket("ws://" + host + ":" + port);
        // save person reference on the web socket
        // so we have access to the person from web socket callbacks
        this.ws.person = this;   
        this.ws.onopen = this.onOpenConnection;
    },

    onOpenConnection : function()   {
        // "this" will be the websocket
        // "this.person" is the person object
        console.log(this); // prints the websocket
        console.log(this.person); // prints the person
        this.send(this.person.name); // works only if one person exists
    }
}
like image 66
jfriend00 Avatar answered Oct 04 '22 14:10

jfriend00


self = this

Your creating a global variable, that's why your code is broken.

Also trying to reference self inside the prototype does not work, use this

function Person(name){
    this.name = name
}

Person.prototype = {
    openConnection : function(host, port){
        this.pointCount = 0
        this.ws = new WebSocket("ws://" + host + ":" + port)
        this.ws.onopen = this.onOpenConnection.bind(this)
    },
    constructor: Person,    
    onOpenConnection : function()   {
        console.log(this) // prints the person
        this.ws.send(this.name) // works only if one person exists
    }
}
like image 32
Raynos Avatar answered Oct 04 '22 14:10

Raynos


When declaring variables in Javascript, if you don't put a var in front, it'll be treated as a global variable, which causes some problem in your case.

While the constructor is behaving as expected, you may want to do the following instead, so name is saved to the instance of Person you are creating:

// Constructor
function Person(name){
    // You don't need to reference "self" here. It's already implied.
    this.name = name;
}

In addition, in WebSocket.onopen, 'this' changes from the instance of a Person to the instance of a WebSocket. You'll need to preserve the 'Person' in order to reference it inside WebSocket.onopen.

// Prototype
Person.prototype = {
    getName : function(){
        // 'this' in this case refers to an instance of Person. 
        // So, when creating John, this.name will be John. 
        return this.name;
    },

    openConnection : function(host, port) {
        // Similar to getName(...), this refers to an instance of Person.
        // In your example, this.pointCount is NOT shared between John and Adam
        this.pointCount = 0;
        this.ws = new WebSocket("ws://" + host + (port ? ':' + port : ''));

        // In WebSocket.onopen below, you're working with a new scope, so you 
        // won't have access to 'this' as the Person anymore. You need to save 
        // 'this' somewhere, so you can reference it in the new scope.
        // *****
        var self = this;   

        this.ws.onopen = function() {
            // In this function, a new scope has been created. 'this' no 
            // longer refers to John/Adam (The instance of Person), but to 
            // WebSocket instead.

            console.log(this); // 'this' references the WebSocket instance
            console.log(self); // 'self' references the 'self' in the outer 
                               // scope. See *****

            // Since this = WebSocket in this scope, all we need to do
            // is this.send(...). If you'd like to obtain the refer
            // to the instance of the Person you worked with, you can
            // use the 'self' variable
            this.send(self.name); 
        };
    }
};

Hope this helps! Here's a JSFiddle to go with it: http://jsfiddle.net/WFdbe/

like image 34
DashK Avatar answered Oct 04 '22 12:10

DashK