1.Virtual DOM
vue 和 React 都使用了 Virtual DOM, 那么什么是 Virtual DOM?
Virtual DOM 的诞生: 改变真实 DOM 状态远比改变一个 JavaScript 对象的开销要大得多.
Virtual DOM 是一个映射真实 DOM 的 JavaScript 对象, 若需要改变任何元素的状态, 先在 Virtual DOM 上进行改变, 一个新的 Virtual DOM 对象会被创建并计算新旧 Virtual DOM 之间的差别, 之后这些差别会应用在真实的 DOM 上以实现只更新必要的局部 DOM.
2.JS 引擎执行机制
2.1 JS 是单线程语言
之所以是单线程, 是为了统一浏览器命令的执行 (最初被设计用于浏览器的).
2.2 JS 执行机制: Event Loop
由于 JS 是单线程语言但需要实现异步, 所以就诞生了解决方案, 事件循环 Event Loop .
示例 1.0 :
- console.log(1)
- setTimeout(function(){
- console.log(2)
- },0)
- console.log(3)
- //result: 1 3 2
- // 当 1, 3 在控制条被打印后, 主线程去 event queue(事件队列) 里查看是否有可执行的函数, 执行 setTimeout 里的函数.
其中, 在自上而下执行代码过程中, 满足一定条件后才去执行的, 这类代码, 我们叫异步代码, 反之则为同步代码. setTimeout 里的函数并没有立即执行, 而是延迟了一段时间, 这就属于异步代码.
所以, 这里我们首先知道了 JS 里的一种分类方式, 就是将任务分为: 同步任务和异步任务.
按照这种分类方式: JS 的执行机制是:
1. 首先判断 JS 是同步还是异步, 同步就进入主进程, 异步就进入 event table;
2. 异步任务在 event table 中注册函数, 当满足触发条件后, 被推入 event queue(事件队列), 即只要异步任务有了运行结果, 就在事件队列之中放置一个事件.
3. 同步任务进入主线程后一直执行, 直到主线程空闲时, 才会去 event queue 中查看是否有可执行的异步任务, 如果有就推入主进程中
以上三步循环执行, 这就是 event loop.
总结:
1. 主线程是唯一执行代码的地方, 主线程上执行的同步任务形成一个执行栈;
2. 同步任务即可直接执行的代码, 反之, 需要等待运行结果返回的即是异步任务;
3. 将已经有了运行结果的异步任务注册为事件推入事件队列当中;
4. 综合以上可知, 主线程执行栈优先完成同步任务之后, 再从事件队列中读取事件进入执行栈.
形象比喻: 食堂排队打饭,
1. 打饭窗口: 主线程
2. 当前正在打饭的同学: 执行栈执行同步任务
3. 后面排队的同学: 事件队列中已有返回结果的异步任务, 等待进入主线程执行
注: 排队的同学必须是准备好来打饭的, 比如课上完了, 碗筷备好等等, 这个过程好比异步任务执行自己的任务, 有了结果后才能进入事件队列排队.
反过来再看看若无 Event Loop 来实现异步, 以同步方式执行会如何?
所有同学统一到打饭窗口排队, 有准备的同学就直接打饭走人 (同步任务), 未准备的同学呢 (异步任务)? 打饭窗口会等待未准备同学离开队伍回去拿碗筷即做好所有准备后再给其打饭, 以此类推下去.
示例 2.0:
- setTimeout(
- function(){
- console.log('定时器开始啦')
- });
- new Promise(function(resolve){
- console.log('马上执行 for 循环啦');
- for(var i = 0; i < 10000; i++){
- i == 99 && resolve();
- }
- }).then(function(){
- console.log('执行 then 函数啦')
- });
- console.log('代码执行结束');
- // 执行结果并不是: 马上执行 for 循环啦 --- 代码执行结束 --- 定时器开始啦 --- 执行 then 函数啦
- // 正确结果: 马上执行 for 循环啦 --- 代码执行结束 --- 执行 then 函数啦 --- 定时器开始啦
尝试按照, 上文我们刚学到的 JS 执行机制去分析:
1.setTimeout 是异步任务, 被放到 event table
2.new Promise 是同步任务, 被放到主进程里, 直接执行打印 console.log('马上执行 for 循环啦')
3..then 里的函数是异步任务, 被放到 event table
4.console.log('代码执行结束') 是同步代码, 被放到主进程里, 直接执行
所以, 结果是: 马上执行 for 循环啦 --- 代码执行结束 --- 定时器开始啦 --- 执行 then 函数啦吗? 然而答案是: 马上执行 for 循环啦 --- 代码执行结束 --- 执行 then 函数啦 --- 定时器开始啦
事实上, 按照异步和同步的划分方式, 并不准确或者说不够细化. 而准确的划分方式是:
macro-task(宏任务): 包括整体代码 script,setTimeout,setInterval
micro-task(微任务):Promise,process.nextTick
按照这种分类方式, JS 的执行机制是:
执行一个宏任务, 过程中如果遇到微任务, 就将其放到微任务的 "事件队列" 里
当前宏任务执行完成后, 会查看微任务的 "事件队列", 并将里面全部的微任务依次执行完.
尝试按照刚学的执行机制, 去分析例 2:
1. 首先执行 script 下的宏任务, 遇到 setTimeout, 将其放到宏任务的 "队列" 里
2. 遇到 new Promise 直接执行, 打印 "马上执行 for 循环啦"
3. 遇到 then 方法, 是微任务, 将其放到微任务的 "队列" 里.
4. 打印 "代码执行结束"
5. 本轮宏任务执行完毕, 查看本轮的微任务, 发现有一个 then 方法里的函数, 打印 "执行 then 函数啦"
6. 到此, 本轮的 event loop 全部完成.
下一轮的循环里, 先执行一个宏任务, 发现宏任务的 "队列" 里有一个 setTimeout 里的函数, 执行打印 "定时器开始啦"
所以最后的执行顺序是: 马上执行 for 循环啦 --- 代码执行结束 --- 执行 then 函数啦 --- 定时器开始啦
关于 setTimeout
- setTimeout(function(){
- console.log('执行了')
- },3000)
- // 准确解释: 3s 后将该函数推入 event queue, 而 event queue 里的任务, 只有在主线程空闲时才会执行.
- // 即在 小于或等于 3s 后主线程空闲的情况下, 才能真正 3 秒后执行该函数, 否则该函数执行时间就等于主线程何时空闲何时执行了, 所以并没有办法保证, 回调函数一定会在 setTimeout() 指定的时间执行.
参考原本:
1.10 分钟理解 JS 引擎的执行机制 https://segmentfault.com/a/1190000012806637
2.JavaScript 运行机制详解: 再谈 Event Loop http://www.ruanyifeng.com/blog/2014/10/event-loop.html
来源: http://www.qdfuns.com/article/33426/877863f8d9eab1219929f22c48834bda.html