JavaScript "класс" и проблемы синглтона - PullRequest
9 голосов
/ 19 апреля 2010

У меня есть одноэлементный объект, который использует другой объект (не одноэлементный), чтобы запросить некоторую информацию для сервера:

var singleton = (function(){

  /*_private properties*/
  var myRequestManager = new RequestManager(params,
    //callbacks
    function(){
        previewRender(response);
    },
    function(){
        previewError();
    }
  );

  /*_public methods*/
  return{

    /*make a request*/
    previewRequest: function(request){
       myRequestManager.require(request);  //err:myRequestManager.require is not a func
    },

    previewRender: function(response){
      //do something
    },

    previewError: function(){
      //manage error
    }
  };
}());

Это «класс», который делает запрос к серверу

function RequestManager(params, success, error){
  //create an ajax manager
  this.param = params;
  this._success = success;  //callbacks
  this._error = error;
}

RequestManager.prototype = {

  require: function(text){
    //make an ajax request
  },
  otherFunc: function(){
     //do other things
  }

}

Проблема в том, что я не могу вызвать myRequestManager.require изнутри одноэлементного объекта. Консоль Firebug говорит: «myRequestManager.require - это не функция», но я не понимаю, в чем проблема. Есть ли лучшее решение для реализации этой ситуации?

1 Ответ

6 голосов
/ 19 апреля 2010

Ваш код в том порядке, в котором вы его цитировали, не так ли? Синглтон появляется над RequestManager в источнике?

Если так, то это твоя проблема. Это довольно тонко (!), Но если предположить, что ваши два бита в кавычках находятся в том порядке, в котором вы их показали, то вот порядок, в котором все происходит (я объясню это ниже):

  1. Функция RequestManager определена.
  2. Ваша анонимная функция, которая создает ваши синглтоны, запускается, включая создание экземпляра RequestManager.
  3. Прототип RequestManager заменен новым.

Поскольку экземпляр myRequestManager был создан за до того, как прототип был изменен, он не имеет функций, которые вы определили в этом (новом) прототипе. Он продолжает использовать объект-прототип, который был на месте, когда он был создан.

Вы можете легко это исправить, переупорядочив код или добавив свойства в прототип RequestManager вместо его замены, например ::

RequestManager.prototype.require = function(text){
    //make an ajax request
};
RequestManager.prototype.otherFunc = function(){
    //do other things
};

Это работает, потому что вы не заменили объект-прототип, вы только что добавили его. myRequestManager видит дополнения, потому что вы добавили их в объект, который он использует (скорее, чем установка нового объекта в свойстве prototype функции конструктора).

Почему это происходит, немного технически, и я буду в основном полагаться на спецификацию. Когда интерпретатор вводит новый «контекст выполнения» (например, функцию или глобальный & ndash; например, уровень страницы & mdash; контекст), порядок, в котором он выполняет свои действия, не является строгим порядком источника сверху вниз, существуют фазы , Одним из первых этапов является создание всех функций, определенных в контексте; это происходит за до выполнения любого пошагового кода. Подробности во всей их красе в разделах 10.4.1 (глобальный код), 10.4.3 (код функции) и 10.5 (привязки объявлений) в спецификации , но в основном функции создаются перед первой строкой пошаговый код. : -)

Это проще всего увидеть на примере изолированного теста:

<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<title>Test Page</title>
<style type='text/css'>
body {
    font-family: sans-serif;
}
</style>
<script type='text/javascript'>
// Uses Thing1
var User1 = (function() {
    var thing1 = new Thing1();

    function useIt() {
        alert(thing1.foo());
    }

    return useIt;
})();

// Uses Thing2
var User2 = (function() {
    var thing2 = new Thing2();

    function useIt() {
        alert(thing2.foo());
    }

    return useIt;
})();

// Thing1 gets its prototype *replaced*
function Thing1() {
    this.name = "Thing1";
}
Thing1.prototype = {
    foo: function() {
        return this.name;
    }
};

// Thing2 gets its prototype *augmented*
function Thing2() {
    this.name = "Thing2";
}
Thing2.prototype.foo = function() {
    return this.name;
};

// Set up to use them
window.onload = function() {
    document.getElementById('btnGo').onclick = go;
}

// Test!
function go() {

    alert("About to use User1");
    try
    {
        User1();
    }
    catch (e)
    {
        alert("Error with User1: " + (e.message ? e.message : String(e)));
    }

    alert("About to use User2");
    try
    {
        User2();
    }
    catch (e)
    {
        alert("Error with User2: " + (e.message ? e.message : String(e)));
    }
}

</script>
</head>
<body><div>
<div id='log'></div>
<input type='button' id='btnGo' value='Go'>
</div></body>
</html>

Как вы можете видеть, если вы запустите его, User1 завершится неудачно, потому что используемый им экземпляр Thing1 не имеет свойства foo (потому что был заменен прототип), но User2 работает, потому что Thing2 экземпляр, который он использует * делает (поскольку прототип был дополнен, а не заменен).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...