在Javascript中强制执行私有方法是个好主意吗?

时间:2012-10-15 20:45:24

标签: javascript oop methods styles

我正与一位开发人员讨论是否有意识地侵入javascript私有函数。

替代方案是:

  1. 具有所有函数的构造函数和原型,非API方法(私有)将使用下划线_function_name命名,以便开发人员知道他们可以调用什么以及他们不能调用什么。
  2. API函数的构造函数和原型,以及作为私有命名空间内的私有函数的自由函数,它将它们隐藏在除此之外的其他命名空间中。
  3. 我们不考虑其他方法,例如在var private_var= function(){}形式的构造函数中创建私有函数,因为每次对象实例化时都会触发所有这些函数的创建,并且每个对象都有自己的集合。

    我们为他们提供的理由:

    1

    • Javascript本身不支持私有函数,事实上,没有私有/受保护/公共可见性的概念,所以这基本上是黑客攻击
    • 在方法名称中使用下划线清楚地标记了给定类/原型的边界,没有必要“强制”它,事实上像Python这样的语言没有私有方法,python用户似乎从不关心关于它除了使用下划线
    • 即使私有被强制执行,它在一种语言中有什么意义,你可以动态地替换公共方法?
    • 影响可读性,非常多,私有函数被分成另一组括号,它们不能使用this或者需要使用function.call(object)function.apply(object) < / LI>

    2

    • 通过封装远离类/原型用户手的私有方法来明确限制
    • 它或多或少是行业标准,很多javascript开发人员都喜欢这样做

    我们怀疑因为很多开发人员都在使用它,所以可能还有另外一个原因,比如性能等等。

    由于我们对Javascript的了解非常有限,我们决定在stackoverflow中发布此内容,以了解哪种方法更好以及原因。

10 个答案:

答案 0 :(得分:4)

你会如何破解javascript私有函数以及出于什么目的?

我认为创建类,私有函数等的不同方法取决于你的动机。

当您尝试进行单元测试时,下划线约定有助于您真正想要从外部分解看起来像一个巨大的方法。

我觉得在其他所有情况下你都应该尝试让事情变得私密。如果您正在尝试为其他人公开一个漂亮,干净的API,那么他们不应该看到幕后的内容。为什么暴露?这导致关于私人和公共的一般情况:Why "private" methods in the object oriented?

您可以选择私有化方法,其中一些选择会影响性能。

下划线约定:

function Pizza() {
   this._pepperoni = function () {};
}

function Pizza() {
}

Pizza.prototype._pepperoni = function () {};

作用域

function Pizza() {
    function pepperoni() {};
}

名字空间/模块

var pizza = pizza || {};
(function() {
    function pepperoni() {};
    function create() {
        pepperoni();
    }
    window.pizza.create = create; // or module.export = pizza or both
}());

模块模式

(function(){
    function pepperoni() {};
    function Pizza() {
        pepperoni();
    }

    window.Pizza = Pizza;
}());

关于重新创建功能与定义它们一次。首先,如果您想使用内部私有成员并仍然使用“this”,只需创建一个名为self的新变量并为其指定this

function Pizza() {
    var self = this;
    function pep() {
       self.x = 1;
    }
}

接下来,我尝试在前面测试重新定义和编写函数之间的性能差异:http://jsperf.com/private-methods我认为每次运行时都可以节省少于20%的操作/秒。

我不推荐任何方法,它们在不同时间都是有效且有用的。有时它更多地是关于语义,有时它是关于性能的,有时它是为了满足某些目的,比如单元测试。

