JavaScript 原型與繼承機制詳解

来源:http://www.cnblogs.com/dong-xu/archive/2017/07/08/7134195.html
-Advertisement-
Play Games

初識 JavaScript 對象的時候,我以為 JS 是沒有繼承這種說法的,雖說 JS 是一門面向對象語言,可是面向對象的一些特性在 JS 中並不存在(比如多態,不過嚴格來說也沒有繼承)。這就困惑了我很長的時間,當我學習到 JS 原型的時候,我才發現了 JS 的新世界。本篇文章講解了 JavaScr... ...


 引言

  初識 JavaScript 對象的時候,我以為 JS 是沒有繼承這種說法的,雖說 JS 是一門面向對象語言,可是面向對象的一些特性在 JS 中並不存在(比如多態,不過嚴格來說也沒有繼承)。這就困惑了我很長的時間,當我學習到 JS 原型的時候,我才發現了 JS 的新世界。本篇文章講解了 JavaScript new 操作符與對象的關係、原型和對象關聯(也就是俗稱的繼承)的原理,適合有一定基礎的同學閱讀。

 一、JavaScript 的類與對象

  許多書籍上都會說到如何在 JS 當中定義“類”,通常來講就是使用如下代碼:

1 function foo () {
2     this.x = 1;
3     this.y = 2;
4 }
5 var obj = new foo();  //{x:1, y:2}

  實際上這一個很糟糕的語言機制,我們首先要明確,在 JS 當中根本沒有“類”這種東西。在瞭解它之前,我們要先來瞭解下 JS 的發展歷史。

  JavaScript 隨著互聯網和瀏覽器而誕生,在早些年代,互聯網還比較貧乏,上網的成本也比較高,網速非常的慢,通常需要花很長的時間才能傳輸完一個純文本的 HTML 文件。所以那時候 Netscape 就提出,需要有一種解決方案,能使一些操作在客戶端進行而不需要通過伺服器處理,比如用戶在填寫郵箱的時候少寫了一個“@”,在客戶端就可以檢查出錯誤並提示用戶而不需要在伺服器進行解析,這樣就可以極大的降低通信操作帶來了延遲和帶寬消耗。而那時候,正巧 JAVA 問世,火的那叫個一塌糊塗,所以 Netscape 決定和 SUN 合作,在瀏覽器當中植入 JAVA 小程式(後來稱Java applet)。不過後來就這一方案產生了爭議,因為瀏覽器本來只需要很小的操作,而 JAVA 語言本身太“重”了,用來處理什麼表單驗證的問題實在是大材小用,所以決定開發一門新的語言來支持客戶端的輕量級操作,而又要借鑒 JAVA 的語法。於是乎 Netscape 開發出了一門新的輕量級語言,在語法方面偏向於 C 和 JAVA,在數據結構方面偏向於 JAVA,這門語言最初叫做 Mocha,後來經過多年的演變,變成了現在的 JavaScript。

  故事說道這裡,好像和本文並沒有什麼關係...別急,馬上就要說道點子上了。這個語言為什麼要取名 JavaScript 呢,其實它和 JAVA 並沒有半毛錢的關係,只是因為在那點年代,面向對象方法問世才不久,所有的程式員都推崇學習面向對象方法,再加上 JAVA 的橫空出世和大力宣傳,只要和 JAVA 沾邊的東西就像是往臉上貼了金一樣,自帶光環。所以便藉助了 JAVA 的名氣來進行宣傳,不過光是嘴皮子宣傳還不行,因為面向對象方法的推崇,大家都習慣於面向對象的語法,也就是 new Class() 的方法編寫代碼。不過 JavaScript 語言本身並沒有類的概念,其是多種語言的大雜燴,為了更加貼合習慣了面向對象語法的程式員,於是 new 操作符誕生了。

  好了,說了這麼大一堆故事,就是想告訴同學們,new 操作符在 JavaScript 當中本身就是一個充滿歧義的東西,它並不存在類的概念,只是貼合程式員習慣而已。那麼在 JavaScript 當中 new 操作符和對象究竟有什麼關係呢?思考下麵這一段代碼:

1 function foo () {
2     this.x = 1;
3     this.y = 2;
4     return {
5         z:3
6     }
7 }
8 var obj = new foo();  //{z:3}

  咦?發生了什麼奇怪的事情,x 和 y 哪裡去了?實際上 new 操作符並不是傳統面向對象語言那樣,創建一個類的實例,new 操作符實際上只是在引擎內部幫我們在函數的開始創建好了一個對象,然後將函數的上下文綁定到這個對象上面,併在函數的末尾返回這個對象。這裡需要註意的問題是,如果我們手動的返回了一個對象,那麼按照函數執行機制,一旦返回了一個值,那麼該函數也就執行結束,後面的代碼將不會執行,所以說在剛纔的例子中我們得到的對象只是我們手動定義的對象,並不是引擎幫我們創建的對象。 new 操作符實際上類似於以下操作:

1 function foo () {
2     //新創建一個對象,將 this 綁定到該對象上
3     
4     //在這裡編寫我們想要的代碼
5 
6     //return this;
7 }

  不過需要註意的是,new 操作符只接受 Object 類型的值,如果我們手動返回的是基本類型,則還是會返回 this :

1 function foo () {
2     this.x = 1;
3     this.y = 2;
4     return 0;
5 }
6 var obj = new foo();  //{x:1, y:2}

  現在我們現在可以將 new 操作符定義成以下方法:

 1 function newOpertor (cls, ...args) {
 2     var obj = {};
 3     cls.apply(obj, args);
 4     return obj;
 5 }
 6 
 7 function foo (x, y) {
 8     this.x = x;
 9     this.y = y;
10 }
11 
12 var obj = newOpertor(foo, 1, 2);  //{x:1, y:2}

 二、對象的原型

   JavaScript 中存在類似繼承的機制,但是又不是標準面向對象的繼承,在 JS 中使用的是原型的機制。要記住,在 JS 中只有對象,沒有類,對象的繼承是由原型來實現,籠統的來說可以這樣理解,一個對象是另一個對象的原型,那麼便可以把它比作父類,子類既然也就繼承了父類的屬性和方法。

1 function foo () {
2     this.x = 1;
3     this.y = 2;
4 }
5 
6 foo.prototype.z = 3
7 
8 var obj = new foo();
9 console.log(obj.z);  //3

  [[prototype]] 是函數的一個屬性,這個屬性的值是一個對象,該對象是所有以該函數為構造器創造的對象的原型。可以把它近似的理解為父類對象,那麼相應的,子類自然會繼承父類的屬性和方法。不過為什麼要區分原型繼承和類繼承的概念呢?標準的面向對象方法,類是不具有實際記憶體空間,只是一個事物的抽象,對象才是事物的實體,而通過繼承得到的屬性和方法,同屬於該對象,不同的對象各自都擁有獨立的繼承而來的屬性。不過在 JavaScript 當中,由於沒有類的概念,一直都是對象,所以我們“繼承”的,是一個具有實際記憶體空間的對象,也是實體,也就是說,所有新創建的子對象,他們共用一個父對象(後面我統稱為原型),不會擁有獨立的屬性:

 1 function foo () {
 2     this.x = 1;
 3     this.y = 2;
 4 }
 5 
 6 foo.prototype.z = 3
 7 
 8 var obj1 = new foo();
 9 
10 console.log(obj1.z);  //3
11 
12 foo.prototype.z = 2
13 
14 console.log(obj1.z);  //2

  還記得我們之前所說的 new 操作符的原理嗎?new 操作符的本質不是實例化一個類,而是引擎貼合習慣了面向對象編程方法的程式員,所以說 [[prototype]] 屬性本質上也是 new 操作符的一個副產物。這個屬性只在函數上面有意義,該屬性定義了 new 操作符產生的對象的原型。除了 [[prototype]] 可以訪問到對象原型以外,還有一個非標準的方法,在每一個對象中都有一個 __proto__ 屬性,這個屬性直接關聯到了該對象的原型。這種方法沒有寫入 W3C 的標準規範,但是卻得到了瀏覽器的廣泛支持,許多瀏覽器都提供了該方法以供訪問對象的原型。(個人覺得 __proto__ 比 [[prototype]] 更能體現原型鏈的本質)

 1 function foo () {
 2     this.x = 1;
 3     this.y = 2;
 4 }
 5 
 6 foo.prototype.z = 3
 7 
 8 var obj1 = new foo();
 9 
