操刀 requirejs,自己動手寫一個

来源:http://www.cnblogs.com/lianmin/archive/2017/01/09/my_requirejs.html
-Advertisement-
Play Games

前沿 寫在文章的最前面 前沿 寫在文章的最前面 這篇文章講的是,我怎麼去寫一個 requirejs 。 去 github 上fork一下,順便star~ requirejs,眾所周知,是一個非常出名的js模塊化工具,可以讓你使用模塊化的方式組織代碼,並非同步載入你所需要的部分。balabala 等等好 ...


前沿 寫在文章的最前面

這篇文章講的是,我怎麼去寫一個 requirejs 。

去 github 上fork一下,順便star~

requirejs,眾所周知,是一個非常出名的js模塊化工具,可以讓你使用模塊化的方式組織代碼,並非同步載入你所需要的部分。balabala 等等好處不計其數。

之所以寫這篇文章,是做一個總結。目前打算動一動,換一份工作。感謝 一線碼農 大大幫忙推了攜程,得到了面試的機會。

面試的時候,聊著聊著感覺問題都問在了自己的“點”上,應答都挺順利,於是就慢慢膨脹了。在說到模塊化的時候,我腦子一抽,憑著感覺說了一下requirejs實現的大概步驟,充滿了表現欲望,廢話一堆。僥幸不可能當場讓我寫一遍,算是過了,事後嘗試了一下,在這裡跟大家分享一下我的實現。

結構劃分

上面是我劃分的項目結構:

  1. tool,工具模塊,存放便捷方法,很多地方需要用到。
  2. async,非同步處理模塊,主要實現了 promisedeferred 。邏輯上的非同步。
  3. requirejs -> loader ,amd載入器,處理模塊的依賴和非同步載入。物理上的非同步。

因為對於非同步流程式控制制方面,研究過一段時間,所以這裡第一時間想到的就是 promise ,如果用這個來做,所有的模塊放入字典,路徑做key,promise做value,所有依賴都結束之後,才進行下一步操作。 不用管複雜的依賴關係,把邏輯儘量簡單化:

  1. 首先有一個字典,存放所有的模塊。key放地址,value放promise,promise在模塊載入完畢的時候resolve。
  2. 如果依賴某個模塊,先根據路徑從字典找key,存在就用該promise,不存在就去載入該模塊並放入字典,並使用該模塊的promise。
  3. 所有的模塊,我只用它的 promise ,在它的回調中寫我的後續操作。它的resolve應該單獨抽離出來,屬於非同步載入方面。

大致思路有了,當然實際寫的時候肯定困難重重,不過沒關係,遇到問題再去解決。

考慮到代碼的簡易性,以及我的個人習慣。我打算用類似於 jquery 的 $.Deferred() 和它的promise,與es6的promise有一定的出入。這樣代碼書寫更簡易,並且邏輯上更清晰,es6的promise用起來確實稍顯麻煩。我需要的是一個 pub/sub 模式,一個地方觸發,多個回調執行的並行方式,es6的promise,需要在then中一次次返回,並且resolve起來也不方便,最最主要的是需要 polyfill 一下,而我想自己寫,寫我熟悉且喜歡的代碼 。

callbacks模塊

