异步,就是非同步....
这节内容可能会有点枯燥,但是却是 JavaScript 中非常重要的概念,非常有必要去学习。
$.ajax({
url: "www.xx.com/api",
async: false, // true
success: function(result) {
console.log(result);
},
});
// 异步批量更新DOM(vue-nextTick)
// <div id="app">{{num}}</div>
new Vue({
el: "#app",
data: {
num: 0,
},
mounted() {
let dom = document.getElementById("app");
while (this.num !== 100) {
this.num++;
}
console.log("Vue num=" + this.num, "DOM num=" + dom.innerHTML);
// Vue num=100,DOM num=0
// nextTick or setTimeout
},
});
原因:单线程(一个时间点,只做一件事),浏览器的 JS 引擎是单线程导致的。
单线程是指在 JS 引擎中负责解释和执行 IavaScript 代码的线程只有一个,不妨叫它主线程。
所谓单线程,就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成再执行后面一个任务。
先看看一下浏览器内核的线程图:
其中,渲染线程和 JS 线程互斥。
假设有两个函数,一个修改一个删除,同时操作一个 DOM 节点,假如有多个线程的话,两个线程一起执行,肯定就死锁了,就会有问题。
为什么 JS 要设计为单线程,因为浏览器的特殊环境。
单线程的优缺点:
这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段 Javascript 代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。
常见的堵塞(死循环):
while (true) {}
JS 在设计之初就以运行在浏览器中的脚本语言,所以也不想搞得这么复杂,就设计成了单线程,也就是,一个时间点,只能做一件事。
为了解决单线程堵塞这个缺点:产生了异步。
拿吃泡面举例:
看电视就是异步操作,热水壶响,就是回调函数。
JS 中大多的代码都是同步执行的,只有极个别的函数是异步执行的,异步执行的代码,则需要异步编程。
setTimeout(() => {
console.log("log2");
}, 0);
console.log("log1");
// ?? log1 log2
异步代码的特点:不是立即执行,而是需要等待,在未来的某一个时间点执行。
同步代码 | 异步代码 |
---|---|
<script> 代码 | 网络请求(Ajax) |
I/O 操作 | 定时器(setTimeout、setInterval) |
渲染操作 | Promise(then) |
async/await |
异步代码最常见的写法就是使用回调函数。
// 注意到click方法中是一个函数而不是一个变量
// 它就是回调函数
$("#btn_1").click(function() {
alert("Btn 1 Clicked");
});
// 或者
function click() {
// 它就是回调函数
alert("Btn 1 Clicked");
}
$("#btn_1").click(click);
回调函数的缺点也很明显,容易产生回调地狱:
function getOneNews() {
$.ajax({
url: topicsUrl,
success: function(res) {
let id = res.data[0].id;
$.ajax({
url: topicOneUrl + id,
success: function(ress) {
console.log(ress);
render(ress.data);
},
});
},
});
}
function getOneNews() {
axios
.get(topicsUrl)
.then(function(response) {
let id = response.data.data[0].id;
return axios.get(topicOneUrl + id);
})
.then((res) => {
render(res.data.data);
})
.catch(function(error) {
console.log(error);
});
}
async function getOneNews() {
let listData = await axios.get(topicsUrl);
let id = listData.data.data[0].id;
let data = await axios.get(topicOneUrl + id);
render(data.data.data);
}
预览地址:http://jsrun.net/s43Kp/embedded/all/light
如果多个异步代码同时存在,那么执行顺序应该是怎样的?那个先执行、那个后执行了?
异步代码的划分,异步代码分宏任务和微任务。
宏任务(不着急) | 微任务(着急) |
---|---|
<script> 整体代码 | Promise |
setTimeout/setInterval |
执行顺序:
<script>
(宏任务)重复从宏任务和微任务队列里拿出任务去执行。
因为浏览器设计的原因,JS 线程和渲染线程互斥,所以 JS 线程被设计成了单线程。
因为单线程执行一些操作(如网络请求)时有堵塞的问题,所有产生了异步。
因为有了异步,所以产生了异步编程,从而有了回调函数。
因为回调函数写多了会产生回调地狱,所有又有了解决回调地狱的 Promise 写法
自 ES7 标准后有了比 Promise 更加优雅的写法 ———— async/await 写法,也是异步编程的最终解决方法。
因为 JS 的代码分为同步和异步代码,同步代码的执行顺序不必多说,自上而下的执行。
但是如果有多个异步的代码,他的执行顺序又是怎么的呢??
为了解决多个异步代码的执行顺序问了,有了事件循环(EventLoop),将异步任务区分为宏任务、微任务,依据规则依次执行。
至此 完!
console.log("script start");
setTimeout(function() {
console.log("timeout1");
}, 10);
new Promise((resolve) => {
console.log("promise1");
resolve();
setTimeout(() => console.log("timeout2"), 10);
}).then(function() {
console.log("then1");
});
console.log("script end");
写出 log 的输出结果,并说出理由。
来自九旬的原创文章
前言 在制作顶部菜单的时候,都会要求制作弹出的二级菜单,早先的做法是用jQuery...
在HTML5中,我们可以使用drawImage方法在canvas上进行画图操作,其基本代码如下...
在CSS中,根据元素显示模式的不同元素标签被分为了两类:行内元素(inline-level...
为表达全国各族人民对抗击新冠肺炎疫情斗争牺牲烈士和逝世通报的深切哀悼,国务...
【内容】: 1.利用background-image 渐变样式 2.可以利用scale缩放 3.给伪元素设...
打开纯文本代码链接时,自动高亮代码,支持191种语言,97个主题 特性 支持191种...
微信搜索【 脑子进煎鱼了 】关注这一只爆肝煎鱼。本文 GitHub github.com/eddycj...
本文是介绍 RecyclerView 入门基础 系列文章 的第四篇。如果您已经对创建 Recycl...
前言: 不知多久能学会 Elisp 上一章: 动态模块 从本章开始,进入这份 Elisp 教...
简介 处理大量并发是 Go 语言的一大优势。语言内置了方便的并发语法,可以非常方...