【THE LAST TIME】徹底吃透 JavaScript 執行機制

来源:https://www.cnblogs.com/Nealyang/archive/2019/09/29/11606260.html
-Advertisement-
Play Games

前言 The last time, I have learned 【THE LAST TIME】一直是我想寫的一個系列,旨在厚積薄發,重溫前端。 也是給自己的查缺補漏和技術分享。 歡迎大家多多評論指點吐槽。 系列文章均首發於公眾號【全棧前端精選】,筆者文章集合詳見 "Nealyang/persona ...


前言

The last time, I have learned

【THE LAST TIME】一直是我想寫的一個系列,旨在厚積薄發,重溫前端。

也是給自己的查缺補漏和技術分享。

歡迎大家多多評論指點吐槽。

系列文章均首發於公眾號【全棧前端精選】,筆者文章集合詳見Nealyang/personalBlog。目錄皆為暫定

執行 & 運行

首先我們需要聲明下,JavaScript 的執行和運行是兩個不同概念的,執行,一般依賴於環境,比如 node、瀏覽器、Ringo 等, JavaScript 在不同環境下的執行機制可能並不相同。而今天我們要討論的 Event Loop 就是 JavaScript 的一種執行方式。所以下文我們還會梳理 node 的執行方式。而運行呢,是指JavaScript 的解析引擎。這是統一的。

關於 JavaScript

此篇文章中,這個小標題下,我們只需要牢記一句話: JavaScript 是單線程語言 ,無論HTML5 裡面 Web-Worker 還是 node 裡面的cluster都是“紙老虎”,而且 cluster 還是進程管理相關。這裡讀者註意區分:進程和線程。

既然 JavaScript 是單線程語言,那麼就會存在一個問題,所有的代碼都得一句一句的來執行。就像我們在食堂排隊打飯,必須一個一個排隊點菜結賬。那些沒有排到的,就得等著~

概念梳理

在詳解執行機制之前,先梳理一下 JavaScript 的一些基本概念,方便後面我們說到的時候大伙兒心裡有個印象和大概的輪廓。

事件迴圈(Event Loop)

什麼是 Event Loop?

其實這個概念還是比較模糊的,因為他必須得結合著運行機制來解釋。

JavaScript 有一個主線程 main thread,和調用棧 call-stack 也稱之為執行棧。所有的任務都會放到調用棧中等待主線程來執行。

暫且,我們先理解為上圖的大圈圈就是 Event Loop 吧!並且,這個圈圈,一直在轉圈圈~ 也就是說,JavaScriptEvent Loop 是伴隨著整個源碼文件生命周期的,只要當前 JavaScript 在運行中,內部的這個迴圈就會不斷地迴圈下去,去尋找 queue 裡面能執行的 task

任務隊列(task queue)

task,就是任務的意思,我們這裡理解為每一個語句就是一個任務

console.log(1);
console.log(2);

如上語句,其實就是就可以理解為兩個 task

queue 呢,就是FIFO的隊列!

所以 Task Queue 就是承載任務的隊列。而 JavaScriptEvent Loop 就是會不斷地過來找這個 queue,問有沒有 task 可以運行運行。

同步任務(SyncTask)、非同步任務(AsyncTask)

同步任務說白了就是主線程來執行的時候立即就能執行的代碼,比如:

console.log('this is THE LAST TIME');
console.log('Nealyang');

代碼在執行到上述 console 的時候,就會立即在控制臺上列印相應結果。

而所謂的非同步任務就是主線程執行到這個 task 的時候,“唉!你等會,我現在先不執行,等我 xxx 完了以後我再來等你執行” 註意上述我說的是等你來執行。

說白了,非同步任務就是你先去執行別的 task,等我這 xxx 完之後再往 Task Queue 裡面塞一個 task 的同步任務來等待被執行

setTimeout(()=>{
  console.log(2)
});
console.log(1);