10 console.log(obj1.__proto__);  //{z:3}

  除了使用 new 操作符和函數的 [[prototype]] 屬性定義對象的原型之外,我們還可以直接在對象上顯示的通過 __proto_ 來定義,這種定義對象原型的方式更能夠體現出 JavaScript 語言的本質,更能夠使初學者理解原型鏈繼承的機制。

1 var father = {x:1};
2 
3 var child = {
4     y:2,
5     __proto__:father
6 };
7 
8 console.log(child.x);  //1

  現在我們來完成之前那個自定義 new 操作(如果你還不能理解這個函數,沒有關係,跳過它,這並不影響你接下來的學習):

 1 function newOpertor (cls, ...args) {
 2     var obj = Object.create(cls.prototype);
 3     cls.apply(obj, args);
 4     return obj;
 5 }
 6 
 7 function foo (x, y) {
 8     this.x = x;
 9     this.y = y;
10 }
11 
12 foo.prototype.z = 3
13 
14 var obj1 = newOpertor(foo, 1, 2)
15 
16 console.log(obj1.z);  //3

 三、原型鏈

  介紹完原型之後,同學們需要明確以下幾個概念:

  •   JavaScript 採用原型的機制實現繼承;
  •   原型是一個具有實際空間的對象,所有關聯的子對象共用一個原型;

  那麼 JavaScript 當中的原型是如何實現相互關聯的呢?JS 引擎又是如何查找這些關聯的屬性呢?如何實現多個對象的關聯形成一條原型鏈呢?

 1 var obj1 = {
 2     x:1
 3 }
 4 
 5 var obj2 = {
 6     y:2,
 7     __proto__:obj1
 8 }
 9 
10 var obj3 = {
11     z:3,
12     __proto__:obj2
13 }
14 
15 console.log(obj3.y);  //2
16 console.log(obj3.x);  //1

  在上面這段代碼,我們可以看出,對象的原型可以實現多層級的關聯的操作,obj1 是 obj2 的原型, obj2 同時又是 obj3 的原型,這種多層級的原型關聯,就是我們常說的原型鏈。在訪問一個處於原型鏈當中的對象的屬性,會沿著原型鏈對象一直向上查找,我們可以把這種原型遍歷操作看成是一個單向的鏈表,每一個處於原型鏈的對象都是鏈表當中的一個節點,JS 引擎會沿著這條鏈表一層一層的向下查找屬性,如果找到了一個與之匹配的屬性名,則返回該屬性的值,如果在原型鏈的末端(也就是 Object.prototype)都沒有找到與之匹配的屬性,則返回 undefined。要註意這種查找方式只會返回第一個與之匹配的屬性,所以會發生屬性屏蔽:

 1 var obj1 = {
 2     x:1
 3 }
 4 
 5 var obj2 = {
 6     x:2,
 7     __proto__:obj1
 8 }
 9 
10 var obj3 = {
11     x:3,
12     __proto__:obj2
13 }
14 
15 console.log(obj3.x);  //3

  若要訪問原型的屬性,則需要一層的一層的先向上訪問原型對象:

1 console.log(obj3.__proto__.x);  //2
2 console.log(obj3.__proto__.__proto__.x);  //1

  要註意的一點是,原型鏈的遍歷只會發生在 [[getter]] 操作上,也就是取值操作,也可以稱之右查找(RHS)。相反,若是進行 [[setter]] 操作,也就是賦值操作,也可以稱作左查找(LHS),則不會遍歷原型鏈,這條原則保證了我們在對對象進行操作的時候不會影響到原型鏈:

 1 var obj1 = {
 2     x:1
 3 }
 4 
 5 var obj2 = {
 6     __proto__:obj1
 7 }
 8 
 9 console.log(obj2.x);  //1