答案 1 :(得分:3)

  • 这不是黑客: 我认为克服这个想法是至关重要的。当你使用语言的核心合法功能 - 闭包 - 实现隐藏对象细节的目标时,你就不会被黑客攻击。

    重要的是要注意,当您在基于类的语言中使用私有时,您只是实现了一个更高级别的概念,即隐藏对象的实现细节。考虑到这一点,我们不是试图在javascript中模仿私有修饰符,我们只是实现一个概念,我们如何实现它并不相关,并且依赖于工具/语言。

    顺便说一下,我来自C#背景,当我开始使用JavaScript时,我发现很难克服这个“黑客”障碍,直到最近,我才考虑闭包,对象文字,构造函数,回调函数......我认为他们都是黑客。只有当我克服了心理障碍,我才开始欣赏JavaScript并编写利用其优势的优秀代码。

  • 至于如何隐藏“私人”功能,您可以将它们隐藏在您建议的命名空间内,或者使用模块模式或其中一个变体初始化为变量偏爱。你说这意味着这些变量将在每个新实例中重新创建,但对于基于类的语言中的私有变量则相同。如果你正在寻找一个等效的静态,那么你可以做MyClass.StaticVariable =“myStaticVariable”,那应该是它。

  • 当您可以更改公共API时,以动态语言隐藏详细信息有何不同?好吧,这让我们回到了我的第一点 - 从基于类的语言的错误眼睛看JavaScript - 从动态语言的角度来看,想象你因为那种动态性而获得的所有灵活性,我只是看着Sinon库它提供了模拟定时器之类的方法(主要是因为你可以劫持几乎所有东西的公共api),想象一下非动态语言会有多难,看看模拟框架,IoC容器,代理生成器,反射的所有需求,您可以使用JavaScript等动态语言免费获得所有这些内容。因此,即使你的论证在技术上是正确的,当把它放到完整的上下文中时,它就变得无关紧要了。

    再次隐藏实现细节时,我们不是在模仿C#/ Java / C ++功能,我们这样做是因为它首先是良好的设计和良好的可重用性和维护概念,但其次 - 在我的view - 我们这样做是因为它是一种语言模式,这就是它在JavaScript中的运作方式。不要低估这一点,你今天编写的代码将明天被其他人阅读,因此遵循常见的模式(OO设计,如隐藏细节和封装,以及模块模式中的特定于语言的模式)大大改善了沟通,这是 - 就其本身而言 - 足以在我看来接受它。如果您使用“JavaScript-way”,那么您已经与其他团队成员,未来将加入团队的JavaScript专家以及社区进行了正确的沟通。

  • 就可读性而言,您可以通过遵循模式和常规约定来帮助您的代码读者。如果你去尝试用C ++做同样的事情,那么你只会影响可读性。

  • 做下划线的事情:约定是好的,特别是在与团队合作时,最大的一个库 jQuery UI 使用这个约定它确实有效使事情更清楚。这样做,即使你的变量没有被隐藏(在jQuery UI中,你仍然可以访问和修改下划线的变量),它为你的代码库带来的清晰度和一致性是非常宝贵的。

最后,我一直提到“以JavaScript的方式做”,但该声明本身就是妄想。我认为对于你提到的问题有一种JavaScript方式,但是在更大的范围内,特别是在框架和库的层面上,几乎没有一种方法可以做。但这是JavaScript的兴奋和美丽的一部分。拥抱它。

答案 2 :(得分:2)

哪种方法更好取决于你想要达到的目标。

您的备选方案1是开发人员就某个命名惯例达成的协议。只要您有一个愿意承诺遵守此协议的开发人员团队并且他们是唯一使用该软件的人,这种方法就更好了。正确地说,这种方法可以很好地支持代码的可读性和更重要的可测试性。此外,您会发现许多开发人员可以理解和维护代码,而不是替代方案2。

您的替代方案2,以及用于实现具有闭包的对象的真正私有成员的任何模式都不是hack,而是javascript的工作方式。只要您确实需要保护对象内的属性,这种方法就更好了。在将这段代码提供给不属于您的团队或使其公开的开发人员时,大多数情况都是如此。但是,您将失去代码的可读性和可测试性,并且您会发现能够理解和维护代码的开发人员较少。

此外,似乎有一些关于你的javascript的混乱。你是正确的javascript不支持私有属性。但javascript也不支持“类”作为语言结构,也不支持“类方法”。 javascript中的所有内容都是(只是)一个对象。类构造函数只是用于创建对象的模式,而方法是(仅)类型为“function”的对象的属性。 javascript中的对象是类的实例,就像传统的面向对象语言一样。

因此,正确地说,在javascript中创建对象之后,可以更改该对象的每个属性,包括函数属性aka methods,(封装在封闭中的属性除外)。这是javascript的强大功能,但是在保护对象免受更改的情况下,这是一个缺点,而不是javascript(还)设计的东西。您将无法阻止用户重写您的对象,但您可以通过闭包使其变得更加困难。

