前面兩篇文章我們介紹了JavaScript中原型的記憶體模型和原型的重寫方法即註意事項。在了解原型之後,我們就可以透過原型來建立JavaScript物件。基於原型的創建方式雖然可以有效的完成封裝,但是仍然會存在一些問題。
透過原型的方式來建立物件主要會產生2個問題:
1、無法透過建構子來設定物件的屬性值。
2、當屬性中有引用類型變數時,可能會存在變數值的重複。
我們來看下面的例子:
function Person(){} Person.prototype = { constructor:Person, name:"Leon", age:22, friends:["Ada","Chris"], say:function(){ console.info(this.name+"["+this.friends+"]"); } }
在上面的程式碼中,我們創建了一個Person類,並透過原型重寫的方式為它設定了一些屬性和方法,其中有一個friends屬性,是一個引用型別的陣列。接下來我們透過Person類別來建立對象,程式碼如下:
var p1 = new Person(); p1.name = "John"; p1.say(); //控制台输出:Jhon[Ada,Chris]
我們透過Person類別建立了物件p1,可以看到使用原型方式建立物件時沒有辦法為物件p1設定屬性,我們只有在物件建立之後才設定p1的name屬性。接著呼叫p1的say()方法,控制台會輸出Jhon[Ada,Chris],到這裡一切都還是正常的。
如果我們接著為物件p1增加一個新的朋友,問題就會出現了。程式碼如下:
p1.friends.push("Mike"); //为p1增加一个朋友(注意这里是在原型中添加) p1.say();
我們透過數組的push方法為p1添加一個新的朋友“Mike”,此時,在物件p1自己的空間中是沒有friends屬性的,所以“Mike”會被添加到Person的原型中,如下圖所示:
由於新加入的數組元素是放置在原型中的,所以後面創建的所有物件都會共享這個屬性,這時我們創建物件p2的話,他的朋友中也會有一個“Mike”。這是我們不希望看到的結果。
var p2 = new Person(); //如果p1 push之后,原型中就多了一个人,p2也多了一个朋友 p2.say();
基於組合原型和建構函數的方式來建立物件
為了解決上面的問題,我們可以使用基於組合原型和建構函數的方式來建立物件。也就是將屬性在建構函式中定義,將方法在原型中定義。這種方式有效的集合了兩者的優點,是我們在JavaScript中最常用的一種創建物件的方式。
function Person(name,age,friends){ //属性在构造函数中定义 this.name = name; this.age = age; this.friends = friends; } Person.prototype = { //方法在原型中定义 constructor:Person, say:function(){ console.info(this.name+"["+this.friends+"]"); } }
透過這種方式創建的對象,所有的屬性都是保存在物件自己的空間中的。此時,在創建物件的時候,我們就可以為物件設定它自己的屬性。
var p1 = new Person("Leon",22,["Ada","Chris"]); p1.name = "John"; p1.say(); //控制台输出: John[Ada,Chris]
完成上面的程式碼後,Person類別及p1物件的記憶體模型如下圖:
此時,我們再為物件p1新增一個新的朋友時,會在p1物件自己的記憶體空間中的friends屬性中新增。這樣,每個物件的屬性都是獨立的,不會互相干擾。
p1.friends.push("Mike"); //为p1增加一个朋友(注意这里是在p1自己的空间中添加) p1.say(); //控制台输出: John[Ada,Chris,Mike] var p2 = new Person(); p2.say(); //控制台输出: John[Ada,Chris]
因此,現在再建立物件p2時,p2物件的friends只會是“Ada”和“Chris”,而沒有“Mike”。
動態原型方式創建物件
雖然基於組合原型和建構函數的方式創建物件已經非常完美了,但是它和純正的物件導向語言創建物件的方式還是有一些差別:類別的方法被定義在類別之外。為了讓定義物件更符合物件導向規格的需求,我們可以把定義方法的原型程式碼放置到Person建構函式中。這種方式我們稱為動態原型方式創建物件。
// 动态原型方式 function Person(name,age,friends){ this.name = name; this.age = age; this.friends = friends; Person.prototype.say = function(){ console.info(this.name+"["+this.friends+"]"); } }
注意在使用動態原型方式創建物件的時候,我們在定義方法的時候不能夠使用原型重寫的方式,例如下面的程式碼是錯誤的:
// 错误的动态原型方式 function Person(name,age,friends){ this.name = name; this.age = age; this.friends = friends; //不能使用原型重写的方式来设置方法 Person.prototype = { constructor:Person, say:function(){ console.info(this.name+"["+this.friends+"]"); } } }
使用動態原型方式創建物件同樣會存在問題,因為類別中的方法是透過Person.prototype.say的方式創建的,這樣每次創建物件的時候,都會在記憶體中創建一個新的say()方法。解決這個問題的方法是我們可以先做一個判斷,看Person.prototype.say方法是否存在,不存在時才創建,否則就不創建。
// 动态原型方式 function Person(name,age,friends){ this.name = name; this.age = age; this.friends = friends; //判断Person.prototype.say是否存在,不存在就创建 if(!Person.prototype.say){ alert("创建say方法"); Person.prototype.say = function(){ console.info(this.name+"["+this.friends+"]"); } } }
为了验证判断条件是否起作用,我们在代码中的判断分支中添加了一个弹出对话框语句。我们可以创建2个对象,然后2个对象分别调用say()方法,在结果中,第一个对象在调用say()方法时会弹出对话框,而第二个对象在调用say()方法时就不会在弹出对话框了,也就是说创建第二个对象时不会再添加say()方法。
以上就是JavaScript面向对象-基于组合和动态原型创建对象的内容,更多相关内容请关注PHP中文网(www.php.cn)!