10 
11 obj2.x = 2;
12 
13 console.log(obj2.x);  //2
14 console.log(obj1.x);  //1(並沒有發生變化)

   在遍歷原型鏈中,如果訪問帶有 this 引用的方法,可能會發生令你意想不到的結果:

 1 var obj1 = {
 2     x:1,
 3     foo: function  () {
 4         console.log(this.x);
 5     }
 6 }
 7 
 8 var obj2 = {
 9     x:2,
10     __proto__:obj1
11 }
12 
13 obj2.foo();  //2

  在上面的內容中,我們討論過,對象的原型相當於父類,我們可以繼承它所擁有的屬性和方法,所以在我們訪問 foo() 函數的時候時候,實際上調用該方法的對象是 obj2 而不是 obj1。關於更詳細的內容,需要瞭解 this 和上下文綁定,這不在本篇文章的討論範圍之內。

  關於原型鏈的問題,大家需要理解的一點是,任何對象的原型鏈終點,都是 Object.prototype,可以把 Object 理解為所有對象的父類,類似於 JAVA 一樣,所以說所有對象都可以調用一些 Object.prototype 上面的方法,比如 Object.prototype.valueOf() 以及 Object.prototype.toString() 等等。所有的 string 類型,其原型為 String.prototype ,String.prototype 是一個對象,所以其原型也就是 Object.prototype。這就是我們為什麼能夠在一個 string 類型的值上調用一些方法,比如 String.prototype.concat() 等等。同理所有數組類型的值其原型是 Array.prototype,數字類型的值其原型是 Number.prototype:

1 console.log({}.__proto__ === Object.prototype);  //true
2 
3 console.log("hello".__proto__ === String.prototype);  //true
4 
5 console.log(1..__proto__ === Number.prototype);  //true
6 //註意用字面量訪問數字類型方法時,第一個點預設是小數標誌
7 
8 console.log([].__proto__ === Array.prototype);  //true

   理解了原型鏈的遍歷操作,我們現在就可以學習如何添加屬於自己的方法。我們現在知道了所有字元串的原型都是 String.prototype ,那麼我們可以對其進行修改來設置我們自己的內置方法:

1 String.prototype.foo = function () {
2     return this + " foo";
3 }
4 
5 console.log("bar".foo());  //bar foo

  所以說,在處理一些瀏覽器相容性問題的時候,我們可以直接修改內置對象來相容一些舊瀏覽器不支持的方法,比如 String.prototype.trim() :

1 if (!String.prototype.trim) {
2     String.prototype.trim = function() {
3         return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
4     };
5 }

  不過需要註意,切忌隨意修改內置對象的原型方法,一是因為這會帶來額外的記憶體消耗,二是這可能會在系統中造成一些隱患,一般只是用來做瀏覽器相容的 polyfill 。

四、 有關原型的方法

   for ... in 語句會遍歷原型鏈上所有可枚舉的屬性(關於屬性的可枚舉性質,可以參考 《JavaScript 常量定義》),有時我們在操作的時候需要忽略掉原型鏈上的屬性,只訪問該對象上的屬性,這時候我們可以使用 Object.prototype.hasOwnProperty() 方法來判斷屬性是否屬於原型屬性:

 1 var obj1 = {
 2     x:1,
 3 }
 4 
 5 var obj2 = {
 6     y:2,
 7     __proto__:obj1
 8 }
 9 
10 for(var key in obj2){
11     console.log(obj2[key]);  //2, 1
12 }
13 
14 for(var key in obj2){
15     if(obj2.hasOwnProperty(key)){
16         console.log(obj2[key]);  //2
17     }
18 }

  我們知道通過 new 操作符創建的對象可以通過 instanceof 關鍵字來查看對象的“類”:

1 function foo () {}
2 
3 var obj = new foo();
4 
5 console.log(obj instanceof foo);  //true

  實際上這個操作也是不嚴謹的,我們現在已經知道了 new 操作符在 JavaScript 當中本是一個具有歧義設計,instanceof 操作符本身也是一個會讓人誤解的操作符,它並沒有實例這種說法,實際上這個操作符只是判斷了對象與函數原型的關聯性,也就是說其返回的是表達式 object.__proto__ === function.prototype 的值。

 1 function foo () {}
 2 
 3 var bar = {
 4     x:1
 5 }
 6 
 7 foo.prototype = bar
 8 
 9 var obj = {
10     __proto__: bar
11 }
12 
13 console.log(obj instanceof foo);  //true

  在這一段代碼中,我們可以看出 obj 和 foo 並沒有任何關係,只是 obj 的原型和 foo.prototype 關聯到了同一個對象上面,所以其結果會返回 true。  

  不過對基本類型類型使用 instanceof 方法的話,可能會產生意外的結果:

