JavaScript'类'和单例问题

Man*_*tto 9 javascript singleton closures

我有一个单例对象使用另一个对象(而不是单例),要求服务器的一些信息:

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
    }
  };
}());
Run Code Online (Sandbox Code Playgroud)

这是向服务器发出请求的"类"

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
  }
Run Code Online (Sandbox Code Playgroud)

}

问题是我无法从单例对象中调用myRequestManager.require.Firebug consolle说:"myRequestManager.require不是函数",但我不明白问题出在哪里.有没有更好的解决方案来实现这种情况?

T.J*_*der 6

你的代码按你引用的顺序排列,不是吗?单身出现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
};
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为你没有替换原型对象,你刚刚添加它.myRequestManager看到添加,因为你已经将它们添加到它正在使用的对象中(而不是在构造函数的prototype属性上设置一个新对象).

为什么会发生这种情况有点技术性,我将主要遵循规范.当解释器输入新的"执行上下文"(例如,函数或全局 - 例如,页面级 - 上下文)时,它执行操作的顺序不是严格的自上而下的源顺序,而是存在阶段.第一阶段之一是实例化上下文中定义的所有函数; 在执行任何分步代码之前发生的事情.详见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>
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,如果您运行它,则User1失败,因为Thing1它正在使用的实例没有foo属性(因为原型已被替换),但是User2因为Thing2它使用的实例*起作用(因为原型是扩充的,而不是替换).