如何在 JS 函数中创建变量闭包?

How to create closure over variables in a JS function?

假设一个网页上可以存在多个滑块,并且每个滑块都需要自己的用户交互状态,例如触摸启动 X 位置和当前是否正在处理滑动的布尔值..

我正在尝试重用一段处理滑动和鼠标拖动事件的代码,但每个滑块都应该有自己的值 initalX 和 isInSwipe:

   function slideInteractions(selector, callback) {
        return function() {
            let initialX = 0;
            let isInSwipe = false;
            document.querySelector(selector).addEventListener('mousedown', (ev) => {
                startInteraction(ev);
                ev.preventDefault();
            });
            document.querySelector(selector).addEventListener('touchstart', startInteraction);
            document.addEventListener('mouseup', (ev) => {
                endInteraction(ev, callback);
            });
            document.addEventListener('touchend', (ev) => {
                endInteraction(ev, callback);
            });
        };
    }

对于页面上的每个滑块,我会像这样使用它,传递幻灯片容器和回调以调用已接受的交互:

slideInteractions('.project-slides', moveProject)();

我认为,由于 initialX 和 isInSwipe 是在返回的函数中定义的,因此会在它们之上创建闭包,以便每次调用 slideInteractions() 时都会创建自己的这些变量副本,但它们似乎会消失作用域一旦函数 returns.

有没有办法解决这个问题,让变量在闭包中正常运行?

编辑

这些变量在 startInteraction()endInteraction() 中使用,但这些函数根本看不到这些变量:它们在那些函数中未声明,这是我感到困惑的地方,因为我假设这些函数可以访问“封闭”变量 no?

要在函数之间共享变量,它们必须在同一块范围内。因此,这意味着您必须在定义变量的同一块内定义 startInteraction 和 endInteraction 函数。

function slideInteractions(selector, callback) {
    return function() {

        let initialX = 0;
        let isInSwipe = false;

        function startInteraction(e,) {
          initialX = e.clientX;
        }
        function endInteraction(e, callback) { 
           console.log(initialX, e.clientX);
           if (callback) callback(initialX);
        }

        document.querySelector(selector).addEventListener('mousedown', (ev) => {
            startInteraction(ev);
            ev.preventDefault();
        });
        document.querySelector(selector).addEventListener('touchstart', startInteraction);
        document.addEventListener('mouseup', (ev) => {
            endInteraction(ev, callback);
        });
        document.addEventListener('touchend', (ev) => {
            endInteraction(ev, callback);
        });
    };
}

另一种选择是传递对象。函数可以在块范围之外。

function startInteraction(e, data) {
  data.initialX = e.clientX;
}
function endInteraction(e, data, callback) { 
   console.log(data.initialX, e.clientX);
   if (callback) callback(data);
}

function slideInteractions(selector, callback) {
    return function() {

        const data = {
          initialX: 0,
          isInSwipe: false,
        };

        document.querySelector(selector).addEventListener('mousedown', (ev) => {
            startInteraction(ev, data);
            ev.preventDefault();
        });
        document.querySelector(selector).addEventListener('touchstart', startInteraction);
        document.addEventListener('mouseup', (ev) => {
            endInteraction(ev, data, callback);
        });
        document.addEventListener('touchend', (ev) => {
            endInteraction(ev, data, callback);
        });
    };
}

更好的解决方案是制作 class

class ClickyThing {
  initialX = 0
  isInSwipe = false

  constructor(elem, callback) {
    this.elem = elem;
    this.callback = callback;
    this.bindEvents();
  }

  bindEvents() {
    this.elem.addEventListener('mousedown', (evt) => {
      evt.preventDefault();
      this.startInteraction(evt);
    });

    document.addEventListener('mouseup', (evt) => {
      this.endInteraction(evt);
    });
  }

  startInteraction(evt) {
    this.isInSwipe = true;
    this.initialX = evt.clientX;
  }

  endInteraction(evt) {
    if (!this.isInSwipe) return;
    console.log(this.initialX, evt.clientX);
    if (this.callback) this.callback(this.initialX, evt.clientX);
    this.isInSwipe = false;
  }

}

document.querySelectorAll(".test").forEach((elem) => {
  const clickyThing = new ClickyThing(elem, () => console.log('click'));
});
.test {
  width: 100px; height: 100px; margin: 10px; background-color: #CCC;
}
<div class="test"></div>
<div class="test"></div>
<div class="test"></div>