最近在复习JS基础,回新手村整理下笔记
回想当初看书的过程,有两位朋友曝光度极高
那就是setTimeout
和setInterval
html
他们的一些迷惑行为,初看实在让人摸不着头脑,但其实背后暴露出了JS的几大特性
若是能全盘理解,也就能基本掌握JS的一些原理了node
setInterval
,是每隔一段时间执行一次函数,而setTimeout
则是一段时间后进行,他们的用法基本同样,因此下面也就只讲解setTimeout
了chrome
setTimeout(function (a,b) { console.log(a+b) }, 2000, 1, 2)
promise
若是不设置浏览器会自动配置时间,在IE,FireFox中,第一次配可能给个很大的数字,100ms上下,日后会缩小到最小时间间隔,Safari,chrome,opera则多为10ms上下。浏览器
id
能够经过clearTimeout(id)
来清除这个定时器
或者也可使用setInterval
的清除方法clearInterval()
只不过从语义上来讲不推荐bash
虽然setTimeout
能够定时执行函数,但实际上它的执行时间不是精确的 这就要说到它的原理了
咱们先看一个极端的例子,把第二个参数设置为0闭包
setTimeout(function () {
console.log('1')
}, 0)
console.log('2')
//2 1
复制代码
虽然设置为0了,但也不是当即执行的
这个API是浏览器提供的,因此浏览器处理后会将setTimeout
要执行的匿名函数添加到异步队列
须要等待到函数调用栈清空以后,即全部可执行代码执行完毕以后,才会开始执行执行这个异步队列,而且是先进先出
而setTimeout设定的延迟时间,并不是相对于setTimeout执行这一刻,而是相对于其余代码执行完毕这一刻。app
promise
setTimeout
和promise
都是异步的,按照队列先进先出的顺序来讲
若是给setTimeout
设置为0,同时放置在promise
以前,那应该执行完同步代码以后就执行setTimeout
的函数异步
setTimeout(function () {
console.log('setTimeout1');
}, 0);
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log('then1')
})
console.log('script end')
复制代码
但实际上的输出结果是async
// script end
// then1
// setTimeout1
复制代码
.then()
比setTimeout
优先执行了
那再有个async
函数的话,执行顺序又是什么呢
若是不能坚决地回答,那说明咱们以前的理解必定还差了点东西
异步队列还分为Task(宏任务)队列和MicroTask(微任务)队列
在最新标准中,它们被分别称为task与jobs。
MicroTask会优先于Task执行。
同时,Javascript引擎在执行Microtask队列的时候,若是期间又加入了新的Microtask,则该Microtask会加入到以前的Microtask队列的尾部,保证Microtask先于Task队列执行。
- 先在执行栈中执行整个script。
- 遇到微任务和宏任务,分别添加到微任务队列和宏任务队列中去。
- 当前宏任务执行完毕,当即执行微任务队列中的任务
- 当前微任务队列中的任务执行完毕,检查渲染,GUI线程接管渲染。
- 继续执行下一个宏任务从事件队列中取。
因此在咱们写下setTimeout(fn,0)的时候他并非在当时当即执行,是从下一个Event loop开始执行,便是等当前全部脚本执行完再运行,就是"尽量早"。
引用自https://juejin.im/post/5b93829de51d450e7579b171
若是想挑战一下,能够看一下这道题目
输出结果
async/await
其实就是
promise
的语法糖
async function
声明将定义一个返回
AsyncFunction
对象的异步函数。
async
函数时,会返回一个
Promise
对象。
await
以后的函数语句至关于被包裹在
.then()
里面,因此被推动了微任务队列
注意:
上面的测试结果是谷歌浏览器73
版本以后输出的结果
在谷歌浏览器73
版本之前,以及node
中,promise
的优先级都要大于这个await
给出的回调函数,因此即使在任务队列中await
的回调是先进入的,也要在promise
的.then()
以后执行
也就是说async1 end
和then3
的顺序会颠倒
不过在73版本以后,为了不await
的执行须要至少3次tick
,性能比较慢,因此 使用对PromiseResolve
的调用来更改await
的语义,以减小在公共awaitPromise
状况下的转换次数。
若是传递给await
的值已是一个Promise
,那么这种优化避免了再次建立Promise
包装器,在这种状况下,咱们从最少三个microtick
到只有一个microtick
因此上图async1 end
会在then3
前面
var x=1
function hhh () {
var x=2
setInterval(function() {
console.log(x)
console.log(this.x)},1000)}
// 2 1
复制代码
this
指针的指向是最使人头疼的,四种绑定很是反直觉
函数中的this
指向的是执行上下文,而这个例子中匿名函数最终执行的环境就是浏览器,因此this.x
就是window.x
关于执行上下文
当浏览器加载script的时候,默认直接进入Global Execution >Context(全局上下文),将全局上下文入栈。若是在代码中调用了函数,则会建立Function Execution Context(函数上下文)并压入调用栈内,变成当前的执行环境上下文。当执行完该函数,该函数的执行上下文便从调用栈弹出返回到上一个执行上下文。 能够看着这个图感觉一下
ES6
的箭头函数必定程度上解决了这个问题,
this
指向的是声明时的上下文
还有相似这样的例子
function User(login) {
this.login = login;
this.sayHi = function() {
console.log(this.login);
}
}
var user = new User('John');
setTimeout(user.sayHi, 1000);
// undefined
复制代码
能够这样调用来解决问题
setTimeout(function() {
user.sayHi();
}, 1000);
复制代码
或者利用bind
进行绑定
也能够用call
或者apply
方法,可是会致使函数当即执行,失去延时效果
setTimeout(user.sayHi.bind(user), 1000);
复制代码
var x=1
function hhh () {
var x=2
setTimeout(function() {
console.log(x)
1000)}
// 2
复制代码
由于在调用setTimeout时发生了闭包
而匿名函数在执行时虽然已经不在hhh函数环境里了,但被定义的时候被告知:执行的时候你去调用hhh函数的x,已经绑定给你了
注意,在定义时只是进行绑定,并无真正传参\
因此下面会发生下面这个老生常谈的问题
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i)
}, i * 1000)
}
//输出5个5,且每隔一秒一次
复制代码
上面这段话,咱们能够把它翻译一下
var i = 5;
function timer() {
console.log(i);
}
setTimeout( timer, 1 * 1000 );
setTimeout( timer, 2 * 1000 );
setTimeout( timer, 3 * 1000 );
setTimeout( timer, 4 * 1000 );
setTimeout( timer, 5 * 1000 );
复制代码
因此一秒一次,以及输出都是5
由于在定义匿名函数的时候,使用了i值来设置时间
可是参数只是进行了绑定,真正执行的时候才会取到那个值,而此时i已经变成了5
解决办法有三种:
方法蛮多,你们应该都会用,就不赘述了
但其实原理都同样,就是不把匿名函数的参数绑定到公用的i值上去,而是每次循环时,将i值保存在一个闭包中,当匿名函数执行时,则访问对应闭包保存的i值便可
setTimeout和setInterval其实并不推荐被大量使用
尤为是setInterval,可能会出现间隔被跳过的问题,这个能够参考这篇文章 www.cnblogs.com/xiaohuochai…
但经过对他们进行研究,能够以小见大地理解JS运行机制 原理部分若是有写的不对的,欢迎指正