如何在 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>
假设一个网页上可以存在多个滑块,并且每个滑块都需要自己的用户交互状态,例如触摸启动 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>