JavaScript(5)— 面向對象 + 原型
- 2020 年 3 月 5 日
- 筆記
面向對象 + 原型
面向對象這個概念並不陌生,如 C++、Java 都是面向對象語言。面向對象而言都會現有一個類的概念 ,先有類再有對象。類是實例的類型模板。
比如人類 是一個類 張三 李四 就是一個個對象,他們都是人類創建出的對象 所以都有人類的共同特性,比如 人類都會吃飯 人類都會走路 所以張三李四也會吃飯和走路。
JavaScript 沒有類的概念,是基於原型的面向對象方式。它們的區別在於:
在基於類的面向對象方式中,對象(object)依靠類(class)來產生。 在基於原型的面向對象方式中,對象(object)則是依靠構造函數(constructor)和原型(prototype)構造出來的。
面向對象語言的第一個特性毫無疑問是封裝,在 JS 中,封裝的過程就是把一些 屬性 和 方法 放到對象中「包裹」起來。
一、創建對象三種方式
1、原始方式創建對象
1) 字面量的方式
示例
var per = { name: "張三", age: 20, sex: "男", say: function () { console.log("說話"); } };
2) Object實例添加屬性方法
示例
var per2=new Object(); per2.name="李四"; per2.age=30; per2.sex="男"; per2.say=function () { console.log("說話"); };
優點:程式碼簡單。
缺點: 創建多個對象會產生大量的程式碼,編寫麻煩,且並沒有實例與原型的概念。
解決辦法:工廠模式。
2、工廠模式
概念
工廠模式是非常常見的一種設計模式,它抽象了創建具體對象的過程。JS 中創建一個函數,把創建新對象、添加對象屬性、返回對象的過程放到這個函數中,
用戶只需調用函數來生成對象而無需關注對象創建細節。
示例
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
操作符來調用,那它就可以作為構造函數。
示例
//自定義構造函數----->實例化對象 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
屬性,它是一個指向構造函數本身的指針,因此就可以檢測對象的類型。
alert(per1.constructor === Person) //true alert(per1 instanceof Person) // true
但是仍然存在問題:
alert(per1.say == per2.say) //false
同一個構造函數中定義了say()
,而不同對象的同名函數卻是不相等的,意味著這兩個同名函數的記憶體空間不一致,也就是構造函數中的方法要在每個實例上重新創建一次。
這顯然增加不必要記憶體空間。
優點:解決了類似對象創建問題,且可以檢測對象類型。
缺點:構造函數方法要在每個實例上新建一次。
解決辦法:原型模式。
二、原型模式
1、概念
在JS中,創建對象的方式有工廠模式和構造函數模式等; 而構造函數模式最大的問題在於:構造函數中的每個方法都需要在實例對象中重新創建一遍,不能復用
,
所以為了解決這一個問題,就需要使用原型模式來創建對象。原型模式是把所有實例共享的方法和屬性放在一個叫做 prototype(原型)
的屬性中 ,在創建一個函數
時都會有個prototype屬性, 這個屬性是一個指針,指向一個對象,是通過調用構造函數而創建的那個對象實例的原型對象。
如果你學習過java,我們可以簡單理解原型就好比我們的靜態方法,任何對象都可以共享這個靜態方法。
作用
共享數據,節省記憶體空間。
2、舉例
使用原型,就意味著我們可以把希望實例共享的屬性和方法放到原型對象中去,而不是放在構造函數中,這樣每一次通過構造函數new
一個實例,原型對象中定義
的方法都不會重新創建一次。
示例
//原型的作用之一:共享數據,節省記憶體空間 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
都一樣了,我們顯然不希望所有實例屬性方法都一樣,它們還是要有自己獨有的屬性方法。
並且如果原型中對象中有引用類型值,實例中獲得的都是該值的引用,意味著一個實例修改了這個值,其他實例中的值都會相應改變。
解決辦法:構造函數+原型模式組合使用。
三、構造函數+原型模式
最後一種方式就是組合使用構造函數和原型模式,構造函數用於定義實例屬性,而共享屬性和方法定義在原型對象中。這樣每個實例都有自己獨有的屬性,
同時又有對共享方法的引用,節省記憶體。
//原型的作用之一:共享數據,節省記憶體空間 //構造函數 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面向對象編程之封裝 基本上參考這篇寫的,因為我認為它寫的非常通俗易懂,不需要我再去整理了。非常感謝
2、js面向對象編程
別人罵我胖,我會生氣,因為我心裡承認了我胖。別人說我矮,我就會覺得好笑,因為我心裡知道我不可能矮。這就是我們為什麼會對別人的攻擊生氣。 攻我盾者,乃我內心之矛(2)。