12

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

替代方案是:

  1. 包含所有函数的构造函数和原型,非 API 方法(私有)将仅使用下划线命名,_function_name以便开发人员知道他们可以调用什么以及不能调用什么。
  2. API 函数的构造函数和原型,以及作为私有函数在私有命名空间中的自由函数,该私有命名空间将它们隐藏在除这个命名空间之外的其余命名空间之外。

我们不考虑其他方法,例如在表单的构造函数中创建私有函数,var private_var= function(){}因为这会在每次实例化对象时触发所有这些函数的创建,并且每个对象都有自己的集合。

我们对他们的理由:

1

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

2

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

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

由于我们对 Javascript 的了解非常有限,我们决定将其发布在 stackoverflow 中,以了解哪种方法更好以及为什么。

4

10 回答 10

6

您将如何侵入 javascript 私有函数以及出于什么目的?

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

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

我觉得在其他任何情况下,您都应该尝试将事情真正保密。如果您试图公开一个漂亮、干净的 API 供其他人使用,他们不需要查看幕后的内容。为什么要曝光?这导致了关于私有和公共的一般讨论:为什么在面向对象中使用“私有”方法?

您确实有一些私有化方法的选择,其中一些选择会影响性能。

下划线约定:

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% 的 ops/sec 来重新创建你的函数时间。

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

于 2012-10-18T05:09:09.627 回答
4
  • 这不是 hack:我认为克服这个想法至关重要。当您使用该语言的核心合法功能 - 闭包 - 来实现隐藏对象细节的目标时,您就不是在进行黑客攻击。

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

    顺便说一句,我来自 C# 背景,当我开始使用 JavaScript 时,我发现很难克服这个“hack”障碍,直到最近,我才考虑过闭包、对象字面量、构造函数、回调函数……我考虑过它们所有黑客。只有当我克服了这种心理障碍时,我才开始欣赏 JavaScript 并编写利用其优势的优秀代码。

  • 至于如何隐藏您的“私有”函数,您可以按照您的建议将它们隐藏在命名空间中,或者使用模块模式 或其变体之一初始化为变量,这将是我的偏好。你说这意味着这些变量将在每个新实例中重新创建,但这对于基于类的语言中的私有变量是相同的。如果您正在寻找静态的等效项,那么您只需执行 MyClass.StaticVariable = "myStaticVariable" 就可以了。

  • 当您可以更改公共 API 时,以动态语言隐藏细节有什么不同?好吧,这让我们回到了我的第一点——以基于类的语言的错误眼光看待 JavaScript——从动态语言的角度来看,想象一下由于动态特性而获得的所有灵活性,我只是在看 Sinon 库它提供了模拟计时器之类的方法(基本上是因为您可以劫持几乎所有东西的公共 api),想象一下在非动态语言中这将是多么困难,了解对模拟框架、IoC 容器、代理生成器、反射的所有需求,您可以使用 JavaScript 这样的动态语言免费获得所有这些。因此,即使您的论点在技术上是正确的,但当放在完整的上下文中时,它就会变得无关紧要。

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

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

  • 做下划线的事情:约定很好,尤其是在与团队合作时,最大的库之一jQuery UI使用了这种约定,它确实有效并使事情变得更清晰。这样做,即使您的变量没有真正隐藏(在 jQuery UI 中,您仍然可以访问和修改带下划线的变量),它为您的代码库带来的清晰度和一致性是无价的。

最后,我一直在提到“以 JavaScript 的方式来做”,但这种说法本身就是骗人的。我认为您提到的问题有一种 JavaScript 方法,但在更大的范围内,特别是在框架和库级别,几乎没有单一的做事方法。但这正是 JavaScript 令人兴奋和美丽的一部分。拥抱它。

于 2012-10-23T23:19:21.190 回答
3

是的,这是一个基于误解的坏主意。人们普遍误解 Java、C# 等语言具有强制执行或无法规避的隐私。但实际上这是完全错误的private,Java、C# 等中的和其他访问修饰符归结为建议和交流,而不是强制执行。

马丁·福勒

访问控制的目的不是阻止访问,而是更多地表明该类更喜欢将某些东西保留给自己。使用访问修饰符,就像编程中的许多事情一样,主要是关于通信

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

