JavaScript(5)--- 面向物件 + 原型
阿新 • • 發佈:2020-03-06
# 面向物件 + 原型
面向物件這個概念並不陌生,如 **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
## 二、原型模式 #### 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