js物件導向設計---建立物件的方式

weixin_33861800發表於2018-04-12

一、Object建構函式或物件字面量

const obj = new Object();
const obj1={
  name: 'xxx'
}
  • 缺點: 使用同一個介面建立很多物件,會產生大量的重複程式碼。

二、工廠模式

function createPerson(name, age, job){    
  const o = new Object();   
  o.name = name;    
  o.age = age;   
  o.job = job;    
  o.sayName = function(){  alert(this.name);    };    
  return o;
}
const person1 = createPerson("Nicholas", 29, "Software Engineer");

函式createPerson()能夠根據接受的引數來構建一個包含所有必要資訊的Person物件。可以無數次地呼叫這個函式,而每次它都會返回一個包含三個屬性一個方法的物件。

  • 優點:然解決了建立多個相似物件的問題
  • 缺點:沒有解決物件識別的問題(即怎樣知道一個物件的型別)

三、建構函式模式

function Person(name, age, job){    
  this.name = name;
  this.age = age;
  this.job = job;
  this.sayName = function(){
    alert(this.name);    
  };
}
const person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
  • 建構函式始終都應該以一個大寫字母開頭.
  • 建構函式與其他函式的唯一區別,就在於呼叫它們的方式不同。

要建立Person的新例項,必須使用new操作符。以這種方式呼叫建構函式實際上會經歷以下4個步驟:
(1)建立一個新物件;
(2)將建構函式的作用域賦給新物件(因此this就指向了這個新物件);
(3)執行建構函式中的程式碼(為這個新物件新增屬性);
(4)返回新物件。

person1和person2分別儲存著Person的一個不同的例項。這兩個物件都有一個constructor(建構函式)屬性,該屬性指向Person,如下所示。

alert(person1.constructor == Person); //true
alert(person2.constructor == Person); //true
alert(person1 instanceof Object); // true
alert(person1 instanceof Person); // true
alert(person2 instanceof Object); // true
alert(person2 instanceof Person); // true

缺點:每個方法都要在每個例項上重新建立一遍。不是同一個Function的例項
建立兩個完成同樣任務的Function例項的確沒有必要;
況且有this物件在,根本不用在執行程式碼前就把函式繫結到特定物件上面。
因此,大可像下面這樣,通過把函式定義轉移到建構函式外部來解決這個問題。

function sayName()
    alert(this.name);    
};
function Person(name, age, job){    
  this.name = name;
  this.age = age;
  this.job = job;
  this.sayName = sayName
}

新問題:在全域性作用域中定義的函式實際上只能被某個物件呼叫,這讓全域性作用域有點名不副實。而更讓人無法接受的是:如果物件需要定義很多方法,那麼就要定義很多個全域性函式,於是我們這個自定義的引用型別就絲毫沒有封裝性可言了。好在,這些問題可以通過使用原型模式來解決。

四、原型模式

我們建立的每個函式都有一個prototype(原型)屬性,這個屬性是一個指標,指向一個物件,而這個物件的用途是包含可以由特定型別的所有例項共享的屬性和方法。

function Person(){}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
  alert(this.name);
};
const person1 = new Person();
//"Nicholas"person1.sayName();var person2 = new Person();
  • 優點: 可以讓所有物件例項共享它所包含的屬性和方法。
  • 缺點:它省略了為建構函式傳遞初始化引數這一環節,結果所有例項在預設情況下都將取得相同的屬性值。原型模式的最大問題是由其共享的本性所導致的。原型中所有屬性是被很多例項共享的,這種共享對於函式非常合適。對於那些包含基本值的屬性倒也說得過去,通過在例項上新增一個同名屬性,可以隱藏原型中的對應屬性。然而,對於包含引用型別值的屬性來說,問題就比較突出了。
function Person(){}

