javascript: прототипы с обратными вызовами и "это"

Я создал прототип класса Person который открывает соединение WebSocket и определяет функции обратного вызова как прототипы.

Потому что внутри обратного вызова this будет ссылаться на объект WebSocket Я использовал другую переменную, держаться за Person this. Однако, когда я имею дело с несколькими экземплярами, переменная перезаписывается.

Вот небольшой фрагмент, который показывает проблему:

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    

Если создано несколько Person, то при попытке отправить сообщение через сокет я получаю следующую ошибку:

Ошибка при сбое: INVALID_STATE_ERR: исключение DOM 11

Таким образом, кажется, что self определяется глобально, и моя попытка получить обращение к Person this внутри обратного вызова не удается. Любые предложения о том, как этого достичь?

Ответ 1

Когда вы выполните:

self = this

Вы неявно создаете глобальную переменную, которая (поскольку она глобальна) будет иметь одинаковое значение для всех экземпляров. Локальные переменные должны иметь var, let или const перед ними, как один из них:

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

Но это не ваше решение. Вы должны использовать this вместо этого. И, если вы собираетесь предоставить обратный вызов для websocket, и вы хотите, чтобы связанный с ним человек, я бы предложил вам просто поместить ссылку на объект Person на websocket, чтобы потом вы могли получить его оттуда. И, что со всеми недостающими точками с запятой заканчивается каждое утверждение? Во всяком случае, вот какой-то исправленный код:

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
    }
}

Ответ 2

При объявлении переменных в Javascript, если вы не ставите переднюю переменную var, она будет рассматриваться как глобальная переменная, что может вызвать некоторые проблемы в вашем случае.

В то время как конструктор ведет себя так, как ожидалось, вы можете сделать следующее вместо этого, поэтому name сохраняется в экземпляре Person, который вы создаете:

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

Кроме того, в WebSocket.onopen "это" изменяется от экземпляра Person к экземпляру WebSocket. Вам нужно будет сохранить "Личность", чтобы ссылаться на нее в 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); 
        };
    }
};

Надеюсь это поможет! Здесь JSFiddle, чтобы пойти с ним: http://jsfiddle.net/WFdbe/

Ответ 3

self = this

Создавая глобальную переменную, почему ваш код сломан.

Кроме того, попытка ссылки на self внутри прототипа не работает, используйте 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
    }
}