回調模塊 callbacks,熟悉jquery的朋友接下來可能會覺得使用方式很熟悉,沒錯,我受jq的影響算是比較深的。以前在學習jq源碼的時候,就覺得這個很好用,你可以從我的代碼裡面看到jq的影子 :

 1 import _ from '../tool/tool';
 2 
 3 /**
 4  * 基礎回調模塊
 5  * 
 6  * @export
 7  * @returns callbacks
 8  */
 9 export default function () {
10     let list = [],
11         _args = (arguments[0] || '').split(' '),           // 參數數組
12         fireState = 0,                                     // 觸髮狀態  0-未觸發過 1-觸發中  2-觸發完畢
13         stopOnFalse = ~_args.indexOf('stopOnFalse'),       // stopOnFalse - 如果返回false就停止
14         once = ~_args.indexOf('once'),                     // once - 只執行一次,即執行完畢就清空
15         memory = ~_args.indexOf('memory') ? [] : null,     // memory - 保持狀態
16         fireArgs = [];                                     // fire 參數
17 
18     /**
19      * 添加回調函數
20      * 
21      * @param {any} cb
22      * @returns callbacks
23      */
24     function add(cb) {
25         if (memory && fireState == 2) {  // 如果是memory模式,並且已經觸發過
26             cb.apply(null, fireArgs);
27         }
28 
29         if (disabled()) return this;      // 如果被disabled
30 
31         list.push(cb);
32         return this;
33     }
34 
35     /**
36      * 觸發
37      * 
38      * @param {any} 任意參數
39      * @returns callbacks
40      */
41     function fire() {
42         if (disabled()) return this; // 如果被禁用
43 
44         fireArgs = _.makeArray(arguments); // 保存 fire 參數
45 
46         fireState = 1; // 觸發中 
47 
48         _.each(list, (index, cb) => { // 依次觸發回調
49             if (cb.apply(null, fireArgs) === false && stopOnFalse) { // stopOnFalse 模式下,遇到false會停止觸發
50                 return false;
51             }
52         });
53 
54         fireState = 2; // 觸髮結束
55 
56         if (once) disable(); // 一次性列表
57 
58         return this;
59     }
60 
61     function disable() {    // 禁止
62         list = undefined;
63         return this;
64     }
65 
66     function disabled() {  // 獲取是否被禁止
67         return !list;
68     }
69 
70     return {
71         add: add,
72         fire: fire,
73         disable: disable,
74         disabled: disabled
75     };
76 }
View Code

 

這是一個工廠方法,每次所需的對象由該方法生成,用閉包來隱藏局部變數,私有方法。而最後暴露(發佈)出來的對象,用 pub/sub 模式,提供了 訂閱觸發禁用查看禁用 4個方法。 這裡要說的是 ,提供了3個參數:stopOnFalseoncememory。觸發的時候,按照訂閱順序依次觸發,如果是 stopOnFalse 模式,當某個訂閱的函數,返回是 false 的時候,停止整個觸發過程。 如果是 once ,表示每個函數只能執行一次,在執行過後,會被移除隊列。而 memory 狀態下,在 callback 觸發後,會被保持狀態,之後添加的方法,添加後會直接執行。

這三種模式,傳參的時候直接傳入字元串,可以隨意組合,用空格分開,比如:callbacks('once memory')

該模塊用於整個項目中,處理所有的回調。使用方式類似於jquery的:$.Callbacks(...)

 

deferred 模塊

deferred ,是對promise的父級模塊,主要提供了 觸發 和 訂閱 2個方法。 promise 是對 deferred 的一個再封裝,僅僅暴露出其中的 訂閱 方法。

從概念上來說,很像 C# 中的委托和事件。

 1 import _ from '../tool/tool';
 2 import callbacks from './callbacks';
 3 
 4 /**
 5  * deferred 模塊
 6  * 
 7  * @export
 8  * @returns deferred
 9  */
10 export default function () {
11     let tuples = [   // 用於存放一系列回調的 tuple 結構
12         // 方法名 - 介面名稱 - 回調列表 - 最終狀態
13         ['resolve', 'then', callbacks('once memory'), 'resolved'],
14         ['reject', 'catch', callbacks('once memory'), 'rejected']
15     ];
16 
17     let _state = 'pending';    // 當前狀態
18 
19     let dfd = {                // 返回的延遲對象
20         state: function () {
21             return _state;
22         },      // 狀態
23         promise: function () { // promise - 僅提供介面用於註冊/訂閱
24             let self = this;
25             let pro = {
26                 state: self.state
27             };
28             _.each(tuples, (i, tuple) => { // 訂閱介面
29                 pro[tuple[1]] = self[tuple[1]];
30             });
31             return pro;
32         }
33     };
34 
35     _.each(tuples, (i, tuple) => {
36         dfd[tuple[0]] = function () {       // 觸發
37             if (_state != "pending") return this;
38             tuple[2].fire.apply(tuple[2], _.makeArray(arguments));
39             _state = tuple[3];
40             return this;
41         };
42         dfd[tuple[1]] = function (cb) {     // 綁定
43             tuple[2].add(cb);
44             return this;
45         };
46     });
47 
48     return dfd;
49 }
View Code

