【问题标题】:Javascript factory pattern variable scopingJavascript 工厂模式变量作用域
【发布时间】:2010-12-10 15:51:36
【问题描述】:

我正在关注一个教程,该教程展示了在 javascript 中创建对象的工厂模式。下面的代码让我很难理解它为什么起作用。

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>6-2.htm</title>
</head>
<body>
<script type="text/javascript">
function createAddress(street, city, state, zip) {
  var obj = new Object();
  obj.street = street;
  obj.city = city;
  obj.state = state;
  obj.zip = zip;
  obj.showLabel = function() {
    //alert(this.street + "\n" + this.city + ", " + this.state + " " + this.zip);
    //var obj;
    alert(obj.street + "\n" + obj.city + ", " + obj.state + " " + obj.zip);
  };
  return obj;
};

var JohnAddr = createAddress("12 A St.", "Johnson City", "TN", 37614);
var JoeAddr = createAddress("10061 Bristol Park", "Pensacola", "FL", 32503);

JohnAddr.showLabel();
JoeAddr.showLabel();
</script>
</body>
</html>

第一个注释行对我来说似乎很合适(在 showLabel 函数中使用 this 关键字)。我不确定在它的位置使用 obj 是如何工作的。 obj 必须在某处引用全局变量,因为在该函数运行时没有定义 obj,对吗?因为我制作了 2 个对象,所以在这种情况下,两者都能正常显示,这不仅仅是运气,obj 内容的旧值也被正确存储和引用。但如何?如果我取消注释第二条评论,那么它就会中断,我明白为什么,现在我明确告诉 js 我在谈论一个局部变量并且没有。

【问题讨论】:

    标签: javascript factory-pattern scoping


    【解决方案1】:

    欢迎来到闭包的世界。你感觉你的感觉是对的,就好像它是一个全球性的但不是完全全球性的。这就是闭包的行为方式。

    基本上在 javascript 中,当函数返回时,并非所有局部变量都必须像在 Java 或 C 中那样被垃圾收集/释放。如果有对该变量的引用,则该变量在定义它的函数范围内仍然存在。

    从技术上讲,机制是不同的,有些人试图以这种方式解释它,最终让很多其他人感到困惑。对我来说,闭包是一种“私有”全局变量,就像全局变量一样,它们是跨函数共享的,但它们没有在全局范围内声明。就像你描述的遇到这个功能时的感受。

    以下是我在 stackoverflow 上与 javascript 闭包相关的其他一些答案,我认为这些答案值得一读:

    Hidden Features of JavaScript?

    Please explain the use of JavaScript closures in loops

    或者你也可以用谷歌搜索“javascript 闭包”这个词来探索这个主题。


    补充答案。

    至于为什么this 在你的代码中工作的解释(而不是*cough* 试图更正你的代码以便this 可以工作,即使它在未更正的版本中工作*cough* ;-):

    Javascript 有后期绑定。很晚,很晚。 this 不仅在编译时未绑定,甚至在运行时也未绑定。它在执行时被绑定——也就是说,在调用一个函数之前,你无法知道 this 真正指向什么。调用者基本上可以决定this 的值是什么,而不是使用this 的函数。

    一些时髦的 javascript 后期绑定技巧:

    function foo () {
        alert(this.bar);
    }
    
    var bar = "hello";
    var obj = {
        foo : foo,
        bar : "hi"
    };
    var second_obj = {
        bar : "bye"
    };
    
    foo(); // says hello, 'this' refers to the global object and this.bar
           // refers to the global variable bar.
    
    obj.foo(); // says hi, 'this' refers to the first thing before the last dot
               // ie, the object foo belongs to
    
    // now this is where it gets weird, an object can borrow/steal methods of
    // another object and have its 'this' re-bound to it
    
    obj.foo.call(second_obj); // says bye because call and apply allows 'this'
                              // to be re-bound to a foreign object. In this case
                              // this refers to second_obj
    

    在您的代码中,this 方便地将调用该函数的对象作为它的方法,这就是为什么即使您显然 使用所谓的 正确 构造函数它也能工作的原因语法。

    【讨论】:

    • 哇,这与我一直相信的完全相反(但仍然很有趣)。我真的很感谢你的意见!我认为对我帮助最大的是,如果我看到括号(传入变量),它的行为会如我所料,但是当我看不到它们时,我应该仔细看看。
    【解决方案2】:

    objshowLabel 函数中起作用的原因是因为 obj 是一个局部变量。每次调用 create address 时都会声明该函数。在 JavaScript 中,我们称之为闭包。

    一般来说,原型对象的创建优于这种工厂模式。

    现在看看这个原型示例:

    var Address = function(street, city, state, zip){
        this.street = street;
        this.city = city;
        this.state = state;
        this.zip= zip;
    };
    
    Address.prototype.showLabel = function(){
        alert(this.street + "\n" + this.city + ", " + this.state + " " + this.zip);
    }
    

    现在当我使用新关键字创建一个新地址时:

    // create new address
    var address = new Address('1', '2', '3', '4');
    address.showLabel(); // alert
    

    代码的行为与您预期的完全一样。但是如果我不使用new关键字this在构造函数内部实际上是window对象。

    // create new address
    var address = Address('1', '2', '3', '4'); // address == undefined
    window.showLabel(); // address was added to window
    

    我希望这能把事情弄清楚一点。

    【讨论】:

    • 原型模式更优雅,我同意。我来自学术 POV,我想了解为什么工厂模式有效。谢谢!
    【解决方案3】:
    function Address(street, city, state, zip) {
    
      this.street = street;
      this.city = city;
      this.state = state;
      this.zip = zip;
      this.showLabel = function() {
        //alert(this.street + "\n" + this.city + ", " + this.state + " " + this.zip);
        //var obj;
        alert(this.street + "\n" + this.city + ", " + this.state + " " + this.zip);
      };
    };
    
    var JohnAddr = new Address(...);
    JohnAddr.showLabel();
    

    【讨论】:

    • +1 没有回答他关于范围的问题,但这是一个不错的优雅解决方案。
    【解决方案4】:

    这是 js 怪异的一部分,showLabel 是一个闭包,并且可以访问 obj,因为它在创建时就在作用域内 - 每次调用 createAddress 时都会创建一个新的闭包。

    要以您期望的方式使用“this”,您需要使用 new 运算符:

    var foo = new createAddress(...

    并将成员变量分配给“this”。

    在这种不使用 new 的情况下,'this' 是全局对象。

    【讨论】:

    • 好的,我可以接受,但是为什么使用第一个注释行有效? (我的意思是没有其他两行)。如果 this.street 真的会分配 window.street,那么为什么对 showLabel 的第二次调用会返回不同的值,并且不会由于覆盖而丢失旧值?
    • 我回答我的问题是否正确:this 是一个特殊变量,尽管已关闭,但它仍将引用该函数所属的对象。还是我完全不明白?
    • @Ankur:看我的回答。总之你说的是对的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-21
    • 1970-01-01
    • 2011-01-19
    • 2012-02-02
    • 2018-05-23
    相关资源
    最近更新 更多