如上述代碼,setTimeout 就是一個非同步任務,主線程去執行的時候遇到 setTimeout 發現是一個非同步任務,就先註冊了一個非同步的回調,然後接著執行下麵的語句console.log(1),等上面的非同步任務等待的時間到了以後,在執行console.log(2)。具體的執行機制會在後面剖析。

  • 主線程自上而下執行所有代碼
  • 同步任務直接進入到主線程被執行,而非同步任務則進入到 Event Table 並註冊相對應的回調函數
  • 非同步任務完成後,Event Table 會將這個函數移入 Event Queue
  • 主線程任務執行完了以後,會從Event Queue中讀取任務,進入到主線程去執行。
  • 迴圈如上

上述動作不斷迴圈,就是我們所說的事件迴圈(Event Loop)。

小試牛刀

ajax({
    url:www.Nealyang.com,
    data:prams,
    success:() => {
        console.log('請求成功!');
    },
    error:()=>{
        console.log('請求失敗~');
    }
})
console.log('這是一個同步任務');
  • ajax 請求首先進入到 Event Table ,分別註冊了onErroronSuccess回調函數。
  • 主線程執行同步任務:console.log('這是一個同步任務');
  • 主線程任務執行完畢,看Event Queue是否有待執行的 task,這裡是不斷地檢查,只要主線程的task queue沒有任務執行了,主線程就一直在這等著
  • ajax 執行完畢,將回調函數pushEvent Queue。(步驟 3、4 沒有先後順序而言)
  • 主線程“終於”等到了Event Queue里有 task可以執行了,執行對應的回調任務。
  • 如此往複。

巨集任務(MacroTask)、微任務(MicroTask)

JavaScript 的任務不僅僅分為同步任務和非同步任務,同時從另一個維度,也分為了巨集任務(MacroTask)和微任務(MicroTask)。

先說說 MacroTask,所有的同步任務代碼都是MacroTask(這麼說其實不是很嚴謹,下麵解釋),setTimeoutsetIntervalI/OUI Rendering 等都是巨集任務。

MicroTask,為什麼說上述不嚴謹我卻還是強調所有的同步任務都是 MacroTask 呢,因為我們僅僅需要記住幾個 MicroTask 即可,排除法!別的都是 MacroTaskMicroTask 包括:Process.nextTickPromise.then catch finally(註意我不是說 Promise)、MutationObserver

瀏覽器環境下的 Event Loop

當我們梳理完哪些是 MicroTask ,除了那些別的都是 MacroTask 後,哪些是同步任務,哪些又是非同步任務後,這裡就應該徹底的梳理下JavaScript 的執行機制了。

如開篇說到的,執行和運行是不同的,執行要區分環境。所以這裡我們將 Event Loop 的介紹分為瀏覽器和 Node 兩個環境下。

先放圖鎮樓!如果你已經理解了這張圖的意思,那麼恭喜你,你完全可以直接閱讀 Node 環境下的 Event Loop 章節了!

setTimeout、setInterval

setTimeout

setTimeout 就是等多長時間來執行這個回調函數。setInterval 就是每隔多長時間來執行這個回調。

let startTime = new Date().getTime();

setTimeout(()=>{
  console.log(new Date().getTime()-startTime);
},1000);

如上代碼,顧名思義,就是等 1s 後再去執行 console。放到瀏覽器下去執行,OK,如你所願就是如此。

但是這次我們在探討 JavaScript 的執行機制,所以這裡我們得探討下如下代碼:

let startTime = new Date().getTime();

console.log({startTime})

setTimeout(()=>{
  console.log(`開始執行回調的相隔時差:${new Date().getTime()-startTime}`);
},1000);

for(let i = 0;i<40000;i++){
  console.log(1)
}

如上運行,setTimeout 的回調函數等到 4.7s 以後才執行!而這時候,我們把 setTimeout 的 1s 延遲給刪了:

let startTime = new Date().getTime();

console.log({startTime})

setTimeout(()=>{
  console.log(`開始執行回調的相隔時差:${new Date().getTime()-startTime}`);
},0);

for(let i = 0;i<40000;i++){
  console.log(1)
}

結果依然是等到 4.7s 後才執行setTimeout 的回調。貌似 setTimeout 後面的延遲並沒有產生任何效果!

