Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JS 问题清单 #155

Open
12 of 13 tasks
xxleyi opened this issue Aug 25, 2019 · 9 comments
Open
12 of 13 tasks

JS 问题清单 #155

xxleyi opened this issue Aug 25, 2019 · 9 comments

Comments

@xxleyi
Copy link
Owner

xxleyi commented Aug 25, 2019

知识清单,以点带面

现代 JS 教程:从基础到高阶

The JavaScript language

@xxleyi xxleyi added this to the 面试 milestone Aug 25, 2019
@xxleyi xxleyi assigned xxleyi and unassigned xxleyi Aug 25, 2019
@xxleyi
Copy link
Owner Author

xxleyi commented Oct 11, 2019

Promise

Promise = class extends Promise{
  constructor(executor) {
    super((resolve, reject) => {
      executor(resolve, reject)
      setTimeout(reject, 1000, new Error("timeout"))
    })
  }
}

new Promise(
  (resolve, reject) => { }
).then(
  res => console.log('then', res)
).catch(
  err => console.log('catch', String(err))
)

这是一段从别处看来的 JS 代码,利用装饰器的思想,给自定义的 Promise 统一添加超时 reject。理顺这段代码,能让自己真正理解 Promise 的诸多细节。

@xxleyi
Copy link
Owner Author

xxleyi commented Oct 12, 2019

async await

JS 是天生的异步小能手,JS 运行时自带 event loop 统筹异步事件:

  • 最开始对付异步的执行顺序是利用 callback => 这极易导致调用地狱(代码的表现形式严重脱离其实际的业务逻辑)
  • 引入 Promise 机制 => 成功规避调用地狱,但冗长的 then chain 依旧存在优化空间,可读性和持续维护性依旧有待提升
  • 引入 async await 关键词,async 修饰下的函数是天然的 promise,而与普通 promise 不同,async 函数内部可以使用 await 将原本冗长的 then chain 改造为形式上的同步代码,从而使得异步代码的持续维护变得和同步代码一样简单

@xxleyi
Copy link
Owner Author

xxleyi commented Oct 14, 2019

vue nextTicket

这是一个 Vue 中很少使用的一个功能,但却不可或缺。原因是:

Vue performs DOM updates asynchronously.

但这不是此功能值得深究的原因所在。理解此功能的实现原理有助于从细节和应用层面把握 JS 的异步队列机制。

现代 JS 的异步队列机制总体来说分为两种:

  • 和浏览器渲染任务并列的异步任务:如 data fetch, setTimeout 等,称之为 macrotask,宏任务
  • 优先级大过浏览器渲染任务的异步任务:Promise, DOM mutation 等,称之为 microtask,微任务

原则上来讲,每一个微任务队列都是在单个宏任务内部触发和消化完毕,除非采取特殊优化。

什么意思呢?每一个宏任务在执行过程中,会产生异步微任务队列,这个微任务队列是单个宏任务的一部分。这样是非常合理的:宏任务内部触发的微任务理应共享 environment state。

说实话,我理解这些东西的过程和我现在阐述它们的顺序刚好是反着的。

在试图把握 vue nextTicket 实现原理时,对宏任务,setTimeout,微任务, Promise,DOM mutation 这些概念还没有一个正确且基本的认识,我能做的就是不局限自己,通过各种积累不断拼凑出一个明晰的版图。

nextTicket 的目标:让函数在 dom update 之后执行,以及如果可能的话,在dom render 之前执行

而 dom update 在 Vue 中是异步的,而且是微任务级别的异步,所以想要实现目标,只需要把函数封装在一个异步微任务中即可。

事情本来是如此简单,却又因为一件事变得复杂:不是所有 JS 宿主都完美支持微任务。

Why?

很简单:本来也确实完全可以没有微任务,原则上不妨碍任何事。这也正是上古浏览器 IE 的状态。

最终结论是:微任务是针对单个宏任务来讲的,如果没有微任务,完全可以退而求其次,使用宏任务来异步。vue nextTicket 必须实现的目标是 defer func execute after current dom update cycle, 所以能用微任务当然最好,能避免跨越浏览器渲染。没有的话,用宏任务也能实现目标,无非是不够及时,以及可能造成重复渲染。

Internally Vue tries native Promise.thenMutationObserver, and setImmediate for the asynchronous queuing and falls back to setTimeout(fn, 0).

看见没,vue nextTicket 和 dom update 的实现原理几乎完全一致:有微任务就用,没有的话就用宏任务来兜底,毕竟所有浏览器都支持 setTimeout 这个宏任务。

@xxleyi xxleyi changed the title 面试之 JS JS 问题清单 Feb 21, 2020
@xxleyi
Copy link
Owner Author

xxleyi commented Feb 21, 2020

Zeros in JS

+0 === -0 // true
Object.is(+0, 0) // true
Object.is(+0, -0) // false
1 / +0 === Infinity // true
1 / -0 === -Infinity // true

@xxleyi
Copy link
Owner Author

xxleyi commented Feb 21, 2020

"falsy" values list:

  • undefined
  • null
  • false
  • +0-0, and NaN
  • ""

有且仅有这些。

对了,补充一点判断 NaN 的方式:

Number.isNaN(v)

