Du Yi master class notes (focus: event loop, browser rendering principle)

Du Yi master class notes (focus: event loop, browser rendering principle)

Responsive Principle (Duyi)

What is Data Responsive?

Association of functions and data (important)

After the data changes, the functions that depend on the data will be automatically rerun (important)

  1. Monitored function

    render, computed callback, watch, watchEffect

  2. Responsive data is used during function execution (responsive data must be an object)

  3. Reactive data changes will cause the function to rerun

defineProperty(Wataichi)

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;
}

The use of constructor (shopping cart case) (Duyi)

image-20230428110145906

The above code is equivalent to the following:

image-20230428110237123

// 单件商品的数据
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);
  }
});

How does the browser render the page? (Waichi)

When the browser's network thread receives the HTML document, it will generate a rendering task and pass it to the message queue of the rendering main thread.

Under the action of the event loop mechanism, the rendering main thread takes out the rendering tasks in the message queue and starts the rendering process.


The entire rendering process is divided into multiple stages, namely: HTML parsing, style calculation, layout, layering, drawing, block, rasterization, drawing

Each stage has a clear input and output, and the output of the previous stage will become the input of the next stage.

In this way, the entire rendering process forms a well-organized production pipeline.


The first step in rendering is to parse the HTML .

When encountering CSS during parsing, parse CSS, and execute JS when encountering JS. In order to improve parsing efficiency, the browser will start a pre-parsing thread before starting parsing, and first download the external CSS file and external JS file in HTML.

If the main thread parses to linkthe location, the external CSS file has not been downloaded and parsed yet, and the main thread will not wait and continue to parse the subsequent HTML. This is because the work of downloading and parsing the CSS happens in the preparsing thread. This is the fundamental reason why CSS does not block HTML parsing.

If the main thread parses to scriptthe position, it will stop parsing the HTML, and wait for the JS file to be downloaded, and the global code parsing is completed before continuing to parse the HTML. This is because the execution of the JS code may modify the current DOM tree, so the generation of the DOM tree must be suspended. This is the root cause of JS blocking HTML parsing.

After the first step is completed, the DOM tree and CSSOM tree will be obtained. The browser's default style, internal style, external style, and inline style will all be included in the CSSOM tree.


The next step in rendering is style calculation .

The main thread will traverse the obtained DOM tree and calculate its final style for each node in the tree in turn, which is called Computed Style.

In this process, many preset values ​​will become absolute values, for example, redit will become rgb(255,0,0); relative units will become absolute units, for example, emit will becomepx

After this step is completed, you will get a DOM tree with styles.


Next is the layout , and the layout tree will be obtained after the layout is completed.

The layout stage traverses each node of the DOM tree in turn and calculates the geometric information of each node. For example, the width and height of the node, and the position relative to the containing block.

Most of the time, there is not a one-to-one correspondence between the DOM tree and the layout tree.

For example display:none, the nodes have no geometric information, so they will not be generated into the layout tree; another example is the use of pseudo-element selectors, although these pseudo-element nodes do not exist in the DOM tree, they have geometric information, so they will be generated into the layout tree. There are also anonymous line boxes, anonymous block boxes, etc., which will lead to a one-to-one correspondence between the DOM tree and the layout tree.


The next step is layering

The main thread uses a complex set of strategies to layer the entire layout tree.

The advantage of layering is that after a certain layer is changed in the future, only the layer will be processed subsequently, thereby improving efficiency.

Styles such as scrollbars, stacking context, transform, opacity, etc. will more or less affect the layered results, and can also will-changeaffect the layered results to a greater extent through attributes.


The next step is to draw

The main thread will generate a separate drawing instruction set for each layer, which is used to describe how to draw the content of this layer.


After the drawing is completed, the main thread submits the drawing information of each layer to the compositing thread, and the rest of the work will be completed by the compositing thread.

The compositing thread first tiles each layer, dividing it into more small regions.

It will take multiple threads from the thread pool to complete the chunked work.


After the block is completed, enter the rasterization stage.

The compositing thread offloads the block information to the GPU process, which completes the rasterization at a very high speed.

The GPU process starts multiple threads to complete the rasterization, and prioritizes chunks that are closer to the viewport area.

The result of rasterization is a bitmap piece by piece


The last stage is to draw

After the compositing thread gets the bitmap of each layer and each block, it generates "guidance (quad)" information one by one.

The guidelines identify where on the screen each bitmap should be drawn, taking into account distortions such as rotation and scaling.

The deformation happens on the compositing thread, not on the main rendering thread, which is transformthe essence of high efficiency.

The compositing thread will submit the quad to the GPU process, which will generate a system call and submit it to the GPU hardware to complete the final screen imaging.

image-20230426175916683

image-20230426175930373

image-20230426175946170

image-20230426175957615

image-20230426180018996

image-20230426180033746

image-20230426180044793

image-20230426180057617

image-20230426180105918 image-20230426180115334 image-20230426180130456

