我從來不理解JavaScript閉包,直到有人這樣向我解釋它...

来源:https://www.cnblogs.com/fundebug/archive/2019/02/12/10363880.html
-Advertisement-
Play Games

正如標題所述,JavaScript閉包對我來說一直有點神秘,看過很多閉包的文章,在工作使用過閉包,有時甚至在項目中使用閉包,但我確實是這是在使用閉包的知識。 最近看國外的一些文章,終於,有人用於一種讓我明白方式對閉包進行瞭解釋,我將在本文中嘗試使用這種方法來解釋閉包。 ...


摘要: 理解JS閉包。

Fundebug經授權轉載,版權歸原作者所有。

正如標題所述,JavaScript閉包對我來說一直有點神秘,看過很多閉包的文章,在工作使用過閉包,有時甚至在項目中使用閉包,但我確實是這是在使用閉包的知識。

最近看國外的一些文章,終於,有人用於一種讓我明白方式對閉包進行瞭解釋,我將在本文中嘗試使用這種方法來解釋閉包。

準備

在理解閉包之前,有個重要的概念需要先瞭解一下,就是 js 執行上下文。

這篇文章是執行上下文 很不錯的入門教程,文章中提到:

當代碼在JavaScript中運行時,執行代碼的環境非常重要,並將概括為以下幾點:

全局代碼——第一次執行代碼的預設環境。

函數代碼——當執行流進入函數體時。

(…) —— 我們當作 執行上下文 是當前代碼執行的一個環境與範圍。

換句話說,當我們啟動程式時,我們從全局執行上下文中開始。一些變數是在全局執行上下文中聲明的。我們稱之為全局變數。當程式調用一個函數時,會發生什麼?

以下幾個步驟:

  • JavaScript創建一個新的執行上下文,我們叫作本地執行上下文。
  • 這個本地執行上下文將有它自己的一組變數,這些變數將是這個執行上下文的本地變數。
  • 新的執行上下文被推到到執行堆棧中。可以將執行堆棧看作是一種保存程式在其執行中的位置的容器。

函數什麼時候結束?當它遇到一個return語句或一個結束括弧}。

當一個函數結束時,會發生以下情況:

  • 這個本地執行上下文從執行堆棧中彈出。
  • 函數將返回值返回調用上下文。調用上下文是調用這個本地的執行上下文,它可以是全局執行上下文,也可以是另外一個本地的執行上下文。這取決於調用執行上下文來處理此時的返回值,返回的值可以是一個對象、一個數組、一個函數、一個布爾值等等,如果函數沒有return語句,則返回undefined。
  • 這個本地執行上下文被銷毀,銷毀是很重要,這個本地執行上下文中聲明的所有變數都將被刪除,不在有變數,這個就是為什麼 稱為本地執行上下文中自有的變數。

基礎的例子

在討論閉包之前,讓我們看一下下麵的代碼:

1: let a = 3
2: function addTwo(x) {
3:   let ret = x + 2
4:   return ret
5: }
6: let b = addTwo(a)
7: console.log(b)

