javascript設計模式——享元模式

来源:http://www.cnblogs.com/xiaohuochai/archive/2017/12/16/8039957.html
-Advertisement-
Play Games

[1]享元模式初識 [2]文件上傳 [3]適用性 [4]對象池 ...


前面的話

  享元(flyweight)模式是一種用於性能優化的模式,“fly”在這裡是蒼蠅的意思,意為蠅量級。享元模式的核心是運用共用技術來有效支持大量細粒度的對象。如果系統中因為創建了大量類似的對象而導致記憶體占用過高,享元模式就非常有用了。在javascript中,瀏覽器特別是移動端的瀏覽器分配的記憶體並不算多,如何節省記憶體就成了一件非常有意義的事情。本文將詳細介紹享元模式

 

享元模式初識

  假設有個內衣工廠,目前的產品有50種男式內衣和50種女士內衣,為了推銷產品,工廠決定生產一些塑料模特來穿上他們的內衣拍成廣告照片。正常情況下需要50個男模特和50個女模特,然後讓他們每人分別穿上一件內衣來拍照。不使用享元模式的情況下,在程式里也許會這樣寫:

var Model = function( sex, underwear){
    this.sex = sex;
    this.underwear= underwear;
};
Model.prototype.takePhoto = function(){
    console.log( 'sex= ' + this.sex + ' underwear=' + this.underwear);
};
for ( var i = 1; i <= 50; i++ ){
    var maleModel = new Model( 'male', 'underwear' + i );
    maleModel.takePhoto();
};
for ( var j = 1; j <= 50; j++ ){
    var femaleModel= new Model( 'female', 'underwear' + j );
    femaleModel.takePhoto();
};

  要得到一張照片,每次都需要傳入sex和underwear參數,如上所述,現在一共有50種男內衣和50種女內衣,所以一共會產生100個對象。如果將來生產了10000種內衣,那這個程式可能會因為存在如此多的對象已經提前崩潰

  下麵來考慮一下如何優化這個場景。雖然有100種內衣,但很顯然並不需要50個男模特和50個女模特。其實男模特和女模特各自有一個就足夠了,他們可以分別穿上不同的內衣來拍照

  現在來改寫一下代碼,既然只需要區別男女模特,那先把underwear參數從構造函數中移除,構造函數只接收sex參數:

var Model = function( sex ){
    this.sex = sex;
};
Model.prototype.takePhoto = function(){
    console.log( 'sex= ' + this.sex + ' underwear=' + this.underwear);
};

  分別創建一個男模特對象和一個女模特對象:

var maleModel = new Model( 'male' ),
femaleModel = new Model( 'female' );

  給男模特依次穿上所有的男裝,併進行拍照:

for ( var i = 1; i <= 50; i++ ){
    maleModel.underwear = 'underwear' + i;
    maleModel.takePhoto();
};

  同樣,給女模特依次穿上所有的女裝,併進行拍照:

for ( var j = 1; j <= 50; j++ ){
    femaleModel.underwear = 'underwear' + j;
    femaleModel.takePhoto();
};

  可以看到,改進之後的代碼,只需要兩個對象便完成了同樣的功能

