Du Yi マスタークラスノート (焦点: イベントループ、ブラウザーレンダリング原理)
応答原則 (Duyi)
データレスポンシブとは何ですか?
機能とデータの関連付け(重要)
データが変更されると、データに依存する機能が自動的に再実行されます (重要)
-
監視対象機能
レンダリング、計算されたコールバック、監視、watchEffect
-
応答データは関数の実行中に使用されます (応答データはオブジェクトである必要があります)
-
リアクティブなデータ変更により関数が再実行されます
defineProperty(渡一)
var obj = {
b: 2,
};
// 得到属性描述符
// var desc = Object.getOwnPropertyDescriptor(obj, 'a');
// console.log(desc);
// 设置属性描述符
Object.defineProperty(obj, 'a', {
value: 10,
writable: false, // 不可重写
enumerable: false, // 不可遍历
configurable: false, // 不可修改描述符本身
});
// Object.defineProperty(obj, 'a', {
// writable: true,
// });
obj.a = 'abc';
console.log(obj.a);
// for (var key in obj) {
// console.log(key);
// }
// var keys = Object.keys(obj);
// console.log(keys);
// console.log(obj);
var aGoods = {
pic: '.',
title: '..',
desc: `...`,
sellNumber: 1,
favorRate: 2,
price: 3,
};
class UIGoods {
get totalPrice() {
return this.choose * this.data.price;
}
get isChoose() {
return this.choose > 0;
}
constructor(g) {
g = { ...g };
Object.freeze(g);
Object.defineProperty(this, 'data', {
get: function () {
return g;
},
set: function () {
throw new Error('data 属性是只读的,不能重新赋值');
},
configurable: false,
});
var internalChooseValue = 0;
Object.defineProperty(this, 'choose', {
configurable: false,
get: function () {
return internalChooseValue;
},
set: function (val) {
if (typeof val !== 'number') {
throw new Error('choose属性必须是数字');
}
var temp = parseInt(val);
if (temp !== val) {
throw new Error('choose属性必须是整数');
}
if (val < 0) {
throw new Error('choose属性必须大于等于 0');
}
internalChooseValue = val;
},
});
console.log(this)
this.a = 1;
Object.seal(this);
}
}
Object.freeze(UIGoods.prototype);
var g = new UIGoods(aGoods);
UIGoods.prototype.haha = 'abc';
// g.data.price = 100;
console.log(g.haha);
/**
* 观察某个对象的所有属性
* @param {Object} obj
*/
function observe(obj) {
for (const key in obj) {
let internalValue = obj[key];
let funcs = [];
Object.defineProperty(obj, key, {
get: function () {
// 依赖收集,记录:是哪个函数在用我
if (window.__func && !funcs.includes(window.__func)) {
funcs.push(window.__func);
}
return internalValue;
},
set: function (val) {
internalValue = val;
// 派发更新,运行:执行用我的函数
for (var i = 0; i < funcs.length; i++) {
funcs[i]();
}
},
});
}
}
function autorun(fn) {
window.__func = fn;
fn();
window.__func = null;
}
コンストラクターの使用 (ショッピング カート ケース) (Duyi)
上記のコードは次のコードと等価です。
// 单件商品的数据
class UIGoods {
constructor(g) {
this.data = g;
this.choose = 0;
}
// 获取总价
getTotalPrice() {
return this.data.price * this.choose;
}
// 是否选中了此件商品
isChoose() {
return this.choose > 0;
}
// 选择的数量+1
increase() {
this.choose++;
}
// 选择的数量-1
decrease() {
if (this.choose === 0) {
return;
}
this.choose--;
}
}
// 整个界面的数据
class UIData {
constructor() {
var uiGoods = [];
for (var i = 0; i < goods.length; i++) {
var uig = new UIGoods(goods[i]);
uiGoods.push(uig);
}
this.uiGoods = uiGoods;
this.deliveryThreshold = 30;
this.deliveryPrice = 5;
}
getTotalPrice() {
var sum = 0;
for (var i = 0; i < this.uiGoods.length; i++) {
var g = this.uiGoods[i];
sum += g.getTotalPrice();
}
return sum;
}
// 增加某件商品的选中数量
increase(index) {
this.uiGoods[index].increase();
}
// 减少某件商品的选中数量
decrease(index) {
this.uiGoods[index].decrease();
}
// 得到总共的选择数量
getTotalChooseNumber() {
var sum = 0;
for (var i = 0; i < this.uiGoods.length; i++) {
sum += this.uiGoods[i].choose;
}
return sum;
}
// 购物车中有没有东西
hasGoodsInCar() {
return this.getTotalChooseNumber() > 0;
}
// 是否跨过了起送标准
isCrossDeliveryThreshold() {
return this.getTotalPrice() >= this.deliveryThreshold;
}
isChoose(index) {
return this.uiGoods[index].isChoose();
}
}
// 整个界面
class UI {
constructor() {
this.uiData = new UIData();
this.doms = {
goodsContainer: document.querySelector('.goods-list'),
deliveryPrice: document.querySelector('.footer-car-tip'),
footerPay: document.querySelector('.footer-pay'),
footerPayInnerSpan: document.querySelector('.footer-pay span'),
totalPrice: document.querySelector('.footer-car-total'),
car: document.querySelector('.footer-car'),
badge: document.querySelector('.footer-car-badge'),
};
var carRect = this.doms.car.getBoundingClientRect();
var jumpTarget = {
x: carRect.left + carRect.width / 2,
y: carRect.top + carRect.height / 5,
};
this.jumpTarget = jumpTarget;
this.createHTML();
this.updateFooter();
this.listenEvent();
}
// 监听各种事件
listenEvent() {
this.doms.car.addEventListener('animationend', function () {
this.classList.remove('animate');
});
}
// 根据商品数据创建商品列表元素
createHTML() {
var html = '';
for (var i = 0; i < this.uiData.uiGoods.length; i++) {
var g = this.uiData.uiGoods[i];
html += `<div class="goods-item">
<img src="${g.data.pic}" alt="" class="goods-pic">
<div class="goods-info">
<h2 class="goods-title">${g.data.title}</h2>
<p class="goods-desc">${g.data.desc}</p>
<p class="goods-sell">
<span>月售 ${g.data.sellNumber}</span>
<span>好评率${g.data.favorRate}%</span>
</p>
<div class="goods-confirm">
<p class="goods-price">
<span class="goods-price-unit">¥</span>
<span>${g.data.price}</span>
</p>
<div class="goods-btns">
<i index="${i}" class="iconfont i-jianhao"></i>
<span>${g.choose}</span>
<i index="${i}" class="iconfont i-jiajianzujianjiahao"></i>
</div>
</div>
</div>
</div>`;
}
this.doms.goodsContainer.innerHTML = html;
}
increase(index) {
this.uiData.increase(index);
this.updateGoodsItem(index);
this.updateFooter();
this.jump(index);
}
decrease(index) {
this.uiData.decrease(index);
this.updateGoodsItem(index);
this.updateFooter();
}
// 更新某个商品元素的显示状态
updateGoodsItem(index) {
var goodsDom = this.doms.goodsContainer.children[index];
if (this.uiData.isChoose(index)) {
goodsDom.classList.add('active');
} else {
goodsDom.classList.remove('active');
}
var span = goodsDom.querySelector('.goods-btns span');
span.textContent = this.uiData.uiGoods[index].choose;
}
// 更新页脚
updateFooter() {
// 得到总价数据
var total = this.uiData.getTotalPrice();
// 设置配送费
this.doms.deliveryPrice.textContent = `配送费¥${this.uiData.deliveryPrice}`;
// 设置起送费还差多少
if (this.uiData.isCrossDeliveryThreshold()) {
// 到达起送点
this.doms.footerPay.classList.add('active');
} else {
this.doms.footerPay.classList.remove('active');
// 更新还差多少钱
var dis = this.uiData.deliveryThreshold - total;
dis = Math.round(dis);
this.doms.footerPayInnerSpan.textContent = `还差¥${dis}元起送`;
}
// 设置总价
this.doms.totalPrice.textContent = total.toFixed(2);
// 设置购物车的样式状态
if (this.uiData.hasGoodsInCar()) {
this.doms.car.classList.add('active');
} else {
this.doms.car.classList.remove('active');
}
// 设置购物车中的数量
this.doms.badge.textContent = this.uiData.getTotalChooseNumber();
}
// 购物车动画
carAnimate() {
this.doms.car.classList.add('animate');
}
// 抛物线跳跃的元素
jump(index) {
// 找到对应商品的加号
var btnAdd = this.doms.goodsContainer.children[index].querySelector(
'.i-jiajianzujianjiahao'
);
var rect = btnAdd.getBoundingClientRect();
var start = {
x: rect.left,
y: rect.top,
};
// 跳吧
var div = document.createElement('div');
div.className = 'add-to-car';
var i = document.createElement('i');
i.className = 'iconfont i-jiajianzujianjiahao';
// 设置初始位置
div.style.transform = `translateX(${start.x}px)`;
i.style.transform = `translateY(${start.y}px)`;
div.appendChild(i);
document.body.appendChild(div);
// 强行渲染
div.clientWidth;
// 设置结束位置
div.style.transform = `translateX(${this.jumpTarget.x}px)`;
i.style.transform = `translateY(${this.jumpTarget.y}px)`;
var that = this;
div.addEventListener(
'transitionend',
function () {
div.remove();
that.carAnimate();
},
{
once: true, // 事件仅触发一次
}
);
}
}
var ui = new UI();
// 事件
ui.doms.goodsContainer.addEventListener('click', function (e) {
if (e.target.classList.contains('i-jiajianzujianjiahao')) {
var index = +e.target.getAttribute('index');
ui.increase(index);
} else if (e.target.classList.contains('i-jianhao')) {
var index = +e.target.getAttribute('index');
ui.decrease(index);
}
});
window.addEventListener('keypress', function (e) {
if (e.code === 'Equal') {
ui.increase(0);
} else if (e.code === 'Minus') {
ui.decrease(0);
}
});
ブラウザはどのようにページをレンダリングするのでしょうか? (和一)
ブラウザのネットワーク スレッドが HTML ドキュメントを受信すると、レンダリング タスクが生成され、レンダリング メイン スレッドのメッセージ キューに渡されます。
イベント ループ メカニズムの動作の下で、レンダリング メイン スレッドはメッセージ キュー内のレンダリング タスクを取り出し、レンダリング プロセスを開始します。
レンダリング プロセス全体は、HTML 解析、スタイル計算、レイアウト、レイヤー化、描画、ブロック、ラスター化、描画という複数の段階に分かれています。
各ステージには明確な入力と出力があり、前のステージの出力は次のステージの入力になります。
このようにして、レンダリング プロセス全体で、よく組織化された制作パイプラインが形成されます。
レンダリングの最初のステップは、HTML を解析することです。
解析中に CSS が見つかった場合は CSS を解析し、JS が見つかった場合は JS を実行します。解析効率を向上させるために、ブラウザは解析を開始する前に事前解析スレッドを開始し、まず HTML 内の外部 CSS ファイルと外部 JS ファイルをダウンロードします。
メイン スレッドがlink
その場所まで解析する場合、外部 CSS ファイルはまだダウンロードされて解析されていないため、メイン スレッドは待機せずに後続の HTML の解析を続行します。これは、CSS のダウンロードと解析の作業が準備解析スレッドで行われるためです。これが、CSS が HTML 解析をブロックしない根本的な理由です。
メインスレッドがscript
その位置まで解析すると、HTML の解析を停止し、JS ファイルがダウンロードされるのを待ちます。グローバル コードの解析が完了してから HTML の解析を続行します。これは、JS コードの実行により現在の DOM ツリーが変更される可能性があるため、DOM ツリーの生成を一時停止する必要があるためです。これが、JS が HTML 解析をブロックする根本原因です。
最初のステップが完了すると、DOM ツリーと CSSOM ツリーが取得され、ブラウザのデフォルト スタイル、内部スタイル、外部スタイル、インライン スタイルがすべて CSSOM ツリーに含まれます。
レンダリングの次のステップはスタイルの計算です。
メイン スレッドは、取得した DOM ツリーを走査し、ツリー内の各ノードの最終スタイルを順番に計算します。これは、計算されたスタイルと呼ばれます。
このプロセスでは、多くのプリセット値が絶対値になり、たとえばred
になりますrgb(255,0,0)
。相対単位は絶対単位になります。たとえば、em
になります。px
このステップが完了すると、スタイルを含む DOM ツリーが得られます。
次にレイアウトですが、レイアウトが完了するとレイアウトツリーが取得されます。
レイアウト ステージでは、DOM ツリーの各ノードを順番に走査し、各ノードの幾何学的情報を計算します。たとえば、ノードの幅と高さ、およびノードを含むブロックに対する相対的な位置です。
ほとんどの場合、DOM ツリーとレイアウト ツリーの間には 1 対 1 の対応関係はありません。
たとえばdisplay:none
、ノードには幾何学的な情報がないため、レイアウト ツリーには生成されません。別の例は、疑似要素セレクターの使用です。これらの疑似要素ノードは DOM ツリーには存在しませんが、幾何学的な情報を持っています。 , したがって、レイアウト ツリーに生成されます。また、匿名のライン ボックス、匿名のブロック ボックスなどもあり、DOM ツリーとレイアウト ツリーが 1 対 1 に対応します。
次のステップはレイヤリングです
メインスレッドは、複雑な戦略セットを使用して、レイアウト ツリー全体を階層化します。
レイヤ化の利点は、将来特定のレイヤが変更された後、そのレイヤのみが引き続き処理されるため、効率が向上することです。
スクロールバー、スタッキングコンテキスト、変換、不透明度などのスタイルは多かれ少なかれレイヤー化された結果に影響を与えますが、will-change
属性を通じてレイヤー化された結果に大きな影響を与えることもあります。
次のステップは絵を描くことです
メインスレッドは、レイヤーごとに個別の描画命令セットを生成します。これは、このレイヤーのコンテンツを描画する方法を記述するために使用されます。
描画が完了すると、メインスレッドは各レイヤーの描画情報を合成スレッドに送信し、残りの作業は合成スレッドによって完了します。
合成スレッドはまず各レイヤーをタイル状に並べて、より小さな領域に分割します。
チャンク化された作業を完了するには、スレッド プールから複数のスレッドが必要になります。
ブロックが完了したら、ラスタライズ段階に入ります。
合成スレッドはブロック情報を GPU プロセスにオフロードし、非常に高速にラスタライズを完了します。
GPU プロセスは、ラスタライズを完了するために複数のスレッドを開始し、ビューポート領域に近いチャンクを優先します。
ラスタライズの結果はビットマップごとに分割されます
最後の段階は描画です
合成スレッドは各レイヤー、各ブロックのビットマップを取得した後、「ガイダンス(クアッド)」情報を1つずつ生成します。
ガイドラインでは、回転や拡大縮小などの歪みを考慮して、各ビットマップを画面上のどこに描画するかを特定します。
変形はメインのレンダリング スレッドではなく合成スレッドで発生します。これがtransform
高効率の本質です。
合成スレッドはクアッドを GPU プロセスに送信します。これによりシステム コールが生成され、それが GPU ハードウェアに送信されて、最終的な画面イメージングが完了します。
リフローとは何ですか?
リフローの本質は、レイアウト ツリーを再計算することです。
レイアウト ツリーに影響を与える操作が実行されると、レイアウト ツリーを再計算する必要があり、これによりレイアウトがトリガーされます。
複数の連続操作によるレイアウト ツリーの計算の繰り返しを避けるために、ブラウザはこれらの操作を組み合わせ、すべての JS コードが完了した後に統一された計算を実行します。したがって、プロパティの変更によるリフローは非同期で行われます。
また、この影響により、JSがレイアウト属性を取得する際に、最新のレイアウト情報が取得できない場合があります。
検討を繰り返した結果、最終的にブラウザは属性を取得したらすぐにリフローすることを決定しました。
リペイントとは何ですか?
リペイントの本質は、階層化された情報に基づいて描画命令を再計算することです。
表示されるスタイルが変更されると、再計算が必要になり、再描画がトリガーされます。
要素のレイアウト情報も可視スタイルに属しているため、リフローすると必ず再描画が発生します。
なぜ変換効率が高いのでしょうか?
変換はレイアウトや描画命令には影響しないため、レンダリング プロセスの最後の「描画」段階にのみ影響します。
描画ステージは合成スレッド内にあるため、変換の変更はレンダリングのメインスレッドにはほとんど影響しません。逆に、レンダリングのメインスレッドがどれほどビジーであっても、変換の変更には影響しません。
イベントループ(Duyi)
ブラウザのプロセスモデル
プロセスとは何ですか?
プログラムを実行するには専用のメモリ空間が必要ですが、このメモリ空間は単純にプロセスとして理解できます。
各アプリケーションには少なくとも 1 つのプロセスがあり、プロセスは互いに独立しているため、通信したい場合でも双方の同意が必要です。
スレッドとは何ですか?
プロセスを作成したら、プログラムのコードを実行できます。
コードを実行する「人」は「スレッド」と呼ばれます。
プロセスには少なくとも 1 つのスレッドがあるため、プロセスの開始後、コードを実行するためのスレッド (メイン スレッドと呼ばれます) が自動的に作成されます。
プログラムが複数のコード ブロックを同時に実行する必要がある場合、メイン スレッドはコードを実行するためにさらに多くのスレッドを開始するため、プロセスには複数のスレッドを含めることができます。
ブラウザにはどのようなプロセスとスレッドがありますか?
ブラウザはマルチプロセス マルチスレッド アプリケーションです
ブラウザの内部動作は非常に複雑です。
相互影響を回避し、シリアルクラッシュの可能性を減らすために、ブラウザの起動時に複数のプロセスが自動的に起動されます。
ブラウザのタスク マネージャーで現在のすべてのプロセスを表示できます。
その中で最も重要なプロセスは次のとおりです。
-
ブラウザのプロセス
主にインターフェース表示、ユーザーインタラクション、サブプロセス管理などを担当します。ブラウザーのプロセス内では、さまざまなタスクを処理するために複数のスレッドが開始されます。
-
ネットワークプロセス
ネットワーク リソースの読み込みを担当します。さまざまなネットワーク タスクを処理するために、ネットワーク プロセス内で複数のスレッドが開始されます。
-
レンダリング プロセス(このレッスンで重点を置くプロセス)
レンダリング プロセスが開始されると、レンダリング メイン スレッドが開始され、メイン スレッドは HTML、CSS、および JS コードの実行を担当します。
デフォルトでは、ブラウザーはタブごとに新しいレンダリング プロセスを開始し、異なるタブが相互に影響を及ぼさないようにします。
デフォルト モードは将来変更される可能性があります。興味のある学生はChrome の公式ドキュメントを参照してください。
レンダリングのメインスレッドはどのように動作するのでしょうか?
メインのレンダリング スレッドはブラウザ内で最も忙しいスレッドであり、処理する必要があるタスクには以下が含まれますが、これらに限定されません。
- HTMLの解析
- CSSの解析
- 計算スタイル
- レイアウト
- レイヤーを操作する
- 1 秒あたり 60 回ページを描画します
- グローバルJSコードを実行する
- イベントハンドラを実行する
- タイマーのコールバック関数を実行する
- …
考えられる質問: レンダリング プロセスでは、これらの処理に複数のスレッドを使用しないのはなぜですか?
非常に多くのタスクを処理するために、メインスレッドは、タスクをどのようにスケジュールするかという前例のない問題に遭遇しました。
例えば:
- JS 関数を実行していますが、実行の途中でユーザーがボタンをクリックした場合、クリック イベント ハンドラーをすぐに実行する必要がありますか?
- JS 関数を実行していて、実行途中でタイマーが時間に達した場合、すぐにそのコールバックを実行する必要がありますか?
- ブラウザのプロセスが「ユーザーがボタンをクリックした」ことを通知し、同時に特定のタイマーが期限切れになった場合、どちらを処理すればよいでしょうか?
- …
レンダリングのメインスレッドは、これを処理するための素晴らしいアイデアを思いつきました: キューイング
- 最初に、レンダリングのメインスレッドが無限ループに入ります。
- 各ループは、メッセージ キューにタスクがあるかどうかを確認します。存在する場合は、最初に実行するタスクを取り出し、タスクを実行した後に次のサイクルに入ります。存在しない場合は、休止状態に入ります。
- 他のすべてのスレッド (他のプロセスのスレッドを含む) はいつでもメッセージ キューにタスクを追加できます。新しいタスクはメッセージ キューの最後に追加されます。新しいタスクを追加するときに、メインスレッドが休止状態の場合は、メインスレッドが起動されて、ループ内のタスクのフェッチが続行されます。
このようにして、各タスクを秩序正しく継続的に実行できます。
この処理全体をイベントループ(メッセージループ)と呼びます。
いくつかの説明
非同期とは何ですか?
コードの実行中に、次のようなすぐに処理できないいくつかのタスクが発生します。
- タイミングが完了した後に実行する必要があるタスク
setTimeout
—— 、setInterval
- ネットワーク通信の完了後に実行する必要があるタスク –
XHR
、Fetch
- ユーザーアクションの後に実行されるタスク –
addEventListener
レンダリングのメイン スレッドがこれらのタスクの到着を待機すると、メイン スレッドが長時間「ブロック」状態になり、ブラウザが「スタック」します。
レンダリングのメインスレッドは非常に重要な作業を引き受けるため、とにかくブロックすることはできません。
したがって、ブラウザはこの問題を解決するために非同期を選択します。
非同期メソッドを使用すると、レンダリングのメインスレッドがブロックされることはありません。
インタビューの質問: JS の非同期性をどのように理解すればよいですか?
参考回答:
JS はブラウザのメイン レンダリング スレッドで実行され、レンダリング メイン スレッドが 1 つだけ存在するため、シングル スレッド言語です。
レンダリングのメインスレッドは多くのタスクを引き受け、ページのレンダリングと JS の実行はすべてその中で実行されます。
同期メソッドを使用すると、メイン スレッドがブロックされる可能性が非常に高く、その結果、メッセージ キュー内の他の多くのタスクが実行できなくなります。このように、一方では、忙しいメインスレッドが無駄に時間を浪費することになり、他方では、ページの更新が間に合わず、ユーザーがスタックしてしまう原因となります。
したがって、ブラウザは非同期の方法を使用してそれを回避します。具体的な方法としては、タイマー、ネットワーク、イベント監視などの特定のタスクが発生すると、メインスレッドが他のスレッドにタスクを引き渡して処理し、直ちに単独でタスクの実行を終了し、後続のコードを実行するというものです。 。他のスレッドが終了したら、事前に渡したコールバック関数をタスクにラップし、メッセージキューの最後に追加し、メインスレッドが実行をスケジュールするのを待ちます。
この非同期モードでは、ブラウザは決してブロックしないため、単一スレッドのスムーズな動作が最大限に保証されます。
JS がレンダリングをブロックするのはなぜですか?
まずはコードを見てください
<h1>Mr.Yuan is awesome!</h1>
<button>change</button>
<script>
var h1 = document.querySelector('h1');
var btn = document.querySelector('button');
// 死循环指定的时间
function delay(duration) {
var start = Date.now();
while (Date.now() - start < duration) {
}
}
btn.onclick = function () {
h1.textContent = '袁老师很帅!';
delay(3000);
};
</script>
ボタンをクリックすると何が起こるでしょうか?
<具体的なデモを参照>
タスクには優先順位が付けられていますか?
タスクには優先順位がなく、メッセージ キューに先入れ先出しされます。
ただしメッセージキューが優先されます
W3C からの最新の説明によると、
- 各タスクにはタスク タイプがあり、同じタイプのタスクは 1 つのキューに存在する必要があり、異なるタイプのタスクは異なるキューに属することができます。
イベント サイクルでは、ブラウザは実際の状況に応じて実行するためにさまざまなキューからタスクをフェッチできます。 - ブラウザはマイクロタスクを準備する必要があり、マイクロキュー内のタスクは他のすべてのタスクよりも優先されます
https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
ブラウザが洗練されるにつれて、W3C はマクロ キューを使用しなくなりました
現在の chrome 実装には、少なくとも次のキューが含まれています。
- 遅延キュー: タイマーの到着後にコールバック タスクを保存するために使用され、優先度は「中」です。
- インタラクションキュー: ユーザー操作後に生成されたイベント処理タスクを優先度「高」で格納するために使用されます。
- マイクロキュー: ユーザーは最も速く実行する必要があるタスクを保存し、優先度は「最高」です。
タスクをマイクロキューに追加する主な方法は、Promise と MutationObserver を使用することです。
例えば:
// 立即把一个函数添加到微队列 Promise.resolve().then(函数)
ブラウザには他にも多くのキューがありますが、開発とはほとんど関係がないため考慮されません。
面接の質問: JS のイベント ループについて説明する
参考回答:
イベント ループはメッセージ ループとも呼ばれ、ブラウザがメイン スレッドをレンダリングする方法です。
Chrome のソース コードでは、無限の for ループが開始され、各ループが実行のためにメッセージ キューから最初のタスクをフェッチし、他のスレッドは適切なタイミングでタスクをキューの最後に追加するだけで済みます。
以前は、メッセージ キューは単純にマクロ キューとマイクロ キューに分けられていましたが、これでは複雑なブラウザ環境に対応できなくなり、より柔軟で変更可能な処理方法に置き換えられました。
W3C 公式の説明によると、各タスクは異なるタイプを持ち、同じタイプのタスクは同じキューに存在する必要があり、異なるタスクは異なるキューに属することができます。タスクキューごとに優先順位が異なるため、イベントループではブラウザがどのキューを取るかを決定します。ただし、ブラウザにはマイクロキューが必要であり、マイクロキューのタスクには最高の優先順位が設定され、最初にスケジュールされて実行される必要があります。
インタビューの質問: JS のタイマーは正確なタイミングを達成できますか? なぜ?
参考回答:
いいえ、理由は次のとおりです。
- コンピューターのハードウェアには原子時計がないため、正確な時刻管理は実現できません。
- OS のタイミング関数自体には若干の誤差があり、JS タイマーは最終的に OS の関数を呼び出すため、この誤差も伴います。
- W3C 標準によれば、ブラウザがタイマーを実装する場合、ネスト レベルが 5 レベルを超える場合、最小時間は 4 ミリ秒となり、計時時間が 4 ミリ秒未満の場合はずれが発生します。
突然変異観察者例えば:
// 立即把一个函数添加到微队列 Promise.resolve().then(函数)
ブラウザには他にも多くのキューがありますが、開発とはほとんど関係がないため考慮されません。
[外部リンク画像転送...(img-HP1VZIML-1683955406483)]
面接の質問: JS のイベント ループについて説明する
参考回答:
イベント ループはメッセージ ループとも呼ばれ、ブラウザがメイン スレッドをレンダリングする方法です。
Chrome のソース コードでは、無限の for ループが開始され、各ループが実行のためにメッセージ キューから最初のタスクをフェッチし、他のスレッドは適切なタイミングでタスクをキューの最後に追加するだけで済みます。
以前は、メッセージ キューは単純にマクロ キューとマイクロ キューに分けられていましたが、これでは複雑なブラウザ環境に対応できなくなり、より柔軟で変更可能な処理方法に置き換えられました。
W3C 公式の説明によると、各タスクは異なるタイプを持ち、同じタイプのタスクは同じキューに存在する必要があり、異なるタスクは異なるキューに属することができます。タスクキューごとに優先順位が異なるため、イベントループではブラウザがどのキューを取るかを決定します。ただし、ブラウザにはマイクロキューが必要であり、マイクロキューのタスクには最高の優先順位が設定され、最初にスケジュールされて実行される必要があります。
インタビューの質問: JS のタイマーは正確なタイミングを達成できますか? なぜ?
参考回答:
いいえ、理由は次のとおりです。
- コンピューターのハードウェアには原子時計がないため、正確な時刻管理は実現できません。
- OS のタイミング関数自体には若干の誤差があり、JS タイマーは最終的に OS の関数を呼び出すため、この誤差も伴います。
- W3C 標準によると、ブラウザがタイマーを実装する場合、ネスト レベルが 5 層を超える場合、最小時間は 4 ミリ秒となり、タイミング時間が 4 ミリ秒未満の場合は偏差が発生します。
- イベントループの影響を受けて、タイマーのコールバック関数はメインスレッドがアイドル状態のときにのみ実行できるため、逸脱が生じます。