What is reflow?

image-20230426180144593

The essence of reflow is to recalculate the layout tree.

When an operation that affects the layout tree is performed, the layout tree needs to be recalculated, which will trigger layout.

In order to avoid repeated calculations of the layout tree caused by multiple consecutive operations, the browser will combine these operations, and perform unified calculations after all JS codes are completed. Therefore, reflow caused by changing properties is done asynchronously.

Also because of this, when JS obtains layout attributes, it may cause failure to obtain the latest layout information.

After repeated weighing, the browser finally decided to reflow immediately after obtaining the attribute.

What is repaint?

The essence of repaint is to recalculate the drawing instructions based on the layered information.

When the visible style is changed, it needs to be recalculated, which will trigger repaint.

Since the layout information of the element also belongs to the visible style, reflow will definitely cause repaint.

Why is the efficiency of transform high?

image-20230426180203897

Because transform does not affect layout or drawing instructions, it only affects the last "draw" stage of the rendering process

Since the draw stage is in the compositing thread, the change of transform will hardly affect the rendering main thread. Conversely, no matter how busy the rendering main thread is, it will not affect the change of transform.

Event loop (Duyi)

The browser's process model

What is a process?

A program needs its own dedicated memory space to run, and this memory space can be simply understood as a process

image-20220809205743532

Each application has at least one process, and the processes are independent of each other. Even if they want to communicate, both parties need to agree.

What is a thread?

Once you have a process, you can run the program's code.

The "person" who runs the code is called a "thread".

A process has at least one thread, so after the process is started, a thread is automatically created to run the code, which is called the main thread.

If the program needs to execute multiple blocks of code at the same time, the main thread will start more threads to execute the code, so a process can contain multiple threads.

image-20220809210859457

What processes and threads does the browser have?

The browser is a multi-process multi-threaded application

The inner workings of browsers are extremely complex.

In order to avoid mutual influence and reduce the chance of serial crashes, when the browser is started, it will automatically start multiple processes.

image-20220809213152371

You can view all current processes in the browser's task manager

Among them, the most important processes are:

  1. browser process

    Mainly responsible for interface display, user interaction, sub-process management, etc. Inside the browser process, multiple threads are started to handle different tasks.

  2. network process

    Responsible for loading network resources. Multiple threads are started inside the network process to handle different network tasks.

  3. Rendering process (the process that this lesson focuses on)

    After the rendering process starts, a rendering main thread will be started , and the main thread is responsible for executing HTML, CSS, and JS codes.

    By default, the browser starts a new rendering process for each tab to ensure that different tabs do not affect each other.

    The default mode may change in the future, interested students can refer to the official chrome documentation

How does the rendering main thread work?

The main rendering thread is the busiest thread in the browser, and the tasks it needs to handle include but are not limited to:

  • parsing HTML
  • parsing CSS
  • calculation style
  • layout
  • work with layers
  • Draw the page 60 times per second
  • Execute global JS code
  • Execute the event handler
  • Execute the callback function of the timer

Thinking question: Why doesn't the rendering process use multiple threads to handle these things?

To handle so many tasks, the main thread has encountered an unprecedented problem: how to schedule tasks?

for example:

  • I am executing a JS function, halfway through the execution, the user clicks the button, should I immediately execute the click event handler?
  • I'm executing a JS function, and a timer reaches the time in the middle of the execution, should I immediately execute its callback?
  • The browser process notifies me that "the user clicked the button", at the same time, a certain timer has also expired, which one should I handle?

The rendering main thread came up with a brilliant idea to handle this: queuing

image-20220809223027806

  1. At the very beginning, the rendering main thread will enter an infinite loop
  2. Each loop checks whether there are tasks in the message queue. If there is, take out the first task to execute, and enter the next cycle after executing one; if not, enter the dormant state.
  3. All other threads (including threads of other processes) can add tasks to the message queue at any time. New tasks are added to the end of the message queue. When adding a new task, if the main thread is dormant, it will be woken up to continue fetching tasks in a loop

In this way, each task can be carried out in an orderly and continuous manner.

The whole process is called event loop (message loop)

some explanations

What is asynchronous?

During the execution of the code, some tasks that cannot be processed immediately, such as:

  • Tasks that need to be performed after the timing is completed—— setTimeout,setInterval
  • Tasks that need to be performed after the network communication is completed – XHR,Fetch
  • Tasks to be performed after user actions –addEventListener

If the rendering main thread waits for the timing of these tasks to arrive, it will cause the main thread to be in a "blocked" state for a long time, causing the browser to "stuck"

image-20220810104344296

The rendering main thread undertakes extremely important work, so it cannot be blocked anyway!

Therefore, browsers choose asynchronous to solve this problem

image-20220810104858857

Using the asynchronous method, the rendering main thread will never block

Interview question: How to understand the asynchrony of JS?

Reference answer:

JS is a single-threaded language because it runs on the browser's main rendering thread, and there is only one rendering main thread.