1 console.log("1" instanceof String);  //false
2 
3 console.log(1 instanceof Number);  //false
4 
5 console.log(true instanceof Boolean);  //false

  但是我們同樣可以使用使用字面量調用原型的方法,這可能會讓人感到困惑,不過我們不用擔心它,並不是原型鏈出現什麼毛病,而是在對基本類型進行字面量操作的時候,會涉及到隱式轉換的問題。JS 引擎會先將字面量轉換成內置對象,然後在調用上面的方法,隱式轉換問題不在本文的討論範圍之類,大家可以參考 Kyle Simpson — 《你不知道的 JavaScript (中捲)》。

  實際對象的 Object.prototype.isPrototypeOf() 方法更能體現出對象原型鏈的關係,此方法判斷一個對象是否是另一個對象的原型,不同於 instanceof 的是,此方法會遍歷原型鏈上所有的節點,若有匹配項則返回 true:

 1 var obj1 = {
 2 }
 3 
 4 var obj2 = {
 5     __proto__:obj1
 6 }
 7 
 8 var obj3 = {
 9     __proto__:obj2
10 }
11 
12 console.log(obj2.isPrototypeOf(obj3));  //true
13 console.log(obj1.isPrototypeOf(obj3));  //true
14 console.log(Object.prototype.isPrototypeOf(obj3));  //true

  在 ES5 當中擁有標準方法 Object.getPrototypeOf() 可以供我們獲得一個對象的原型,在ES6 當中擁有新的方法 Object.setPrototypeOf() 可以設置一個對象的原型,不過在使用之前請先查看瀏覽器相容性。

 1 var obj1 = {
 2     x:1
 3 }
 4 
 5 var obj2 = {
 6     y:2
 7 }
 8 
 9 Object.setPrototypeOf(obj2, obj1);
10 
11 console.log(Object.getPrototypeOf(obj2) === obj1);  //true

  我們現在知道,通過 new 操作符創建的對象,其原型會關聯到函數的 [[prototype]] 上面,實際上這是一個很糟糕的寫法,一味的貼合面向對象風格的編程模式,使得很多人無法領域 JavaScript 當中的精髓。許多書籍都會寫到 JavaScript 中有許多奇怪的地方,然後教你如何避開這些地雷,實際上這不是一個好的做法,並不是因為 JavaScript 是一門稀奇古怪的語言,而是我們不願意去面對它的特性,正確的理解這些特性,才能讓我們寫出更加高效的程式。Object.create() 方法對於對象之間的關聯和原型鏈的機制更加清晰,比 new 操作符更加能夠理解 JavaScript 的繼承機制。該方法創建一個新對象,並使新對象的原型關聯到參數對象當中:

1 var obj1 = {
2     x:1
3 }
4 
5 var obj2 = Object.create(obj1);
6 
7 console.log(obj1.isPrototypeOf(obj2));  //true

  不過使用的時候還需要註意瀏覽器的相容性,下麵給出 MDN 上面的 polyfill:

 1 (function() {
 2     if (typeof Object.create != 'function') {
 3         Object.create = (function() {
 4             function Temp() {}
 5             var hasOwn = Object.prototype.hasOwnProperty;
 6             return function(O) {
 7                 if (typeof O != 'object') {
 8                     throw TypeError('Object prototype may only be an Object or null');
 9                 }
10                 Temp.prototype = O;
11                 var obj = new Temp();
12                 Temp.prototype = null;
13                 if (arguments.length > 1) {
14                     var Properties = Object(arguments[1]);
15                     for (var prop in Properties) {
16                         if (hasOwn.call(Properties, prop)) {
17                             obj[prop] = Properties[prop];
18                         }
19                     }
20                 }
21                 return obj;
22             };
23         })();
24     }
25 })();

  關於 Object.create() 方法要註意的一點是,如果參數為 null 那麼會創建一個空鏈接的對象,由於這個對象沒有任何原型鏈,所以說它不具有任何原生的方法,也無法進行原型的判斷操作,這種特殊的對象常被稱作“字典”,它完全不會受原型鏈的干擾,所以說適合用來存儲數據:

 1 var obj = Object.create(null);
 2 obj.x = 1
 3 
 4 var bar = Object.create(obj);
 5 bar.y = 2;
 6 
 7 console.log(Object.getPrototypeOf(obj));  //null
 8 
 9 console.log(Object.prototype.isPrototypeOf(obj));  //false
