22.1 高級函數【JavaScript高級程式設計第三版】

来源:http://www.cnblogs.com/itzhoubao/archive/2017/04/28/6781802.html
-Advertisement-
Play Games

函數是JavaScript 中最有趣的部分之一。它們本質上是十分簡單和過程化的,但也可以是非常複雜和動態的。一些額外的功能可以通過使用閉包來實現。此外,由於所有的函數都是對象,所以使用函數指針非常簡單。這些令JavaScript 函數不僅有趣而且強大。以下幾節描繪了幾種在JavaScript 中使用 ...


函數是JavaScript 中最有趣的部分之一。它們本質上是十分簡單和過程化的,但也可以是非常複雜和動態的。一些額外的功能可以通過使用閉包來實現。此外,由於所有的函數都是對象,所以使用函數指針非常簡單。這些令JavaScript 函數不僅有趣而且強大。以下幾節描繪了幾種在JavaScript 中使用函數的高級方法。

22.1.1 安全的類型檢測

JavaScript 內置的類型檢測機制並非完全可靠。事實上,發生錯誤否定及錯誤肯定的情況也不在少數。比如說typeof 操作符吧,由於它有一些無法預知的行為,經常會導致檢測數據類型時得到不靠譜的結果。Safari(直至第4 版)在對正則表達式應用typeof 操作符時會返回"function",因此很難確定某個值到底是不是函數。
再比如,instanceof 操作符在存在多個全局作用域(像一個頁麵包含多個frame)的情況下,也是問題多多。一個經典的例子(第5 章也提到過)就是像下麵這樣將對象標識為數組。

var isArray = value instanceof Array;

以上代碼要返回true,value 必須是一個數組,而且還必須與Array 構造函數在同個全局作用域中。(別忘了,Array 是window 的屬性。)如果value 是在另個frame 中定義的數組,那麼以上代碼就會返回false。

在檢測某個對象到底是原生對象還是開發人員自定義的對象的時候,也會有問題。出現這個問題的原因是瀏覽器開始原生支持JSON 對象了。因為很多人一直在使用Douglas Crockford 的JSON 庫,而該庫定義了一個全局JSON 對象。於是開發人員很難確定頁面中的JSON 對象到底是不是原生的。

解決上述問題的辦法都一樣。大家知道,在任何值上調用Object 原生的toString()方法,都會返回一個[object NativeConstructorName]格式的字元串。每個類在內部都有一個[[Class]]屬性,這個屬性中就指定了上述字元串中的構造函數名。舉個例子吧。

alert(Object.prototype.toString.call(value)); //"[object Array]"

由於原生數組的構造函數名與全局作用域無關,因此使用toString()就能保證返回一致的值。利用這一點,可以創建如下函數:

function isArray(value) {
	return Object.prototype.toString.call(value) == "[object Array]";
}

同樣,也可以基於這一思路來測試某個值是不是原生函數或正則表達式:

function isFunction(value) {
	return Object.prototype.toString.call(value) == "[object Function]";
}
function isRegExp(value) {
	return Object.prototype.toString.call(value) == "[object RegExp]";
}

不過要註意,對於在IE 中以COM 對象形式實現的任何函數,isFunction()都將返回false(因為它們並非原生的JavaScript 函數,請參考第10 章中更詳細的介紹)。
這一技巧也廣泛應用於檢測原生JSON 對象。Object 的toString()方法不能檢測非原生構造函數的構造函數名。因此,開發人員定義的任何構造函數都將返回[object Object]。有些JavaScript 庫會包含與下麵類似的代碼。

var isNativeJSON = window.JSON && Object.prototype.toString.call(JSON) == "[object JSON]";

在Web 開發中能夠區分原生與非原生JavaScript 對象非常重要。只有這樣才能確切知道某個對象到底有哪些功能。這個技巧可以對任何對象給出正確的結論。

請註意,Object.prototpye.toString()本身也可能會被修改。本節討論的技巧假設Object.prototpye.toString()是未被修改過的原生版本。

22.1.2 作用域安全的構造函數