Person.prototype = { 
  constructor: Person,
  name : "Nicholas",
  age : 29,
  job : "Software Engineer",
  friends : ["Shelby", "Court"],
  sayName() { console.log(this.name);}
};
var person1 = new Person();
var person2 = new Person();person1.friends.push("Van"); 
//"Shelby,Court,Van" 
console.log(person1.friends);
//"Shelby,Court,Van" 
console.log(person2.friends);
//true
console.log(person1.friends === person2.friends);

四、組合使用建構函式模式和原型模式

目前在ECMAScript中使用最廣泛、認同度最高的一種建立自定義型別的方法。
建立自定義型別的最常見方式,就是組合使用建構函式模式與原型模式。

  • 1.建構函式模式用於定義例項屬性,
  • 2.原型模式用於定義方法和共享的屬性。結

每個例項都會有自己的一份例項屬性的副本,但同時又共享著對方法的引用,最大限度地節省了記憶體。另外,這種混成模式還支援向建構函式傳遞引數

function Person(name, age, job){    this.name = name; 
 this.age = age; 
 this.job = job; 
 this.friends = ["Shelby", "Court"];
}

Person.prototype = { 
  constructor : Person, 
  sayName : function(){ alert(this.name); }
}

const person1 = new Person("Nicholas", 29, "Software Engineer");
const person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends);//"Shelby,Count,Van"
alert(person2.friends);//"Shelby,Count"
alert(person1.friends === person2.friends);//false
alert(person1.sayName === person2.sayName);//true

五、動態原型模式

把所有資訊都封裝在了建構函式中,而通過在建構函式中初始化原型(僅在必要的情況下),又保持了同時使用建構函式和原型的優點。換句話說,可以通過檢查某個應該存在的方法是否有效,來決定是否需要初始化原型。

function Person(name, age, job){    //屬性    
  this.name = name;
  this.age = age;
  this.job = job;
  //方法     
  if (typeof this.sayName != "function"){ 
    Person.prototype.sayName = function(){
      alert(this.name);
   };
  } 
}

const friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();

使用動態原型模式時,不能使用物件字面量重寫原型。前面已經解釋過了,如果在已經建立了例項的情況下重寫原型,那麼就會切斷現有例項與新原型之間的聯絡。

六 寄生建構函式模式

基本思想是建立一個函式,該函式的作用僅僅是封裝建立物件的程式碼,然後再返回新建立的物件;

function Person(name, age, job){    
  var o = new Object();    
  o.name = name;    
  o.age = age;    
  o.job = job;    
  o.sayName = function(){        
    alert(this.name);    
  };    
  return o;
}

const friend = new Person("Nicholas", 29, "Software Engineer");
//"Nicholas"
friend.sayName();

返回的物件與建構函式或者與建構函式的原型屬性之間沒有關係;

七 穩妥建構函式模式

所謂穩妥物件,指的是沒有公共屬性,而且其方法也不引用this的物件。穩妥物件最適合在一些安全的環境中(這些環境中會禁止使用this和new),或者在防止資料被其他應用程式(如Mashup程式)改動時使用。穩妥建構函式遵循與寄生建構函式類似的模式,但有兩點不同:一是新建立物件的例項方法不引用this;二是不使用new操作符呼叫建構函式。

function Person(name, age, job){    
  //建立要返回的物件    
  var o = new Object();    
  //可以在這裡定義私有變數和函式   
   //新增方法    
  o.sayName = function(){       
   alert(name);    
  };    
//返回物件   
 return o;

注意,在以這種模式建立的物件中,除了使用sayName()方法之外,沒有其他辦法訪問name的值。可以像下面使用穩妥的Person建構函式。

var friend = Person("Nicholas", 29, "Software Engineer");
//"Nicholas"
friend.sayName();

這樣,變數person中儲存的是一個穩妥物件,而除了呼叫sayName()方法外,沒有別的方式可以訪問其資料成員。

參考文獻:《javascript 高階程式設計 第三版》

相關文章