我希望这对你的决定有所帮助。

答案 3 :(得分:2)

是的,基于误解是一个坏主意。人们常常误解语言就像 Java,C#等具有强制执行或无法规避的隐私。但实际上 这是完全错误的,Java,C#等中的private和其他访问修饰符来了 建议和沟通,而不是执法。

By Martin Fowler

  

访问控制的目的不是阻止访问,而是更多地表示该类更喜欢保留一些东西。使用访问修饰符,就像编程中的许多内容一样,主要是关于沟通

这正是下划线前缀的作用。使用下划线前缀,您不会与语言对象模型作斗争。

您无法以任何理由宣传闭包,但每种其他语言都可以这样做。具有讽刺意味的是,你所做的就是你所做的 Javascript是他们最不灵活的语言。

因为您使用变量而不是对象属性,所以不能使用

基本上,闭包所带来的OOP功能几乎仅限于对一组函数进行分组 一起。这只适用于最初的玩具示例。如果你坚持常识那样的话 隐私必须由机器强制执行,然后你甚至不能拥有“包私有”,“受保护” 或者许多种更复杂的“访问控制”。

顺便说一下,你还需要为每个实例的每个方法创建唯一的函数对象(因为它们具有可观察的标识)。

答案 4 :(得分:1)

我建议使用下一版JavaScript,ES6:WeakMap中的工具。我已经在IE8 +中实现了这一点,我已经写过并广泛使用它。

function createStorage(creator){
  creator = creator || Object.create.bind(null, null, {});
  var map = new WeakMap;
  return function storage(o, v){
    if (1 in arguments) {
      map.set(o, v);
    } else {
      v = map.get(o);
      if (v == null) {
        v = creator(o);
        map.set(o, v);
      }
    }
    return v;
  };
}


var _ = createStorage(function(o){ return new Backing(o) });

function Backing(o){
  this.facade = o;
}
Backing.prototype.doesStuff = function(){
  return 'real value';
}

function Facade(){
  _(this);
}
Facade.prototype.doSomething = function doSomething(){
  return _(this).doesStuff();
}

更多信息:

  1. http://bbenvie.com/articles/2012-07-25/JavaScript-Classes-with-private-protected-and-super
  2. http://benvie.github.com/WeakMap/
  3. http://benvie.github.com/harmony-collections/

答案 5 :(得分:0)

在我工作的地方,推荐的模式使用“命名空间模式”并像处理任何OO语言一样处理命名空间。

您可以看到in this answer

的方式

答案 6 :(得分:0)

我认为私有方法很好,如果你想 阻止 其他开发人员访问这些方法,即使他们知道不应该这样做。

请阅读此Crockford article,特别是关于OOP和private members的部分

希望这个帮助

答案 7 :(得分:0)

我更喜欢使用揭示模块模式。查看here

答案 8 :(得分:0)

虽然这不是天气问题的真正答案,但严格执行私人方法是一个好主意我只想指出可以使用闭包这样做。

它基本上是var foo=function技巧,但它并没有为对象的每个实例创建一个新函数。这样做的方法是:

// Create a closure that we can share with our constructor
var MyConstructor = (function(){

    // Within this closure you can declare private functions
    // and variables:
    var private_function = function () {};
    var private_var = {};        

    // Declare the actual constructor below:
    return function () {

    }
})()

就我个人而言,我有两种想法。来自一个工作环境,同时有很多人在同一组文件上工作,我真的很欣赏能够将内容设置为私有的能力,以便试图不正确地使用该对象的人不会破坏代码。 / p>

另一方面,来自Perl背景我也很欣赏这样一个事实,即设计该对象的原始程序员并非无所不能,并且无法预见代码将来的所有使用方式。有时你真的需要在一些极端情况下改变对象的内部。这一直是经验丰富的Perl程序员的标准接受智慧。

最后,两种方式都有效。 Perl的CPAN存储库中的大多数模块都非常稳定,即使没有真正的隐私,也可以直接使用。另一方面,我一直致力于人们因此而引入错误的项目。我想这归结为你的团队如何一起工作。如果每个人都尊重编码指南和文化,那么Perl风格&#34; _&#34;技巧很棒。另一方面,如果您不相信未来的维护者会破坏您的代码,那么更严格地执行隐私可能会更好。

