面向對象 + 原型
面向對象這個概念並不陌生,如 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)。