deferred 使用了 callbacks 模塊來處理其中所有的回調函數。是一個工廠方法,deferred() 返回的是一個deferred對象(發佈),包含了3種狀態:pendingresolvedrejected;提供了 thencatch 去訂閱;通過 resolvereject 去 改變(觸發) 狀態。

deferred 對象,提供了一個 promise() 方法去返回一個promise對象,區別就是promise對象屏蔽了觸發的方法。就像委托和事件,前者可以訂閱和觸發,而後者只能訂閱。之所以如此,是想只提供訂閱的介面,而如何觸發,何時觸發,由我自己控制,是我邏輯內部的事情,而其他部分,只需要知道也只能去訂閱。

Tuple ,是一種約定的、按照某個規則進行存儲的數據結構(類?), c# ,typescript 中都有這個東西,之前在學習jq的時候,看到了它的內部也這麼用,於是學到了。其實在我看來,使用tuple,就是節約代碼,笑。不必要去定義某個類,或者其他的東西,只需要在定義和使用的時候,遵循某個約定好的規則,那麼就可以省去一大堆的代碼,讓邏輯部分也清晰不少。


all 模塊

 1 import deferred from './deferred';
 2 import _ from '../tool/tool';
 3 
 4 export default function (promises) {
 5     promises = _.makeArray(promises);
 6     let len = promises.length,    // promise 個數
 7         resNum = 0,               // resolve 的數量
 8         argsArr = new Array(len), // 每個reject的參數
 9         dfd = deferred(),    // 用於當前task控制的deferred
10         pro = dfd.promise();      // 用於當前返回的promise
11 
12     if (len === 0) {   // 如果是個空數組,直接就返回了
13         dfd.resolve();
14         return pro;
15     }
16 
17     function addThen() {   // 檢測是否全部完成
18         resNum++;
19         let args = _.makeArray(arguments);
20         let index = args.shift(); // 當前參數在promises中的索引
21 
22         if (args.length <= 1) {             // 保存到數組,用戶回調
23             argsArr[index] = args[0];
24         } else {
25             argsArr[index] = args;
26         }
27 
28         if (resNum >= len) {         // 如果所有promise都resolve完畢
29             dfd.resolve(argsArr);
30         }
31     }
32 
33     function addCatch() {  // 如果某個promise發生了reject 
34         var args = _.makeArray(arguments);
35         dfd.reject(...args);
36     }
37 
38     _.each(promises, (index, promise) => {
39         promise.then(function () {
40             addThen(index, ...arguments);
41         }).catch(addCatch);
42     });
43 
44     return pro;
45 }
View Code

 

all,其實就是es6中, Promise.all 或者 $.when 的一種實現。參數是一系列的promise,本身返回一個promise對象,在所有參數中的promise對象都處於 resolved狀態 時,本身也會被resolve掉,由此來執行通過then訂閱的方法。

all本身,是通過一個觸發器來實現在最後一個promise完成時回調。內部用一個int值來存儲resolved的參數的個數,給每個參數通過 then 添加一個回調來執行這個觸發器,當 完成數量 >= 參數個數 的時候,就表示所有promise已經完成,可以進行後續的操作。 用 >= 來代替 == 是個好習慣 :D

 


 

模塊分析 模塊定義、模塊獲取

到此為止,async 部分已經完成,準備工作已經做好。我們開始 amd 模塊部分的分析。

amd 模塊在我看來,主要分為兩個部分:模塊定義模塊獲取。先說模塊獲取:

模塊獲取

模塊的獲取,並不複雜。先從字典中根據路徑(key)去找該模塊,如果有該模塊,就去載入。如果不存在,就去載入該js,根據onload來確定該模塊的名稱(如果是匿名模塊);然後根據該模塊的返回值==》 一個promise,給該promise添加一個回調,去管理 getModule 的返回值狀態==》另一個promise。在使用一個模塊的時候,從本質上來講,是給該模塊的promise的then介面添加回調函數,一層層往下處理。