【內部狀態與外部狀態】

  上面的這個例子便是享元模式的雛形,享元模式要求將對象的屬性劃分為內部狀態與外部狀態(狀態在這裡通常指屬性)。享元模式的目標是儘量減少共用對象的數量,關於如何劃分內部狀態和外部狀態,下麵的幾條經驗提供了一些指引

  1、內部狀態存儲於對象內部。

  2、內部狀態可以被一些對象共用。

  3、內部狀態獨立於具體的場景,通常不會改變。

  4、外部狀態取決於具體的場景,並根據場景而變化,外部狀態不能被共用

  這樣一來,便可以把所有內部狀態相同的對象都指定為同一個共用的對象。而外部狀態可以從對象身上剝離出來,並儲存在外部

  剝離了外部狀態的對象成為共用對象,外部狀態在必要時被傳入共用對象來組裝成一個完整的對象。雖然組裝外部狀態成為一個完整對象的過程需要花費一定的時間,但卻可以大大減少系統中的對象數量,相比之下,這點時間或許是微不足道的。因此,享元模式是一種用時間換空間的優化模式

  在上面的例子中,性別是內部狀態,內衣是外部狀態,通過區分這兩種狀態,大大減少了系統中的對象數量。通常來講,內部狀態有多少種組合,系統中便最多存在多少個對象,因為性別通常只有男女兩種,所以該內衣廠商最多只需要2個對象

  使用享元模式的關鍵是如何區別內部狀態和外部狀態。可以被對象共用的屬性通常被劃分為內部狀態,如同不管什麼樣式的衣服,都可以按照性別不同,穿在同一個男模特或者女模特身上,模特的性別就可以作為內部狀態儲存在共用對象的內部。而外部狀態取決於具體的場景,並根據場景而變化,就像例子中每件衣服都是不同的,它們不能被一些對象共用,因此只能被劃分為外部狀態

  上面的例子還不是一個完整的享元模式,存在以下兩個問題

  1、通過構造函數顯式new出了男女兩個model對象,在其他系統中,也許並不是一開始就需要所有的共用對象

  2、給model對象手動設置了underwear外部狀態,在更複雜的系統中,這不是一個最好的方式,因為外部狀態可能會相當複雜,它們與共用對象的聯繫會變得困難

  通過一個對象工廠來解決第一個問題,只有當某種共用對象被真正需要時,它才從工廠中被創建出來。對於第二個問題,可以用一個管理器來記錄對象相關的外部狀態,使這些外部狀態通過某個鉤子和共用對象聯繫起來

 

文件上傳

【基本版本】

  在文件上傳模塊的開發中,文件上傳功能雖然可以選擇依照隊列,一個一個地排隊上傳,但也支持同時選擇2000個文件。每一個文件都對應著一個javascript上傳對象的創建,往程式里同時new了2000個upload對象,結果可想而知,Chrome中還勉強能夠支撐,IE下直接進入假死狀態

  文件支持好幾種上傳方式,比如瀏覽器插件、Flash和表單上傳等,為了簡化例子,先假設只有插件和Flash這兩種。不論是插件上傳,還是Flash上傳,原理都是一樣的,當用戶選擇了文件之後,插件和Flash都會通知調用Window下的一個全局javascript函數,它的名字是startUpload,用戶選擇的文件列表被組合成一個數組files塞進該函數的參數列表裡,代碼如下:

var id = 0;
window.startUpload = function( uploadType, files ){ // uploadType 區分是控制項還是flash
    for ( var i = 0, file; file = files[ i++ ]; ){
        var uploadObj = new Upload( uploadType, file.fileName, file.fileSize );
        uploadObj.init( id++ ); // 給upload 對象設置一個唯一的id
    }
};

  當用戶選擇完文件之後,startUpload函數會遍歷files數組來創建對應的upload對象。接下來定義Upload構造函數,它接受3個參數,分別是插件類型、文件名和文件大小。這些信息都已經被插件組裝在files數組裡返回,代碼如下:

var Upload = function( uploadType, fileName, fileSize ){
    this.uploadType = uploadType;
    this.fileName = fileName;
    this.fileSize = fileSize;
    this.dom= null;
};
Upload.prototype.init = function( id ){
    var that = this;
    this.id = id;
    this.dom = document.createElement( 'div' );
    this.dom.innerHTML =
    '<span>文件名稱:'+ this.fileName +', 文件大小: '+ this.fileSize +'</span>' +
    '<button class="delFile">刪除</button>';
    this.dom.querySelector( '.delFile' ).onclick = function(){
        that.delFile();
    }
    document.body.appendChild( this.dom );
};

  為了簡化示例,暫且去掉了upload對象的其他功能,只保留刪除文件的功能,對應的方法是Upload.prototype.delFile。該方法中有一個邏輯:當被刪除的文件小於3000KB時,該文件將被直接刪除。否則頁面中會彈出一個提示框,提示用戶是否確認要刪除該文件,代碼如下:

Upload.prototype.delFile = function(){
    if ( this.fileSize < 3000 ){
        return this.dom.parentNode.removeChild( this.dom );
    }
    if ( window.confirm( '確定要刪除該文件嗎? ' + this.fileName ) ){
        return this.dom.parentNode.removeChild( this.dom );
    }
};

  接下來分別創建3個插件上傳對象和3個Flash上傳對象:

startUpload( 'plugin', [
{
    fileName: '1.txt',
    fileSize: 1000
},
{
    fileName: '2.html',
    fileSize: 3000
},
{
    fileName: '3.txt',
    fileSize: 5000
}
]);
startUpload( 'flash', [
{
    fileName: '4.txt',
    fileSize: 1000
},
{
    fileName: '5.html',
    fileSize: 3000
},
{
    fileName: '6.txt',
    fileSize: 5000
}
]);

【享元模式重構】

  上一節的代碼是第一版的文件上傳,在這段代碼里有多少個需要上傳的文件,就一共創建了多少個upload對象,接下來用享元模式重構它

  首先,需要確認插件類型uploadType是內部狀態,那為什麼單單uploadType是內部狀態呢?在文件上傳的例子里,upload對象必須依賴uploadType屬性才能工作,這是因為插件上傳、Flash上傳、表單上傳的實際工作原理有很大的區別,它們各自調用的介面也是完全不一樣的,必須在對象創建之初就明確它是什麼類型的插件,才可以在程式的運行過程中,讓它們分別調用各自的start、pause、cancel、del等方法

  一旦明確了uploadType,無論使用什麼方式上傳,這個上傳對象都是可以被任何文件共用的。而fileName和fileSize是根據場景而變化的,每個文件的fileName和fileSize都不一樣,fileName和fileSize沒有辦法被共用,它們只能被劃分為外部狀態

  明確了uploadType作為內部狀態之後,再把其他的外部狀態從構造函數中抽離出來,Upload構造函數中只保留uploadType參數:

var Upload = function( uploadType){
    this.uploadType = uploadType;
};

  Upload.prototype.init函數也不再需要,因為upload對象初始化的工作被放在了upload-Manager.add函數裡面,接下來只需要定義Upload.prototype.del函數即可:

Upload.prototype.delFile = function( id ){
    uploadManager.setExternalState( id, this ); // (1)
    if ( this.fileSize < 3000 ){
        return this.dom.parentNode.removeChild( this.dom );
    }
    if ( window.confirm( '確定要刪除該文件嗎? ' + this.fileName ) ){
        return this.dom.parentNode.removeChild( this.dom );
    }
}

  在開始刪除文件之前,需要讀取文件的實際大小,而文件的實際大小被儲存在外部管理器uploadManager中,所以在這裡需要通過uploadManager.setExternalState方法給共用對象設置正確的fileSize,上段代碼中的(1)處表示把當前id對應的對象的外部狀態都組裝到共用對象中

  接下來定義一個工廠來創建upload對象,如果某種內部狀態對應的共用對象已經被創建過,那麼直接返回這個對象,否則創建一個新的對象:

var UploadFactory = (function(){
    var createdFlyWeightObjs = {};
    return {
        create: function( uploadType){
            if ( createdFlyWeightObjs [ uploadType] ){
                return createdFlyWeightObjs [ uploadType];
            }
            return createdFlyWeightObjs [ uploadType] = new Upload( uploadType);
        }
    }
})();

  現在來完善前面提到的uploadManager對象,它負責向UploadFactory提交創建對象的請求,並用一個uploadDatabase對象保存所有upload對象的外部狀態,以便在程式運行過程中給upload共用對象設置外部狀態,代碼如下:

var uploadManager = (function(){
    var uploadDatabase = {};
    return {
        add: function( id, uploadType, fileName, fileSize ){
            var flyWeightObj = UploadFactory.create( uploadType );
            var dom = document.createElement( 'div' );
            dom.innerHTML =
            '<span>文件名稱:'+ fileName +', 文件大小: '+ fileSize +'</span>' +
            '<button class="delFile">刪除</button>';
            dom.querySelector( '.delFile' ).onclick = function(){
                flyWeightObj.delFile( id );
            }

            document.body.appendChild( dom );
            uploadDatabase[ id ] = {
                fileName: fileName,
                fileSize: fileSize,
                dom: dom
            };
            return flyWeightObj ;
        },
        setExternalState: function( id, flyWeightObj ){
            var uploadData = uploadDatabase[ id ];
            for ( var i in uploadData ){
                flyWeightObj[ i ] = uploadData[ i ];
            }
        }
    }
})();

  然後是開始觸發上傳動作的startUpload函數:

var id = 0;
window.startUpload = function( uploadType, files ){
    for ( var i = 0, file; file = files[ i++ ]; ){
        var uploadObj = uploadManager.add( ++id, uploadType, file.fileName, file.fileSize );
    }
};

  最後是測試時間,運行下麵的代碼後,可以發現運行結果跟用享元模式重構之前一致:

startUpload( 'plugin', [
{
    fileName: '1.txt',
    fileSize: 1000
},
{
    fileName: '2.html',
    fileSize: 3000
},
{
    fileName: '3.txt',
    fileSize: 5000
}
]);
startUpload( 'flash', [
{
    fileName: '4.txt',
    fileSize: 1000
},
{
    fileName: '5.html',
    fileSize: 3000
},
{
    fileName: '6.txt',

    fileSize: 5000
}
]);

  享元模式重構之前的代碼里一共創建了6個upload對象,而通過享元模式重構之後,對象的數量減少為2,更幸運的是,就算現在同時上傳2000個文件,需要創建的upload對象數量依然是2

 

適用性

  享元模式是一種很好的性能優化方案,但它也會帶來一些複雜性的問題,從前面兩組代碼的比較可以看到,使用了享元模式之後,需要分別多維護一個factory對象和一個manager對象,在大部分不必要使用享元模式的環境下,這些開銷是可以避免的

  享元模式帶來的好處很大程度上取決於如何使用以及何時使用,一般來說,以下情況發生時便可以使用享元模式

  1、一個程式中使用了大量的相似對象

  2、由於使用了大量對象,造成很大的記憶體開銷

  3、對象的大多數狀態都可以變為外部狀態

  4、剝離出對象的外部狀態之後,可以用相對較少的共用對象取代大量對象。可以看到,文件上傳的例子完全符合這四點

  實現享元模式的關鍵是把內部狀態和外部狀態分離開來。有多少種內部狀態的組合,系統中便最多存在多少個共用對象,而外部狀態儲存在共用對象的外部,在必要時被傳入共用對象來組裝成一個完整的對象。現在來考慮兩種極端的情況,即對象沒有外部狀態和沒有內部狀態的時候

【沒有內部狀態的享元】

  在文件上傳的例子中,分別進行過插件調用和Flash調用,即startUpload('plugin',[])和startUpload(flash,[]),導致程式中創建了內部狀態不同的兩個共用對象。在文件上傳程式里,一般都會提前通過特性檢測來選擇一種上傳方式,如果瀏覽器支持插件就用插件上傳,如果不支持插件,就用Flash上傳。那麼,什麼情況下既需要插件上傳又需要Flash上傳呢?

  實際上這個需求是存在的,很多網盤都提供了極速上傳(控制項)與普通上傳(Flash)兩種模式,如果極速上傳不好使(可能是沒有安裝控制項或者控制項損壞),用戶還可以隨時切換到普通上傳模式,所以這裡確實是需要同時存在兩個不同的upload共用對象

  但不是每個網站都必須做得如此複雜,很多小一些的網站就只支持單一的上傳方式。假設我們是這個網站的開發者,不需要考慮極速上傳與普通上傳之間的切換,這意味著在之前的代碼中作為內部狀態的uploadType屬性是可以刪除掉的

  在繼續使用享元模式的前提下,構造函數Upload就變成了無參數的形式:

var Upload = function(){};

  其他屬性如fileName、fileSize、dom依然可以作為外部狀態保存在共用對象外部。在uploadType作為內部狀態的時候,它可能為控制項,也可能為Flash,所以當時最多可以組合出兩個共用對象。而現在已經沒有了內部狀態,這意味著只需要唯一的一個共用對象。現在要改寫創建享元對象的工廠,代碼如下:

var UploadFactory = (function(){ 
  var uploadObj;
  return {
   create: function(){ 
     if ( uploadObj ){
      return uploadObj;
    }
    return uploadObj = new Upload();
    })();
  }
}

  管理器部分的代碼不需要改動,還是負責剝離和組裝外部狀態。可以看到,當對象沒有內部狀態的時候,生產共用對象的工廠實際上變成了一個單例工廠。雖然這時候的共用對象沒有內部狀態的區分,但還是有剝離外部狀態的過程,依然傾向於稱之為享元模式

 

對象池

  對象池維護一個裝載空閑對象的池子,如果需要對象的時候,不是直接new,而是轉從對象池裡獲取。如果對象池裡沒有空閑對象,則創建一個新的對象,當獲取出的對象完成它的職責之後,再進入池子等待被下次獲取

  對象池的原理很好理解,比如我們組人手一本《javascript權威指南》,從節約的角度來講,這並不是很划算,因為大部分時間這些書都被閑置在各自的書架上,所以我們一開始就只買一本,或者一起建立一個小型圖書館(對象池),需要看書的時候就從圖書館里借,看完了之後再把書還回圖書館。如果同時有三個人要看這本書,而現在圖書館里只有兩本,那我們再馬上去書店買一本放入圖書館

  對象池技術的應用非常廣泛,HTTP連接池和資料庫連接池都是其代表應用。在Web前端開發中,對象池使用最多的場景大概就是跟DOM有關的操作。很多空間和時間都消耗在了DOM節點上,如何避免頻繁地創建和刪除DOM節點就成了一個有意義的話題

  假設開發一個地圖應用,地圖上經常會出現一些標誌地名的小氣泡,叫它toolTip。在搜索附近地圖的時候,頁面里出現了2個小氣泡。當再搜索附近的蘭州拉麵館時,頁面中出現了6個小氣泡。按照對象池的思想,在第二次搜索開始之前,並不會把第一次創建的2個小氣泡刪除掉,而是把它們放進對象池。這樣在第二次的搜索結果頁面里,只需要再創建4個小氣泡而不是6個

  先定義一個獲取小氣泡節點的工廠,作為對象池的數組成為私有屬性被包含在工廠閉包里,這個工廠有兩個暴露對外的方法,create表示獲取一個div節點,recover表示回收一個div節點:

var toolTipFactory = (function(){
    var toolTipPool = []; // toolTip 對象池
    return {
        create: function(){
            if ( toolTipPool.length === 0 ){ // 如果對象池為空
                var div = document.createElement( 'div' ); // 創建一個dom
                document.body.appendChild( div );
                recovereturn div;
            }else{ // 如果對象池裡不為空
                return toolTipPool.shift(); // 則從對象池中取出一個dom
            }
        },
        recover: function( tooltipDom ){
            return toolTipPool.push( tooltipDom ); // 對象池回收dom
        }
    }
})();

  現在把時鐘撥回進行第一次搜索的時刻,目前需要創建2個小氣泡節點,為了方便回收,用一個數組ary來記錄它們:

var ary = [];
for ( var i = 0, str; str = [ 'A', 'B' ][ i++ ]; ){
    var toolTip = toolTipFactory.create();
    toolTip.innerHTML = str;
    ary.push( toolTip );
};

  接下來假設地圖需要開始重新繪製,在此之前要把這兩個節點回收進對象池:

for ( var i = 0, toolTip; toolTip = ary[ i++ ]; ){
    toolTipFactory.recover( toolTip );
};

  再創建6個小氣泡:

for ( var i = 0, str; str = [ 'A', 'B', 'C', 'D', 'E', 'F' ][ i++ ]; ){
    var toolTip = toolTipFactory.create();
    toolTip.innerHTML = str;
};

  頁面中出現了內容分別為A、B、C、D、E、F的6個節點,上一次創建好的節點被共用給了下一次操作。對象池跟享元模式的思想有點相似,雖然innerHTML的值A、B、C、D等也可以看成節點的外部狀態,但在這裡並沒有主動分離內部狀態和外部狀態的過程

【通用對象池實現】

  還可以在對象池工廠里,把創建對象的具體過程封裝起來,實現一個通用的對象池:

var objectPoolFactory = function( createObjFn ){
    var objectPool = [];
    return {
        create: function(){
            var obj = objectPool.length === 0 ?
            createObjFn.apply( this, arguments ) : objectPool.shift();
            return obj;
        },
        recover: function( obj ){
            objectPool.push( obj );

        }
    }
};

var iframeFactory = objectPoolFactory( function(){
    var iframe = document.createElement( 'iframe' );
    document.body.appendChild( iframe );
    iframe.onload = function(){
        iframe.onload = null; // 防止iframe 重覆載入的bug
        iframeFactory.recover( iframe ); // iframe 載入完成之後回收節點
    }
    return iframe;
});

var iframe1 = iframeFactory.create();
iframe1.src = 'http:// baidu.com';
var iframe2 = iframeFactory.create();
iframe2.src = 'http:// QQ.com';
setTimeout(function(){
    var iframe3 = iframeFactory.create();
    iframe3.src = 'http:// 163.com';
}, 3000 );

  對象池是另外一種性能優化方案,它跟享元模式有一些相似之處,但沒有分離內部狀態和外部狀態這個過程。文件上傳的程式其實也可以用對象池+事件委托來代替實現

  享元模式是為解決性能問題而生的模式,這跟大部分模式的誕生原因都不一樣。在一個存在大量相似對象的系統中,享元模式可以很好地解決大量對象帶來的性能問題

 


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

-Advertisement-
Play Games
更多相關文章
  • 用xmapp中mysql 資料庫實現.sql文件上傳已經實現資料庫的增刪改查。 ...
  • 使用GIT前請閱讀(有git基礎可略過) git指引 :http://www.bootcss.com/p/git-guide/ 一、 下載git http://git-scm.com/download/ 二、 安裝 全部預設配置即可。 三、 配置 運行Git Bash (一) 配置用戶信息: 輸入以 ...
  • 前天開源了框架:開源:Sagit.Framework For IOS 開發框架,所以註定要追補一套開發教程了,所以開始第一篇入門了... ...
  • 先上一個能用的js代碼: 後臺獲取不到值的解決方案: 1、保證前臺能獲取到輸入框的值。 2、url,data的key-value書寫正確。 3、註意HTTP請求的Header,headers類型: 預設:application/x-www-form-urlencoded 上傳文件時:multipar ...
  • 查看本地git配置信息 git config --global -e 查看自己科學上網的代理地址和埠信息 為git添加代理 git config --global http.proxy https://127.0.0.1:51643 git config --global https.proxy ...
  • [1]電商訂單 [2]職責鏈模式重構 [3]AOP [4]文件上傳 ...
  • 至此已完成NodeJsInputFileSysten模塊的講解,下一步就是實際實用的模塊: 掛載到compiler對象上的輸入模塊其實是帶有緩存的輸入模塊,源碼整理如下(用ES6的class重寫): 這裡的核心是利用Storage來生成一個緩存容器,緩存對應的讀操作。 有兩個需要註意的地方。 一個是 ...
  • 在cachedInput、output、watch三大文件系統中,output非常簡單,沒有必要講,其餘兩個模塊依賴於input模塊,而input主要是引用了graceful-fs的部分API,所以這節來講講graceful-fs。 上一節整理的源碼如下: 內容包含: 1、工具方法 2、patch引 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...