javascript 面向物件(多種建立物件的方式)
建立物件
第一種:基於Object物件
var person = new Object(); person.name = 'My Name'; person.age = 18; person.getName = function(){ return this.name; }
第二種:物件字面量方式(比較清楚的查詢物件包含的屬性及方法)
var person = { name : 'My name', age : 18, getName : function(){ return this.name; } }
使用Object建構函式或物件字面量都可以建立物件,但缺點是建立多個物件時,會產生大量的重複程式碼,因此下面介紹可解決這個問題的建立物件的方法
1、工廠模式
function createPerson(name, age) { var o = new Object(); o.name = name; o.age = age; o.getAge = function () { return this.age; }; return o; } var person = createPerson('張三', 23); console.log(person.name); //'張三' console.log(person.age); //23 console.log(person.getAge()); //23
缺點:建立物件交給一個工廠方法來實現,可以傳遞引數,但主要缺點是無法識別物件型別,因為建立物件都是使用Object的原生建構函式來完成的。
2、建構函式模式
function Person(name, age) { this.name = name; this.age = age; this.getAge = function () { return this.age; }; } var person = new Person('張三', 23); console.log(person.name); //'張三' console.log(person.age); //23 console.log(person.getAge()); //23
alert(person instanceof Person); //true;
alert(person instanceof Object); //true;
2.1 使用自定義的建構函式(與普通函式一樣,只是用它來建立物件),定義物件型別(如:Person)的屬性和方法。它與工廠方法區別在於:
- 沒有顯式地建立物件
- 直接將屬性和方法賦值給this物件;
- 沒有return語句;
此外,要建立Person的例項,必須使用new關鍵字,以Person函式為建構函式,傳遞引數完成物件建立;
2.2 要建立 Person 的新例項,必須使用 new 操作符。以這種方式呼叫建構函式實際上會經歷以下 4個步驟:
(1) 建立一個新物件;
(2) 將建構函式的作用域賦給新物件(因此 this 就指向了這個新物件);
(3) 執行建構函式中的程式碼(為這個新物件新增屬性);
(4) 返回新物件。
在前面例子的最後, person1 和 person2 分別儲存著 Person 的一個不同的例項。這兩個物件都有一個 constructor (建構函式)屬性,該屬性指向 Person ,如下所示。
alert(person1.constructor == Person); //true
alert(person2.constructor == Person); //true
物件的 constructor 屬性最初是用來標識物件型別的。
建立自定義的建構函式意味著將來可以將它的例項標識為一種特定的型別;而這正是建構函式模式勝過工廠模式的地方。
function Person(name, age) { this.name = name; this.age = age; this.getAge = new Function ("return this.age"); //和上面是一樣的,會重複建立多個函式 }
缺點:上述程式碼,建立多個例項時,會重複呼叫new Function();建立多個函式例項,這些函式例項還不是一個作用域中,當然這一般不會有錯,但這會造成記憶體浪費。
3、原型模式
function Person(name) { this.name = name; } Person.prototype.age = 23; Person.prototype.getAge = function () { return this.age; }; var person = new Person('張三'); console.log(person.name); //'張三' console.log(person.age); //23 console.log(person.getAge()); //23
JS每個函式都有一個prototype(原型)屬性,這個屬性是一個指標,指向一個物件,它是所有通過new操作符使用函式建立的例項的原型物件。
原型物件最大特點是,所有物件例項共享它所包含的屬性和方法,也就是說,所有在原型物件中建立的屬性或方法都直接被所有物件例項共享。
例項屬性或方法的訪問過程是一次搜尋過程:
- 首先從物件例項本身開始,如果找到屬性就直接返回該屬性值;
- 如果例項本身不存在要查詢屬性,就繼續搜尋指標指向的原型物件,在其中查詢給定名字的屬性,如果有就返回;
基於以上分析,原型模式建立的物件例項,其屬性是共享原型物件的;但也可以自己例項中再進行定義,在查詢時,就不從原型物件獲取,而是根據搜尋原則,得到本例項的返回;簡單來說,就是例項中屬性會遮蔽原型物件中的屬性;
可以通過使用hasOwnProperty()方法來判斷,屬性是例項本身的,還是原型上的。
person.hasOwnProperty("name"); //true person.hasOwnProperty("age"); //false
缺點:最主要是當物件的屬性是引用型別時,它的值是不變的,總是引用同一個外部物件,所有例項對該物件任何一個地方產生的改動會引起其他例項的變化。
function Person(name) { this.name = name; } Person.prototype.age = 23; Person.prototype.color = ['red', 'yellow']; var person1 = new Person('張三'); console.log(person1.name); //'張三' console.log(person1.color); //["red", "yellow"] person1.color.push('black'); var person2 = new Person('李四'); console.log(person2.name); //'李四'
console.log(person2.color); //["red", "yellow", "black"] //person1的修改影響了person2
4、組合使用建構函式模式及原型模式
目前最為常用的定義型別方式,是組合使用建構函式模式與原型模式。
建構函式模式用於定義例項的屬性,而原型模式用於定義方法和共享的屬性。結果,每個例項都會有自己的一份例項屬性的副本,但同時又共享著對方方法的引用,最大限度的節約記憶體。
此外,組合模式還支援向建構函式傳遞引數,可謂是集兩家之所長。
function Person(name, age) { this.name = name; this.age = age; this.color = ['red', 'yellow']; } Person.prototype = { constructor : Person, //原型字面量形式會將物件的constructor變Object,此外強制指回Person; getAge : function () { return this.age; } }; var person1 = new Person('張三', 23); person1.color.push('black'); console.log(person1.name); //張三 console.log(person1.color); //["red", "yellow", "black"] console.log(person1.getAge()); //23 var person2 = new Person('李四', 24); console.log(person2.name); //李四 console.log(person2.color); //['red','yellow'] console.log(person2.getAge()); //24
5、動態原型模式
組合模式中例項屬性與共享方法(由原型定義)是分離的,這與純面嚮物件語言不太一致;動態原型模式將所有構造資訊都封裝在建構函式中,又保持了組合的優點。其原理就是通過判斷建構函式的原型中是否已經定義了共享的方法或屬性,如果沒有則定義,否則不再執行定義過程。該方式只原型上方法或屬性只定義一次,且將所有構造過程都封裝在建構函式中,對原型所做的修改能立即體現所有例項中:
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.lessons = ['Math', 'Physics']; } if (typeof this.getName != 'function') {//通過判斷例項封裝 Person.prototype = { constructor: Person,//原型字面量方式會將物件的constructor變為Object,此外強制指回Person getName: function () { return this.name; } } } var person1 = new Person('Jack', 19, 'SoftWare Engneer'); person1.lessons.push('Biology'); var person2 = new Person('Lily', 39, 'Mechanical Engneer'); alert(person1.lessons);//Math,Physics,Biology alert(person2.lessons);//Math,Physics alert(person1.getName === person2.getName);//true,//共享原型中定義方法