可以共享作用域和访问实例的私有原型方法

Private prototype methods that can share scope and access the instance

我正在寻找一种模式,既允许我创建我的函数原型可以访问的私有范围,又需要能够从该范围内访问实例。

比如我目前是这样实现的"private methods"(代码具体是干什么的先不管,看结构)

function InfoPreview() {
   this.element = document.createElement('div');
}

//Private Methods
InfoPreview.prototype.__newLine = function () {
  this.element.appendChild(createElement({tagName:'br'}));
}; 

InfoPreview.prototype.__padLeft = function(level) {
  var padding = createElement({tagName: 'span'});
  this.element.appendChild(padding);
  $(padding).width(level * 10);
};

InfoPreview.prototype.__print = function(string) {
  var span = createElement({ tagName: 'span', textContent: string });
  this.element.appendChild(span);
  this.element.style["margin-right"]='10px';
};

InfoPreview.prototype.__puts = function(string) {
  this.__print(string);
  this.__newLine();
};

//Public Methods
InfoPreview.prototype.update = function(info) {
  $(this.element).empty();
  for (var record in info) {
    this.__puts(record);
  }
};   

请注意,我根本没有创建私有方法,只是使用了命名约定。另外请注意,我无法缓存链查找,例如 this.element

我想通过使用显示模块模式来创建私有作用域,如下所示:

InfoPreview.prototype = (function() {
  var self = this, //<- `this` is actually the global object now.
      el = self.element;

  var newLine = function () {
    el.appendChild(createElement({tagName:'br'}));
  }; 

  var padLeft = function(level) {
    var padding = createElement({tagName: 'span'});
    el.appendChild(padding);
    $(padding).width(level * 10);
  };

  var print = function(string) {
    var span = createElement({ tagName: 'span', textContent: string });
    el.appendChild(span);
    el.style["margin-right"]='10px';
  };

  var puts = function(string) {
    print(string);
    newLine();
  };

  var update = function(info) {
    $(el).empty();
    for (var record in info) {
      puts(record);
    }
  };

  return {
    update: update
  }; 
})();

但是上述方法不起作用,因为 IIFE 中 this 的值是全局对象,而不是实例。我需要一种访问实例的方法。

也许是类似的东西,没有原型:

https://jsfiddle.net/ynwun1xb

var Fn = function(el) {
    this.el = el;

    var myMethod = function() {
        console.log('do something in method with element', this.el);
    }.bind(this);

    return {
        myPublicMethod: function() {
            return myMethod();
        }
    }
}

var instancedFn = new Fn('first instance element')
    .myPublicMethod()
;

var instancedFn2 = new Fn('second instance element')
    .myPublicMethod()
;

在每个函数中,您将可以访问您想要的this值。

var Example = function() {};

Example.prototype = (function() {
  var privateUpdate = function() {
    document.getElementById('answer').innerHTML = this.foo;
  }
  
  return {
    update: privateUpdate
  }
})();

var e = new Example();
e.foo = 'bar';
e.update();
<div id="answer"></div>

作为 Pointy 建议的变体,您可以试试这个模式;

infoPreview.prototype = (function() {
  var self = null;

  var update = function(info) {
      ....
  };

  var firstUpdate = function(info) {
      self = this;
      functions.update = update;
      update(info);  
  }

  var functions = {
    update: firstUpdate
  }; 
  return functions;
})();

使用构造函数模式有什么缺点吗?

function Foo(constructorArg) {
    
    /* private variables */
    var privVar = 'I am private',
        cArg = constructorArg;
    
    /* public variables */
    this.pubVar = 'I am public';
    
    /* private function */
    function privFunc() {
        return 'I am a private function';
        
    }
    
    /* public function */
    this.publicFunc = function() {
        return 'I am a public function and I call privVar->"' + privVar + '" and privFunc->"' + privFunc() + '"';
    }
}

var foo = new Foo('something');

console.log('foo.pubVar', foo.pubVar); //ok
console.log('foo.publicFunc()', foo.publicFunc()); // ok

console.log('foo.privVar', foo.privVar); // undefined
console.log('foo.privFunc', foo.privFunc()); //error

为什么要使用它(如评论中所要求):

简单地说,因为这是创建 "true private scope" 的唯一(合理)方法,这是您的问题。

另一种方法是使用一种约定来告诉开发人员哪些属性和方法是私有的,通常是在它们前面加上下划线前缀 _,您已经实现了但不喜欢。

请注意,构造函数和原型是不同的东西,它们使您能够做不同的事情。没有什么能阻止你把两者混为一谈。

内存使用

关于内存使用,在现代js引擎中,比如Google的V8JavaScript引擎,the constructor pattern might actually be faster

V8 has hidden types created internally for objects at runtime; objects with the same hidden class can then use the same optimized generated code.

例如:

function Point(x, y) {
  this.x = x;
  this.y = y;
}

var p1 = new Point(11, 22);
var p2 = new Point(33, 44);
// At this point, p1 and p2 have a shared hidden class
p2.z = 55;
// warning! p1 and p2 now have different hidden classes!

原型链接总是需要两次查找,所以它甚至可能会稍微慢一点。 注意:无法对此进行备份,jsperf.com 已关闭!

构造函数模式有问题(原文如此)

Performance was my reason. I hadn't realized that. However it still feels dirty to me

不知道你为什么觉得构造函数模式很脏。可能是因为它有一些 "specifics"、限制和您应该注意的潜在陷阱

  1. this 可能意味着不同的事情
  2. 由于共享状态,很容易忘记 new 关键字导致奇怪且难以调试的错误
  3. 您无法轻松地将对象拆分到多个文件中(不借助构建工具或某些第 3 方注入器)

但是,1 和 2 也适用于原型声明样式,所以...

如果您觉得这还不够,您可能需要查看模块模式。