模塊定義

這裡的重點是 載入模塊,大家都知道,amd的每個模塊,對應一個js文件,載入模塊就是去載入這個js。

再看看模塊的定義,有 3種重載:

  1. define(sender)
  2. define(deps,sender)
  3. define(name,deps,sender)

sender 是一個函數,或者某個對象。deps 是一個數組,表示該模塊依賴的其他模塊。name 是表示當前模塊是一個命名模塊,強制使用該名稱,一般是打包工具生成這種模塊,不建議自己直接這麼寫。

從上面我們可以看到,模塊是通過執行一個函數,用傳參的方式把所要用到的模塊載入到某個地方保存起來。那麼看到這個你們有沒有想到什麼呢?我首先想到的就是 jsonp ,動態執行一個函數,把數據放進去,對得上,完美。從這個思路,我實驗了一下,在這裡直接說結論: script標簽在動態載入到頁面後,首先去伺服器拿對應地址的數據,然後在文件下載完全後,執行該js文件中的內容,執行完畢後,會觸發該script標簽的load事件。

也就是說,通過給load事件註冊方法,我們可以知道最後一個載入的模塊(js文件),來自哪裡,什麼時候執行完全。這樣就確定了,並行載入多個js文件時,匿名模塊所屬來源。這裡不討論相容的問題,低版本ie對應的是其他事件:onreadystatechange,我沒用過。

 

在模塊載入後,我們用一個函數來將模塊填充到字典中,類似於一個 觸發器,每次載入一個模塊,模塊中包含這個函數並執行,處理依賴關係,並將最後的結果保存。

在模塊的載入中,因為可能會同時載入多個模塊(js文件),並不能確定到底是哪一個先載入完全。但是我們知道,js是單線程,在js文件下載完全後,會先把js文件中的內容執行完畢,然後再觸發load事件,這個順序是可以保證的,所以就可以使用一個變數來保存最近載入的模塊,來知道匿名模塊的所屬路徑。

不論是匿名模塊,還是命名模塊,都可能依賴其他的模塊,所以並不能確定在模塊載入完之後,就可以立即使用,要等待所有的依賴項都載入完畢,所以一個模塊的最終返回值我使用的一個promise來保存。這樣就可以方便的在狀態變更後才添加下一步的處理操作,從邏輯上簡化整個流程式控制制。

模塊入口 require

 1 /**
 2  * 程式入口, require
 3  * 
 4  * @export
 5  * @param {any} deps 依賴項
 6  * @param {any} callback 程式入口
 7  */
 8 export function requireModule(deps, callback) {
 9     setTimeout(function () {  // 避免阻塞同文件中,使用名稱定義的模塊
10         deps = deps.map(url => getModule(_.resolvePath(core.rootUrl, url)));
11         all(deps).then(function (args) {
12             callback(...args);
13         });
14     }, 0);
15 }
View Code

 這裡的代碼比較簡單,唯一要註意的就是這個 setTimeout(action,0)  。因為js是單線程,從上往下依次執行。模塊可能會被打包工具合併成一個文件,那麼在一個文件中就含有了模塊入口、命名模塊。如果模塊入口在最上方,,,在依賴某個命名模塊的時候,就會試圖去載入這個名稱的js文件,而這註定是會失敗的。所以使用一個setTimeout,把模塊入口的邏輯,放入事件隊列中,讓js邏輯線程優先去執行文件後面的代碼,就避免了這個問題。