其實這麼說,又應該回到上面的那張 JavaScript 執行的流程圖了。

setTimeout這裡就是簡單的非同步,我們通過上面的圖來分析上述代碼的一步一步執行情況

  • 首先 JavaScript 自上而下執行代碼
  • 遇到遇到賦值語句、以及第一個 console.log({startTime}) 分別作為一個 task,壓入到立即執行棧中被執行。
  • 遇到 setTImeout 是一個非同步任務,則註冊相應回調函數。(非同步函數告訴你,js 你先別急,等 1s 後我再將回調函數:console.log(xxx)放到 Task Queue 中)
  • OK,這時候 JavaScript 則接著往下走,遇到了 40000 個 for 迴圈的 task,沒辦法,1s 後都還沒執行完。其實這個時候上述的回調已經在Task Queue 中了。
  • 等所有的立即執行棧中的 task 都執行完了,在回頭看 Task Queue 中的任務,發現非同步的回調 task 已經在裡面了,所以接著執行。

打個比方

其實上述的不僅僅是 timeout,而是任何非同步,比如網路請求等。

就好比,我六點鐘下班了,可以安排下自己的活動了!

然後收拾電腦(同步任務)、收拾書包(同步任務)、給女朋友打電話說出來吃飯吧(必然是非同步任務),然後女朋友說你等會,我先化個妝,等我畫好了call你。

那我不能幹等著呀,就接著做別的事情,比如那我就在改個 bug 吧,你好了通知我。結果等她一個小時後說我化好妝了,我們出去吃飯吧。不行!我 bug 還沒有解決掉呢?你等會。。。。其實這個時候你的一小時化妝還是 5 分鐘化妝都已經毫無意義了。。。因為哥哥這會沒空~~

如果我 bug 在半個小時就解決完了,沒別的任務需要執行了,那麼就在這等著呀!必須等著!隨時待命!。然後女朋友來電話了,我化完妝了,我們出去吃飯吧,那麼剛好,我們在你的完成了請求或者 timeout 時間到了後我剛好閑著,那麼我必須立即執行了。

setInterval

說完了 setTimeout,當然不能錯過他的孿生兄弟:setInterval。對於執行順序來說,setInterval會每隔指定的時間將註冊的函數置入 Task Queue,如果前面的任務耗時太久,那麼同樣需要等待。

這裡需要說的是,對於 setInterval(fn,ms) 來說,我們制定沒 xx ms執行一次 fn,其實是沒 xx ms,會有一個fn 進入到 Task Queue 中。一旦 setInterval 的回調函數fn執行時間超過了xx ms,那麼就完全看不出來有時間間隔了。 仔細回味回味,是不是那麼回事?

Promise

關於 Promise 的用法,這裡就不過過多介紹了,後面會在寫《【THE LAST TIME】徹底吃透 JavaScript 非同步》 一文的時候詳細介紹。這裡我們只說 JavaScript 的執行機制。

如上所說,promise.thencatchfinally 是屬於 MicroTask。這裡主要是非同步的區分。展開說明之前,我們結合上述說的,再來“扭曲”梳理一下。

為了避免初學者這時候腦子有點混亂,我們暫時忘掉 JavaScript 非同步任務! 我們暫且稱之為待會再執行的同步任務。

有瞭如上約束後,我們可以說,JavaScript 從一開始就自上而下的執行每一個語句(Task),這時候只能遇到立馬就要執行的任務和待會再執行的任務。對於那待會再執行的任務等到能執行了,也不會立即執行,你得等js 執行完這一趟才行

再打個比方

就像做公交車一樣,公交車不等人呀,公交車路線上有人就會停(農村公交!麽得站牌),但是等公交車來,你跟司機說,我肚子疼要拉x~這時候公交不會等你。你只能拉完以後等公交下一趟再來(大山裡!一個路線就一趟車)。

OK!你拉完了。。。等公交,公交也很快到了!但是,你不能立馬上車,因為這時候前面有個孕婦!有個老人!還有熊孩子,你必須得讓他們先上車,然後你才能上車!

