setTimeout小结

125 阅读3分钟

setTimeout from MDN

setTimeout(function | code, delay, arg1, arg2, ...)
  • function 是想要在到期时间 (delay毫秒) 之后执行的函数
  • code 类似eval,不推荐
  • delay 延迟的毫秒数 (一秒等于 1000 毫秒);如果省略该参数,delay 取默认值 0,意味着“马上”执行,或者尽快执行。不管是哪种情况,实际的延迟时间可能会比期待的 (delay 毫秒数) 值长
  • arg1, ..., argN 作为参数传递给function

this

let obj = {
  a: 1,
  name: "A,l",
  init: function () {
    console.log("this in init", this);
  },
  initArrow: () => {
    console.log("this in init", this);
  }
};

obj.init(); // Node中指向obj 浏览器中指向obj
setTimeout(obj.init, 1000); // Node中指向 Timeout  浏览器中指向window
setTimeout(obj.initArrow, 1000); // Node中指向{} 浏览器中指向window
setTimeout(obj.init.bind(obj), 1000) // Node中指向obj 浏览器中指向obj
setTimeout(obj.initArrow.bind(obj), 1000) // Node中指向{} 浏览器中指向window

由setTimeout()调用的代码运行在与所在函数完全分离的执行环境上。
这会导致,这些代码中包含的 this 关键字

  • 在非严格模式会指向 window (或全局) 对象
  • 严格模式下为 undefined,这和所期望的this的值是不一样的。

setTimeout中的箭头函数中的this依然是指向setTimeout外层context的this,详见 arrowFunc.js

有很多因素会导致 setTimeout 的回调函数执行比设定的预期值更久

在浏览器中,setTimeout()/setInterval() 的每调用一次定时器的最小间隔是 4ms,这通常是由于函数嵌套导致(嵌套层级达到一定深度),或者是由于已经执行的 setInterval 的回调函数阻塞导致的

面试题

经典for循环

主要考察setTimeout与闭包的结合

// setTimeout作为异步宏任务,会先被存入执行队列,等到for循环结束才执行,此时i=6;所以结果就是6个6
for(var i=0; i<=5; i++){
    setTimeout(function(){
        console.log(i);
    }, 1000);
}

要想打印 0 1 2 3 4 5,有三种方式

第一种 闭包

for(var i=0; i<=5; i++){
   (function(j) {
    setTimeout(function(){
      console.log(j);
    }, 1000);
   })(i);
}

第二种 使用ES6的let

for(let i=0; i<=5; i++){
  setTimeout(function(){
      console.log(i);
  }, 1000);
}

第三种 使用setTimeout的第三个参数

// setTimeout内部同样是用到了闭包传参给回调函数
for(var i=0; i<=5; i++){
    setTimeout(function(){
        console.log(i);
    }, 1000, i);
}

setTimeout与React函数组件

函数式组件捕获了渲染所使用的值

lazy1里面的setTimeout异步执行,获取的是点击lazy1时的number值

从number = 0开始;先点击lazy1,然后点击+按钮将number加到2,在点击完lazy1三秒后,number又变成了1;

因为点击lazy1时,number=0;此时的setTimeout只能获取到这次渲染的number值也就是0;点击+按钮的渲染不会被其捕获,所以等到setTImeout执行时,执行的是 setNumber(0+1)

CodeSandbox代码https://codesandbox.io/s/usestate-zhi-geng-xin-he-han-shu-geng-xin-6zxqld

export default function App() {
  const [clickCount, setClickCount] = useState(0);
  const [number, setNumber] = useState(0);

  const lazy1 = () => {
    // setClickCount(clickCount + 1);
    setTimeout(() => {
      // 获取点击按钮时的 state
      setNumber(number + 1);
    }, 3000);
  };

  const lazy2 = () => {
    // setClickCount(clickCount + 1);
    setTimeout(() => {
      // 每次执行时都会再去获取新的 state,而不是使用点击触发时的 state
      setNumber((number) => number + 1);
    }, 3000);
  };

  return (
    <div className="App">
      <p>
        数字:{number} 点击次数:{clickCount}
      </p>
      <button onClick={() => setNumber(number + 1)}>+</button>
      <br />
      <p>n次点击下面这个按钮,只更新一次</p>
      <button onClick={lazy1}>lazy1:只能获取点击按钮时候的状态</button>
      <br />
      <p>n次点击下面这个按钮,更新n次</p>
      <button onClick={lazy2}>
        lazy2:每次执行都会重新获取state, 所以获取的都是最新的state
      </button>
    </div>
  );
}