為了理解JavaScript引擎是如何工作的,讓我們詳細分析一下:

  • 在第1行,我們在全局執行上下文中聲明瞭一個新變數a,並將賦值為3。
  • 接下來就變得棘手了,第2行到第5行實際上是在一起的。這裡發生了什麼? 我們在全局執行上下文中聲明瞭一個名為addTwo的新變數,我們給它分配了什麼?一個函數定義。兩個括弧{}之間的任何內容都被分配給addTwo,函數內部的代碼沒有被求值,沒有被執行,只是存儲在一個變數中以備將來使用。
  • 現在我們在第6行。它看起來很簡單,但是這裡有很多東西需要拆開分析。首先,我們在全局執行上下文中聲明一個新變數,並將其標記為b,變數一經聲明,其值即為undefined。
  • 接下來,仍然在第6行,我們看到一個賦值操作符。我們準備給變數b賦一個新值,接下來我們看到一個函數被調用。當您看到一個變數後面跟著一個圓括弧(…)時,這就是調用函數的信號,接著,每個函數都返回一些東西(值、對象或 undefined),無論從函數返回什麼,都將賦值給變數b
  • 但是首先我們需要調用標記為addTwo的函數。JavaScript將在其全局執行上下文記憶體中查找名為addTwo的變數。噢,它找到了一個,它是在步驟2(或第2 - 5行)中定義的。變數add2包含一個函數定義。註意,變數a作為參數傳遞給函數。JavaScript在全局執行上下文記憶體中搜索變數a,找到它,發現它的值是3,並將數字3作為參數傳遞給函數,準備好執行函數。
  • 現在執行上下文將切換,創建了一個新的本地執行上下文,我們將其命名為“addTwo執行上下文”,執行上下文被推送到調用堆棧上。在addTwo執行上下文中,我們要做的第一件事是什麼?
  • 你可能會說,“在addTwo執行上下文中聲明瞭一個新的變數ret”,這是不對的。正確的答案是,我們需要先看函數的參數。在addTwo執行上下文中聲明一個新的變數`x``,因為值3是作為參數傳遞的,所以變數x被賦值為3。
  • 下一步是:在addTwo執行上下文中聲明一個新的變數ret。它的值被設置為 undefined(第三行)。
  • 仍然是第3行,需要執行一個相加操作。首先我們需要x的值,JavaScript會尋找一個變數x,它會首先在addTwo執行上下文中尋找,找到了一個值為3。第二個操作數是數字2。兩個相加結果為5就被分配給變數ret
  • 第4行,我們返回變數ret的內容,在addTwo執行上下文中查找,找到值為5,返回,函數結束。
  • 第4 - 5行,函數結束。addTwo執行上下文被銷毀,變數xret被消去了,它們已經不存在了。addTwo執行上下文從調用堆棧中彈出,返回值返回給調用上下文,在這種情況下,調用上下文是全局執行上下文,因為函數addTwo是從全局執行上下文調用的。
  • 現在我們繼續第4步的內容,返回值5被分配給變數b,程式仍然在第6行。
  • 在第7行,b的值 5 被列印到控制台了。

對於一個非常簡單的程式,這是一個非常冗長的解釋,我們甚至還沒有涉及閉包。但肯定會涉及的,不過首先我們得繞一兩個彎。

詞法作用域(Lexical scope)

我們需要理解詞法作用域的一些知識。請看下麵的例子:

1: let val1 = 2
2: function multiplyThis(n) {
3:   let ret = n * val1
4:   return ret
5: }
6: let multiplied = multiplyThis(6)
7: console.log('example of scope:', multiplied)

這裡想說明,我們在函數執行上下文中有變數,在全局執行上下文中有變數。JavaScript的一個複雜之處在於它如何查找變數,如果在函數執行上下文中找不到變數,它將在調用上下文中尋找它,如果在它的調用上下文中沒有找到,就一直往上一級,直到它在全局執行上下文中查找為止。(如果最後找不到,它就是 undefined)。

下麵列出向個步驟來解釋一下(如果你已經熟悉了,請跳過):

  • 在全局執行上下文中聲明一個新的變數val1,並將其賦值為2。
  • 行2 - 5,聲明一個新的變數 multiplyThis,並給它分配一個函數定義。
  • 第六行,聲明一個在全局執行上下文 multiplied 新變數。
  • 從全局執行上下文記憶體中查找變數multiplyThis,並將其作為函數執行,傳遞數字 6 作為參數。
  • 新函數調用(創建新執行上下文),創建一個新的 multiplyThis 函數執行上下文。
  • multiplyThis 執行上下文中,聲明一個變數n並將其賦值為6
  • 第 3 行。在multiplyThis執行上下文中,聲明一個變數ret
  • 繼續第 3 行。對兩個操作數 n 和 val1 進行乘法運算.在multiplyThis執行上下文中查找變數 n。我們在步驟6中聲明瞭它,它的內容是數字6。在multiplyThis執行上下文中查找變數val1multiplyThis執行上下文沒有一個標記為 val1 的變數。我們向調用上下文查找,調用上下文是全局執行上下文,在全局執行上下文中尋找 val1。哦,是的、在那兒,它在步驟1中定義,數值是2。
  • 繼續第 3 行。將兩個操作數相乘並將其賦值給ret變數,6 * 2 = 12,ret現在值為 12。
  • 返回ret變數,銷毀multiplyThis執行上下文及其變數 retn 。變數 val1 沒有被銷毀,因為它是全局執行上下文的一部分。
  • 回到第6行。在調用上下文中,數字 12 賦值給 multiplied 的變數。
  • 最後在第7行,我們在控制臺中列印 multiplied 變數的值

在這個例子中,我們需要記住一個函數可以訪問在它的調用上下文中定義的變數,這個就是詞法作用域(Lexical scope)

返回函數的函數

在第一個例子中,函數addTwo返回一個數字。請記住,函數可以返回任何東西。讓我們看一個返回函數的函數示例,因為這對於理解閉包非常重要。看粟子:

 1: let val = 7
 2: function createAdder() {
 3:   function addNumbers(a, b) {
 4:     let ret = a + b
 5:     return ret
 6:   }
 7:   return addNumbers
 8: }
 9: let adder = createAdder()
10: let sum = adder(val, 8)
11: console.log('example of function returning a function: ', sum)

讓我們回到分步分解:

  • 第一行。我們在全局執行上下文中聲明一個變數val並賦值為 7。
  • 行2 - 8。我們在全局執行上下文中聲明瞭一個名為 createAdder 的變數,併為其分配了一個函數定義。第3至7行描述了上述函數定義,和以前一樣,在這一點上,我們沒有直接討論這個函數。我們只是將函數定義存儲到那個變數(createAdder)中。
  • 第9行。我們在全局執行上下文中聲明瞭一個名為 adder 的新變數,暫時,值為 undefined。
  • 第9行。我們看到括弧(),我們需要執行或調用一個函數,查找全局執行上下文的記憶體並查找名為createAdder 的變數,它是在步驟2中創建的。好吧,我們調用它。
  • 調用函數時,執行到第2行。創建一個新的createAdder執行上下文。我們可以在createAdder的執行上下文中創建自有變數。js 引擎將createAdder的上下文添加到調用堆棧。這個函數沒有參數,讓我們直接跳到它的主體部分.
  • 第 3 - 6 行。我們有一個新的函數聲明,我們在createAdder執行上下文中創建一個變數addNumbers。這很重要,addnumber只存在於createAdder執行上下文中。我們將函數定義存儲在名為 `addNumbers`` 的自有變數中。
  • 在第7行,我們返回變數addNumbers的內容。js引擎查找一個名為addNumbers的變數並找到它,這是一個函數定義。好的,函數可以返回任何東西,包括函數定義。我們返addNumbers的定義。第4行和第5行括弧之間的內容構成該函數定義。
  • 返回時,createAdder執行上下文將被銷毀。addNumbers 變數不再存在。但addNumbers函數定義仍然存在,因為它返回並賦值給了adder 變數。
  • 第10行。我們在全局執行上下文中定義了一個新的變數 sum,先負值為 undefined;
  • 接下來我們需要執行一個函數。哪個函數?是名為adder變數中定義的函數。我們在全局執行上下文中查找它,果然找到了它,這個函數有兩個參數。
  • 讓我們查找這兩個參數,第一個是我們在步驟1中定義的變數val,它表示數字7,第二個是數字8。
  • 現在我們要執行這個函數,函數定義概述在第3-5行,因為這個函數是匿名,為了方便理解,我們暫且叫它adder吧。這時創建一個adder函數執行上下文,在adder執行上下文中創建了兩個新變數 ab。它們分別被賦值為 7 和 8,因為這些是我們在上一步傳遞給函數的參數。
  • 第 4 行。在adder執行上下文中聲明瞭一個名為ret的新變數,
  • 第 4 行。將變數a的內容和變數b的內容相加得15並賦給ret變數。
  • ret變數從該函數返回。這個匿名函數執行上下文被銷毀,從調用堆棧中刪除,變數abret不再存在。
  • 返回值被分配給我們在步驟9中定義的sum變數。
  • 我們將sum的值列印到控制台。
  • 如預期,控制台將列印15。我們在這裡確實經歷了很多困難,我想在這裡說明幾點。首先,函數定義可以存儲在變數中,函數定義在程式調用之前是不可見的。其次,每次調用函數時,都會(臨時)創建一個本地執行上下文。當函數完成時,執行上下文將消失。函數在遇到return或右括弧}時執行完成。