第6 章講述了用於自定義對象的構造函數的定義和用法。你應該還記得,構造函數其實就是一個使用new 操作符調用的函數。當使用new 調用時,構造函數內用到的this 對象會指向新創建的對象實例,如下麵的例子所示:

function Person(name, age, job) {
	this.name = name;
	this.age = age;
	this.job = job;
}
var person = new Person("Nicholas", 29, "Software Engineer");

上面這個例子中,Person 構造函數使用this 對象給三個屬性賦值:name、age 和job。當和new操作符連用時,則會創建一個新的Person 對象,同時會給它分配這些屬性。問題出在當沒有使用new操作符來調用該構造函數的情況上。由於該this 對象是在運行時綁定的,所以直接調用Person(),this 會映射到全局對象window 上,導致錯誤對象屬性的意外增加。例如:

var person = Person("Nicholas", 29, "Software Engineer");
alert(window.name); //"Nicholas"
alert(window.age); //29
alert(window.job); //"Software Engineer"

運行一下
這裡,原本針對Person 實例的三個屬性被加到window 對象上,因為構造函數是作為普通函數調用的,忽略了new 操作符。這個問題是由this 對象的晚綁定造成的,在這裡this 被解析成了window對象。由於window 的name 屬性是用於識別鏈接目標和frame 的,所以這裡對該屬性的偶然覆蓋可能會導致該頁面上出現其他錯誤。這個問題的解決方法就是創建一個作用域安全的構造函數。
作用域安全的構造函數在進行任何更改前,首先確認this 對象是正確類型的實例。如果不是,那麼會創建新的實例並返回。請看以下例子:

function Person(name, age, job) {
	if (this instanceof Person) {
		this.name = name;
		this.age = age;
		this.job = job;
	} else {
		return new Person(name, age, job);
	}
}
var person1 = Person("Nicholas", 29, "Software Engineer");
alert(window.name); //""
alert(person1.name); //"Nicholas"
var person2 = new Person("Shelby", 34, "Ergonomist");
alert(person2.name); //"Shelby"

運行一下

這段代碼中的Person 構造函數添加了一個檢查並確保this 對象是Person 實例的if 語句,它表示要麼使用new 操作符,要麼在現有的Person 實例環境中調用構造函數。任何一種情況下,對象初始化都能正常進行。如果this 並非Person 的實例,那麼會再次使用new 操作符調用構造函數並返回結果。最後的結果是,調用Person 構造函數時無論是否使用new 操作符,都會返回一個Person 的新實例,這就避免了在全局對象上意外設置屬性。關於作用域安全的構造函數的貼心提示。實現這個模式後,你就鎖定了可以調用構造函數的環境。

如果你使用構造函數竊取模式的繼承且不使用原型鏈,那麼這個繼承很可能被破壞。這裡有個例子:

function Polygon(sides) {
	if (this instanceof Polygon) {
		this.sides = sides;
		this.getArea = function() {
			return 0;
		};
	} else {
		return new Polygon(sides);
	}
}
function Rectangle(width, height) {
	Polygon.call(this, 2);
	this.width = width;
	this.height = height;
	this.getArea = function() {
		return this.width * this.height;
	};
}
var rect = new Rectangle(5, 10);
alert(rect.sides); //undefined

運行一下

在這段代碼中,Polygon 構造函數是作用域安全的,然而Rectangle 構造函數則不是。新創建一個Rectangle 實例之後,這個實例應該通過Polygon.call()來繼承Polygon 的sides 屬性。但是,由於Polygon 構造函數是作用域安全的,this 對象並非Polygon 的實例,所以會創建並返回一個新的Polygon 對象。Rectangle 構造函數中的this 對象並沒有得到增長,同時Polygon.call()返回的值也沒有用到,所以Rectangle 實例中就不會有sides 屬性。

如果構造函數竊取結合使用原型鏈或者寄生組合則可以解決這個問題。考慮以下例子:

function Polygon(sides) {
	if (this instanceof Polygon) {
		this.sides = sides;
		this.getArea = function() {
			return 0;
		};
	} else {
		return new Polygon(sides);
	}
}
function Rectangle(width, height) {
	Polygon.call(this, 2);
	this.width = width;
	this.height = height;
	this.getArea = function() {
		return this.width * this.height;
	};
}
Rectangle.prototype = new Polygon();
var rect = new Rectangle(5, 10);
alert(rect.sides); //2

運行一下

上面這段重寫的代碼中,一個Rectangle 實例也同時是一個Polygon 實例,所以Polygon.call()會照原意執行,最終為Rectangle 實例添加了sides 屬性。

多個程式員在同一個頁面上寫JavaScript 代碼的環境中,作用域安全構造函數就很有用了。屆時,對全局對象意外的更改可能會導致一些常常難以追蹤的錯誤。除非你單純基於構造函數竊取來實現繼承,推薦作用域安全的構造函數作為最佳實踐。

22.1.3 惰性載入函數

因為瀏覽器之間行為的差異,多數JavaScript 代碼包含了大量的if 語句,將執行引導到正確的代碼中。看看下麵來自上一章的createXHR()函數。

function createXHR() {
	if (typeof XMLHttpRequest != "undefined") {
		return new XMLHttpRequest();
	} else if (typeof ActiveXObject != "undefined") {
		if (typeof arguments.callee.activeXString != "string") {
			var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"],
			i,
			len;
			for (i = 0, len = versions.length; i < len; i++) {
				try {
					new ActiveXObject(versions[i]);
					arguments.callee.activeXString = versions[i];
					break;
				} catch(ex) {
					//跳過
				}
			}
		}
		return new ActiveXObject(arguments.callee.activeXString);
	} else {
		throw new Error("No XHR object available.");
	}
}

每次調用createXHR()的時候,它都要對瀏覽器所支持的能力仔細檢查。首先檢查內置的XHR,然後測試有沒有基於ActiveX 的XHR,最後如果都沒有發現的話就拋出一個錯誤。每次調用該函數都是這樣,即使每次調用時分支的結果都不變:如果瀏覽器支持內置XHR,那麼它就一直支持了,那麼這種測試就變得沒必要了。即使只有一個if 語句的代碼,也肯定要比沒有if 語句的慢,所以如果if 語句不必每次執行,那麼代碼可以運行地更快一些。解決方案就是稱之為惰性載入的技巧。

惰性載入表示函數執行的分支僅會發生一次。有兩種實現惰性載入的方式,第一種就是在函數被調用時再處理函數。在第一次調用的過程中,該函數會被覆蓋為另外一個按合適方式執行的函數,這樣任何對原函數的調用都不用再經過執行的分支了。例如,可以用下麵的方式使用惰性載入重寫createXHR()。

function createXHR() {
	if (typeof XMLHttpRequest != "undefined") {
		createXHR = function() {
			return new XMLHttpRequest();
		};
	} else if (typeof ActiveXObject != "undefined") {
		createXHR = function() {
			if (typeof arguments.callee.activeXString != "string") {
				var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"],
				i,
				len;
				for (i = 0, len = versions.length; i < len; i++) {
					try {
						new ActiveXObject(versions[i]);
						arguments.callee.activeXString = versions[i];
						break;
					} catch(ex) {
						//skip
					}
				}
			}
			return new ActiveXObject(arguments.callee.activeXString);
		};
	} else {
		createXHR = function() {
			throw new Error("No XHR object available.");
		};
	}
	return createXHR();
}

運行一下
在這個惰性載入的createXHR()中,if 語句的每一個分支都會為createXHR 變數賦值,有效覆蓋了原有的函數。最後一步便是調用新賦的函數。下一次調用createXHR()的時候,就會直接調用被分配的函數,這樣就不用再次執行if 語句了。
第二種實現惰性載入的方式是在聲明函數時就指定適當的函數。這樣,第一次調用函數時就不會損失性能了,而在代碼首次載入時會損失一點性能。以下就是按照這一思路重寫前面例子的結果。

