這是基友面試 RingCenter 時被問到的一個題目javascript
表面上考察的是機率論等基礎知識,實際可能還會問到事件循環等底層知識,以及 React Fiberhtml
說蒙特卡洛可能不太理解,換個說法 -- 隨機抽樣java
構造一個單位正方形和 1/4 單位圓,往單位正方形中投入點,根據點與原點間的距離判斷點是落在圓內仍是圓外,分別統計落在兩個區域的點的個數 n1,n2 ,n1/(n1+n2)
即 1/4 圓的面積估計值,從而求得 πreact
如下是 js 代碼git
function inCicle() {
var x = Math.random();
var y = Math.random();
return Math.pow(x, 2) + Math.pow(y, 2) < 1
}
function calcPi() {
const N = 1e+6
let pointsInside = 0
for(let i=0;i<N;i++){
if(inCicle()){
pointsInside++;
}
}
return 4 * pointsInside / N
}
calcPi()
複製代碼
直接在控制檯運行,會發現有卡頓和掉幀發生,下面咱們來談談如何解決github
calcPi 是個耗時任務,會阻塞主線程,甚至致使掉幀,有什麼解決方法?web
提供幾個思路面試
Web Worker 是啥就再也不介紹了,不懂的自行 MDN 搜索api
咱們新建一個 Worker 線程進行耗時任務計算,然後再把結果發送給主線程app
function createWorker () {
let text = ` function inCicle() { var x = Math.random(); var y = Math.random(); return Math.pow(x, 2) + Math.pow(y, 2) < 1 } function calcPi() { const N = 1e+6 let pointsInside = 0 for(let i=0;i<N;i++){ if(inCicle()){ pointsInside++; } } return 4 * pointsInside / N } this.addEventListener('message', (msg) => { let pi = calcPi() this.postMessage(pi); }, false); `
let blob = new Blob([text]);
let url = window.URL.createObjectURL(blob);
return new Worker(url)
}
let worker = createWorker()
worker.onmessage = (evt) => {
console.log('PI: ', evt.data)
};
worker.postMessage("calc");
複製代碼
缺點就是計算次數是固定的,同時不能看到實時計算的結果
利用 requestIdleCallback 在幀空餘時間執行任務的特色進行耗時任務的計算
<!DOCTYPE html>
<html> <head> <title>Scheduling background tasks using requestIdleCallback</title> </head> <body> <script> var requestId = 0; var pointsTotal = 0; var pointsInside = 0; function piStep() { var r = 1; var x = Math.random() * r; var y = Math.random() * r; return (Math.pow(x, 2) + Math.pow(y, 2) < Math.pow(r, 2)) } function refinePi(deadline) { while (deadline.timeRemaining() > 0) { if (piStep()) pointsInside++; pointsTotal++; } currentEstimate = (4 * pointsInside / pointsTotal); textElement = document.getElementById("piEstimate"); textElement.innerHTML = "Pi Estimate: " + currentEstimate; requestId = window.requestIdleCallback(refinePi); } function start() { textElement = document.getElementById("piEstimate"); textElement.innerHTML = "Pi Estimate: " + "loading"; requestId = window.requestIdleCallback(refinePi); } function stop() { // alert(1) if (requestId) window.cancelIdleCallback(requestId); requestId = 0; } </script> <button onclick="start()">Click me to start!</button> <button onclick="stop()">Click me to stop!</button> <div id="piEstimate">Not started</div> </body> </html>
複製代碼
幾個要點
stop 時 piEstimate innerHTML 幀渲染先後不一致
requestAnimationFrame 將在事件循環中 UI Render 階段的實際渲染前執行,能夠簡單理解爲幀渲染初期
MessageChannel 用來收發消息開啓一個宏任務,相比 setTimeout 能夠更快執行(4ms的緣由)
咱們在 requestAnimationFrame 設置一個標記時間點 markPoint ,並經過 MessageChannel 發起一個宏任務,設置該宏任務的過時時間爲 markPoint + timeout(16ms) ,超過這個時間,任務再也不執行
這樣能夠保證宏任務不會由於執行過久致使卡頓和掉幀
<!DOCTYPE html>
<html> <head> <title>Scheduling background tasks using requestIdleCallback</title> </head> <body> <script> const timeout = 16 // 默認一幀爲16ms var requestId = 0; var pointsTotal = 0; var pointsInside = 0; let currentTask = { startTime: 0, endTime: 0, } var channel = new MessageChannel(); var sender = channel.port2; // port2 用來發消息 channel.port1.onmessage = function (event) { if (performance.now() > currentTask.endTime) { // 多是插入了其餘宏任務致使該任務過時,直接 rAF requestId = requestAnimationFrame(markPoint) return } refinePi(currentTask.endTime) requestId = requestAnimationFrame(markPoint) } function piStep() { var r = 1; var x = Math.random() * r; var y = Math.random() * r; return (Math.pow(x, 2) + Math.pow(y, 2) < Math.pow(r, 2)) } function refinePi(deadline) { while (performance.now() < deadline) { if (piStep()) { pointsInside++; } pointsTotal++; } currentEstimate = (4 * pointsInside / pointsTotal); textElement = document.getElementById("piEstimate"); textElement.innerHTML = "Pi Estimate: " + currentEstimate; } function markPoint(timestamp) { currentTask.startTime = timestamp currentTask.endTime = timestamp + timeout // 下輪宏任務 sender.postMessage("") } function start() { requestId = requestAnimationFrame(markPoint) } function stop() { // alert(1) if (requestId) window.cancelAnimationFrame(requestId); requestId = 0; } function handle() { let start = performance.now() while (performance.now() - start < 100) { } } </script> <button onclick="start()">Click me to start!</button> <button onclick="stop()">Click me to stop!</button> <button onclick="handle()">執行耗時任務,觀察 PI 的計算狀況</button> <div id="piEstimate">Not started</div> </body> </html>
複製代碼