而這些 孕婦、老人、熊孩子所組成的就是傳說中的 MicroTask Queue,而且,就在你和你的同事、朋友就必須在他們後面上車。

這裡我們沒有非同步的概念,只有同樣的一次迴圈回來,有了兩種隊伍,一種優先上車的隊伍叫做MicroTask Queue,而你和你的同事這幫壯漢組成的隊伍就是巨集隊伍(MacroTask Queue)。

一句話理解:一次事件迴圈回來後,開始去執行 Task Queue 中的 task,但是這裡的 task優先順序。所以優先執行 MicroTask Queue 中的 task
,執行完後在執行MacroTask Queue 中的 task

小試牛刀

理論都扯完了,也不知道你懂沒懂。來,期中考試了!

console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

沒必要搞個 setTimeout 有加個 Promise,Promise 裡面再整個 setTimeout 的例子。因為只要上面代碼你懂了,無非就是公交再來一趟而已!

如果說了這麼多,還是沒能理解上圖,那麼公眾號內回覆【1】,手摸手指導!

Node 環境下的 Event Loop

Node中的Event Loop是基於libuv實現的,而libuv是 Node 的新跨平臺抽象層,libuv使用非同步,事件驅動的編程方式,核心是提供i/o的事件迴圈和非同步回調。libuvAPI包含有時間,非阻塞的網路,非同步文件操作,子進程等等。

Event Loop就是在libuv中實現的。所以關於 Node 的 Event Loop學習,有兩個官方途徑可以學習:

在學習 Node 環境下的 Event Loop 之前呢,我們首先要明確執行環境,Node 和瀏覽器的Event Loop是兩個有明確區分的事物,不能混為一談。nodejs的event是基於libuv,而瀏覽器的event loop則在html5的規範中明確定義。

   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

Node 的 Event Loop 分為 6 個階段:

  • timers:執行setTimeout()setInterval()中到期的callback。
  • pending callback: 上一輪迴圈中有少數的I/O callback會被延遲到這一輪的這一階段執行
  • idle, prepare:僅內部使用
  • poll: 最為重要的階段,執行I/O callback,在適當的條件下會阻塞在這個階段
  • check: 執行setImmediate的callback
  • close callbacks: 執行close事件的callback,例如socket.on('close'[,fn])http.server.on('close, fn)

上面六個階段都不包括 process.nextTick()(下文會介紹)

整體的執行機制如上圖所示,下麵我們具體展開每一個階段的說明

timers 階段

timers 階段會執行 setTimeoutsetInterval 回調,並且是由 poll 階段控制的。

在 timers 階段其實使用一個最小堆而不是隊列來保存所有的元素,其實也可以理解,因為timeout的callback是按照超時時間的順序來調用的,並不是先進先出的隊列邏輯)。而為什麼 timer 階段在第一個執行階梯上其實也不難理解。在 Node 中定時器指定的時間也是不准確的,而這樣,就能儘可能的準確了,讓其回調函數儘快執行。

以下是官網給出的例子:

const fs = require('fs');

function someAsyncOperation(callback) {
  // Assume this takes 95ms to complete
  fs.readFile('/path/to/file', callback);
}

const timeoutScheduled = Date.now();

setTimeout(() => {
  const delay = Date.now() - timeoutScheduled;

  console.log(`${delay}ms have passed since I was scheduled`);
}, 100);

// do someAsyncOperation which takes 95 ms to complete
someAsyncOperation(() => {
  const startCallback = Date.now();

  // do something that will take 10ms...
  while (Date.now() - startCallback < 10) {
    // do nothing
  }
});

當進入事件迴圈時,它有一個空隊列(fs.readFile()尚未完成),因此定時器將等待剩餘毫秒數,當到達95ms時,fs.readFile()完成讀取文件並且其完成需要10毫秒的回調被添加到輪詢隊列並執行。

當回調結束時,隊列中不再有回調,因此事件迴圈將看到已達到最快定時器的閾值,然後回到timers階段以執行定時器的回調。
在此示例中,您將看到正在調度的計時器與正在執行的回調之間的總延遲將為105毫秒。