10 
11 console.log(obj instanceof Object);  //false
12 
13 console.log(bar.x);  //1
14 
15 obj.isPrototypeOf(bar);  //TypeError: obj.isPrototypeOf is not a function
16 
17 /**
18  * 註意由於對象沒有關聯到 Object.prototype 上面,所以無法調用原生方法,但這並不影響此對象的關聯操作。
19  */

 總結

  原型鏈是 JavaScript 當中非常重要的一點,同時也是比較難理解的一點,因為其與傳統的面向對象語言有著非常大的區別,但這是正是 JavaScript 這門語言的精髓所在,關於原型與原型鏈,我們需要知道以下這幾點:

  •   JavaScript 通過原型來實現繼承操作;
  •   幾乎所有對象都有原型鏈,其末端是 Object.prototype;
  •   原型鏈上的 [[getter]] 操作會遍歷整條原型鏈,[[setter]] 操作只會針對於當前對象;
  •   我們可以通過修改原型鏈上的方法來添加我們想要的操作(最好不要這樣做);

  關於 JavaScript 原型鏈,在一開始人們都稱為“繼承”,其實這是一種不嚴謹的說法,因為這不是標準的面向對象方法,不過初期人人常常這麼理解。現在我往往稱之為關聯委托,關聯指的是一個對象關聯到另一個對象上,而委托則指的是一個對象可以調用另一個對象的方法。

  本篇文章均為個人理解,如有不足或紕漏,歡迎在評論區指出。

參考文獻:

  Kyle Simpson — 《你不知道的 JavaScript (上捲)》

  MDN — Object - JavaScript | MDN

  阮一峰 — JavaScript 語言的歷史


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

-Advertisement-
Play Games
更多相關文章
  • 技術點:ES6+Webpack+HTML5 Audio+Sass 這裡,我們將一步步的學到如何從零去實現一個H5音樂播放器。 首先來看一下最終的實現效果:Demo鏈接界面: 接下來就步入正題: 抽離公共方法,在播放器中有很多可能需要抽離的公共方法如:點擊播放進度條和音量進度條時需要計算滑鼠距離進度條 ...
  • .text() //獲得或更改元素文本; .html() //獲得或更改元素標簽; .val() //獲得或更改input值; .css() //獲得或更改元素樣式; .click() //點擊觸發事件; .focus() //獲得焦點觸發事件; .blur() //失去焦點觸發事件; .keyup ...
  • 實際項目開發中,我們經常會用一些版本控制器來托管自己的代碼,今天就來總結下Git的相關用法,廢話不多說,直接開寫。 目的:通過Git管理github托管項目代碼 一、下載安裝Git 1、下載Git 官方地址為:https://git-scm.com/download/win 2、下載完之後,雙擊安裝 ...
  • export export用於輸出模塊的對外介面。export命令只要處於模塊頂層就可以使用,也就是說,如果處於某個函數作用域、判斷語句、條件語句內,就會報錯。export命令有幾種輸出形式。 import export命令用於輸出模塊的對外介面,import命令用於引入其他模塊提供的功能介面。與e ...
  • 1.聲明變數: let 聲明變數 作用域代碼塊作用域{} 盡在模塊 先使用後聲明 會報錯 { let a= 12; alert(a) } let 不允許重覆聲明同一個變數 const 聲明是一個常量,一旦被賦值就不允許修改 作用域在代碼塊內 沒有變數的預解析 不支持先聲明後解析 { const a ...
  • <script> var dt=new Date(); var year=dt.getFullYear(); var month=dt.getMonth()+1; var date=dt.getDate(); var day=dt.getDay(); var dayColl=['星期一','星期二' ...
  • 在官網看到的例子 給chart添加一個排序功能的handler 配置chart(根據orderByField這個欄位進行排序) ...
  • 如上代碼(實現輪播圖的部分代碼),若以內聯形式寫在html里,就不會出現異常。但是如果放在外部js文件中,併在html的head中調用,就會出現異常(不報錯,但是輪播邏輯出現異常)。解決:不使用全局變數$inBox,而直接使用$(".out>.in")。即將第20行、31行的$inBox替換成$(" ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...