碼部署後可能存在的BUG沒法實時知道,事後為瞭解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug

最後,一個閉包

看看下麵的代碼,並試著弄清楚會發生什麼。

 1: function createCounter() {
 2:   let counter = 0
 3:   const myFunction = function() {
 4:     counter = counter + 1
 5:     return counter
 6:   }
 7:   return myFunction
 8: }
 9: const increment = createCounter()
10: const c1 = increment()
11: const c2 = increment()
12: const c3 = increment()
13: console.log('example increment', c1, c2, c3)

現在,我們已經從前兩個示例中掌握了它的訣竅,讓我們按照預期的方式快速執行它:

  • 行1 - 8。我們在全局執行上下文中創建了一個新的變數createCounter,並賦值了一個的函數定義。
  • 第9行。我們在全局執行上下文中聲明瞭一個名為increment的新變數。
  • 第9行。我們需要調用createCounter函數並將其返回值賦給increment變數。
  • 行1 - 8。調用函數,創建新的本地執行上下文。
  • 第2行。在本地執行上下文中,聲明一個名為counter的新變數並賦值為 0;
  • 行3 - 6。聲明一個名為myFunction的新變數,變數在本地執行上下文中聲明,變數的內容是為第4行和第5行所定義。
  • 第7行。返回myFunction變數的內容,刪除本地執行上下文。變數myFunctioncounter不再存在。此時控制權回到了調用上下文。
  • 第9行。在調用上下文(全局執行上下文)中,createCounter返回的值賦給了increment,變數increment現在包含一個函數定義內容為createCounter返回的函數。它不再標記為myFunction``,但它的定義是相同的。在全局上下文中,它是的標記為labeledincrement`。
  • 第10行。聲明一個新變數(c1)。
  • 繼續第10行。查找increment變數,它是一個函數並調用它。它包含前面返回的函數定義,如第4-5行所定義的。
  • 創建一個新的執行上下文。沒有參數。開始執行函數。
  • 第4行。counter=counter + 1。在本地執行上下文中查找counter變數。我們只是創建了那個上下文,從來沒有聲明任何局部變數。讓我們看看全局執行上下文。這裡也沒有counter變數。Javascript會將其計算為counter = undefined + 1,聲明一個標記為counter的新局部變數,並將其賦值為number 1,因為undefined被當作值為 0。
  • 第5行。我們變數counter的值(1),我們銷毀本地執行上下文和counter變數。
  • 回到第10行。返回值(1)被賦給c1。
  • 第11行。重覆步驟10-14,c2也被賦值為1。
  • 第12行。重覆步驟10-14,c3也被賦值為1。
  • 第13行。我們列印變數c1 c2和c3的內容。

你自己試試,看看會發生什麼。你會將註意到,它並不像從我上面的解釋中所期望的那樣記錄1,1,1。而是記錄1,2,3。這個是為什麼?

不知怎麼滴,increment函數記住了那個cunter的值。這是怎麼回事?

counter是全局執行上下文的一部分嗎?嘗試 console.log(counter),得到undefined的結果,顯然不是這樣的。

也許,當你調用increment時,它會以某種方式返回它創建的函數(createCounter)?這怎麼可能呢?變數increment包含函數定義,而不是函數的來源,顯然也不是這樣的。

所以一定有另一種機制。閉包,我們終於找到了,丟失的那塊。

它是這樣工作的,無論何時聲明新函數並將其賦值給變數,都要存儲函數定義和閉包。閉包包含在函數創建時作用域中的所有變數,它類似於背包。函數定義附帶一個小背包,它的包中存儲了函數定義創建時作用域中的所有變數。

所以我們上面的解釋都是錯的,讓我們再試一次,但是這次是正確的。

 1: function createCounter() {
 2:   let counter = 0
 3:   const myFunction = function() {
 4:     counter = counter + 1
 5:     return counter
 6:   }
 7:   return myFunction
 8: }
 9: const increment = createCounter()
10: const c1 = increment()
11: const c2 = increment()
12: const c3 = increment()
13: console.log('example increment', c1, c2, c3)
  • 同上, 行1 - 8。我們在全局執行上下文中創建了一個新的變數createCounter,它得到了指定的函數定義。
  • 同上,第9行。我們在全局執行上下文中聲明瞭一個名為increment的新變數。
  • 同上,第9行。我們需要調用createCounter函數並將其返回值賦給increment變數。
  • 同上,行1 - 8。調用函數,創建新的本地執行上下文。
  • 同上,第2行。在本地執行上下文中,聲明一個名為counter的新變數並賦值為 0 。
  • 行3 - 6。聲明一個名為myFunction的新變數,變數在本地執行上下文中聲明,變數的內容是另一個函數定義。如第4行和第5行所定義,現在我們還創建了一個閉包,並將其作為函數定義的一部分。閉包包含作用域中的變數,在本例中是變數counter(值為0)。
  • 第7行。返回myFunction變數的內容,刪除本地執行上下文。myFunctioncounter不再存在。控制權交給了調用上下文,我們返回函數定義和它的閉包,閉包中包含了創建它時在作用域內的變數。
  • 第9行。在調用上下文(全局執行上下文)中,createCounter返回的值被指定為increment,變數increment現在包含一個函數定義(和閉包),由createCounter返回的函數定義,它不再標記為myFunction,但它的定義是相同的,在全局上下文中,稱為increment
  • 第10行。聲明一個新變數(c1)。
  • 繼續第10行。查找變數increment,它是一個函數,調用它。它包含前面返回的函數定義,如第4-5行所定義的。(它還有一個帶有變數的閉包)。
  • 創建一個新的執行上下文,沒有參數,開始執行函數。
  • 第4行。counter = counter + 1,尋找變數 counter,在查找本地或全局執行上下文之前,讓我們檢查一下閉包,瞧,閉包包含一個名為counter的變數,其值為0。在第4行表達式之後,它的值被設置為1。它再次被儲存在閉包里,閉包現在包含值為1的變數 counter
  • 第5行。我們返回counter的值,銷毀本地執行上下文。
  • 回到第10行。返回值(1)被賦給變數c1
  • 第11行。我們重覆步驟10-14。這一次,在閉包中此時變數counter的值是1。它在第12步設置的,它的值被遞增並以2的形式存儲在遞增函數的閉包中,c2被賦值為2。
  • 第12行。重覆步驟10-14,c3被賦值為3。
  • 第13行。我們列印變數c1 c2和c3的值。

您可能會問,是否有任何函數具有閉包,甚至是在全局範圍內創建的函數?答案是肯定的。在全局作用域中創建的函數創建閉包,但是由於這些函數是在全局作用域中創建的,所以它們可以訪問全局作用域中的所有變數,閉包的概念並不重要。

當函數返回函數時,閉包的概念就變得更加重要了。返回的函數可以訪問不屬於全局作用域的變數,但它們僅存在於其閉包中。

閉包不是那麼簡單

有時候閉包在你甚至沒有註意到它的時候就會出現,你可能已經看到了我們稱為部分應用程式的示例,如下麵的代碼所示:

let c = 4
const addX = x => n => n + x
const addThree = addX(3)
let d = addThree(c)
console.log('example partial application', d)

如果箭頭函數讓您感到困惑,下麵是同樣效果:

let c = 4
function addX(x) {
  return function(n) {
     return n + x
  }
}
const addThree = addX(3)
let d = addThree(c)
console.log('example partial application', d)

我們聲明一個能用加法函數addX,它接受一個參數(x)並返回另一個函數。返回的函數還接受一個參數並將其添加到變數x中。

變數x是閉包的一部分,當變數addThree在本地上下文中聲明時,它被分配一個函數定義和一個閉包,閉包包含變數x。

所以當addThree被調用並執行時,它可以從閉包中訪問變數x以及為參數傳遞變數n並返回兩者的和 7。

總結

我將永遠記住閉包的方法是通過背包的類比。當一個函數被創建並傳遞或從另一個函數返回時,它會攜帶一個背包。背包中是函數聲明時作用域內的所有變數。

參考

關於Fundebug

Fundebug專註於JavaScript、微信小程式、微信小游戲、支付寶小程式、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有Google、360、金山軟體、百姓網等眾多品牌企業。歡迎大家免費試用


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

-Advertisement-
Play Games
更多相關文章
  • --1.創建臨時空間 create temporary tablespace zyj_temp tempfile 'D:\app2\user\virtual\oradata\orcl\zyj_temp.dbf' size 50m autoextend on next 50m maxsize 2048... ...
  • 1. WordCount程式 1.1 WordCount源程式 1.2 運行程式,Run As->Java Applicatiion 1.3 編譯打包程式,產生Jar文件 2 運行程式 2.1 建立要統計詞頻的文本文件 wordfile1.txt Spark Hadoop Big Data word ...
  • 第二章 shanzm [TOC] 第二章 查詢結果排序 2.1 以指定順序返回查詢結果 問題: 你想顯示部門編號為 10 的員工的名字、職位和工資,並根據工資從低到高排序。 解決方案: 使用 代碼如下: 當然你可以使用多欄位排序 比如先按照sal升序排序,然自後按照入職日期降序排列 【註意】 1. ...
  • --以下幾個為相關表SELECT * FROM v$lock;SELECT * FROM v$sqlarea;SELECT * FROM v$session;SELECT * FROM v$process ;SELECT * FROM v$locked_object;SELECT * FROM al ...
  • hive是基於Hadoop的一個數據倉庫工具,可以將結構化的數據文件映射為一張資料庫表,並提供簡單的sql查詢功能,可以將sql語句轉換為MapReduce任務進行運行。Metastore (hive元數據)Hive將元數據存儲在資料庫中,比如mysql ,derby.Hive中的元數據包括表的名稱 ...
  • ...
  • [20190211]簡單測試埠是否打開.txt--//昨天看一個鏈接,提到如果判斷一個埠是否打開可以簡單執行如下:--//參考鏈接:https://dba010.com/2019/02/04/check-if-a-port-on-a-remote-system-is-reachable-with ...
  • 題接上篇的文章的項目,還是那個空貨管理app。本篇文章用於講解基於Flutter的app項目的升級方案。 在我接觸Flutter之前,做過一個比較失敗的基於DCloud的HTML5+技術的app,做過幾個RN項目。在這兩種不同機制的app升級方案中,RN採用的是微軟的CodePush技術。而那個比較 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...