【问题标题】:Javascript Closure and Data VisibilityJavascript 闭包和数据可见性
【发布时间】:2009-06-03 21:25:23
【问题描述】:

我正试图围绕类、数据可见性和闭包(特别是在 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 声明变量 counter,它已经在函数/类定义中的局部范围内。据我所知,从一开始就无法从外部访问它。从数据可见性的角度来看,我是否遗漏了什么。

其次,写上面的类和下面的类有什么优势:

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 风格”。我只是想知道第一个例子中是否有我没有完全理解的优势或其他细微差别。如果我是正确的,这两个示例都创建了闭包,因为它们正在访问在自己的范围之外声明的数据。

http://docs.jquery.com/Types#Closures

【问题讨论】:

    标签: javascript closures


    【解决方案1】:

    首先,两个版本都使用闭包是正确的。

    第一个版本更简洁(在我看来)并且在现代 javascript 中更受欢迎。第一种样式的主要潜在缺点是您无法有效地将对象分配给构造函数的原型,如果您要创建大量相同的对象,这很有用(并且更有效)。

    第二种风格,我实际上从未在生产 Javascript 中见过。通常,你会用new 实例化create,而不是在create() 函数中返回this,如下所示:

    function create() {
      var counter = 0;
      this.increment = function() {
          counter++;
      }
      this.print = function() {
          console.log(counter);
      }
    }
    var c = new create();
    c.increment();
    c.print(); // 1
    

    【讨论】:

    • 就个人而言,这是我在尝试了无数技术之后更喜欢创建对象的方式。我认为这更清晰,更容易理解。但这就是我。 :)
    • 次要 nit:返回“this”相当于在使用 new 运算符时根本不显式返回任何内容。如果没有 new 运算符,“this”就是当前上下文(通常是全局的),这样你最终会破坏全局变量。
    【解决方案2】:

    通过声明变量计数器 关键字 var,它已经在本地了 作用于函数/类内部 定义。据我所知和可以 告诉,它不能从 从外面开始。我失踪了吗 来自数据可见性的东西 观点。

    并不是说counter 变量不能从函数外部访问,而是create 函数退出后incrementprint 函数可以访问它,这使得closures太有用了。

    【讨论】:

    • +1 用于回答其他人出于某种原因而回避的部分问题
    【解决方案3】:

    您应该将该示例与此 sn-p 进行比较

    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() 时,它会使用两种方法和一个实例变量(即:计数器)初始化新对象。 Javascript 本身没有封装,因此您可以访问 c.counter,如下所示:

    var c = new create();
    c.increment();
    c.counter = 0;
    c.print(); // 0
    

    通过使用闭包(如您的示例所示),计数器现在不再是实例字段,而是局部变量。一方面,您不能从 create() 函数外部访问。另一方面, increment() 和 print() 可以访问,因为它们关闭了封闭范围。所以我们最终得到了一个非常好的对象封装模拟。

    【讨论】:

      【解决方案4】:

      好吧,我不想就如何在 JavaScript 中创建对象而陷入宗教战争,因为有些人强烈认为这样做有对与错。

      但是,我想在您的第二组代码中指出一些不太有趣的东西 - 即,您正在为 this 关键字中包含的对象分配新属性这一事实 - 您是否意识到那个对象是?除非您使用如下实例化语法,否则它不是空对象:

      var c = new create();
      

      当您这样做时,构造函数主体内的 this 关键字被分配一个全新的对象,就好像主体中的第一行是这样的:

      this = {};
      

      但是,当您将 create() 作为函数调用时,正如您在该示例中所做的那样,您正在更改函数定义之外的范围(正如 cmets 中的 @seanmonster 所暗示的那样)。

      【讨论】:

      • 除非将函数分配给原型或其他对象,否则“this”实际上是全局对象(窗口)。
      • 嘻嘻,我的错 - 你评论的语法是如此微妙,我认为它说的是与它所说的相反的东西。对于那个很抱歉。我得戒掉这些奇怪的药物……
      • 感谢您的回答 - 虽然通常是正确的,但更准确地说,正如我在现在编辑的答案中所反映的那样,this 关键字可能不是全局对象,具体取决于函数的位置找到定义。如果他的“create”函数嵌套在一个完全有效的构造函数中,那么 this 关键字将不同于全局范围。所以,再次感谢您提出这个问题,因为这可能是一件棘手的事情......
      • 它更棘手。如果您在构造函数中确实有一个函数,则它不一定绑定到新对象。例如: var Toy = function() { this.type = 'ball'; var create = function() { this.prop = '我刚刚访问了窗口'; } 创造(); } 任何时候设置 var something = function() { },默认情况下“this”都是全局对象。仅当它是对象的属性时(var obj = { create: function() { //this == obj} })或 Toy.prototype.create = function() { //this == new Toy } )。如果不注意可能会很可怕。
      【解决方案5】:

      您的第二个示例仍然使用闭包,因为 increment 和 print 函数仍然作用于变量,否则超出范围 - 当您调用 c.increment() 时,create 函数已经退出。

      我喜欢第一个示例,因为它避免了“this”关键字,并且在 javascript 中“this”可能很棘手——它并不总是指它看起来应该的样子。

      【讨论】:

      • 我很确定在匿名函数中使用变量的行为意味着它将持续存在,即使函数已经退出并且通常该变量会被丢弃。
      【解决方案6】:

      Christian Heilmann 在the module pattern 上发表了一篇相当不错的文章,您所描述的这篇文章可能会帮助您理解它以及它为何有用。

      【讨论】:

        【解决方案7】:

        对于来自 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());
        

        【讨论】:

          【解决方案8】:
          MYAPP = (function(){
             var v1,v2;
             return {
              method1:function(){},
              method2:function(){}
             };
          })();
          

          我总是在我的应用程序中使用这样的闭包,就像这样,我自己定义的所有方法都在 MYAPP 命名空间中,v1 和 v2 只能由 MYAPP 中的方法访问。在我的应用程序中,我经常只写一个“app .js”文件,里面有我所有的js代码。我想你可以定义一个名为“registy”的方法来定义 MYAPP 中的私有变量,然后你可以在你的方法中使用它。当你想在 html 文件中添加额外的代码时,所有额外的变量和方法都应该由 registy 方法定义,就像 JQuery.extend 方法一样。我听说如果在 IE 浏览器中使用过多的闭包,很容易导致堆栈溢出。 (在我看来)

          【讨论】:

            【解决方案9】:

            在第二个示例中,当您在函数范围内调用 create() 时,this 是全局对象(当您调用“裸”函数时总是如此,而不将其用作构造函数或将其作为属性访问(例如“方法”调用))。在浏览器中,全局对象是window。因此,当您随后调用 create 时,它​​会创建新的闭包,但是您随后会将它们分配给与以前相同的全局对象,从而覆盖旧函数,这不是您想要的:

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

            【讨论】:

              猜你喜欢
              • 2011-07-25
              • 2012-01-28
              • 1970-01-01
              • 2013-02-26
              • 2012-09-24
              • 2011-07-30
              • 2013-06-01
              • 1970-01-01
              相关资源
              最近更新 更多