### 并发(concurrency)和并行(parallelism)区别
**并发与并行的区别?**
* 并发是宏观概念,我分别有任务A和任务B,在一段时间内通过任务间的切换完成了这两个任务。
* 并行是微观概念,假设CPU中存在两个核心,那么我就可以同时完成任务A、B。同时完成多个任务的情况称之为并行。
### 回调函数(Callback)
**什么是回调函数?回调函数有什么缺点?如何解决回调地狱问题?**
~~~
ajax(url,()=>{ //处理逻辑})
~~~
回调函数有个致命的弱点:容易写出回调地狱(Callback hell)。不利于阅读和维护。
~~~
ajax(url, ()=>{
//处理逻辑
ajax(url1,()=>{
//处理逻辑
ajax(url2,()=>{
//处理逻辑
})
})
})
~~~
回调地狱的根本问题:
1. 嵌套函数存在耦合性,一旦有所改动,就会牵一发动全身
2. 嵌套函数一多,就很难处理错误
回调函数还有其他缺点:不能使用try catch捕获错误,不能直接return。
### Generator
**你理解的Generator是什么?**
Generator(生成器),ES6新特性。通过 `function*`来定义的函数称之为“生成器函数”,它的特点是可以中断函数的执行,每次执行yield语句之后,函数即暂停执行,直到调用返回的生成器对象的`next()`函数它才会继续执行。
Generator函数是一个状态机,封装了多个内部状态。执行Generator函数返回一个遍历器对象(一个指向内部状态的指针对象),调用遍历器对象的next方法,使得指针移向下一个状态。每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,知道遇到下一个yield表达式(或return)
~~~
function *foo(x){
let y = 2 * (yield(x+1));
let z = yield( y/3);
return ( x + y + z);
}
let it = foo(5);
console.log(it.next());//{value:6,done:false}
console.log(it.next(12)) //{value:8,done:false}
console.log(it.next(13)) //{value: ,done:true} //13 +28 + 9
~~~
分析:
1. Generator函数调用和普通函数不同,它返回一个迭代器
2. 当执行第一次next时,传参会被忽略,并且函数暂停在`yield(x+1)`处,所以返回6;
3. 当执行第二次next时,**传入参数等于上一个yield的返回值**,**如果你不传参,yield永远返回undefined**。此时 `let y = 2* 12`,所以第二个yield等于 `2 *12 /3 = 8`
4. 当执行第三次next时,传入的参数会传递给`z`,所以 `z=13,x = 5, y=24`,
Generator函数一般见到的不多,其实也于他有点绕的关系,并且一般会配合co库去使用。可以通过Generator函数解决回调地狱的问题。
~~~
function *fetch(){
yield ajax(url, ()=>{})
yield ajax(url1, ()=>{})
yield ajax(url2,()=>{})
}
let it = fetch();
let result1 = it.next();
let result2 = it.next();
let result3 = it.next();
~~~
### Promise
Promise的特点是什么?优缺点?什么是Promise链?Promise构造函数执行和then函数执行有什么区别?
Promise:承诺,在未来有一个确切的答复,并且该承诺有三种状态:
* 等待中(pending)
* 完成了(resolved)
* 拒绝了(rejected)
从等待状态变成其他状态永远不能改变状态。
1. 当我们在构造`Promise`的时候,**构造函数内部的代码是立即执行的**
~~~
new Promise( (resolve,reject)=>{
console.log('new Promise')
resolve('success')
} )
console.log('finish')
//输出:new Promise fininsh
~~~
2. Promise实现了链式调用,每次调用then之后返回的都是一个Promise,并且是一个全新的Promise(原因是因为状态不可变)
3. 如果在then中使用了return,那么return的值会被Promise.resolve()包装
~~~
Promise.resolve(1)
.then( res => { consoel.log(res); return 2})//包装成Promise.resolve(2)
.then(res =>{ console.log(res)} ) //2
~~~
4. Promise可以很好的解决了回调地狱的问题
~~~
ajax(url)
.then( res =>{ console.log(res); return ajax(url1) })
.then( res =>{ console.log(res); return ajax(url2) })
.then(res =>{ console.log(res)})
~~~
5. 缺点:无法取消Promise,**错误需要通过回调函数捕获**。??
### async及await
**async及await特点,优缺点?await原理?**
1. 一个函数如果加上async,那么该函数就会返回一个Promise
~~~
async function test(){
return "1"
}
console.log(test()) //Promise对象 test(),不是test
~~~
2. async就是讲函数返回值使用Promise.resolve()包裹了下,和then中处理返回值一样,并且await只能配套async使用
~~~
async function test(){
let value = await sleep()
}
~~~
3. async和await可以说是异步终极解决方案了,
* 相比于Promise,优势在于处理then的调用链,能够清晰准确的写出代码,一堆then也不好
* 能优雅的解决回调地狱问题
* 缺点:await将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了await,会导致性能上的降低。
~~~
async function test() {
// 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
// 如果有依赖性的话,其实就是解决回调地狱的例子了
await fetch(url)
await fetch(url1)
await fetch(url2)
}
~~~
~~~
let a = 0;
let b = async() =>{
a = a + await 10
console.log("2",a) //2 11
}
b(); //b是异步
a++;//a++是同步
console.log("1",a) // 1 1
~~~
解析:
* 首先函数b先执行,在执行到`await 10`之前变量a还是0,因为**await内部实行了generator**,**generator会保留堆栈中东西,所以a = 0被保存下来**。
* 因为await是异步操作,后来的表达式不返回Promise的话,就会包装成Promise.reslove(返回值),然后去执行函数外的同步代码。
* 同步代码执行完毕或开始执行异步代码,将保存下来的值拿出来使用 0+10
await内部实行了generator,其实await就是generator加上Promise的语法糖,且内部实现了自动执行generator。
### 常用定时器函数
**setTimeout、setInterval、requestAnimation各有什么特点?**
错误观点:setTimeout是延时多久,那就应该多久后执行
因为js是单线程执行的,如果前面的代码影响了性能,就会导致**setTimeout不会按期执行**。
我们可以通过代码去修正setTimeout,从而使定时器相对准确
~~~
let period = 60 * 1000 * 60 * 2
let startTime = new Date().getTime()
let count = 0
let end = new Date().getTime() + period
let interval = 1000
let currentInterval = interval
function loop() {
count++
// 代码执行所消耗的时间
let offset = new Date().getTime() - (startTime + count * interval);
let diff = end - new Date().getTime()
let h = Math.floor(diff / (60 * 1000 * 60))
let hdiff = diff % (60 * 1000 * 60)
let m = Math.floor(hdiff / (60 * 1000))
let mdiff = hdiff % (60 * 1000)
let s = mdiff / (1000)
let sCeil = Math.ceil(s)
let sFloor = Math.floor(s)
// 得到下一次循环所消耗的时间
currentInterval = interval - offset
console.log('时:'+h, '分:'+m, '毫秒:'+s, '秒向上取整:'+sCeil, '代码执行时间:'+offset, '下次循环间隔'+currentInterval)
setTimeout(loop, currentInterval)
}
setTimeout(loop, currentInterval)
~~~
setInterval,和setTimeout基本一致,每个一段时间执行一次回调函数
通常不建议使用setInterval:和setTimeout一样,不能保证在预期的时间执行任务;存在执行累积的问题
~~~
function demo(){
setInterval(function(){
console.log(2)
},1000);
sleep(2000)
}
~~~
以上代码在浏览器中,如果定时器执行过程中出现耗时操作,**多个回调会在耗时操作结束以后同时执行**,这样可能就会带来性能上的问题。
**requestAnimationFrame**:是浏览器用于定时循环操作的一个接口,类似于setTimeout,主要用途是**按帧对网页进行重绘**。
目的:为了让各种网页动画效果(DOM动画、Canvas动画、SVG动画,WebGLobal动画)能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。代码中使用这个API,就告诉浏览器希望执行一个动画,让浏览器在下一个动画帧安排一次网页重绘。
requestAnimationFrame的优势:充分利用显示器的刷新机制,比较节省系统资源。显示器有固定的刷新频率(60Hz或75Hz)
requestAnimationFrame的基本思想就是与这个刷新频率保持同步,利用这个刷新频率进行网页重绘。此外,使用这个API,一旦页面不处于浏览器的当前标签,就会自动停止刷新。
requestAnimationFrame是在主线程上完成。如果主线程非常繁忙,requestAnimationFrame的动画效果会大打折扣。
如果你有循环定时器的需求,其实完全可以通过requestAnimationFrame来实现
~~~
function setInterval(callback, interval){
let timer;
const now = Date.now;
let startTime = now();
let endTime = startTime
const loop = () =>{
timer = window.requestAnimationFrame(loop);
endTime = now();
if( endTime - startTime >= interval){
startTime = endTime = now();
callback(timer);
}
}
timer = window.requestAnimationFrame(loop)
return timer
}
let a = 0;
setInterval(timer =>{
console.log(1)
a++;
if(a ===3) cancelAnimationFrame(timer);
},1000)
~~~
cancelAnimationFrame:用于取消重绘
~~~
window.cancelAnimationFrame(requestID)
//它的参数是requestAnimationFrame返回的一个代表任务ID的整数值。
~~~
requestAnimationFrame:自带函数节流功能,基本可以保证在16.6毫秒内只执行一次(不掉帧的情况下),并且该函数的延时效果是精确的,没有其他定时器时间不准的问题。
### 手写Promise(*****)
手写一个符合Promise/A+规范的Promise来深入理解它,
#### 简易版的Promise
1. 搭建构建函数的大体框架
~~~
const PENDING ="pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function MyPromise(fn){
const that = this;
that.state = PENDING;
that.value = null;
that.resolvedCallbacks =[];
that.rejectedCallbacks = [];
//待完善resolve和reject函数
//待完善执行fn函数
}
~~~
分析:
* 手写创建三个常量表示状态(便于开发和维护)
* 创建that常量,因为代码异步执行,用于获取正确的this对象
* 一开始Promise的状态是pending
* value变量用于报错resolve或者reject传入的值
* resolveCallbacks 和rejectedCallbacks用于保存then中的回调,因为当执行完Promise时状态可能还是等待中,这时候应该把then中的回调保存起来,用于状态改变时使用。
2. 接下来完善resolve和reject函数,添加在MyPromise函数内部
~~~
function resolve(value){
if(that.state === PENDING){
that.state = RESOLVED;
that.value = value;
that.resolveCallbacks.map(cb=> cb( that.vale));
}
}
function reject(value){
if(that.state == PENDING){
that.state = REJECTED;
that.value = value;
that.rejectedCallbacks.map(cb=>cb(that.value))
}
}
~~~
解析:
* 两个函数**都得判断当前状态是否为等待中**,因为规范规定只有等待态才可以改变状态。
* 将当前状态更改为对应状态,并且将传入的值给value
* 遍历回到组并执行。
3. 完成以上两个函数后,我们就该实现**如何执行Promise中传入的函数**了
~~~
try{
fn(resolve, reject);
}catch(e){
reject(e);
}
~~~
解析:
* 执行传入的参数,并且将之前的两个函数当做参数穿进去
* 注意,可能执行函数过程中会遇到错误,需要捕获错误并且执行reject函数
4. 实现较为复杂的then函数
~~~
MyPromise.prototype.then = function(onFulfilled, onRejected){
const that = this;
onFulfilled = typeof onFulfilled ==='function' ? onFulfilled :v =>v
onRejected = typeof onRejected === 'function' ? onRejected:r=>{throw r};
if( that.state === PENDING){
that.resolvedCallbacks.push(onFulfilled);
that.rejectedCallbacks.push(onRejected);
}
if( that.state === RESOLVED){
onFulfilled(that,value)
}
if(that.state === REJECTED){
onRejected(that.value)
}
}
~~~
* 首先判断两个参数是否为函数类型,因为两个参数是可选参数
* 当参数不是函数类型时,需要创建一个函数赋值给对应的参数,同时也实现了透传
~~~
//该代码目前在简单版中会报错
//只是作为一个透传的例子
Promise.resolve(4).then().then((value)=>{console.log(value)})
~~~
* 接下来是一系列判断状态的逻辑,当状态不是等待态是,就去执行相对应的函数。如果状态是等待态的话,就往回调函数中push函数,比如如下代码就会进入等待态的逻辑
~~~
new MyPromise((resolve,reject)=>{
setTimeout(()=>{
resolve(1);
},0)
}).then(value =>{console.log(value)})
~~~
~~~
const PENDING ="pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function MyPromise(fn){
const that = this;
that.state = PENDING;
that.value = null;
that.resolvedCallbacks =[];
that.rejectedCallbacks = [];
//待完善resolve和reject函数
function resolve(value){
if(that.state === PENDING){
that.state = RESOLVED;
that.value = value;
that.resolvedCallbacks.map(cb=> cb( that.value));
}
}
function reject(value){
if(that.state == PENDING){
that.state = REJECTED;
that.value = value;
that.rejectedCallbacks.map(cb=>cb(that.value))
}
}
//待完善执行fn函数
try{
//将resolve和reject暴露出来
fn(resolve, reject);
}catch(e){
reject(e);
}
}
MyPromise.prototype.then = function(onFulfilled, onRejected){
const that = this;
onFulfilled = typeof onFulfilled ==='function' ? onFulfilled :v =>v
onRejected = typeof onRejected === 'function' ? onRejected:r=>{throw r};
if( that.state === PENDING){
that.resolvedCallbacks.push(onFulfilled);
that.rejectedCallbacks.push(onRejected);
}
if( that.state === RESOLVED){
onFulfilled(that,value)
}
if(that.state === REJECTED){
onRejected(that.value)
}
}
new MyPromise((resolve,reject)=>{
setTimeout(()=>{
resolve(1);
},0)
}).then(value =>{console.log(value)})
~~~
目前不支持链式操作
### 实现一个符合Promise/A+规范的Promise
待复习
## Event loop
### 进程与线程
**进程和线程的区别?JS单线程带来的好处?**
两个名词都是CPU工作时间片的一个描述
**进程**:描述了**CPU在运行指令及加载和保存上下文所需的时间**,放在应用上来说就代表了一个程序。
**线程**:是进程中的更小单位,描述了执行一段指令所需的时间。
浏览器中:当你打开一个Tab,创建了一个进程,一个进程可是有多个线程,比如渲染线程、js引擎线程、HTTP请求线程等。当发起一个请求,就是创建一个线程,请求结束后,该线程可能就会被销毁
JS引擎线程和渲染线程是**互斥**的:JS运行的时候可能会阻止UI渲染(JS可以修改DOM,如果在JS执行的时候UI线程还在工作,可能导致不能按期的渲染UI)(单线程好处)
单线程好处:达到节省内存,节约上下文切换时间,没有锁的问题的好处。
### 执行栈
可以认为是一个存储**函数调用**的**栈结构**,遵循先进后出的原则,后执行的函数会先弹出栈。
当我们使用递归的时候,因为站可存放的函数是有限制的,一旦存放了过多的函数且没有得到释放的话,就会出现爆栈的问题
~~~
function bar(){
bar()
}
bar()
~~~
### 浏览器中的Event Loop
**异步代码执行顺序?什么是Event Loop?**
执行栈:当我们执行JS代码的时候,其实就是执行栈中放入函数。当遇到异步函数的代码时,**会被挂起并在需要执行的时候加入到Task(多种Task)队列中**,一旦执行栈为空,**Event Loop就从Task队列中拿出需要执行的代码并放入执行栈中执行**。从本质上来说,JS中的异步还是同步行为。
不同的任务源会被分配到不同的Task队列中,任务源可以分为**微任务(microtask)和宏任务(macrotask)**,ES6规范中,microtask成为jobs,macrotask成为task。
~~~
console.log(' script start');
async function async1(){
await async2();
console.log('async1 end');
}
async function async2(){
console.log("async2 end")
}
async1();
setTimeout(function(){
console.log('setTimeout')
})
new Promise(resolve=>{
console.log('promise')
resolve();
})
.then(function(){
console.log('promise1')
})
.then(function(){
console.log('promise2')
})
console.log('script end')
// script start --> async2 end --> promise -->script end -->async1 end -->promise1-->promise2 -->setTimeout(新浏览器)
//script start --> async2 end --> promise -->script end -->promise1-->promise2 -->async1 end -->setTimeout(旧浏览器)
~~~
async和await:调用async1函数时,会马上输出async2 end,并且函数返回一个Promise,接下来会**遇到await**的时候就**让出线程**开始**执行async1外的代码**,所以,我们完全可以把**await看成是让出线程的标志**。
接下来执行所以同步代码,执行完毕后,
去执行所以的异步代码,又回到await的位置执行返回的Promise的resolve函数,这又会吧resolve丢到微任务队列中,接下来去执行then中的回调,当两个then中的回调全部执行完毕以后,又会回到await的位置处理返回值,这个时候可以看成是`Promise.resolve(返回值).then()`,然后**await后的代码全部被包裹进了then的回到中**
~~~
async function async1(){
await async2();
console.log('async1 end');
}
async function async2(){
console.log("async2 end")
}
~~~
等价于
~~~
new Promise((resolve,reject)=>{
console.log('async2 end')
//Promise.resolve()将代码插入微任务队列尾部
//resolve再次插入微任务队列尾部
resolve(Promise.resolve())
}).then(()=>{
console.log('async1 end')
})
~~~
也就是说,如果await后面跟着Promise的话,async1 end需要等待三个tick才能执行到,性能还是略慢的,V8团队借鉴了Node 8中的一个bug,在引擎底层将三个tick减少到二次tick。
**Event Loop执行顺序:**
1. 首先执行同步代码,属于宏任务
2. 当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行
3. 执行所以微任务
4. 当执行完所有微任务后,如有必要渲染页面
5. 然后开始下一轮Event Loop,执行宏任务中的异步代码,也就是setTimeout中的回调函数。
**微任务:process.nextTick,promise,MutationObserver
宏任务:script,setTimeout,setInterval,setImmediate,I/O,UI rendering**。
错误观点:微任务快于宏任务(宏任务包括了script,浏览器会先执行一个宏任务,接下来异步代码的话才回先执行微任务)
![](https://box.kancloud.cn/13d8b4237236276b77645ac1ad4d9b5a_696x332.png)
## Node中的Event loop
和浏览器完全不同的东西
- 空白目录
- 双樾
- JS基础知识
- JS-WEB-API
- 开发环境
- 运行环境
- ES6
- 原型
- 异步
- 虚拟dom
- mvvm
- 组件化和React
- hybrid
- 其他
- 补充
- 技巧
- 快乐动起来呀
- css
- 掘金小册子
- js基础知识
- ES6知识点
- JS异步
- JS进阶知识
- 思考题
- DevTools Tips
- 浏览器基础知识
- 浏览器缓存机制0
- 浏览器渲染原理
- 安全防范知识点0
- 从V8中看JS性能优化0
- 性能优化琐碎事
- Webpack性能优化0
- 实现小型打包工具0
- React和Vue
- Vue生命周期
- vue基础知识点
- Vue响应式
- vue高级
- React基础
- Vue.js技术解密
- 准备工作
- 数据驱动
- new Vue()
- vue实例挂载
- 组件化
- 深入响应式原理
- 编译
- 扩展
- Vue Router
- Vuex