The rendering main thread undertakes many tasks, rendering pages and executing JS are all running in it.

If you use a synchronous method, it is very likely that the main thread will be blocked, which will cause many other tasks in the message queue to be unable to be executed. In this way, on the one hand, the busy main thread will waste time in vain, and on the other hand, the page cannot be updated in time, causing the user to be stuck.

So the browser uses an asynchronous way to avoid it. The specific method is that when certain tasks occur, such as timers, networks, and event monitoring, the main thread will hand over the tasks to other threads for processing, and immediately end the execution of the tasks by itself, and then execute subsequent codes. When other threads are finished, wrap the callback function passed in advance into a task, add it to the end of the message queue, and wait for the main thread to schedule execution.

In this asynchronous mode, the browser never blocks, thus ensuring the smooth operation of the single thread to the greatest extent.

Why does JS block rendering?

Look at the code first

<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>

What happens when the button is clicked?

<see specific demo>

image-20230426104307271

Are tasks prioritized?

Tasks have no priority, first in first out in the message queue

But the message queue has priority

According to the latest explanation from W3C:

  • Each task has a task type, tasks of the same type must be in one queue, and tasks of different types can belong to different queues.
    In an event cycle, the browser can fetch tasks from different queues for execution according to the actual situation.
  • The browser must prepare a microtask, and tasks in the microqueue take priority over all other tasks
    https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint

As browsers grow in sophistication, the W3C no longer uses macro queues

In the current chrome implementation, at least the following queues are included:

  • Delay queue: used to store callback tasks after the timer arrives, with priority "medium"
  • Interaction queue: used to store event processing tasks generated after user operations, with a priority of "high"
  • Micro-queue: the user stores the tasks that need to be executed the fastest, and the priority is "highest"

The main way to add tasks to the microqueue is to use Promise and MutationObserver

For example:

// 立即把一个函数添加到微队列
Promise.resolve().then(函数)

There are many other queues in the browser, which are not considered because they have little to do with our development

image-20230426104359281

Interview question: Explain the event loop of JS

Reference answer:

The event loop, also known as the message loop, is the way the browser renders the main thread.

In the source code of Chrome, it starts an endless for loop, each loop fetches the first task from the message queue for execution, and other threads only need to add the task to the end of the queue at an appropriate time.

In the past, message queues were simply divided into macro queues and micro queues. This statement can no longer satisfy the complex browser environment, and a more flexible and changeable processing method has been replaced.

According to the official W3C explanation, each task has a different type, tasks of the same type must be in the same queue, and different tasks can belong to different queues. Different task queues have different priorities. In an event loop, the browser decides which queue to take. But the browser must have a micro-queue, and the tasks of the micro-queue must have the highest priority, and must be scheduled and executed first.

Interview question: Can the timer in JS achieve accurate timing? Why?

Reference answer:

No, because:

  1. Computer hardware does not have atomic clocks, so precise timekeeping cannot be achieved
  2. The timing function of the operating system itself has a small amount of deviation. Since the JS timer finally calls the function of the operating system, it also carries these deviations.
  3. According to W3C standards, when a browser implements a timer, if the nesting level exceeds 5 levels, it will have a minimum time of 4 milliseconds, which will cause a deviation when the timing time is less than 4 milliseconds. If you use Promise,
    MutationObserver

For example:

// 立即把一个函数添加到微队列
Promise.resolve().then(函数)

There are many other queues in the browser, which are not considered because they have little to do with our development

[External link image transfer...(img-HP1VZIML-1683955406483)]

Interview question: Explain the event loop of JS

Reference answer:

The event loop, also known as the message loop, is the way the browser renders the main thread.

In the source code of Chrome, it starts an endless for loop, each loop fetches the first task from the message queue for execution, and other threads only need to add the task to the end of the queue at an appropriate time.

In the past, message queues were simply divided into macro queues and micro queues. This statement can no longer satisfy the complex browser environment, and a more flexible and changeable processing method has been replaced.

According to the official W3C explanation, each task has a different type, tasks of the same type must be in the same queue, and different tasks can belong to different queues. Different task queues have different priorities. In an event loop, the browser decides which queue to take. But the browser must have a micro-queue, and the tasks of the micro-queue must have the highest priority, and must be scheduled and executed first.

Interview question: Can the timer in JS achieve accurate timing? Why?

Reference answer:

No, because:

  1. Computer hardware does not have atomic clocks, so precise timekeeping cannot be achieved
  2. The timing function of the operating system itself has a small amount of deviation. Since the JS timer finally calls the function of the operating system, it also carries these deviations.
  3. According to the W3C standard, when the browser implements the timer, if the nesting level exceeds 5 layers, it will have a minimum time of 4 milliseconds, which will cause a deviation when the timing time is less than 4 milliseconds
  4. Affected by the event loop, the callback function of the timer can only run when the main thread is idle, so it brings a deviation

Guess you like

Origin blog.csdn.net/qq_53461589/article/details/130656446