首页 > 解决方案 > JavaScript 闭包变量在实例之间共享

问题描述

这是 JavaScript 代码(或JSFiddle)。我想强制每个测试的实例使用它自己的self变量引用。我怎样才能做到这一点?

(function (w) {
  var self;

  w.Test = function (name) {
    // how to redeclare `self` here to not use it by reference? This doesn't help:
    // self = undefined;
    // delete self;

    self = this;
    self.name = name;
  };

  w.Test.prototype.getName = function () {
    return self.name;
  }

  w.Test.prototype.test = function () {
    console.debug(self.name);
    console.debug(self == this);
  }

})(window);

var a = new Test("a");
var b = new Test("b");

console.log(a.getName() + b.getName());

// expected: ab
// actual: bb

a.test();
b.test();

// expected: a > true > b > true
// actual: b > false > b > true

第二次调用覆盖self变量。我怎样才能得到预期的结果?我知道常见的解决方案是self在每个测试的方法中使用本地方法,但是有没有其他方法可以在没有这种重复的情况下做到这一点?有没有办法重新声明闭包变量?

我想使用self,而不是this因为我的大多数方法都有一些带有回调的异步函数调用,它有自己的上下文,并且在每个方法中我都需要单独的self变量。

标签: javascriptclosures

解决方案


在 javascript 中,闭包是对全局变量概念的概括——它是一个在多个范围内可见的变量。全局变量实际上可以看作是一个闭包本身——在全局范围内。调用全局变量闭包没有矛盾或混淆(虽然某些引擎可能以不同的方式实现它们,但实际上它们可能使用完全相同的机制来实现)。

为了显示:

var a;
var b;

(function () {
  var shared; // a shared variable behaving like a sub-global variable

  a = function (x) {shared = x}
  b = function () {return shared}

})()

function c {return shared};

a(100);
b(); // returns 100
c(); // errors out because it is not part of the shared scope.

函数创建作用域的实例,不幸的是,实例的技术名称被称为闭包,因为闭包这个词也指的是创建这种事物的底层算法,并且非正式地指的是闭包捕获的变量(它的技术名称是封闭变量,但人们通常只说“闭包”)。另一方面,OOP 对每个概念都有完全不同的词——类、实例、实例化、属性和方法。

范围的副本

由于函数会创建范围(闭包)的实例,因此您可以通过多次调用函数来拥有多个范围的实例:

function makeShared () {
  var shared // this thing will have more than one copy/instance in RAM

  return {
    a: function (x) {shared = x},
    b: function () {return shared}
  }
}

var x = makeShared();
var y = makeShared();

x.a(100);
y.a(200);
x.b();     // returns 100
y.b();     // returns 200

如您所见,理论上闭包和对象在概念上是相似的。事实上,有一篇论文宣称它们是完全相同的东西。OOP 几乎为零,我们创建了一个对象系统(几乎只是因为我们返回一个对象字面量,但如果 js 像 Perl 或 PHP 一样具有真正的映射/哈希/关联数组,我们可以用零 OOP 来实现)。

TLDR

那么我们怎样才能得到你想要的呢?好吧,我们可以在 js 中使用一种称为模块模式的设计模式(不要与 js 模块混淆)。实际上就是makeShared我上面说明的代码——我们放弃了 js 中的 OOP 特性,转而使用函数式编程来发明我们自己的 OOP 系统。

您的代码在模块模式中看起来像这样:

function newTest (name) { // in case you like the word "new"

  var self = {};
  self.name = name;

  self.getName = function () {
    return self.name;
  }

  self.test = function () {
    console.debug(self.name);
    console.debug(self == this);
  }

  return self;
};

var a = newTest("a"); // note: newTest instead of new Test
var b = newTest("b");

console.log(a.getName() + b.getName());

在 javascript 复兴的日子里,当人们开始认真对待它作为一种编程语言时(大约在它首次创建后 10 年),模块模式成为该语言爱好者的最爱。不像原型的东西,它看起来像一个类,整个定义都包含在一对大括号中{}。您甚至可以推出自己的继承系统(使用从寄生继承到原型克隆的方法)。但这破坏了instanceof. 对于大多数人来说,这是一个很好的权衡,因为大多数人认为需要了解其自身类型的代码是一种代码气味。但是图书馆经常需要做这样的事情。

TLDR 2

我们可以将模块模式与构造函数结合起来,几乎是两全其美。我们可以这样做:

function Test (name) {
  var self = this;
  self.name = name;

  self.getName = function () {
    return self.name;
  }

  self.test = function () {
    console.debug(self.name);
    console.debug(self == this);
  }

  // constructors don't need to return anything
};

var a = new Test("a"); // note: new
var b = new Test("b");

console.log(a.getName() + b.getName());

对这种编程风格的主要反对意见是你现在有多个实例,getName因此test它不像使用原型东西那样内存效率高(这也适用于模块模式)。但有些人会认为这是一个很好的权衡。

TLDR 3

您实际上根本不需要别名this。您需要做的就是了解它是如何工作的并与之共存(阅读我对另一个问题的回答:Javascript 中的“this”关键字如何在对象文字中起作用?,我保证只要答案保持更新我还活着)。

删除所有self东西,然后使用this. 对于异步回调,您只需要知道如何正确调用您的方法:

var a = new Test("a");

// either:

someAsyncThing(function() {
  a.getName();
});

// or:

anotherAsyncThing(a.getName.bind(a));

// with promises:

promiseSomething.then(a.getName.bind(a));

推荐阅读