loader 模塊代碼

  1 import core from './core';
  2 import deferred from './async/deferred';
  3 import all from './async/all';
  4 import _ from './tool/tool';
  5 
  6 let lastNameDfd = null; // 最後一個載入的module的name的 deferred
  7 
  8 
  9 /**
 10  * 程式入口, require
 11  * 
 12  * @export
 13  * @param {any} deps 依賴項
 14  * @param {any} callback 程式入口
 15  */
 16 export function requireModule(deps, callback) {
 17     setTimeout(function () {  // 避免阻塞同文件中,使用名稱定義的模塊
 18         deps = deps.map(url => getModule(_.resolvePath(core.rootUrl, url)));
 19         all(deps).then(function (args) {
 20             callback(...args);
 21         });
 22     }, 0);
 23 }
 24 
 25 /**
 26  * 模塊定義,url,deps,sender
 27  * 
 28  * @export
 29  */
 30 export function defineModule() {
 31     let args = _.makeArray(arguments);
 32     let name = "",     // 模塊名稱
 33         proArr,   // 模塊依賴
 34         sender; // 模塊的主體
 35 
 36     let argsLen = args.length; // 參數的個數,用來重載
 37 
 38     if (argsLen == 1) {  // 重載一下   sender
 39         proArr = [];
 40         sender = args[0];
 41     }
 42     else if (argsLen == 2) {  // deps,sender
 43         proArr = args[0];
 44         sender = args[1];
 45     }
 46     else if (argsLen == 3) {  // name,deps,sender
 47         name = args[0];
 48         proArr = args[1];
 49         sender = args[2];
 50     }
 51     else {
 52         throw Error('參數個數異常');
 53     }
 54 
 55     let dfdThen = (_name, lastModule) => {
 56         _name = _.normalizePath(_name); // 名稱,路徑
 57 
 58         proArr = proArr.map(url => {  // 各個依賴項 
 59             url = _.resolvePath(_name, url); // 以當前路徑為基準,合併路徑
 60             return getModule(url);
 61         });
 62 
 63         all(proArr).then(function (_args) {  // 在依賴項載入完畢後,進行模塊處理
 64             _args = _args || [];
 65             let result; // 最終結果
 66             let _type = _.type(sender); // 回調模塊類型
 67 
 68             if (_type == "function") {
 69                 result = sender(..._args);
 70             }
 71             else if (_type == "object") {
 72                 result = sender;
 73             }
 74             else {
 75                 throw Error("參數類型錯誤");
 76             }
 77 
 78             lastModule.resolve(result);
 79 
 80         });
 81     };
 82 
 83     if (argsLen < 3) {  // 如果是匿名模塊,使用 onload 來判斷js的名稱/路徑
 84         lastNameDfd = deferred();  // 先獲取當前模塊名稱
 85 
 86         lastNameDfd.then(dfdThen);
 87     }
 88     else {  // 如果是自定義模塊名,直接觸發,命名模塊直接添加
 89         let lastModule = deferred();
 90         let dictName = _.resolvePath(core.rootUrl, name);
 91         core.dict[dictName] = lastModule;
 92 
 93         let namedDfd = deferred().then(dfdThen);
 94 
 95         setTimeout(function () {   // 避免同文件中,多個命名模塊註冊阻塞,先把名字註冊了,具體內容等待一下 event loop 
 96             namedDfd.resolve(dictName, lastModule);
 97         }, 0);
 98     }
 99 
100 }
101 
102 /**
103  * 根據 路徑/名稱 ,載入/獲取模塊的promise
104  * 
105  * @param {any} name
106  * @returns promise
107  */
108 function getModule(name) {
109     let dict = core.dict;
110     if (dict[name]) {
111         return dict[name];
112     }
113 
114     let script = addScript(name);
115 
116     let dfd = deferred();
117     dict[name] = dfd;
118 
119     script.onload = function () {  // 模塊載入完畢,立馬會觸發 load 事件,由此來確定模塊所屬
120         let lastModule = deferred();
121         lastNameDfd.resolve(name, lastModule); // 綁定當前模塊的名稱
122 
123         lastModule.then(result => {  // 在模塊載入完畢之後,觸發該模塊的 resolve
124             dfd.resolve(result);
125         });
126     };
127 
128     return dfd.promise();
129 }
130 
131 /**
132  * 添加 script 標簽
133  * 
134  * @export
135  * @param {any} name
136  * @returns
137  */
138 export function addScript(name) {
139     let script = document.createElement('script');
140     script.type = "text/javascript";
141     script.async = true;
142     script.charset = "utf-8";
143     script.src = name + ".js";
144     document.head.appendChild(script);
145     return script;
146 }
View Code

 