var createXHR = (function() {
	if (typeof XMLHttpRequest != "undefined") {
		return function() {
			return new XMLHttpRequest();
		};
	} else if (typeof ActiveXObject != "undefined") {
		return function() {
			if (typeof arguments.callee.activeXString != "string") {
				var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"],
				i,
				len;
				for (i = 0, len = versions.length; i < len; i++) {
					try {
						new ActiveXObject(versions[i]);
						arguments.callee.activeXString = versions[i];
						break;
					} catch(ex) {
						//skip
					}
				}
			}
			return new ActiveXObject(arguments.callee.activeXString);
		};
	} else {
		return function() {
			throw new Error("No XHR object available.");
		};
	}
})();

運行一下
這個例子中使用的技巧是創建一個匿名、自執行的函數,用以確定應該使用哪一個函數實現。實際的邏輯都一樣。不一樣的地方就是第一行代碼(使用var 定義函數)、新增了自執行的匿名函數,另外每個分支都返回正確的函數定義,以便立即將其賦值給createXHR()。
惰性載入函數的優點是只在執行分支代碼時犧牲一點兒性能。至於哪種方式更合適,就要看你的具體需求而定了。不過這兩種方式都能避免執行不必要的代碼。

22.1.4 函數綁定

另一個日益流行的高級技巧叫做函數綁定。函數綁定要創建一個函數,可以在特定的this 環境中以指定參數調用另一個函數。該技巧常常和回調函數與事件處理程式一起使用,以便在將函數作為變數傳遞的同時保留代碼執行環境。請看以下例子:

var handler = {
	message: "Event handled",
	handleClick: function(event) {
		alert(this.message);
	}
};
var btn = document.getElementById("my-btn");

EventUtil.addHandler(btn, "click", handler.handleClick);

在上面這個例子中,創建了一個叫做handler 的對象。handler.handleClick()方法被分配為一個DOM 按鈕的事件處理程式。當按下該按鈕時,就調用該函數,顯示一個警告框。雖然貌似警告框應該顯示Event handled , 然而實際上顯示的是undefiend 。這個問題在於沒有保存handler.handleClick()的環境,所以this 對象最後是指向了DOM按鈕而非handler(在IE8 中,this 指向window。)可以如下麵例子所示,使用一個閉包來修正這個問題。

var handler = {
	message: "Event handled",
	handleClick: function(event) {
		alert(this.message);
	}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click",
function(event) {
	handler.handleClick(event);
});

這個解決方案在onclick 事件處理程式內使用了一個閉包直接調用handler.handleClick()。

當然,這是特定於這段代碼的解決方案。創建多個閉包可能會令代碼變得難於理解和調試。因此,很多JavaScript 庫實現了一個可以將函數綁定到指定環境的函數。這個函數一般都叫bind()。

一個簡單的bind()函數接受一個函數和一個環境,並返回一個在給定環境中調用給定函數的函數,並且將所有參數原封不動傳遞過去。語法如下:

function bind(fn, context) {
	return function() {
		return fn.apply(context, arguments);
	};
}

這個函數似乎簡單,但其功能是非常強大的。在bind()中創建了一個閉包,閉包使用apply()調用傳入的函數,並給apply()傳遞context 對象和參數。註意這裡使用的arguments 對象是內部函數的,而非bind()的。當調用返回的函數時,它會在給定環境中執行被傳入的函數並給出所有參數。
bind()函數按如下方式使用:

var handler = {
	message: "Event handled",
	handleClick: function(event) {
		alert(this.message);
	}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler));

在這個例子中,我們用bind()函數創建了一個保持了執行環境的函數,並將其傳給EventUtil.addHandler()。event 對象也被傳給了該函數,如下所示:

var handler = {
	message: "Event handled",
	handleClick: function(event) {
		alert(this.message + ":" + event.type);
	}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler));

運行一下

handler.handleClick()方法和平時一樣獲得了event 對象,因為所有的參數都通過被綁定的函數直接傳給了它。

ECMAScript 5 為所有函數定義了一個原生的bind()方法,進一步簡單了操作。換句話說,你不用再自己定義bind()函數了,而是可以直接在函數上調用這個方法。例如:

var handler = {
	message: "Event handled",
	handleClick: function(event) {
		alert(this.message + ":" + event.type);
	}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler));

