我试图围绕类,数据可见性和闭包(特别是在Javascript)的想法,我在类型的jQuery文档页面上,它提到闭包用于隐藏数据:
该模式允许您使用方法创建对象,这些方法对外部不可见的数据进行操作 - 这是面向对象编程的基础。
示例:
function create() {
var counter = 0;
return {
increment: function() {
counter++;
},
print: function() {
console.log(counter);
}
}
}
var c = create();
c.increment();
c.print(); // 1
通过使用关键字var声明变量计数器,它已经在函数/类定义中本地作用域。据我所知并且可以说,从外面开始无法访问。我是否从数据可视性角度遗漏了一些内容。
其次,如上所述编写类是否有优势,如下所示:
function create() {
var counter = 0;
this.increment = function() {
counter++;
}
this.print = function() {
console.log(counter);
}
return this;
}
var c = create();
c.increment();
c.print(); // 1
据我所知,这些或多或少在语义上是相同的 - 第一个是更多的“jQuery风格”。我只是想知道从第一个例子中我是否有一个优势或其他细微差别。如果我是正确的,两个示例都会创建闭包,因为它们正在访问在自己的范围之外声明的数据。
答案 0 :(得分:10)
首先,你说两个版本都使用闭包是正确的。
第一个版本更清晰(在我看来)并且在现代javascript中更受欢迎。第一种风格的主要潜在缺点是你不能有效地将对象分配给构造函数的原型,如果你要创建许多相同的对象,这是有用的(并且更有效)。
第二种风格,我实际上从未见过生产Javascript。通常,您可以使用create
实例化new
,而不是在this
函数中返回create()
,如下所示:
function create() {
var counter = 0;
this.increment = function() {
counter++;
}
this.print = function() {
console.log(counter);
}
}
var c = new create();
c.increment();
c.print(); // 1
答案 1 :(得分:4)
通过声明变量计数器 关键字var,它已经在本地 范围内的函数/类 定义。据我所知,可以 告诉它,它无法访问 外面开始。我错过了吗 来自数据可见性的东西 透视图。
并非counter
变量无法从函数外部访问,而是在increment
之后print
和create
函数可以访问它函数已退出,使closures非常有用。
答案 2 :(得分:2)
好吧,我并不关心如何在JavaScript中创建对象的宗教战争,因为有些人强烈认为有正确和错误的方法。
但是,我想在你的第二组代码中指出一些不太美味的东西 - 即你在this
关键字中包含的对象上分配新属性的事实 - 你呢?意识到那个对象是什么?除非您使用如下的实例化语法,否则它不是空对象:
var c = new create();
当你这样做时,构造函数体内的this
关键字被赋予一个全新的对象,好像正文中的第一行是这样的:
this = {};
但是当您将create()
作为函数调用时,就像在该示例中那样,您正在改变函数定义之外的范围(由@seanmonster在评论中提到)。
答案 3 :(得分:2)
您应该将示例与此代码段进行比较
function create() {
this.counter = 0;
this.increment = function() {
this.counter++;
};
this.print = function() {
console.log(counter);
}
}
var c = new create();
c.increment();
c.print(); // 1
因此,当调用new create()时,它使用两个方法和一个实例变量(即:counter)初始化新对象。 Javascript本身没有封装,因此您可以访问c.counter,如下所示:
var c = new create();
c.increment();
c.counter = 0;
c.print(); // 0
通过使用闭包(如示例所示),计数器现在比实例字段更长,而是局部变量。一方面,您无法从外部访问create()函数。另一方面,increment()和print()可以访问,因为它们在封闭范围内关闭。因此,我们最终得到了一个非常好的仿真,即对象方式的封装。
答案 4 :(得分:1)
你的第二个例子仍然使用闭包,因为增量和打印功能仍然作用于变量,否则超出范围 - 当你调用c.increment()
创建函数已经退出时。
我喜欢第一个示例,因为它避免使用“this
”关键字,并且在javascript“this
”中可能会很棘手 - 它并不总是指它应该是什么样的。< / p>
答案 5 :(得分:1)
Christian Heilmann在the module pattern上有一篇相当不错的文章,你可以帮助你解决这个问题以及为什么它有用。
答案 6 :(得分:1)
这种语法对我来说更有意义来自OOP背景:
Create = function {
// Constructor info.
// Instance variables
this.count = 10;
}
Create.prototype = {
// Class Methods
sayHello : function() {
return "Hello!";
},
incrementAndPrint : function() {
this.count++;
// Inner method call.
this.print();
},
print : function() {
return this.count;
}
}
var c = new Create();
alert(c.incrementAndPrint());
答案 7 :(得分:1)
MYAPP = (function(){
var v1,v2;
return {
method1:function(){},
method2:function(){}
};
})();
我总是在我的应用程序中使用这样的闭包,就像这样,所有我自己定义的方法都在MYAPP命名空间中,v1和v2只能通过MYAPP中的方法访问。在我的应用程序中,我经常只编写一个“app” .js“文件,里面的所有js代码。我想你可以定义一个名为“registy”的方法来定义MYAPP中的私有变量,然后你可以在你的方法中使用它。当你想在html文件中添加额外的代码时,所有额外的变量和方法都应该由registy方法定义,就像JQuery.extend方法一样。我听说如果在IE浏览器中使用太多的闭包,你很容易得到堆栈溢出。 (在我看来)
答案 8 :(得分:0)
在你的第二个例子中,当你调用create()
时,在函数范围内,this
是全局对象(当你调用“裸”函数时总是如此,不使用它作为构造函数或作为属性访问它(例如“方法”调用))。在浏览器中,全局对象为window
。因此,当您调用后续创建时,它会创建新的闭包,但您可以将它们分配给与以前相同的全局对象,覆盖旧函数,这不是您想要的:
var c = create(); // c === window
c.increment();
c.print(); // 1
var c2 = create(); // c2 === c === window
c.print(); // 0
c2.print(); // 0
increment(); // called on the global object
c.print(); // 1
c2.print(); // 1
正如其他人所指出的那样,解决方案是使用new create()
。