在闭包内声明的类与没有闭包的标准类

Posted

技术标签:

【中文标题】在闭包内声明的类与没有闭包的标准类【英文标题】:class declared inside closure vs standard class without closure 【发布时间】:2013-08-16 06:26:44 【问题描述】:

通常我使用基于原型的标准 OOP 方法,我的类看起来像这样

var std = function()
   this.log = function(msg) console.log("want to be private. " + msg) ;
;

std.prototype = 
    logInfo: function(msg)
        this.log(msg);
    
;

但在这种情况下,log 是公共方法,任何人都可以使用它。但我想将其设为私有,但仍可在原型中声明的方法中使用。为此,我们需要闭包。代码将更改为此

var closureStd = (function()
var std = function();
var log = function(msg) console.log("I'm really private, youhooo!" + msg) ;

std.prototype = 
    logInfo: function(msg)
        log(msg);
    
;

return std;
)();

所以我的问题是:stdclosureStd 之间有什么区别?我需要付出什么代价才能从原型中调用私有方法?

【问题讨论】:

对您有用的链接:The cost of privacy 和 javascript module pattern 感谢有趣的文章。它给了我一些分数,但不要回答问题( 抱歉,没有注意到您添加了第 2 篇文章。也需要阅读。 【参考方案1】:

std和closureStd有什么区别?

std 构造函数在每次调用时都会创建一个新方法,而 closureStd 不会。你应该成功了

function std()
std.prototype = 
    log: function(msg) console.log("want to be private. " + msg) ,
    logInfo: function(msg) this.log(msg); 
;

当然(你已经知道)closureStd 中的log 函数存储在一个(私有)变量中,而在std 实例上,它可以从外部在每个实例(或在他们的原型上)。在闭包中,它是作用域链中的变量查找(可以假定为静态),而对于方法,它是对象(及其原型链)上的属性查找,它可能是动态的,但在现代引擎中同样优化。

从原型调用私有方法需要付出什么代价?

没有。模块模式常见且便宜,静态链中的变量查找非常快。我宁愿担心内存,因为您在构造函数方法中创建了如此多的方法实例。

【讨论】:

【参考方案2】:

我还制作了一些tests 来衡量性能(结果将在控制台中),我发现将类置于闭包中与将方法置于构造函数中相比表现出更好的性能。您还有继承选项。所以现在我没有看到缺点,当我需要私有方法时,我总是会在闭包中使用类。

【讨论】:

【参考方案3】:

请注意,link Bondye 提供的模块模式不包含私有 instance 属性。它适用于函数属性,因为函数不会因每个实例而改变,但对于值属性的工作有点不可预测,如下代码所示:

var module = (function () 
    // private variables and functions
    var foo = 'bar';
    // constructor
    var module = function (name) 
      // instance variables
      this.name=name;
    ;
    // prototype
    module.prototype = 
        constructor: module,
        something: function () 
          // notice we're not using this.foo
          console.log("foo in "+this.name+" is:",foo);
          foo="new value";
        
    ;
    // return module
    return module;
)();

var m1 = new module("m1");
var m2 = new module("m2");
m1.something();// foo in m1 is: bar
m1.something();// foo in m1 is: new value
m2.something();// foo in m2 is: new value

正如您在最后一行代码中看到的那样,m1 和 m2 实例共享一个名为 foo 的私有变量(所有实例都将指向相同的值)。

我可以从 Bondye 发布的 other link 中看出,很难在 JavaScript 中模拟私有性,您可以考虑使用命名约定来指示属性是私有的。您可以使用 google 闭包编译器之类的东西并注释您的 JS 代码以指示私有;但是使用闭包编译器有它自己的缺点(除非与闭包编译器兼容并且代码必须采用某种格式才能在高级模式下编译,否则无法编译使用的库)。

另一种选择是完全抛弃原型(至少对于使用私有属性的所有内容)并将所有内容放在构造函数的主体中(或使用返回对象的函数)。

// function returning an object

function makeObj(name)
  // private vars:
  var foo =  "bar";
  return 
    name:name,
    something:function()
      console.log("foo in "+this.name+" is:",foo);
      foo="new value";
    
    

var m1=makeObj("m1");
var m2=makeObj("m2");
m1.something();// foo in m1 is: bar
m1.something();// foo in m1 is: new value
m2.something();// foo in m2 is: bar

// constructor with everything in the constructor's body:

function Obj(name)
  // private vars:
  var foo =  "bar";
  this.name=name;
  this.something=function()
    console.log("foo in "+this.name+" is:",foo);
    foo="new value";
  

Obj.prototype.someOtherFunction=function()
  // anything here can't access the "private" variables

var m1=new Obj("m1");
var m2=new Obj("m2");
m1.something();// foo in m1 is: bar
m1.something();// foo in m1 is: new value
m2.something();// foo in m2 is: bar

使用私有实例值可能会遇到的另一个问题是它们只能在实例中访问。当你想克隆一个对象并在你的对象中定义一个克隆函数来创建一个新实例时,你必须编写公共访问器函数来设置私有值,因为你不能像Java private fields那样直接设置它们。

在此处使用构造函数的更多信息:Prototypical inheritance - writing up

【讨论】:

感谢您的回答。第一个例子非常有趣,我花了一些时间来理解为什么会这样。现在我看到闭包对于私有字段来说并不是完美的变体,但最初的问题是关于方法的。在我看来,将所有东西都放在构造函数中并不是一个好习惯,主要是因为性能。 @silent_coder 您是对的,第一个示例适用于私有函数,它是来自 Bondye 提供的链接的代码。由于 SO 在 google 上的得分很高,我认为值得一提的是私有实例值并不那么容易。

以上是关于在闭包内声明的类与没有闭包的标准类的主要内容,如果未能解决你的问题,请参考以下文章

为啥 C# 8.0 中使用声明的变量的闭包不同?

Groovy闭包 Closure ( 闭包类 Closure 简介 | thisownerdelegate 成员区别 | 静态闭包变量 | 闭包中定义闭包 )

如何在 swift 4 的闭包中分配类属性

关于闭包

js闭包

使用闭包跨域开发