pending callbacks 階段

pending callbacks 階段其實是 I/O 的 callbacks 階段。比如一些 TCP 的 error 回調等。

舉個慄子:如果TCP socket ECONNREFUSED在嘗試connectreceives,則某些* nix系統希望等待報告錯誤。 這將在pending callbacks階段執行。

poll 階段

poll 階段主要有兩個功能:

  • 執行 I/O 回調
  • 處理 poll 隊列(poll queue)中的事件

當時Event Loop 進入到 poll 階段並且 timers 階段沒有任何可執行的 task 的時候(也就是沒有定時器回調),將會有以下兩種情況

  • 如果 poll queue 非空,則 Event Loop就會執行他們,知道為空或者達到system-dependent(系統相關限制)
  • 如果 poll queue 為空,則會發生以下一種情況
    • 如果setImmediate()有回調需要執行,則會立即進入到 check 階段
    • 相反,如果沒有setImmediate()需要執行,則 poll 階段將等待 callback 被添加到隊列中再立即執行,這也是為什麼我們說 poll 階段可能會阻塞的原因。

一旦 poll queue 為空,Event Loop就回去檢查timer 階段的任務。如果有的話,則會回到 timer 階段執行回調。

check 階段

check 階段在 poll 階段之後,setImmediate()的回調會被加入check隊列中,他是一個使用libuv API 的特殊的計數器。

通常在代碼執行的時候,Event Loop 最終會到達 poll 階段,然後等待傳入的鏈接或者請求等,但是如果已經指定了setImmediate()並且這時候 poll 階段已經空閑的時候,則 poll 階段將會被中止然後開始 check 階段的執行。

close callbacks 階段

如果一個 socket 或者事件處理函數突然關閉/中斷(比如:socket.destroy()),則這個階段就會發生 close 的回調執行。否則他會通過 process.nextTick() 發出。

setImmediate() vs setTimeout()

setImmediate()setTimeout()非常的相似,區別取決於誰調用了它。

  • setImmediate在 poll 階段後執行,即check 階段
  • setTimeout 在 poll 空閑時且設定時間到達的時候執行,在 timer 階段

計時器的執行順序將根據調用它們的上下文而有所不同。 如果兩者都是從主模塊中調用的,則時序將受到進程性能的限制。

例如,如果我們運行以下不在I / O周期(即主模塊)內的腳本,則兩個計時器的執行順序是不確定的,因為它受進程性能的約束:

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

setImmediate(() => {
  console.log('immediate');
});
$ node timeout_vs_immediate.js
timeout
immediate

$ node timeout_vs_immediate.js
immediate
timeout

如果在一個I/O 周期內移動這兩個調用,則始終首先執行立即回調:

// timeout_vs_immediate.js
const fs = require('fs');

fs.readFile(__filename, () => {
  setTimeout(() => {
    console.log('timeout');
  }, 0);
  setImmediate(() => {
    console.log('immediate');
  });
});
$ node timeout_vs_immediate.js
immediate
timeout

$ node timeout_vs_immediate.js
immediate
timeout

所以與setTimeout()相比,使用setImmediate()的主要優點是,如果在I / O周期內安排了任何計時器,則setImmediate()將始終在任何計時器之前執行,而與存在多少計時器無關。

nextTick queue

可能你已經註意到process.nextTick()並未顯示在圖中,即使它是非同步API的一部分。 所以他擁有一個自己的隊列:nextTickQueue

這是因為process.nextTick()從技術上講不是Event Loop的一部分。 相反,無論當前事件迴圈的當前階段如何,都將在當前操作完成之後處理nextTickQueue

如果存在 nextTickQueue,就會清空隊列中的所有回調函數,並且優先於其他 microtask 執行。

setTimeout(() => {
 console.log('timer1')
 Promise.resolve().then(function() {
   console.log('promise1')
 })
}, 0)
process.nextTick(() => {
 console.log('nextTick')
 process.nextTick(() => {
   console.log('nextTick')
   process.nextTick(() => {
     console.log('nextTick')
     process.nextTick(() => {
       console.log('nextTick')
     })
   })
 })
})
// nextTick=>nextTick=>nextTick=>nextTick=>timer1=>promise1