core 模塊

 1 /**
 2  * 預設核心載體
 3  */
 4 export default {
 5     /**
 6      *  版本
 7      */
 8     ver: "0.0.1",
 9     /**
10      * 模塊定義名稱
11      */
12     defineName: "define",
13     /**
14      * 程式入口函數
15      */
16     requireName: "require",
17     /**
18      * 暴露的全局名稱,可用於配置
19      */
20     coreName: "requirejs",
21     /**
22      * 根目錄,入口文件目錄
23      */
24     rootUrl: "",
25     /**
26      * 依賴模塊存儲字典
27      */
28     dict: {  // 模塊字典 {key:string,value:promise}
29 
30     }
31 };
View Code

core,主要存的是一些配置信息,和模塊的字典,比較簡單。

 

 

總結、Github

寫到這裡,就已經結束了。本文講了對於requirejs,我的實現思路,列舉了可能遇到的問題,及我的解決方式。希望能給大家的學習提供點幫助。

去 github 上fork一下,順便star~

上面是github的地址,求star啊,作為一個虛榮的人,我對這個很看重的,哈哈,也就這點追求了。再次感激 一線碼農 大哥的推薦,還有 linkFly 的經驗指導。


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

-Advertisement-
Play Games
更多相關文章
  • 委托是一個類,它定義了方法的類型,使得可以將方法當作另一個方法的參數來進行傳遞。事件是一種特殊的委托。 1.委托的聲明 (1). delegate delegate我們常用到的一種聲明 Delegate至少0個參數,至多32個參數,可以無返回值,也可以指定返回值類型。 例:public delega ...
  • APS.NET MVC中(以下簡稱“MVC”)的每一個請求,都會分配給相應的控制器和對應的行為方法去處理,而在這些處理的前前後後如果想再加一些額外的邏輯處理。這時候就用到了過濾器。 MVC支持的過濾器類型有四種,分別是:Authorization(授權),Action(行為),Result(結果)和 ...
  • 本篇博文,給大家講解一下裝飾模式,還是老樣子,有一個簡單的例子逐步演繹 一、舉例 用一個簡單的控制台實現 一個人穿各種各樣衣服 的功能 然後我們會很自然的寫出一下代碼: 先寫一個Person類 然後客戶端調用這個Person類 這樣就寫完了。 二、演繹 ①現在,我各種裝扮都寫到了Person類中,有 ...
  • 1 OAuth2解決什麼問題的? 舉個慄子先。小明在QQ空間積攢了多年的照片,想挑選一些照片來列印出來。然後小明在找到一家提供線上列印並且包郵的網站(我們叫它PP吧(Print Photo縮寫 😂))。 那麼現在問題來了,小明有兩個方案來得到列印的服務。 針對方案(1):小明要去下載這些照片,然後 ...
  • 如何快速開發Xamarin.Forms的Plugin?自己開發的Plugin如何使用Nuget打包?本地Package如何參照引用?本文通過TextToSpeech實例為你講解。 ...
  • 配置一個如上圖所示的菜單 1.打開文件MpaNavigationProvider.cs 【..\MyCompanyName.AbpZeroTemplate.Web\Areas\Mpa\Startup\MpaNavigationProvider.cs】 添加如下代碼(如下圖所示) .AddItem(n ...
  • 這是一篇關於純C++RPC框架的文章。所以,我們先看看,我們有什麼? 1、一個什麼都能幹的C++。(前提是,你什麼都幹了) 2、原始的Socket介面,還是C API。還得自己去二次封裝... 3、C++11,這是最令人興奮的。有了它,才能夠有這篇文章;否則,CORBA之類的才是唯一的選擇。(因為需 ...
  • 今天分享一下簡單導航欄的製作方法: 第一步:引入css樣式表,新建一個id為nav的層,使用<ul>、<li>、<a>標簽來製作完成效果。 第二步設置CSS樣式: 1.設置nav的屬性 展示效果如下所示: 2.清除<ul>標簽前面自帶的點 3.設置<ul>下包含的<a>標簽的屬性 4.設置滑鼠滑過效 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...