運行一下
原生的bind()方法與前面介紹的自定義bind()方法類似,都是要傳入作為this 值的對象。支持原生bind()方法的瀏覽器有IE9+、Firefox 4+和Chrome。
只要是將某個函數指針以值的形式進行傳遞,同時該函數必須在特定環境中執行,被綁定函數的效用就突顯出來了。它們主要用於事件處理程式以及 setTimeout() 和 setInterval()。然而,被綁定函數與普通函數相比有更多的開銷,它們需要更多記憶體,同時也因為多重函數調用稍微慢一點,所以最好只在必要時使用。

22.1.5 函數柯里化

與函數綁定緊密相關的主題是函數柯里化(function currying),它用於創建已經設置好了一個或多個參數的函數。函數柯里化的基本方法和函數綁定是一樣的:使用一個閉包返回一個函數。兩者的區別在於,當函數被調用時,返回的函數還需要設置一些傳入的參數。請看以下例子。

function add(num1, num2) {
	return num1 + num2;
}
function curriedAdd(num2) {
	return add(5, num2);
}
alert(add(2, 3)); //5
alert(curriedAdd(3)); //8

這段代碼定義了兩個函數:add()和curriedAdd()。後者本質上是在任何情況下第一個參數為5的add()版本。儘管從技術上來說curriedAdd()並非柯里化的函數,但它很好地展示了其概念。
柯里化函數通常由以下步驟動態創建:調用另一個函數併為它傳入要柯里化的函數和必要參數。下麵是創建柯里化函數的通用方式。

function curry(fn) {
	var args = Array.prototype.slice.call(arguments, 1);
	return function() {
		var innerArgs = Array.prototype.slice.call(arguments);
		var finalArgs = args.concat(innerArgs);
		return fn.apply(null, finalArgs);
	};
}

curry()函數的主要工作就是將被返回函數的參數進行排序。curry()的第一個參數是要進行柯里化的函數,其他參數是要傳入的值。為了獲取第一個參數之後的所有參數,在arguments 對象上調用了slice()方法,並傳入參數1 表示被返回的數組包含從第二個參數開始的所有參數。然後args 數組包含了來自外部函數的參數。在內部函數中,創建了innerArgs 數組用來存放所有傳入的參數(又一次用到了slice())。有了存放來自外部函數和內部函數的參數數組後,就可以使用concat()方法將它們組合為finalArgs,然後使用apply()將結果傳遞給該函數。註意這個函數並沒有考慮到執行環境,所以調用apply()時第一個參數是null。curry()函數可以按以下方式應用。

function add(num1, num2) {
	return num1 + num2;
}
var curriedAdd = curry(add, 5);
alert(curriedAdd(3)); //8

在這個例子中,創建了第一個參數綁定為5 的add()的柯里化版本。當調用curriedAdd()並傳入3 時,3 會成為add()的第二個參數,同時第一個參數依然是5,最後結果便是和8。你也可以像下麵例子這樣給出所有的函數參數:

function add(num1, num2) {
	return num1 + num2;
}
var curriedAdd = curry(add, 5, 12);
alert(curriedAdd()); //17

運行一下
在這裡,柯里化的add()函數兩個參數都提供了,所以以後就無需再傳遞它們了。
函數柯里化還常常作為函數綁定的一部分包含在其中,構造出更為複雜的bind()函數。例如:

function bind(fn, context) {
	var args = Array.prototype.slice.call(arguments, 2);
	return function() {
		var innerArgs = Array.prototype.slice.call(arguments);
		var finalArgs = args.concat(innerArgs);
		return fn.apply(context, finalArgs);
	};
}

對curry()函數的主要更改在於傳入的參數個數,以及它如何影響代碼的結果。curry()僅僅接受一個要包裹的函數作為參數,而bind()同時接受函數和一個object 對象。這表示給被綁定的函數的參數是從第三個開始而不是第二個,這就要更改slice()的第一處調用。另一處更改是在倒數第3 行將object 對象傳給apply()。當使用bind()時,它會返回綁定到給定環境的函數,並且可能它其中某些函數參數已經被設好。當你想除了event 對象再額外給事件處理程式傳遞參數時,這非常有用,例如:

