实例之间的JavaScript关闭变量共享

时间:2019-02-27 12:25:55

标签: javascript closures

这是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变量。如何获得预期的结果?我知道常见的解决方案是在每个Test方法中使用本地self方法,但是有没有其他方法可以做到这一点呢?有什么办法可以重新声明闭包变量吗?

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

1 个答案:

答案 0 :(得分:2)

在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几乎为零的情况下,我们创建了一个对象系统( almost 只是因为我们返回了一个对象文字,但是如果js像Perl或PHP那样具有真实的maps / hashhes / associative数组,那么我们可以使用零OOP来实现)

TLDR

那么我们如何得到您想要的?好吧,我们可以在js中使用称为 module pattern 的设计模式(不要与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());

这种编程风格的主要异议是您现在拥有getNametest的多个实例,因此它的内存效率不如使用原型的东西(这也适用于模块模式) )。但是有些人会认为这是一个很好的权衡。

TLDR 3

您实际上根本不需要别名this。您需要做的就是了解它的工作原理以及如何使用它(请阅读我对另一个问题的回答:How does the "this" keyword in Javascript act within an object literal?,我保证只要我还活着,就一直在更新答案)。

删除所有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));