3

我正在关注一个教程,该教程显示了在 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>

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

4

4 回答 4

6

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

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

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

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

JavaScript 的隐藏特性?

请解释在循环中使用 JavaScript 闭包

或者你可以只用谷歌搜索“javascriptclosure”这个词来探索这个主题。


补充回答。

至于为什么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方便地将调用该函数的对象称为它的方法,这就是它工作的原因,即使您显然没有使用所谓的正确构造函数语法。

于 2010-12-10T16:21:13.340 回答
2

obj在函数内部起作用的原因showLabel是因为 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

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

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

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

于 2010-12-10T15:56:56.347 回答
1
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();
于 2010-12-10T16:04:26.317 回答
0

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

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

var foo = 新创建地址(...

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

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

于 2010-12-10T16:02:13.743 回答