process.nextTick() vs setImmediate()

從使用者角度而言,這兩個名稱非常的容易讓人感覺到困惑。

  • process.nextTick()在同一階段立即觸發
  • setImmediate()在事件迴圈的以下迭代或“tick”中觸發

貌似這兩個名稱應該呼喚下!的確~官方也這麼認為。但是他們說這是歷史包袱,已經不會更改了。

這裡還是建議大家儘可能使用setImmediate。因為更加的讓程式可控容易推理。

至於為什麼還是需要 process.nextTick,存在即合理。這裡建議大家閱讀官方文檔:why-use-process-nexttick

Node與瀏覽器的 Event Loop 差異

一句話總結其中:瀏覽器環境下,microtask的任務隊列是每個macrotask執行完之後執行。而在Node.js中,microtask會在事件迴圈的各個階段之間執行,也就是一個階段執行完畢,就會去執行microtask隊列的任務。

上圖來自浪里行舟

最後

來~期末考試了

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

評論區留下你的答案吧~~老鐵!

參考文獻

學習交流

關註公眾號: 【全棧前端精選】 每日獲取好文推薦。

公眾號內回覆 【1】,加入全棧前端學習群,一起交流。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • js的抖動 在 js 中 改變視窗大小 & 上下滾動滾動條 & 反覆向輸入框中輸入內容 ... , 如果綁定了相應的事件 , 這些事件的觸發頻率非常高, 嚴重影響用戶體驗和伺服器的性能 , 這種問題 在js中 就叫 js 的抖動 . 解決方法 : 防抖 & 節流 js的防抖 就是在 觸發事件 中設置 ...
  • 1、圖片和多媒體 (1) 圖片:img元素 src 屬性:圖片路徑; alt 屬性:圖片無法顯示時使用的替代文字; title:滑鼠懸停時顯示的文字 ; (2) 視頻:video元素 src 屬性:視頻路徑 ; controls 屬性:【布爾屬性】指定後,會顯示播放控制項; autoplay :【布爾 ...
  • axios是一個基於Promise 用於瀏覽器和 nodejs 的 HTTP 客戶端,它可以在瀏覽器和node環境下運行,在github上已經有六七萬個星了,axios使用很方便,很多人在使用他,vue官方也推薦使用axios了,技術這東西還是隨主流吧,大家都用肯定有它的特長所在。 axios現在最 ...
  • 因為bootstrap自帶的固定列效果滿足不了公司需求,所以藉助fixed-table這個插件完成了iview固定列的效果 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>左右兩側固定列,中間內容可以橫向 ...
  • 公司最近有需求要做樹形式table。因為是前後端不分離項目,且之前已經引入了bootstrap table插件,現把實現方式分享一下: <!DOCTYPE HTML> <html lang="zh-cn"> <head> <meta charset="utf-8" /> <meta http-equ ...
  • 1.html定義: html是一種超文本標記語言,“超文本”是指頁面可以包含圖片、鏈接、音樂、程式等非文字元素。 Html不是一種編程語言。 2.html5的新特性: 用於繪畫的canvas元素; 用於媒介回放的video和audio元素; 對本地離線存儲的更好支持; 新的特殊內容元素如articl ...
  • 寫在前面 權重這個概念,相信對許多進行過前端開發的小伙伴來說肯定並不陌生,有時候一個樣式添加不上,我們就會一個 !important 懟上去,一切就好像迎刃而解了。但還有的時候,!important也並不能解決我們的問題,下麵請跟隨我來詳細瞭解一下css的權重吧! 探索權重 指某一因素或指標相對於某 ...
  • 1、聲明:@keyframes name{ }; 2、涉及到的屬性 animation-name:動畫名稱 animation-duration:單次動畫總時長 animation-timing-function:時間函數 animation-delay:播放前延時的時長 animation-ite ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...