答案 9 :(得分:0)

您的问题有几种方法,您选择哪种方法取决于几个因素,包括(1)目标浏览器支持,(2)项目目标,以及(3)个人偏好。我将重点介绍一些我特别有意见的方法。

符号

我首先强调这一点,因为它是最强大的解决方案,它是JavaScript的未来:它将包含在即将发布的ECMAScript标准版本中。今天可以在ES5兼容的浏览器中使用垫片。这意味着它基本上适用于过去两年发布的所有A级浏览器。主要的缺点是它在IE 8中不受支持,但IE 9和10(当然还有所有现代版本的FF,Chrome和Safari)都支持它。如果IE8对你来说仍然很重要,那么你还不能选择。

垫片可以在这里下载:SymbolsForES5。这个库使您可以开始使用此功能,并且当将来符号本身包含在浏览器中时,您的代码应该能够很好地转换。

以下是为私人成员使用符号的示例:

var x = { };
var a = new Symbol();
x[a] = 5;
console.log(x[a]); // => 5

只要您有权访问a符号对象,就可以从x对象中读取该属性,但是如果有人无法访问a,则可以读它。这允许您真正拥有私人成员:

var Person = (function() {

    var firstName = new Symbol(),
        lastName = new Symbol();

    function Person(first, last) {
        this[firstName] = first;
        this[lastName] = last;
    }

    Person.prototype.getFullName = function() {
        return this[firstName] + ' ' + this[lastName];
    };

    return Person;

})();

var john = new Person('John', 'Smith');
john.getFullName(); // => 'John Smith'

Object.getOwnPropertyNames(john); // => [ ]

使用不要访问字符

正如您所提到的,您始终可以使用字符(例如下划线)为属性添加前缀,以指示外部代码不应访问该属性。主要缺点是该物业仍然可以访问。但是有一些好的好处:(1)高效记忆(2)完全使用原型继承的能力(3)易于使用< strong>(4)易于调试(因为属性显示在调试器中)(5)适用于所有浏览器。

我过去曾广泛使用过这种方法取得了巨大的成功。作为下划线的替代,在one framework I developed (joi)中,我使用了#符号,因为它使得属性更难访问(您必须使用方括号表示法来访问它),作为温和提醒任何试图访问它的人都应该单独留下它:

function Person(first, last) {
    this['#firstName'] = first;
    this['#lastName'] = last;
}

Person.prototype.getFullName = function() {
    return this['#firstName'] + ' ' + this['#lastName'];
};

var john = new Person('John', 'Smith');
john.getFullName(); // => 'John Smith'

我已经使用这种技术大约7年了,取得了巨大的成功,如果符号不符合您的需求,我会强烈推荐它。

构造函数内的私有

我知道你说由于内存/性能原因你决定不采用这种技术,但如果你想要的东西接近真正的私有,你不能使用Symbols,因为你需要传统的浏览器支持,这是一个很好的选项。我工作的公司在大型应用程序上使用此模型,并且消耗的内存确实不是问题。此外,我听说有研究已经发现了在这个模型中优化内存和性能的方法,我相信现代浏览器(至少V8 / Chrome,如果不是其他的话)开始实现这些优化。 (这些信息来自我从Brendan Eich听到的一个演讲;我很抱歉,我不知道哪个演讲是我的头脑。)

最后,有专家主张使用这种技术,虽然这不是我的偏好,但我的公司已经取得了成功,我可以保证它是一个可靠的模型。

为完整起见,它看起来像这样:

function Person(first, last) {
    this.getFullName = function() {
        return first + ' ' + last;
    };
}

var john = new Person('John', 'Smith');
john.getFullName(); // => 'John Smith'

JavaScript引擎可以通过不为每个getFullName方法创建一个新函数来优化这一点(即使按照本书的说法,也应该如此),因为在这种情况下,无法确定它是否实际创建每次都有一个新的函数对象。棘手的部分是一旦引入代码,它将能够确定每次是否创建新的函数对象,引擎需要切换到实际执行该操作。

WeakMaps

在另一个答案 benvie (提及WeakMaps)[http://stackoverflow.com/a/12955077/1662998]。如果需要IE8支持并且您希望属性真正模拟私有成员,我会考虑这个替代方案。