var handler = {
	message: "Event handled",
	handleClick: function(name, event) {
		alert(this.message + ":" + name + ":" + event.type);
	}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler, "my-btn"));

運行一下

在這個更新過的例子中,handler.handleClick()方法接受了兩個參數:要處理的元素的名字和event 對象。作為第三個參數傳遞給bind()函數的名字,又被傳遞給了handler.handleClick(),而handler.handleClick()也會同時接收到event 對象。

ECMAScript 5 的bind()方法也實現函數柯里化,只要在this 的值之後再傳入另一個參數即可。

var handler = {
	message: "Event handled",
	handleClick: function(name, event) {
		alert(this.message + ":" + name + ":" + event.type);
	}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler, "my-btn"));

運行一下
JavaScript 中的柯里化函數和綁定函數提供了強大的動態函數創建功能。使用bind()還是curry()要根據是否需要object 對象響應來決定。它們都能用於創建複雜的演算法和功能,當然兩者都不應濫用,因為每個函數都會帶來額外的開銷。


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

-Advertisement-
Play Games
更多相關文章
  • java 企業網站源碼 前後臺都有 靜態模版引擎, 代碼生成器大大提高開發效率 前臺: 支持兩套模版, 可以在後臺切換 系統介紹: 1.網站後臺採用主流的 SSM 框架 jsp JSTL,網站後臺採用freemaker靜態化模版引擎生成html 2.因為是生成的html,所以訪問速度快,輕便,對服務 ...
  • 1..net ajax顯示後臺返回值 <script> $(document).ready(function () { $("#btn").click(function () { //var data = new string(); $.ajax({ type: "POST", //要用post方式 ...
  • 註意 轉載須保留原文鏈接(http://www.cnblogs.com/wzhiq896/p/6783296.html ) 作者:wangwen896 整理 1、分類 2、註釋方式 3、簡單指令 4、變數命名 5、NaN和isNaN 6、轉義字元 7、邏輯短路、邏輯中斷 8、優先順序 9、類型轉換(t ...
  • 最近接觸了Bootstrap,涉及到了LESS,CSS的預處理器使用最廣泛的就是LESS和Sass,都是努力把CSS武裝成為開發語言,讓它從簡單的描述性語言過渡到具有程式式特性的語言,主要的特性就是:變數、Mixins、嵌套、繼承等。就像教程里說的:CSS的預處理器就是讓CSS從設計師的工具,變為開 ...
  • 收錄待用,修改轉載已取得 "騰訊雲" 授權 作者 | 殷源 編輯 | 迷鹿 殷源,專註移動客戶端開發,微軟Imagine Cup中國區特等獎獲得者,現就職於騰訊。 接 "JavaScriptCore全面解析 (上篇)" 六、 JSExport JSExport協議提供了一種聲明式的方法去向 "Jav ...
  • 1、什麼是HTML語義化? “語義化”指的是在需要更少的人類干預的情況下,能夠研究和手機信息,讓網頁能夠被機器理解,最終讓人類受益。 語義化的目的就是讓大家直觀的認識標簽(markup)和屬性(attribute)的用途和作用。很明顯Hx系列看起來很像標題,因為擁有粗體和較大的字型大小。<strong> ...
  • 收錄待用,修改轉載已取得 "騰訊雲" 授權 作者 | 殷源 編輯 | 迷鹿 殷源,專註移動客戶端開發,微軟Imagine Cup中國區特等獎獲得者,現就職於騰訊。 JavaScript越來越多地出現在我們客戶端開發的視野中,從ReactNative到JSpatch,JavaScript與客戶端相結合 ...
  • Bootstrap是HTML、CSS 和 JS 框架,用於開發響應式佈局、移動設備優先的 WEB 項目。 可以自動適配任何設備,解決了響應式實現的繁瑣問題,可以修改其中的各種樣式,同樣,其內部功能的強大包含了整個HTML,CSS,JS的各個方面。 通過Bootstrap製作的網站,極大地節省了代碼, ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...