你不能以任何理由绕过闭包,但这在所有其他语言中都是可能的。具有讽刺意味的是,您所做的只是使 Javascript 成为所有语言中最不灵活的语言。

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

基本上,通过闭包获得的 OOP 功能几乎仅限于将一堆函数组合在一起。这仅适用于最初的玩具示例。而且,如果您坚持认为隐私必须由机器强制执行的常识,那么您甚至无法拥有“包私有”、“受保护”或许多更复杂级别的“访问控制”。

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

于 2013-08-08T10:31:48.360 回答
2

哪种方法更好在很大程度上取决于您要实现的目标。

您的替代方案 1 是开发人员就特定命名约定达成的协议。每当您有一个愿意承诺遵守此协议的开发人员团队并且他们是唯一使用该软件的人时,这种方法会更好。正如您正确指出的那样,这种方法非常好地支持代码的可读性和更重要的可测试性。此外,与替代方案 2 相比,您会发现更多能够理解和维护代码的开发人员。

您的替代方案 2,以及任何使用闭包实现对象的真正私有成员的模式,都不是 hack,而是 javascript 的工作方式。当您确实需要保护对象内部的属性时,这种方法会更好。在向不属于您的团队的开发人员提供一段代码或使其公开可用时,通常会出现这种情况。但是你会失去一些代码的可读性和可测试性,你会发现能够理解和维护代码的开发人员更少。

您这边似乎也对javascript有些困惑。您对 javascript 不(尚未)支持私有属性是正确的。但是 javascript 也不支持“类”作为语言结构,也不支持“类方法”。javascript 中的一切都是(只是)一个对象。类构造函数只是创建对象的一种模式,方法是(只是)“函数”类型的对象的属性。javascript 中的对象不是类的实例,就像在传统的面向对象语言中那样。

因此,正如您正确指出的那样,在 javascript 中创建对象后,该对象的每个属性,包括函数属性,也就是方法,都可以更改(封装在闭包中的属性除外)。这是 javascript 的强大功能,但在保护对象不被更改的情况下,这是一个缺点,而不是 javascript(尚未)设计的东西。您将无法阻止用户重写您的对象,但您可以使用闭包使其变得更加困难。

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

于 2012-10-18T21:18:06.950 回答
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/
于 2012-10-18T12:48:07.580 回答
0

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

你可以在这个答案中看到

于 2012-10-15T20:48:29.280 回答
0

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

请阅读这篇 Crockford文章,特别是关于 OOP 和私人成员的部分

希望这有帮助

于 2012-10-15T21:11:54.377 回答
0

我更喜欢使用显示模块模式。在这里检查

于 2012-10-15T21:55:45.870 回答
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 () {

    }
})()

就个人而言,我对此有两种看法。来自一个有很多人同时处理同一组文件的工作环境,我真的很欣赏使东西真正私有的能力,这样代码就不会被试图不正确使用对象的人破坏。

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

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

于 2012-10-18T06:05:35.587 回答
0

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

符号

我首先强调这一点,因为它是最强大的解决方案,它是 JavaScript 的未来:它将包含在即将发布的 ECMAScript 标准版本中。今天,在符合 ES5 的浏览器中使用 shim 是可能的。这意味着它基本上适用于过去 2 年发布的所有 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

只要您有权访问aSymbol 对象,您就可以从该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); // => [ ]

使用DON'T ACCESS字符

正如您所提到的,您始终可以在属性前面加上一个字符(例如下划线),以表明它不应该被外部代码访问。主要缺点是该物业仍然可以访问。然而,有一些好的好处:(1)高效的内存(2)使用原型继承的全部能力(3)易于使用(4)易于调试(因为属性显示在调试器中)(5)适用于所有浏览器。

我过去曾广泛使用这种方法并取得了巨大的成功。作为下划线的替代方案,在我开发的一个框架 (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方法真正创建一个新函数来优化这一点(即使按照书本,它应该这样做),因为在这种情况下,无法确定它是否每次都实际创建一个新函数对象。棘手的部分是,一旦引入了能够确定每次是否创建新函数对象的代码,引擎就需要切换到实际执行该操作。

弱地图

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

于 2012-10-24T05:19:35.733 回答