单线程和异步
- 单线程:一次只能做一件事情,js就是单线程的。
- 异步:为了让单线程不阻塞,就出现了异步。
- js是单线程的,但是遇到等待(网络请求/定时任务)的时候不能卡住,此时就需要异步。
- 异步通常是基于回调函数的形式,当然还有其他的方法实现异步,比如Promise
- JS是单线程语言,只能同时做一件事。
- 浏览器和nodejs已支持JS启动进程,如Web Worker
- JS和DOM渲染共用同一个线程,因为JS可修改DOM结构
异步与同步
- 何时需要异步:
- 同步异步的区别就是会不会阻塞后面程序的运行,那么我们需要后面程序不被阻塞的时候就要使用异步。
- 在可能发生等待的时候,等待过程中不能像alert一样阻塞程序运行,因此,所以的“等待的情况”都需要异步。
- 异步:
- 例子: setTimeout是异步,依次打印100 300 200,异步是不会阻塞后面程序的进行的。
- 注意:就算setTimeout没有设置时间,但执行栈中还是先执行同步任务,setTimeout会被拿出去放在任务队列中,等同步任务执行完毕才执行他,这和他需不需要等待没关系。
- 执行第一行,打印100
- 执行setTimeout后,传入setTimeout的函数会被暂存到任务队列,不会立即执行(单线程的特点,不能同时干两件事)
- 执行最后一行,打印300
- 待所有程序执行完,处于空闲状态时,会立马看有没有在任务队列暂存起来的要执行。
- 发现暂存起来的setTimeout中的函数无需等待时间,就立即来过来执行
- 例子: setTimeout是异步,依次打印100 300 200,异步是不会阻塞后面程序的进行的。
- 同步:
- 例子: alert是同步,打印100 200后需要等待用户点击确认,如不点击将一直卡在200,永远不会打印300。同步是会阻塞后面程序的进行。
前端使用异步的场景
- 定时任务:“setTimeout()与setInterval()”
- 网络请求:ajax请求,动态
<img>
加载(即src赋值的过程是异步的) - 事件绑定:事件函数绑定以后并不会等触发事件函数才继续执行程序,程序照常执行,等需要触发的时候才会执行事件绑定函数。
ajax请求的例子
可参考ajax请求完整笔记
打印“start”、“end”,然后等./data1.json
中的数据请求成功后执行函数-打印获取到的数据data1
。(关于$.get()
可参考笔记)
动态<img>
加载的例子
关于createElement()
可以参考笔记“JS中修改/创建/移动/删除 HTML DOM元素”onload事件
就是callback的一种形式
执行顺序:
打印“start”=》创建一个图片元素img(可参考笔记)=》给img的src赋值,src 属性值更新时浏览器会自动加载并显示出新图像。(可参考笔记) =》图片加载过程中打印“end” =》 等img加载完成才会执行onload绑定的函数,打印loaded
(可参考笔记)
很明显src赋值的过程(图片动态加载)是异步的,不会阻塞end的打印
事件绑定的例子
addEventListener()相关笔记
或者通过object.onclick=function(){SomeJavaScriptCode};
绑定onclick事件
也是一样的。
可用作JS异步编程的方法
- 回调函数,这是异步编程最基本的方法。上面提到的定时器、ajax请求、图片的动态加载、事件绑定 实际上都是回调函数的形式。
- 例:假定有两个函数f1和f2,后者等待前者的执行结果,如果f1是一个很耗时的任务,可以考虑改写f1,把f2写成f1的回调函数。【此时f2是异步的,她耗费的时间并不影响同步任务的执行】
1
2
3
4
5
6
7
8
9
10
11
12
13function f2() {
console.log("f2");
}
console.log("start");
function f1(callback) {
console.log("f1");
setTimeout(function () {
callback();
}, 1000);
}
f1(f2);
console.log("end");
// start f1 end f2
- 例:假定有两个函数f1和f2,后者等待前者的执行结果,如果f1是一个很耗时的任务,可以考虑改写f1,把f2写成f1的回调函数。【此时f2是异步的,她耗费的时间并不影响同步任务的执行】
- Promises对象,Promises 对象是CommonJS 工作组提出的一种规范,目的是为异步编程提供统一接口。
- 简单说,它的思想是,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。回调函数(回调地狱)变成了链式写法,程序的流程可以看得很清楚,而且有一整套的配套方法,可以实现许多强大的功能。
- async,async函数返回的是一个 Promise 对象,可以使用 then 方法添加回调函数,async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数。当函数执行的时候,一旦遇到await就会先执行await的相关异步操作,等到异步操作完成,再接着执行函数体内后面的语句(包括同步任务也先等着await执行)。
- 比起Promise,await则不需要then,
- 事件监听,任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
- 例:为f1绑定一个事件,当f1发生done事件,就执行f2。
- 发布/订阅,与”事件监听”类似。
- 我们假定,存在一个”信号中心”,某个任务执行完成,就向信号中心”发布”(publish)一个信号,其他任务可以向信号中心”订阅”(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做”发布/订阅模式”(publish-subscribe pattern),又称”观察者模式”(observer pattern)。
- 这种方法的性质与”事件监听”类似,但是明显优于后者。因为我们可以通过查看”消息中心”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
Promise
- Promise的出现解决了回调地狱的问题
- 层层嵌套的方式不符合我们的思维顺序
事件循环(JS的执行机制)
可参考笔记从输入url到页面显示都经历了什么
注意:异步任务被挂起时会由浏览器的引擎去执行他们,而同步任务则是由JS的引擎去执行。
相关题目
同步和异步的区别,分别举例子
- 异步是基于JS是单线程语言而出现的
- 同步任务会阻塞代码执行,而异步不会
- alert是同步,setTimeout是异步
手写用Promise加载一张图片
1 | // 加载一张图片 |
Promise加载2张图片(解决回调地域)
1 | // 加载2张图片,即使用Promise解决回调地域 |
关于setTimeout的笔试题
连续打印1 3 5 2
一秒后打印4
1 | console.log(1); |
注意所有的同步任务在主线程上执行,形成一个执行栈。异步任务有了 运行结果 才会在任务队列中放置一个事件。脚本运行时先依次运行执行栈中的所有同步任务,然后会从任务队列里提取事件,选择需要首先执行的异步任务然后执行。
4先有结果,所以4先出现在任务队列了
前端使用异步的场景有哪些
- 定时任务:setTimeout,setInverval(可参考笔记“setTimeout()与setInterval()”)
- 网络请求:ajax请求,动态
<img>
加载 - 事件绑定:事件函数绑定以后并不会等触发事件函数才继续执行程序,程序照常执行,等需要触发的时候才会执行事件绑定函数。
实现异步编程的方法
- 回调函数
- Promise
- 事件绑定
- 监听/订阅
Date
Date是日期的构造函数
可参考笔记Date对象
Math.random()
- Math的其他方法可参考笔记Math对象
- 在前端,Math.random()一般用于清除缓存
在项目中,有时改完js文件之后怎么也不能生效,只有清除浏览器缓存或者CTRL+F5强制刷新之后才能出现最新的版本,这样调试起来十分的不方便,此时可以在请求地址之后加上:
1 | "?ran=" + Math.random(); //当然这里参数 ran可以任意取了 |
采用随机数的方式,使每一次的请求都是一个新请求,从而防止浏览器从缓存中读取旧版本。
注意:因为Math.random() 只能在Javascript 下起作用,故只能通过Javascript的调用才可以。
除了加入随机数还可以加入当前时间:
1 | "?time=" + new Date().getTime(); |
同样可以使每次请求的地址都是不一样的,从而防止浏览器使用缓存。
Date、Math相关题目
Date、Math、数组API、对象API 的相关题目。
数组API、对象API可以理解为数组、对象他们自带的那些方法。
获取2017-06-10格式的日期
- 1-18 函数 formatDate 接受一个Date对象,返回一个格式化后的日期
- 2-4 为防止报错,如果没有传入参数,就新建一个Date对象作为参数dt
- 5
getFullYear()
getFullYear()获取年份
获取年份 - 6
getMonth()
获取月份,由于获取到的是0-11,所以需要+1 - 7
getDate()
获取日期 - 8-15 由于我们需要格式化的日期是两位数的,所以当
month<10
的时候我们需要在前面添加0。日期也是同理。- 这里涉及到之前讲的字符串拼接,字符串
"0"
和数字month拼接后相当于**强制类型转换**,month将成为字符串。
- 这里涉及到之前讲的字符串拼接,字符串
- 17 通过
-
连接,将数字拼接字符串 得到 字符串
- 19 新建一个Date对象dt
- 20 将dt传入函数formatDate,得到格式化后的日期formatDate
- 21 打印格式化后的日期formatDate
获取长度一致的字符串随机数
题目:获取随机数,要求是长度一致的字符串格式
注意:字符串也有slice(),和数组的slice()类似,用于截取字符串
1 获取随机数random,小数部分长度不确定
2 随机数后加上10个0,保证截取的时候有10位(保证不报错)。数字拼接字符串后等于强制类型转换,random 变为字符串
3 使用字符串的slice()截取random的前10位(注意:10不在截取范围)
遍历对象和数组的通用forEach函数
题目:写一个能遍历对象和数组的通用forEach函数
(jquery中有现成的方法,数组本身就有forEach())
- 1-14 定义forEach函数
- 参数1:对象/数组。参数2:函数。
- 注意:我们这里定义的forEach函数的index和item的顺序和数组自己的forEach()是反的
- 3-7 判断传入的是对象/数组,如果是数组,就用数组自己的forEach(),遍历数组后将index和item传入 参数2(fn函数),通过fn函数将index与item的位置调换一下后打印。
- 8-13 如果是对象,就通过
for in
遍历对象属性,并将 属性名称key
和 属性值obj[key]
传给 参数2(fn函数),被调用后会被打印。- 注意:in运算符在 数组 中是判断有无 数组下标 的,不是数组元素。在 对象 中判断有无 属性。返回值是boolean。
- 15 定义一个数组arr
- 17-19 调用我们定义的forEach函数,传入参数1为数组arr,参数2为一个函数,该函数接收两个参数并打印他们。
- 注意:这里的index和item的顺序和数组的forEach()是反的,主要是为了照顾后面对象的
key
、value
- 注意:这里的index和item的顺序和数组的forEach()是反的,主要是为了照顾后面对象的
- 21 定义一个对象obj
- 22-24 让对象obj调用我们定义的forEach函数
- key:属性名称。value:属性值。