如何在每个调用堆栈中只调用一次函数?

How to invoke a function only once per call stack?

假设我有一个服务器,每当客户端的数据发生变化时,它就会向其客户端发送数据。客户端 class 看起来像这样:

function Client() {
    this.data = {};
    this.update = function (key, value) {
        this.data[key] = value;
        this.emitUpdate();
    };

    this.emitUpdate = function () {
        // tell server to send this client's data
    };
}

var myClient = new Client();

如果我只改变一件事:

myClient.update("name", "John");

服务器使用此客户端的新信息更新所有客户端。太好了。

但是...

如果我在我的应用程序的不同位置一次更改多个内容:

if (something === true) {
    myClient.update("something", true);
} else {
    myClient.update("something_else", true);
}

myClient.update("age", Date.now());

总会有两件事改变,emitUpdate()会被调用两次。服务器将发送两次数据,所有客户端都必须渲染两次。想象一下发生 100 次这样的更改......这将是一个很大的开销,考虑到服务器可以只发送一次更新,因为所有这些更改都在一个时间范围内发生。

如何让 emitUpdate() 每个调用堆栈只调用一次?我使用 underscore.js 并检查了 defer()throttle() 函数。问题是,我需要结合它们的效果。

var log = function () {
  console.log("now");
};

// logs "now" 10 times
for (let i = 0; i < 10; i++) {
  _.defer(log);
}

// logs "now" 10 times
var throttled = _.throttle(log, 0);
for (let i = 0; i < 10; i++) {
  throttled();
}

如果我使用 throttle() 和不同数量的 wait,例如 12,结果是不一致的 - 有时它被调用一次,有时被调用多次。

我需要这样的东西:

// logs "now" once
var magic = _.deferottle(log);
for (let i = 0; i < 10; i++) {
  magic();
}

有什么办法可以实现吗?

这应该适用于对 update() 方法的多个同步调用:

function Client() {
  this.data = {};
  this.update = function (key, value) {
    this.data[key] = value;
    if (!this.aboutToUpdate) {
      this.aboutToUpdate = setTimeout(() => {
        this.aboutToUpdate = false
        this.emitUpdate()
      })
    }
  };

  this.emitUpdate = function () {
    // tell server to send this client's data
    console.log('update sent', this.data)
  };
}

// This should only log 'update sent' once
var client = new Client()
for (var i = 0; i < 5; i++) {
  client.update(i, i)
}

实际上您不需要 throttle,只需将 defer 与一些布尔标志检查结合使用即可。

像这样:

this.update = function (key, value) {
    this.data[key] = value;
    if (this.isQueued) return;
    this.isQueued = true;
    _.defer(function () {
        this.isQueued = false;
        this.emitUpdate();
    }.bind(this));
};

我在洗澡的时候突然想到了一个解决办法:

var deferottle = function(func) {
  var called, args, ctx;

  return function() {
    args = arguments;
    ctx = this;

    if (!called) {
      called = true;
      setTimeout(function() {
        func.apply(ctx, args);
        called = false;
      });
    }
  };
};

var magic = deferottle(function(num) {
  console.log("num: ", num);
});

for (let i = 0; i < 10; i++) {
  magic(i);
}

事实证明 debounce() 等待 0 也可以使用:

var magic = _.debounce(function(num) {
  console.log("num: ", num);
}, 0);

for (let i = 0; i < 10; i++) {
  magic(i);
}
<script src="http://underscorejs.org/underscore-min.js"></script>