javascript 中的范围、词法环境和执行上下文

Scope, lexical environment and Execution Context in javascript

在使用脚本自动添加事件侦听器时,出现了这样一种情况:在函数内声明的变量 let activeModal; 在函数执行后仍然保持 "alive",但在另一个函数内创建的变量 let currentPlayTime; 在函数执行后不存在。正如预期的那样,使 let currentPlayTime; 变量成为全局变量显然有效。

我的主要问题是为什么变量 let activeModal; "survive" 在函数完成后?

第二个问题,我将如何避免使 let currentPlayTime; 全球化?

我怀疑它与作用域、词法环境或执行上下文有关,但到目前为止,我对这些主题的阅读让我有些困惑。 问候。

//THIS WORKS
function addModalHandlers()
  {
// Get the modal
let modalsCollection = document.getElementsByClassName("modal");
let btn = document.getElementsByClassName('stations-pointer');
let span = document.getElementsByClassName("close");
let modals = [];
let modalsContents = []

let activeModal; //<----RELEVANT POINT

console.log("modalsCollection");
console.log(modalsCollection);

//convert HTML Collection to array for later using Array.includes() method
for (let i=0;i<modalsCollection.length; i++)
{
  modals[i] = modalsCollection[i]
  // modals[i].style.opacity = "0";
}


// Attach event listeners to buttons to open the modals
for (let i=0;i<btn.length;i++)
{
btn[i].onclick = function() {
setTimeout(function (){
  modals[i].style.display = "block";
  console.log("TIMEOUT");
},1200);

activeModal = modals[i];
}
(...)
}
//THIS WORKS
let currentPlayTime;

function toggleAnimation()
  {

    console.log(path.style.transition);
    if (path.style.transition == 'none 0s ease 0s')
    {
      toggleIcon()
      console.log("currentPlayTime on play: "+currentPlayTime);
      wavesurfer.play(currentPlayTime);
      compatibleTransition(wavesurfer.getDuration() - currentPlayTime );
      path.style.strokeDashoffset = 0;
    }
    else
    {
    toggleIcon()
    path.style.strokeDashoffset = window.getComputedStyle(path).strokeDashoffset;
    path.style.transition = 'none';
    currentPlayTime = wavesurfer.getCurrentTime();
    console.log("currentPlayTime on pause: "+currentPlayTime);
    wavesurfer.pause();
    }
  }
//DOES NOT WORK
function toggleAnimation()
  {
  let currentPlayTime; //<---- SAME RATIONALE AS addModalHandlers() ABOVE BUT WORKS NOT

    console.log(path.style.transition);
    if (path.style.transition == 'none 0s ease 0s')
    {
      toggleIcon()
      console.log("currentPlayTime on play: "+currentPlayTime);
      wavesurfer.play(currentPlayTime);
      compatibleTransition(wavesurfer.getDuration() - currentPlayTime );
      path.style.strokeDashoffset = 0;
    }
    else
    {
    toggleIcon()
    path.style.strokeDashoffset = window.getComputedStyle(path).strokeDashoffset;
    path.style.transition = 'none';
    currentPlayTime = wavesurfer.getCurrentTime();
    console.log("currentPlayTime on pause: "+currentPlayTime);
    wavesurfer.pause();
    }
  }

当你这样做时

for (let i=0;i<btn.length;i++) {

  btn[i].onclick = function() {  
    setTimeout(function (){
      modals[i].style.display = "block";
      console.log("TIMEOUT");
     },1200
   );

   activeModal = modals[i];

  }

您基本上是在告诉编译器在每个按钮上附加一个事件处理程序。此外,无论何时调用此回调函数,编译器都要确保 activeModal 已更新。

现在,你给编译器制造了一个难题。它喜欢保持东西干净。只要有机会,它就会清除未使用的引用。现在,它不能在 activeModal 的情况下执行此操作,因为您已告诉它稍后在触发点击处理程序时更新该值。因此,它会保留它并且您可以使用它。


现在,在您的 toggleAnimation 函数中,currentPlayTime 不会在其父函数范围之外被访问,这与 activeModal 不同,后者由在父函数上执行的事件处理程序访问全球范围。因此,一旦执行了 toggleAnimation 函数,就可以清理 currentPlayTime.

如果你想避免使用全局变量,一种实现方法是创建一个函数 returns 另一个函数创建一个闭包以安全保存 currentPlayTime.

function toggleAnimation() {
  let currentPlayTime;

  return function doDomething() {
    if (path.style.transition == 'none 0s ease 0s') {
      toggleIcon()
      console.log("currentPlayTime on play: "+currentPlayTime);
      wavesurfer.play(currentPlayTime);
      compatibleTransition(wavesurfer.getDuration() - currentPlayTime );
      path.style.strokeDashoffset = 0;
    }
    else {
      toggleIcon()
      path.style.strokeDashoffset = window.getComputedStyle(path).strokeDashoffset;
      path.style.transition = 'none';
      currentPlayTime = wavesurfer.getCurrentTime();
      console.log("currentPlayTime on pause: "+currentPlayTime);
      wavesurfer.pause();
    }
  }
}

现在,你需要做

const variableName = toggleAnimation();

variableName();

所以这里发生的是, toggleAnimation returns 执行时需要 currentPlayTime 的函数。

这意味着包含返回函数的块将是其执行所必需的。因此,编译器将保留此闭包,以便无论何时调用它都可以执行。