1. 程式人生 > >JavaScript(5)--- 面向物件 + 原型

JavaScript(5)--- 面向物件 + 原型

#
面向物件 + 原型
面向物件這個概念並不陌生,如 **C++、Java** 都是面嚮物件語言。面向物件而言都會現有一個類的概念 ,先有類再有物件。類是例項的型別模板。 比如人類 是一個類 張三 李四 就是一個個物件,他們都是人類創建出的物件 所以都有人類的共同特性,比如 人類都會吃飯 人類都會走路 所以張三李四也會吃飯和走路。 JavaScript 沒有類的概念,是**基於原型的面向物件方式**。它們的區別在於: ``` 在基於類的面向物件方式中,物件(object)依靠類(class)來產生。 在基於原型的面向物件方式中,物件(object)則是依靠建構函式(constructor)和原型(prototype)構造出來的。 ``` 面嚮物件語言的第一個特性毫無疑問是封裝,在 JS 中,封裝的過程就是把一些 **屬性** 和 **方法** 放到物件中“包裹”起來。 ## 一、建立物件三種方式 #### 1、原始方式建立物件 **1) 字面量的方式** `示例` ```javascript var per = { name: "張三", age: 20, sex: "男", say: function () { console.log("說話"); } }; ``` **2) Object例項新增屬性方法** `示例` ```javascript var per2=new Object(); per2.name="李四"; per2.age=30; per2.sex="男"; per2.say=function () { console.log("說話"); }; ``` **優點**:程式碼簡單。 **缺點**: 建立多個物件會產生大量的程式碼,編寫麻煩,且並沒有例項與原型的概念。 **解決辦法**:工廠模式。 #### 2、工廠模式 `概念` 工廠模式是非常常見的一種設計模式,它抽象了建立具體物件的過程。JS 中建立一個函式,把建立新物件、新增物件屬性、返回物件的過程放到這個函式中, 使用者只需呼叫函式來生成物件而無需關注物件建立細節。 `示例` ```javascript function createObject(name,age) { this.name=name; this.age=age; this.say=function () { console.log("說話"); }; } var per1=createObject("張三",20); var per2=createObject("李四",30); ``` **優點**:工廠模式解決了物件字面量建立物件程式碼重複問題,建立相似物件可以使用同一API。 **缺點**:因為是呼叫函建立物件,無法識別物件的型別。 **解決辦法**:建構函式 #### 3、建構函式 JS 中建構函式與其他函式的唯一區別,就在於呼叫它的方式不同。任何函式,只要通過`new` 操作符來呼叫,那它就可以作為建構函式。 `示例` ```javascript //自定義建構函式----->
例項化物件 function Person(name,age,sex) { this.name=name; this.age=age; this.sex=sex; this.say=function () { console.log("說話"); }; } //建構函式---->建立物件 var per1=new Person("張三",20,"女"); var per2=new Person("李四",30,"女"); ``` 通過建構函式`new`一個例項經歷了四步: ``` 1. 建立一個新物件; 2. 將建構函式內的 this 繫結到新物件上; 3. 為新物件新增屬性和方法; 4. 返回新物件(JS 引擎會預設新增 return this;)。 ``` 而通過建構函式建立的物件都有一個`constructor`屬性,它是一個指向建構函式本身的指標,因此就可以檢測物件的型別。 ```javascript alert(per1.constructor === Person) //true alert(per1 instanceof Person) // true ``` 但是仍然存在問題: ```javascript alert(per1.say == per2.say) //false ``` 同一個建構函式中定義了`say()`,而不同物件的同名函式卻是不相等的,意味著這兩個同名函式的記憶體空間不一致,也就是**建構函式中的方法**要在每個例項上重新建立一次。 這顯然增加不必要記憶體空間。 **優點**:解決了類似物件建立問題,且可以檢測物件型別。 **缺點**:建構函式方法要在每個例項上新建一次。 **解決辦法**:原型模式。
## 二、原型模式 #### 1、概念 在JS中,建立物件的方式有工廠模式和建構函式模式等; 而建構函式模式最大的問題在於:`建構函式中的每個方法都需要在例項物件中重新建立一遍,不能複用`, 所以為了解決這一個問題,就需要使用原型模式來建立物件。原型模式是把所有例項共享的方法和屬性放在一個叫做 `prototype(原型)`的屬性中 ,在建立一個函式 時都會有個prototype屬性, 這個屬性是一個指標,指向一個物件,是通過呼叫建構函式而建立的那個物件例項的原型物件。 如果你學習過java,我們可以簡單理解原型就好比我們的靜態方法,任何物件都可以共享這個靜態方法。 `作用` 共享資料,節省記憶體空間。 #### 2、舉例 使用原型,就意味著我們可以把希望例項共享的屬性和方法放到原型物件中去,而不是放在建構函式中,這樣每一次通過建構函式`new`一個例項,原型物件中定義 的方法都不會重新建立一次。 `示例` ```javascript //原型的作用之一:共享資料,節省記憶體空間 function Person() { } //通過建構函式的原型新增屬性和方法 Person.prototype.name = "張三"; Person.prototype.age = "20"; Person.prototype.say = function() { alert('通過原型建立吃飯方法'); }; var person1 = new Person(); var person2 = new Person(); alert(person1.name); //"張三" alert(person2.name); //"張三" alert(person1.say == person2.say); //true 通過原型建立的方法就為true ``` **優點**:與單純使用建構函式不一樣,原型物件中的方法不會在例項中重新建立一次,節約記憶體。 **缺點**:使用空建構函式,例項 person1 和 person2 的 `name`都一樣了,我們顯然不希望所有例項屬性方法都一樣,它們還是要有自己獨有的屬性方法。 並且如果原型中物件中有引用型別值,例項中獲得的都是該值的引用,意味著一個例項修改了這個值,其他例項中的值都會相應改變。 **解決辦法**:建構函式+原型模式組合使用。
## 三、建構函式+原型模式 最後一種方式就是組合使用建構函式和原型模式,建構函式用於定義例項屬性,而共享屬性和方法定義在原型物件中。這樣每個例項都有自己獨有的屬性, 同時又有對共享方法的引用,節省記憶體。 ```javascript //原型的作用之一:共享資料,節省記憶體空間 //建構函式 function Person(age,sex) { this.age=age; this.sex=sex; } //通過建構函式的原型新增一個方法 Person.prototype.eat=function () { console.log("通過原型建立吃飯方法"); }; var per1=new Person(20,"男"); var per2=new Person(20,"女"); alert(per1.eat == per2.eat); //通過原型建立的方法就為true ``` 這種建構函式與原型模式混成的模式,是目前在 JS 中使用最為廣泛的一種建立物件的方法。
### 參考 1、[JS面向物件程式設計之封裝](https://segmentfault.com/a/1190000015843072) 基本上參考這篇寫的,因為我認為它寫的非常通俗易懂,不需要我再去整理了。非常感謝 2、[js面向物件程式設計](https://zhuanlan.zhihu.com/p/41656666) 3、[JavaScript面向物件](https://www.jianshu.com/p/f9792fdd9915)

``` 別人罵我胖,我會生氣,因為我心裡承認了我胖。別人說我矮,我就會覺得好笑,因為我心裡知道我不可能矮。這就是我們為什麼會對別人的攻擊生氣。 攻我盾者,乃我內心之矛(2