感受:JS 虽是极为灵活的动态类型语言,却又满身静态类型语言的残毒。最明显的一条:竟然学着 Java 区分所谓原始数据类型和对象数据类型。

为什么会有这种感受:JS 是 Brendan Eich 受雇于网景公司(Netscape),出于明确的商业目标,在管理层明确的靠近 Java,但又要区别于 Java 的要求之下,所设计的一种语言。

因此,JS 身上有了 Java 的影子,面向对象,但使用原型链实现。

Brendan Eich 的自主意志体现在:除却这些表面上的东西,JS 骨子里和 lisp 一脉相承。

更多细节参见:JS 诞生记

@xxleyi
Copy link
Owner Author

xxleyi commented Feb 21, 2020

Data Type

image
image
image
image

@xxleyi
Copy link
Owner Author

xxleyi commented Feb 21, 2020

browser window

image

@xxleyi
Copy link
Owner Author

xxleyi commented Feb 22, 2020

Event Loop and Queue

The Node.js Event Loop, Timers, and process.nextTick()

image

setImmediate() vs setTimeout()

setImmediate() and setTimeout() are similar, but behave in different ways depending on when they are called.

  • setImmediate() is designed to execute a script once the current poll phase completes.
  • setTimeout() schedules a script to be run after a minimum threshold in ms has elapsed.

process.nextTick() vs setImmediate()

We have two calls that are similar as far as users are concerned, but their names are confusing.

  • process.nextTick() fires immediately on the same phase
  • setImmediate() fires on the following iteration or 'tick' of the event loop

In essence, the names should be swapped. process.nextTick() fires more immediately than setImmediate(), but this is an artifact of the past which is unlikely to change. Making this switch would break a large percentage of the packages on npm. Every day more new modules are being added, which means every day we wait, more potential breakages occur. While they are confusing, the names themselves won't change.

Analysis of Microtask and Macrotask in JS event cycle

Macrotask

Macrotasks includes parsing HTML, generating DOM, executing main thread JS code and other events such as page loading, input, network events, timer events, etc. From the browser’s point of view, Macrotask represents some discrete and independent work.

Common applications
setTimeout,setInterval,setImmediate,requestAnimationFrame,I/O,UI rendering

Microtask

Microtasks is to complete some minor tasks to update the application state, such as processing the callback of Promise and DOM modification, so that these tasks can be executed before the browser re-renders. Microtask should be executed asynchronously as soon as possible, so their cost is less than Macrotask, and it can make us execute before UI rendering again, avoiding unnecessary UI rendering.

Common applications
process.nextTick,Promises,Object.observe [Obsolete],MutationObserver

Execution sequence

The implementation of Event Loop requires at least one Macrotask Queue and at least one Microtask Queue. For ease of understanding, we have all simplified into one.
In short, the Microtask Queue has a higher priority, that is, after executing a Microtask task, the entire Microtask Queue will be cleared, and if there is a new microtask added at this time, it will also be executed.

image

script start -> script end -> promise1 -> promise2 -> setTimeout

image

The overall execution sequence is as follows:
General code->promises[dom mutation observer Callback]->Events and setTimeout etc.

判断下面代码的输出

setTimeout(() => {
  console.log('timeout');
}, 0);

let now = Date.now()
while (Date.now() - now < 3000) {}

setImmediate(() => {
  console.log('immediate');
});

Promise.resolve().then(
  () => console.log("promise 1")
).then(
  () => console.log("promise 2")
)

答案是:需要看宿主环境对 setImmediate 的实现。

在 Node 中,需要分情况,在非 IO 周期中,callback 的顺序不具有确定性。在 IO 周期内,始终是 setImmediate 在前。

The main advantage to using setImmediate() over setTimeout() is setImmediate() will always be executed before any timers if scheduled within an I/O cycle, independently of how many timers are present.

所以针对 Node,即使如下恶性等待代码也会是先输出 immediate。因为是在 IO 周期中。

const fs = require('fs');

fs.readFile('.', () => {
  setTimeout(() => {
    console.log('timeout');
  }, 0);

  let now = Date.now()
  while (Date.now() - now < 3000) {}

  setImmediate(() => {
    console.log('immediate');
  });
});

@xxleyi
Copy link
Owner Author

xxleyi commented Apr 8, 2020

一个对 event loop 比较好的总结

The more detailed algorithm of the event loop (though still simplified compare to the specification):

  1. Dequeue and run the oldest task from the macrotask queue (e.g. “script”).
  2. Execute all microtasks:
    • While the microtask queue is not empty:
      • Dequeue and run the oldest microtask.
  3. Render changes if any.
  4. If the macrotask queue is empty, wait till a macrotask appears.
  5. Go to step 1.

To schedule a new macrotask:

  • Use zero delayed setTimeout(f).

That may be used to split a big calculation-heavy task into pieces, for the browser to be able to react on user events and show progress between them.

Also, used in event handlers to schedule an action after the event is fully handled (bubbling done).

To schedule a new microtask

  • Use queueMicrotask(f).
  • Also promise handlers go through the microtask queue.

There’s no UI or network event handling between microtasks: they run immediately one after another.

So one may want to queueMicrotask to execute a function asynchronously, but within the environment state.

@xxleyi xxleyi removed this from the 基础相关 milestone Sep 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant