javascript設計模式——裝飾者模式

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

[1]概念 [2]javascript裝飾者 [3]裝飾函數 [4]AOP [5]AOP應用實例 [6]裝飾者模式和代理模式 ...


前面的話

  在程式開發中,許多時候都並不希望某個類天生就非常龐大,一次性包含許多職責。那麼可以使用裝飾者模式。裝飾者模式可以動態地給某個對象添加一些額外的職責,而不會影響從這個類中派生的其他對象。本文將詳細介紹裝飾者模式

 

概念

  在傳統的面向對象語言中,給對象添加功能常常使用繼承的方式,但是繼承的方式並不靈活,還會帶來許多問題:一方面會導致超類和子類之間存在強耦合性,當超類改變時,子類也會隨之改變;另一方面,繼承這種功能復用方式通常被稱為“白箱復用”,“白箱”是相對可見性而言的,在繼承方式中,超類的內部細節是對子類可見的,繼承常常被認為破壞了封裝性

  使用繼承還會帶來另外一個問題,在完成一些功能復用的同時,有可能創建出大量的子類,使子類的數量呈爆炸性增長。比如現在有4種型號的自行車,為每種自行車都定義了一個單獨的類。現在要給每種自行車都裝上前燈、尾燈和鈴鐺這3種配件。如果使用繼承的方式來給每種自行車創建子類,則需要4×3=12個子類。但是如果把前燈、尾燈、鈴鐺這些對象動態組合到自行車上面,則只需要額外增加3個類

  這種給對象動態地增加職責的方式稱為裝飾者(decorator)模式。裝飾者模式能夠在不改變對象自身的基礎上,在程式運行期間給對象動態地添加職責。跟繼承相比,裝飾者是一種更輕便靈活的做法,這是一種“即用即付”的方式,比如天冷了就多穿一件外套,需要飛行時就在頭上插一支竹蜻蜓

  作為一門解釋執行的語言,給javascript中的對象動態添加或者改變職責是一件再簡單不過的事情,雖然這種做法改動了對象自身,跟傳統定義中的裝飾者模式並不一樣,但這無疑更符合javascript的語言特色。代碼如下:

var obj ={
  name:'match',
  address:'北京'
};
obj.address= obj.address + '平谷區';

  傳統面向對象語言中的裝飾者模式在javascript中適用的場景並不多,如上面代碼所示,通常並不太介意改動對象自身

  假設在編寫一個飛機大戰的游戲,隨著經驗值的增加,操作的飛機對象可以升級成更厲害的飛機,一開始這些飛機只能發射普通的子彈,升到第二級時可以發射導彈,升到第三級時可以發射原子彈

  下麵來看代碼實現,首先是原始的飛機類:

var Plane = function(){};

Plane.prototype.fire = function(){
    console.log( '發射普通子彈' );
}

  接下來增加兩個裝飾類,分別是導彈和原子彈:

var MissileDecorator = function( plane ){
    this.plane = plane;
}
MissileDecorator.prototype.fire = function(){
    this.plane.fire();
    console.log( '發射導彈' );
}
var AtomDecorator = function( plane ){
    this.plane = plane;
}
AtomDecorator.prototype.fire = function(){
    this.plane.fire();
    console.log( '發射原子彈' );
}

  導彈類和原子彈類的構造函數都接受參數plane對象,並且保存好這個參數,在它們的fire方法中,除了執行自身的操作之外,還調用plane對象的fire方法。這種給對象動態增加職責的方式,並沒有真正地改動對象自身,而是將對象放入另一個對象之中,這些對象以一條鏈的方式進行引用,形成一個聚合對象。這些對象都擁有相同的介面(fire方法),當請求達到鏈中的某個對象時,這個對象會執行自身的操作,隨後把請求轉發給鏈中的下一個對象

  因為裝飾者對象和它所裝飾的對象擁有一致的介面,所以它們對使用該對象的客戶來說是透明的,被裝飾的對象也並不需要瞭解它曾經被裝飾過,這種透明性使得可以遞歸地嵌套任意多個裝飾者對象

  在《設計模式》成書之前,GoF原想把裝飾者(decorator)模式稱為包裝器(wrapper)模式。從功能上而言,decorator能很好地描述這個模式,但從結構上看,wrapper的說法更加貼切。裝飾者模式將一個對象嵌入另一個對象之中,實際上相當於這個對象被另一個對象包裝起來,形成一條包裝鏈。請求隨著這條鏈依次傳遞到所有的對象,每個對象都有處理這條請求的機會

 

javascript裝飾者

  javascript語言動態改變對象相當容易,可以直接改寫對象或者對象的某個方法,並不需要使用“類”來實現裝飾者模式

var plane = {
    fire: function(){
        console.log( '發射普通子彈' );
    }
}
var missileDecorator = function(){
    console.log( '發射導彈' );
}
var atomDecorator = function(){
    console.log( '發射原子彈' );
}
var fire1 = plane.fire;
plane.fire = function(){
    fire1();
    missileDecorator();
}
var fire2 = plane.fire;
plane.fire = function(){
    fire2();
    atomDecorator();
}
plane.fire();
// 分別輸出: 發射普通子彈、發射導彈、發射原子彈

 

裝飾函數

  在javascript中可以很方便地給某個對象擴展屬性和方法,但卻很難在不改動某個函數源代碼的情況下,給該函數添加一些額外的功能。在代碼的運行期間,很難切入某個函數的執行環境。要想為函數添加一些功能,最簡單粗暴的方式就是直接改寫該函數,但這是最差的辦法,直接違反了開放——封閉原則

var a = function(){
  alert(1);
}
//改成:
var a = function(){
  alert(1);
  alert(2);
}

  很多時候不想去碰原函數,也許原函數是由其他同事編寫的,裡面的實現非常雜亂。現在需要一個辦法,在不改變函數源代碼的情況下,能給函數增加功能,通過保存原引用的方式就可以改寫某個函數:

var a =  function(){
  alert(1);
}
var _a = a;

a = function(){
  _a();
  alert(2);
}
a();

  這是實際開發中很常見的一種做法,比如想給window綁定onload事件,但是又不確定這個事件是不是已經被其他人綁定過,為了避免覆蓋掉之前的window.onload函數中的行為,一般都會先保存好原先的window.onload,把它放入新的window.onload里執行:

window.onload=function(){
  alert(1);
}
var _onload=window.onload||function(){};
window.onload=function(){
  _onload();
  alert(2);
}

  這樣的代碼當然是符合開放——封閉原則的,在增加新功能的時候,確實沒有修改原來的window.onload代碼,但是這種方式存在以下兩個問題

  1、必須維護_onload這個中間變數,雖然看起來並不起眼,但如果函數的裝飾鏈較長,或者需要裝飾的函數變多,這些中間變數的數量也會越來越多

  2、遇到了this被劫持的問題,在window.onload的例子中沒有這個煩惱,是因為調用普通函數_onload時,this也指向window,跟調用window.onload時一樣(函數作為對象的方法被調用時,this指向該對象,所以此處this也只指向window)。現在把window.onload換成document.getElementById,代碼如下:

var _getElementById = document.getElementById;
document.getElementById= function(id){
  alert(1);
  return _getElementById(id);    //(1)
}
var button = document.getElementById('button');

  執行這段代碼,看到在彈出alert(1)之後,緊接著控制台拋出了異常:

//輸出:Uncaught TypeError:Illegal invocation

  異常發生在(1)處的_getElementById(id)這句代碼上,此時_getElementById是一個全局函數,當調用一個全局函數時,this是指向window的,而document.getElementById方法的內部實現需要使用this引用,this在這個方法內預期是指向document,而不是window,這是錯誤發生的原因,所以使用現在的方式給函數增加功能並不保險

  改進後的代碼可以滿足需求,要手動把document當作上下文this傳入_getElementById:

<button id="button"></button>
<script>
var _getElementById = document.getElementById;
document.getElementById=function(){
  alert(1);
  return _getElementById.apply(document,arguments);
}
var button = document.getElementById('button');
</script>

  但這樣做顯然很不方便

 

AOP

  下麵使用AOP來提供一種完美的方法給函數動態增加功能

  首先給出Function.prototype.before方法和Function.prototype.after方法:

Function.prototype.before = function( beforefn ){
    var __self = this; // 保存原函數的引用
    return function(){ // 返回包含了原函數和新函數的"代理"函數
        beforefn.apply( this, arguments ); // 執行新函數,且保證this 不被劫持,新函數接受的參數
    // 也會被原封不動地傳入原函數,新函數在原函數之前執行
        return __self.apply( this, arguments ); // 執行原函數並返回原函數的執行結果,
    // 並且保證this 不被劫持
    }
}
Function.prototype.after = function( afterfn ){
    var __self = this;
    return function(){
        var ret = __self.apply( this, arguments );
        afterfn.apply( this, arguments );
        return ret;
    }
};

  Function.prototype.before接受一個函數當作參數,這個函數即為新添加的函數,它裝載了新添加的功能代碼。接下來把當前的this保存起來,這個this指向原函數,然後返回一個“代理”函數,這個“代理”函數只是結構上像代理而已,並不承擔代理的職責(比如控制對象的訪問等)。它的工作是把請求分別轉發給新添加的函數和原函數,且負責保證它們的執行順序,讓新添加的函數在原函數之前執行(前置裝飾),這樣就實現了動態裝飾的效果。通過Function.prototype.apply來動態傳入正確的this,保證了函數在被裝飾之後,this不會被劫持。Function.prototype.after的原理跟Function.prototype.before一模一樣,唯一不同的地方在於讓新添加的函數在原函數執行之後再執行

  下麵是一個例子

<button id="button"></button>
<script>
    Function.prototype.before = function( beforefn ){
        var __self = this;
        return function(){
            beforefn.apply( this, arguments );
            return __self.apply( this, arguments );
        }
    }
    document.getElementById = document.getElementById.before(function(){
        alert (1);
    });
    var button = document.getElementById( 'button' );
    console.log( button );
</script>

  再回到window.onload的例子,用Function.prototype.before來增加新的window.onload事件非常簡單

window.onload = function(){
    alert (1);
}
window.onload = ( window.onload || function(){} ).after(function(){
    alert (2);
}).after(function(){
    alert (3);
}).after(function(){
    alert (4);
});

  值得提到的是,上面的AOP實現是在Function.prototype上添加before和after方法,但許多人不喜歡這種污染原型的方式,那麼可以做一些變通,把原函數和新函數都作為參數傳入before或者after方法:

var before = function( fn, beforefn ){
    return function(){
        beforefn.apply( this, arguments );
        return fn.apply( this, arguments );
    }
}
var a = before(
    function(){alert (3)},
    function(){alert (2)}
    );
a = before( a, function(){alert (1);} );
a();

 

AOP應用實例

  用AOP裝飾函數的技巧在實際開發中非常有用。不論是業務代碼的編寫,還是在框架層面,都可以把行為依照職責分成粒度更細的函數,隨後通過裝飾把它們合併到一起,這有助於編寫一個松耦合和高復用性的系統

【數據統計上報】

  分離業務代碼和數據統計代碼,無論在什麼語言中,都是AOP的經典應用之一。在項目開發的結尾階段難免要加上很多統計數據的代碼,這些過程可能讓我們被迫改動早已封裝好的函數。比如頁面中有一個登錄button,點擊這個button會彈出登錄浮層,與此同時要進行數據上報,來統計有多少用戶點擊了這個登錄button

<html>
<button tag="login" id="button">點擊打開登錄浮層</button>
<script>
    var showLogin = function(){
        console.log( '打開登錄浮層' );
        log( this.getAttribute( 'tag' ) );
    }
    var log = function( tag ){
        console.log( '上報標簽為: ' + tag );
// (new Image).src = 'http://xx.com/report?tag=' + tag; // 真正的上報代碼略
}
document.getElementById( 'button' ).onclick = showLogin;
</script>
</html>

  在showLogin函數里,既要負責打開登錄浮層,又要負責數據上報,這是兩個層面的功能,在此處卻被耦合在一個函數里。使用AOP分離之後,代碼如下:

<html>
<button tag="login" id="button">點擊打開登錄浮層</button>
<script>
    Function.prototype.after = function( afterfn ){
        var __self = this;
        return function(){
            var ret = __self.apply( this, arguments );
            afterfn.apply( this, arguments );
            return ret;
        }
    };
    var showLogin = function(){
        console.log( '打開登錄浮層' );
    }
    var log = function(){
        console.log( '上報標簽為: ' + this.getAttribute( 'tag' ) );
    }

    showLogin = showLogin.after( log ); // 打開登錄浮層之後上報數據
    document.getElementById( 'button' ).onclick = showLogin;
</script>
</html>

【用AOP動態改變函數的參數】

  觀察Function.prototype.before方法:

Function.prototype.before=function(beforefn){
  var self = this;
  return function(){
    beforefn.apply(this,arguments);    //(1)
    return __self.apply(this,arguments);    //(2)
  }
}

  從這段代碼的(1)處和(2)處可以看到,beforefn和原函數__self共用一組參數列表arguments,在beforefn的函數體內改變arguments時,原函數__self接收的參數列表自然也會變化

  下麵的例子展示瞭如何通過Function.prototype.before方法給函數func的參數param動態地添加屬性b:

var func = function(param){
  console.log(param);    //輸出:{a:"a",b:"b"}
}

func = func.before(
  function(param){
    param.b='b';
});

func({a:'a'});

  現在有一個用於發起ajax請求的函數,這個函數負責項目中所有的ajax非同步請求:

var ajax =f unction(type,url,param){
  console.dir(param);
  //發送ajax請求的代碼略
};
ajax('get','http://xx.com/userinfo',{name:'match'});

  上面的偽代碼表示向後臺cgi發起一個請求來獲取用戶信息,傳遞給cgi的參數是{name:'match'}。ajax函數在項目中一直運轉良好,跟cgi的合作也很愉快。直到有一天,網站遭受了CSRF攻擊。解決CSRF攻擊最簡單的一個辦法就是在HTTP請求中帶上一個Token參數。假設已經有一個用於生成Token的函數:

var getToken = function(){
  return'Token';
}

  現在的任務是給每個ajax請求都加上Token參數:

var ajax = function(type,url,param){
  param=param||{};
  Param.Token=getToken();    //發送ajax請求的代碼略...
};

  雖然已經解決了問題,但ajax函數相對變得僵硬了,每個從ajax函數里發出的請求都自動帶上了Token參數,雖然在現在的項目中沒有什麼問題,但如果將來把這個函數移植到其他項目上,或者把它放到一個開源庫中供其他人使用,Token參數都將是多餘的。也許另一個項目不需要驗證Token,或者是Token的生成方式不同,無論是哪種情況,都必須重新修改ajax函數

  為瞭解決這個問題,先把ajax函數還原成一個乾凈的函數:

var ajax = function(type,url,param){
  console.log(param);    //發送ajax請求的代碼略
};

  然後把Token參數通過Function.prototyte.before裝飾到ajax函數的參數param對象中:

var getToken =function(){
  return'Token';
}
ajax=ajax.before(function(type,url,param){
  param.Token=getToken();
});

ajax('get','http://xx.com/userinfo',{name:'match'});

  從ajax函數列印的log可以看到,Token參數已經被附加到了ajax請求的參數中:

{name:"match",Token:"Token"}

  明顯可以看到,用AOP的方式給ajax函數動態裝飾上Token參數,保證了ajax函數是一個相對純凈的函數,提高了ajax函數的可復用性,它在被遷往其他項目的時候,不需要做任何修改

【插件式表單驗證】

  在一個Web項目中,可能存在非常多的表單,如註冊、登錄、修改用戶信息等。在表單數據提交給後臺之前,常常要做一些校驗,比如登錄的時候需要驗證用戶名和密碼是否為空,代碼如下:

<body>
    用戶名:<input id="username" type="text"/>
    密碼: <input id="password" type="password"/>
    <input id="submitBtn" type="button" value="提交"></button>
<script>
    var username = document.getElementById( 'username' ),
    password = document.getElementById( 'password' ),
    submitBtn = document.getElementById( 'submitBtn' );
    var formSubmit = function(){
        if ( username.value === '' ){
            return alert ( '用戶名不能為空' );
        }
        if ( password.value === '' ){
            return alert ( '密碼不能為空' );
        }
        var param = {
            username: username.value,
            password: password.value
        }
        ajax( 'http://xx.com/login', param ); // ajax 具體實現略
    }
    submitBtn.onclick = function(){
        formSubmit();
    }
</script>
</body>

  formSubmit函數在此處承擔了兩個職責,除了提交ajax請求之外,還要驗證用戶輸入的合法性。這種代碼一來會造成函數臃腫,職責混亂,二來談不上任何可復用性。下麵來分離校驗輸入和提交ajax請求的代碼,把校驗輸入的邏輯放到validata函數中,並且約定當validata函數返回false的時候,表示校驗未通過,代碼如下:

var validata = function(){
    if ( username.value === '' ){
        alert ( '用戶名不能為空' );
        return false;
    }
    if ( password.value === '' ){
        alert ( '密碼不能為空' );
        return false;
    }
}

var formSubmit = function(){
    if ( validata() === false ){ // 校驗未通過
        return;
    }
    var param = {
        username: username.value,
        password: password.value
    }
    ajax( 'http:// xxx.com/login', param );
}

submitBtn.onclick = function(){
    formSubmit();
}

  現在的代碼已經有了一些改進,把校驗的邏輯都放到了validata函數中,但formSubmit函數的內部還要計算validata函數的返回值,因為返回值的結果表明瞭是否通過校驗。接下來進一步優化這段代碼,使validata和formSubmit完全分離開來。首先要改寫Function.prototype.before,如果beforefn的執行結果返回false,表示不再執行後面的原函數,代碼如下:

Function.prototype.before = function( beforefn ){
    var __self = this;
    return function(){
        if ( beforefn.apply( this, arguments ) === false ){
        // beforefn 返回false 的情況直接return,不再執行後面的原函數
            return;
        }
        return __self.apply( this, arguments );
    }
}

var validata = function(){
    if ( username.value === '' ){
        alert ( '用戶名不能為空' );
        return false;
    }
    if ( password.value === '' ){
        alert ( '密碼不能為空' );
        return false;
    }
}
var formSubmit = function(){
    var param = {
        username: username.value,
        password: password.value
    }
    ajax( 'http://xx.com/login', param );
}

formSubmit = formSubmit.before( validata );

submitBtn.onclick = function(){
    formSubmit();
}

  在這段代碼中,校驗輸入和提交表單的代碼完全分離開來,它們不再有任何耦合關係,formSubmit=formSubmit.before(validata)這句代碼,如同把校驗規則動態接在formSubmit函數之前,validata成為一個即插即用的函數,它甚至可以被寫成配置文件的形式,這有利於分開維護這兩個函數。再利用策略模式稍加改造,就可以把這些校驗規則都寫成插件的形式,用在不同的項目當中

  值得註意的是,因為函數通過Function.prototype.before或者Function.prototype.after被裝飾之後,返回的實際上是一個新的函數,如果在原函數上保存了一些屬性,那麼這些屬性會丟失。代碼如下:

var func = function(){
  alert(1);
}
func.a='a';
func=func.after(function(){
  alert(2);
});
alert(func.a);    //輸出:undefined

  另外,這種裝飾方式也疊加了函數的作用域,如果裝飾的鏈條過長,性能上也會受到一些影響

 

裝飾者模式和代理模式

  裝飾者模式和代理模式的結構看起來非常相像,這兩種模式都描述了怎樣為對象提供一定程度上的間接引用,它們的實現部分都保留了對另外一個對象的引用,並且向那個對象發送請求。代理模式和裝飾者模式最重要的區別在於它們的意圖和設計目的。代理模式的目的是,當直接訪問本體不方便或者不符合需要時,為這個本體提供一個替代者。本體定義了關鍵功能,而代理提供或拒絕對它的訪問,或者在訪問本體之前做一些額外的事情。裝飾者模式的作用就是為對象動態加入行為。換句話說,代理模式強調一種關係(Proxy與它的實體之間的關係),這種關係可以靜態的表達,也就是說,這種關係在一開始就可以被確定。而裝飾者模式用於一開始不能確定對象的全部功能時。代理模式通常只有一層代理——本體的引用,而裝飾者模式經常會形成一條長長的裝飾鏈

 


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

-Advertisement-
Play Games
更多相關文章
  • mongoDB之數據類型 Object ID :文檔的id String: 字元串,最常用,必須是utf-8 Boolean:布爾值,true 或者false Integer:整數 Double:浮點數 Arrays:數組或者列表,多個值存儲到一個鍵 Object:用於嵌入文檔,即一個值為一個文檔 ...
  • mongoDB中的資料庫操作 查看資料庫名稱: db 查看所有資料庫: show dbs 切換資料庫: use 資料庫名稱 註意:如果資料庫不存在,則指向資料庫,但不會創建。直到插入數據或者是創建集合時資料庫才會創建; 資料庫的刪除: db.dropDatabase(); ...
  • 一個資料庫被映射到多個不同的文件,這些文件由底層的操作系統來維護。每個文件分成定長的存儲單元,稱為塊(bolck),塊是存儲分配和數據傳輸的基本單元。資料庫預設的塊在4-8k之間。通常沒有記錄比塊更大(圖片音頻等大文件先不考慮),此外還要求每條記錄保存在單個塊中。 一、定長記錄instructor表 ...
  • LivingMongo是一個mongodb資料庫的GUI操作系統,支持對數據欄位的修改、數據搜索、集合的分類、索引管理、空間統計、慢查詢等 demo地址 : http://living-mongo.kupposhadow.com使用介紹 : 開源MongoDB GUI - LivingMongogi ...
  • 引言 在 "大數據學習系列之一 Hadoop環境搭建(單機)" 成功的搭建了Hadoop的環境,在 "大數據學習系列之二 HBase環境搭建(單機)" 成功搭建了HBase的環境以及相關使用介紹。本文主要講解如何搭建Hadoop+Hive的環境。 一、環境準備 1,伺服器選擇 本地虛擬機 操作系統: ...
  • 在安卓系統中預設每次啟動一個Activity時,系統會創建一個實例,並按照先進後出的原則放入任務棧中,當我們按back鍵時,就會有一個activity從任務棧頂移除,重覆下去,直到任務棧為空,系統就會回收這個任務棧。但是這樣以來,系統多次啟動同一個Activity時就會重覆創建多個實例,這種做法顯然... ...
  • ListView,就如其名,是用來顯示列表的一種View,而RecycleView,是其的加強版,今天帶來的是這兩個幾乎具有相同的功能的對比使用 先從ListView說起吧 ListView: 1.在佈局文件中使用ListView,併為其定義一個id,方便我們之後的調用,寬高與父控制項相同 2.準備數 ...
  • [1]初識狀態模式 [2]通用結構 [3]文件上傳 [4]優缺點 [5]狀態機 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...