1)Promise基本特性
- 1、Promise有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)
- 2、Promise对象接受一个回调函数作为参数, 该回调函数接受两个参数,分别是成功时的回调resolve和失败时的回调reject;另外resolve的参数除了正常值以外, 还可能是一个Promise对象的实例;reject的参数通常是一个Error对象的实例。
- 3、then方法返回一个新的Promise实例,并接收两个参数onResolved(fulfilled状态的回调);onRejected(rejected状态的回调,该参数可选)
- 4、catch方法返回一个新的Promise实例
- 5、finally方法不管Promise状态如何都会执行,该方法的回调函数不接受任何参数
- 6、Promise.all()方法将多个多个Promise实例,包装成一个新的Promise实例,该方法接受一个由Promise对象组成的数组作为参数(Promise.all()方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例),注意参数中只要有一个实例触发catch方法,都会触发Promise.all()方法返回的新的实例的catch方法,如果参数中的某个实例本身调用了catch方法,将不会触发Promise.all()方法返回的新实例的catch方法
- 7、Promise.race()方法的参数与Promise.all方法一样,参数中的实例只要有一个率先改变状态就会将该实例的状态传给Promise.race()方法,并将返回值作为Promise.race()方法产生的Promise实例的返回值
- 8、Promise.resolve()将现有对象转为Promise对象,如果该方法的参数为一个Promise对象,Promise.resolve()将不做任何处理;如果参数thenable对象(即具有then方法),Promise.resolve()将该对象转为Promise对象并立即执行then方法;如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的Promise对象,状态为fulfilled,其参数将会作为then方法中onResolved回调函数的参数,如果Promise.resolve方法不带参数,会直接返回一个fulfilled状态的 Promise 对象。需要注意的是,立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。
- 9、Promise.reject()同样返回一个新的Promise对象,状态为rejected,无论传入任何参数都将作为reject()的参数
2)Promise优点
①统一异步 API
- Promise 的一个重要优点是它将逐渐被用作浏览器的异步 API ,统一现在各种各样的 API ,以及不兼容的模式和手法。
②Promise 与事件对比
- 和事件相比较, Promise 更适合处理一次性的结果。在结果计算出来之前或之后注册回调函数都是可以的,都可以拿到正确的值。 Promise 的这个优点很自然。但是,不能使用 Promise 处理多次触发的事件。链式处理是 Promise 的又一优点,但是事件却不能这样链式处理。
③Promise 与回调对比
- 解决了回调地狱的问题,将异步操作以同步操作的流程表达出来。
④Promise 带来的额外好处是包含了更好的错误处理方式(包含了异常处理),并且写起来很轻松(因为可以重用一些同步的工具,比如 Array.prototype.map() )。
1)状态管理
- redux-sage 是 redux 的一个异步处理的中间件。
- mobx 是数据管理库,和 redux 一样。
2)设计思想
- redux-sage 属于 flux 体系, 函数式编程思想。
- mobx 不属于 flux 体系,面向对象编程和响应式编程。
3)主要特点
- redux-sage 因为是中间件,更关注异步处理的,通过 Generator 函数来将异步变为同步,使代码可读性高,结构清晰。action 也不是 action creator 而是 pure action,
- 在 Generator 函数中通过 call 或者 put 方法直接声明式调用,并自带一些方法,如 takeEvery,takeLast,race等,控制多个异步操作,让多个异步更简单。
- mobx 是更简单更方便更灵活的处理数据。 Store 是包含了 state 和 action。state 包装成一个可被观察的对象, action 可以直接修改 state,之后通过 Computed values 将依赖 state 的计算属性更新 ,之后触发 Reactions 响应依赖 state 的变更,输出相应的副作用 ,但不生成新的 state。
4)数据可变性
- redux-sage 强调 state 不可变,不能直接操作 state,通过 action 和 reducer 在原来的 state 的基础上返回一个新的 state 达到改变 state 的目的。
- mobx 直接在方法中更改 state,同时所有使用的 state 都发生变化,不生成新的 state。
5)写法难易度
- redux-sage 比 redux 在 action 和 reducer 上要简单一些。需要用 dispatch 触发 state 的改变,需要 mapStateToProps 订阅 state。
- mobx 在非严格模式下不用 action 和 reducer,在严格模式下需要在 action 中修改 state,并且自动触发相关依赖的更新。
6)使用场景
- redux-sage 很好的解决了 redux 关于异步处理时的复杂度和代码冗余的问题,数据流向比较好追踪。但是 redux 的学习成本比 较高,代码比较冗余,不是特别需要状态管理,最好用别 的方式代替。
- mobx 学习成本低,能快速上手,代码比较简洁。但是可能因为代码编写的原因和数据更新时相对黑盒,导致数据流向不利于追踪。
1)背景
- react在进行组件渲染时,从setState开始到渲染完成整个过程是同步的(“一气呵成”)。如果需要渲染的组件比较庞大,js执行会占据主线程时间较长,会导致页面响应度变差,使得react在动画、手势等应用中效果比较差。
- 页面卡顿:Stack reconciler的工作流程很像函数的调用过程。父组件里调子组件,可以类比为函数的递归;对于特别庞大的vDOM树来说,reconciliation过程会很长(x00ms),超过16ms,在这期间,主线程是被js占用的,因此任何交互、布局、渲染都会停止,给用户的感觉就是页面被卡住了。
2)实现原理
旧版 React 通过递归的方式进行渲染,使用的是 JS 引擎自身的函数调用栈,它会一直执行到栈空为止。而Fiber实现了自己的组件调用栈,它以链表的形式遍历组件树,可以灵活的暂停、继续和丢弃执行的任务。实现方式是使用了浏览器的requestIdleCallback这一 API。 Fiber 其实指的是一种数据结构,它可以用一个纯 JS 对象来表示:
const fiber = { stateNode, // 节点实例 child, // 子节点 sibling, // 兄弟节点 return, // 父节点 }
react内部运转分三层:
- Virtual DOM 层,描述页面长什么样。
- Reconciler 层,负责调用组件生命周期方法,进行 Diff 运算等。
- Renderer 层,根据不同的平台,渲染出相应的页面,比较常见的是 ReactDOM 和 ReactNative。
为了实现不卡顿,就需要有一个调度器 (Scheduler) 来进行任务分配。优先级高的任务(如键盘输入)可以打断优先级低的任务(如Diff)的执行,从而更快的生效。任务的优先级有六种:
- synchronous,与之前的Stack Reconciler操作一样,同步执行
- task,在next tick之前执行
- animation,下一帧之前执行
- high,在不久的将来立即执行
- low,稍微延迟执行也没关系
- offscreen,下一次render时或scroll时才执行
Fiber Reconciler(react )执行阶段:
- 阶段一,生成 Fiber 树,得出需要更新的节点信息。这一步是一个渐进的过程,可以被打断。
- 阶段二,将需要更新的节点一次过批量更新,这个过程不能被打断。
Fiber树:React 在 render 第一次渲染时,会通过 React.createElement 创建一颗 Element 树,可以称之为 Virtual DOM Tree,由于要记录上下文信息,加入了 Fiber,每一个 Element 会对应一个 Fiber Node,将 Fiber Node 链接起来的结构成为 Fiber Tree。Fiber Tree 一个重要的特点是链表结构,将递归遍历编程循环遍历,然后配合 requestIdleCallback API, 实现任务拆分、中断与恢复。
从Stack Reconciler到Fiber Reconciler,源码层面其实就是干了一件递归改循环的事情
在NodeJS中,中间件主要是指封装所有Http请求细节处理的方法。一次Http请求通常包含很多工作,如记录日志、ip过滤、查询字符串、请求体解析、Cookie处理、权限验证、参数验证、异常处理等,但对于Web应用而言,并不希望接触到这么多细节性的处理,因此引入中间件来简化和隔离这些基础设施与业务逻辑之间的细节,让开发者能够关注在业务的开发上,以达到提升开发效率的目的。
中间件的行为比较类似Java中过滤器的工作原理,就是在进入具体的业务处理之前,先让过滤器处理。
const http = require('http') function compose(middlewareList) { return function (ctx) { function dispatch (i) { const fn = middlewareList[i] try { return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1))) } catch (err) { Promise.reject(err) } } return dispatch(0) } } class App { constructor(){ this.middlewares = [] } use(fn){ this.middlewares.push(fn) return this } handleRequest(ctx, middleware) { return middleware(ctx) } createContext (req, res) { const ctx = { req, res } return ctx } callback () { const fn = compose(this.middlewares) return (req, res) => { const ctx = this.createContext(req, res) return this.handleRequest(ctx, fn) } } listen(...args) { const server = http.createServer(this.callback()) return server.listen(...args) } } module.exports = App
1)为什么会有Event Loop
JavaScript的任务分为两种
同步
和异步
,它们的处理方式也各自不同,同步任务是直接放在主线程上排队依次执行,异步任务会放在任务队列中,若有多个异步任务则需要在任务队列中排队等待,任务队列类似于缓冲区,任务下一步会被移到调用栈然后主线程执行调用栈的任务。调用栈:调用栈是一个栈结构,函数调用会形成一个栈帧,帧中包含了当前执行函数的参数和局部变量等上下文信息,函数执行完后,它的执行上下文会从栈中弹出。
JavaScript是
单线程
的,单线程是指 js引擎中解析和执行js代码的线程只有一个(主线程),每次只能做一件事情,然而ajax
请求中,主线程在等待响应的过程中回去做其他事情,浏览器先在事件表注册ajax的回调函数,响应回来后回调函数被添加到任务队列中等待执行,不会造成线程阻塞,所以说js处理ajax请求的方式是异步的。 综上所述,检查调用栈是否为空以及讲某个任务添加到调用栈中的个过程就是event loop,这就是JavaScript实现异步的核心。