【问题标题】:Private prototype methods that can share scope and access the instance可以共享范围和访问实例的私有原型方法
【发布时间】:2015-07-22 13:49:01
【问题描述】:

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

例如,这就是我目前实现“私有方法”的方式(忽略代码实际做了什么,只看结构。)

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 的值是全局对象,而不是实例。我需要一种访问实例的方法。

【问题讨论】:

  • 当您拨打instance.update(info) 时,您将可以访问this。您可以将其显式传递给私有函数,或者使用.call() 调用它们以设置this
  • @Pointy Ahh,这样我就可以访问每个函数中的实例,但是您知道缓存植根于实例的查找并在 之间共享它们的好方法吗所有的功能? (而不必在多个函数中重复相同的初始化代码)
  • 好吧,您将通过一种或另一种方式来处理实例引用。我不知道“缓存”的概念是否真的有意义;这些函数只有一个闭包,而不是每个实例一个闭包。
  • @Pointy 是的,我认为你是对的。我想我可以为更长的链制作 getter 函数并将结果缓存在 getter 函数本身中(作为次优解决方案)
  • 缓存是什么意思?

标签: javascript design-patterns prototype private private-members


【解决方案1】:

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

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

为什么要使用它(根据 cmets 的要求):

简单地说,因为它是创建“真正的私有范围”的唯一(理智)方式,这是您的问题。

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

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

内存使用

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

V8 在运行时为对象内部创建了隐藏类型;具有相同隐藏类的对象可以使用相同的优化生成代码。

例如:

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 已关闭!

构造函数模式很脏(原文如此)

性能是我的理由。我没有意识到这一点。但是我还是觉得很脏

我不知道你为什么觉得构造函数模式很脏。也许是因为它有一些你应该注意的“细节”、限制和潜在的陷阱

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

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

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

【讨论】:

  • 你能想出一种方法来适应这种情况,以便我可以在我的原型方法中使用这种模式吗? (我不能)
  • 并非没有极其复杂的反模式。为什么需要使用原型?
  • 这正是我在这个用例中所需要的。我正在寻找可以与我的实例方法一起使用的通用模式。我不想将方法复制到每个单独的实例。看来,拥有我的蛋糕并吃掉它的唯一方法是编写我自己的迷你框架,它允许私有函数使用外部范围内的机制注册依赖关系。 (虽然这比我想做的工作要多)
  • 我不明白...您不需要将方法复制到每个单独的实例。 Foo 的每个实例都有您在构造函数中分配的方法。
  • 我不是说手动,我只是不想复制它们。我希望他们共享相同的方法。
【解决方案2】:

每个函数中,您都可以访问所需的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();
&lt;div id="answer"&gt;&lt;/div&gt;

【讨论】:

  • 经过测试,在从其他私有函数中调用私有函数时,您仍然需要使用call 或显式传递this。此外,我无法单独在私有函数之间共享状态
【解决方案3】:

作为 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;
})();

【讨论】:

  • 我也在考虑这个问题,尽管随着方法的数量和我需要访问的实例变量数量的增加,这会变成很多(可能是不必要的)重新计算。
  • 这也不适用于一些对象,因为在调用第一个对象后 - 它缓存并用于第二个对象
【解决方案4】:

也许是这样,没有原型:

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()
;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-17
    • 1970-01-01
    • 2011-12-08
    • 1970-01-01
    • 2014-03-20
    相关资源
    最近更新 更多