創(chuàng)建對象的幾種方法

1.工廠模式

考慮到ECMAScript中無法創(chuàng)建類,開發(fā)人員就發(fā)明了一種函數(shù),用函數(shù)來封裝以特定接口創(chuàng)建對象的細(xì)節(jié),如下所示:

function createPerson(name,age,job){
  var obj = new Object();
  obj.name = name;
  obj.age = age;
  obj.job = job;
  obj.sayName = function(){
    console.log(this.name);
  }
  return obj;
}

var person1 = createPerson('Tom',20,'teacher');
var person2 = createPerson('Jone',27,'Doctor');

函數(shù)createPerson()能夠根據(jù)接受的參數(shù)來構(gòu)建一個(gè)包含所有必要信息的Person對象??梢詿o數(shù)次的調(diào)用這個(gè)函數(shù),而每次他都會返回一個(gè)包含三個(gè)屬性一個(gè)方法的對象。工廠模式雖然解決了創(chuàng)建多個(gè)相似對象的問題,但卻沒有解決對象識別的問題。

2.構(gòu)造函數(shù)模式

我們可以使用ECMAScript中的構(gòu)造函數(shù)來創(chuàng)建特定類型的對象,此外,也可以創(chuàng)建自定義的構(gòu)造函數(shù),從而定義自定義對象類型的屬性和方法。可以使用構(gòu)造函數(shù)模式將前面的例子重寫。

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

var person1 = new Person('Tom',20,'Teacher');
var person2 = new Person('Jone',27,'Doctor');

在這個(gè)例子中,Person 函數(shù)取代了createPerson 函數(shù),兩者存在以下的不同之處:

  • 沒有顯示的創(chuàng)建對象
  • 直接將屬性和方法賦值給 this
  • 沒有 return 語句

此外,還應(yīng)該注意到函數(shù)名Person使用的是大寫字母P。按照慣例,構(gòu)造函數(shù)始終都應(yīng)該以一個(gè)大寫字母開頭,而非構(gòu)造函數(shù)應(yīng)該以一個(gè)小寫字母開頭。

要?jiǎng)?chuàng)建Person的新實(shí)例,必須使用new操作符。以這種方式調(diào)用構(gòu)造函數(shù)實(shí)際上會經(jīng)歷以下4個(gè)步驟:
(1)創(chuàng)建一個(gè)新對象;
(2)將構(gòu)造函數(shù)的作用域賦給新對象(因此this就指向了這個(gè)新對象);
(3)執(zhí)行構(gòu)造函數(shù)中的代碼(為這個(gè)新對象添加屬性);
(4)返回新對象。

其中 person1person2 分別著Person 的一個(gè)不同的實(shí)例,它們都有一個(gè)constructor (構(gòu)造器)屬性,且該屬性都指向Person

console.log(person1.constructor == Person);  //true
console.log(person2.constructor == Person);  //true

對象的constructor屬性最初是用來標(biāo)示對象類型的。但是,提到檢測對象類型,還是instanceof操作符要更可靠一些。

console.log(person1 instanceof Object);  //true
console.log(person1 instanceof Person);  //true
console.log(person2 instanceof Object);  //true
console.log(person2 instanceof Person);  //true

將構(gòu)造函數(shù)當(dāng)作函數(shù)

構(gòu)造函數(shù)與其他函數(shù)的唯一區(qū)別,就在于調(diào)用他們的方式不同。任何函數(shù),只要通過new操作符來調(diào)用,那它就可以作為構(gòu)造函數(shù);反之,就是普通函數(shù)。

//當(dāng)作構(gòu)造函數(shù)調(diào)用
var person = new Person('Tom',20,'Teacher');
person.sayName();

//作為普通函數(shù)調(diào)用
Person('Jone',27,'Doctor');
window.sayName();

//在另一個(gè)對象的作用域中調(diào)用
var obj = new Object();
Person.call(obj,'Grey',25,'Nurse');
obj.sayName();

構(gòu)造函數(shù)的問題

當(dāng)我們每創(chuàng)建一個(gè)對象,那么對象中的方法也會被重新創(chuàng)建一遍,這樣就會導(dǎo)致不同的作用域鏈和標(biāo)識解析。
我們可以像下面這樣,通過把函數(shù)定義轉(zhuǎn)移到構(gòu)造函數(shù)外部來解決這個(gè)問題。

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

function sayName(){
    console.log(this.name)
}

var person = new Person('Tom',20,'Teacher');

如果對象需要定義很多方法,那么就要定義很多個(gè)全局函數(shù),于是我們這個(gè)自定義的應(yīng)用類型就絲毫沒有封裝性可言了。好在,這些問題可以通過使用原型模式來解決。

3.原型模式

我們創(chuàng)建的每一個(gè)函數(shù)都會有一個(gè) prototype(原型)屬性,這個(gè)屬性是一個(gè)指針,指向一個(gè)對象,這個(gè)對象的作用就是包含由特定類型的所有實(shí)例所共享的屬性和方法。使用原型對象的好處是可以讓所有對象實(shí)例共享它所包含的屬性和方法。

function Person(){
}

Person.prototype.name = 'Grey';
Person.prototype.age = 20;
Person.prototype.job = 'Teacher';
Person.prototype.sayName = function(){
  console.log(this.name);
}

var person1 = new Person();
person1.sayName();  //'Grey"

var person2 = new Person();
person2.sayName();  //'Grey"

console.log(person1.sayName == person2.sayName);  //true

在這我們將nameage、job 、sayName()直接添加到了 Person 中的prototype 屬性中,personprototype 中的屬性都被 Person 的實(shí)例化對象所共享。

理解原型對象

每當(dāng)我們創(chuàng)建一個(gè)函數(shù),就會根據(jù)一種特定的規(guī)則為該函數(shù)創(chuàng)建一個(gè) prototype 屬性,這個(gè)屬性指向函數(shù)的原型對象。在默認(rèn)情況下,所有的原型對象都會有一個(gè) constructor 屬性,這個(gè)屬性指向 prototype 屬性所在的函數(shù)的指針。Person.prototype.constructor 就指向 Person。而通過這個(gè)構(gòu)造函數(shù),我們還可以繼續(xù)為原型對象添加其他屬性和方法。

以前面使用Person構(gòu)造函數(shù)和Person.prototype創(chuàng)建實(shí)例的代碼為例,下圖展示了各個(gè)對象之間的關(guān)系:

每當(dāng)讀取某個(gè)屬性時(shí),都會進(jìn)行一次搜索。首先會搜索對象實(shí)例本身,如果搜索到了該具體名字的屬性,就會返回該屬性的值,如果沒有搜索到,那么就會繼續(xù)搜索指針指向的原型對象,在原型對象中如果搜索到的話就會返回屬性值。

可以發(fā)現(xiàn),在搜索的時(shí)候是先搜索的是對象實(shí)例本身,然后才是原型對象。如果在對象本身和原型對象含有相同的屬性,那么原型對象中的屬性和方法就會被對象實(shí)例中相應(yīng)的屬性和方法所覆蓋。如下所示:

function Person(){
}

Person.prototype.name = 'Grey';
Person.prototype.age = 20;
Person.prototype.job = 'Teacher';
Person.prototype.sayName = function(){
  console.log(this.name);
}

var person1 = new Person();
person1.name = 'Tom';  
person1.sayName();  //'Tom'

var person2 = new Person();
person2.sayName();  //'Grey'

當(dāng)為對象實(shí)例添加一個(gè)屬性時(shí),這個(gè)屬性就會屏蔽原型對象中保存的同名屬性,換居話說,添加這個(gè)屬性只會阻止我們訪問原型中的那個(gè)屬性,但不會修改那個(gè)屬性(使用delete操作符則可以完全刪除實(shí)例屬性,從而讓我們能夠重新訪問原型中的屬性)。

更簡單的原型語法

前面的例子中每添加一個(gè)屬性和方法就要前一遍Person.prototype。為了減少不必要的輸入,更常見的做法是用一個(gè)包含所有屬性和方法的對象字面量來重寫整個(gè)原型對象。

function Person(){
}

Person.prototype = {
  name:'Grey',
  age:20,
  job:'Teacher',
  sayName:function(){
  console.log(this.name);
  }
}

在上面的代碼中,我們將Person.prototype設(shè)置為等于一個(gè)以字面量形式創(chuàng)建的新對象。最終結(jié)果相同,但有一個(gè)例外:constructor屬性不再指向Person了。
如果constructor的值真的很重要,可以像下面這樣特意將它設(shè)置回適當(dāng)?shù)闹怠?/p>

function Person(){
}

Person.prototype = {
  constructor:Person,
  name:'Grey',
  age:20,
  job:'Teacher',
  sayName:function(){
  console.log(this.name);
  }
}

原型對象的問題

原型對象省略了構(gòu)造函數(shù)傳遞參數(shù)初始化的步驟,結(jié)果導(dǎo)致所有的實(shí)例都會共享相同的屬性,這是非常不方便的。如果創(chuàng)建的屬性而引用類型的話,那么會造成不同的實(shí)例的混亂。

function Person(){
}

Person.prototype = {
  constructor:Person,
  name:'Grey',
  age:20,
  job:'Teacher',
  friends:['Jone','Tom'],
  sayName:function(){
  console.log(this.name);
  }
}
var person1 = new Person();
person1.friends.push('Van');
console.log(person1.friends);  //["Jone", "Tom", "Van"]

var person2 = new Person();
console.log(person2.friends);  //["Jone", "Tom", "Van"]

4.組合使用構(gòu)造函數(shù)模式和原型模式

構(gòu)造函數(shù)模式用于定義實(shí)例屬性,而原型模式用于定義方法和共享的屬性。這樣每個(gè)實(shí)例都會有一份實(shí)例屬性副本,又同時(shí)含有一份共享的屬性和方法,這樣最大限度的節(jié)省了內(nèi)存。另外,這種混合模式還支持向構(gòu)造函數(shù)傳遞參數(shù)。

function Person(name,age,job){
  this.name = name;
  this.age = age;
  this.job = job;
  this.friends = ['Jone','Tom'];
}

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

var person1 = new Person('Grey',20,'Teacher');
person1.friends.push('Van');
console.log(person1.friends);  //["Jone", "Tom", "Van"]

var person2 = new Person('Nicholas',27,'Doctor');
console.log(person2.friends);  //["Jone", "Tom"]

console.log(person1.friends === person2.friends);  //false
console.log(person1.sayName === person2.sayName);  //true

動態(tài)原型模式

動態(tài)原型模式將所有信息都封裝在了構(gòu)造函數(shù)中,而通過在構(gòu)造函數(shù)中初始化原型,又保持了同時(shí)使用構(gòu)造函數(shù)和原型的優(yōu)點(diǎn)。

function Person(name,age,job){
  this.name = name;
  this.age = age;
  this.job = job;
  this.friends = ['Jone','Tom'];
  if(typeof this.sayName != 'function'){
    Person.prototype.sayName = function(){
      console.log(this.name);
    }
  }
}

var person1 = new Person('Grey',20,'Teacher');
person1.sayName();

5.寄生構(gòu)造函數(shù)模式

function Person(name,age,job){
  var obj = new Object();
  obj.name = name;
  obj.age = age;
  obj.job = job;
  obj.sayName = function(){
    console.log(this.name);
  }
  return obj;
}

var person = new Person('Grey',20,'Teacher');
person.sayName();  //'Grey'

這個(gè)模式可以在特殊的情況下用來為對象構(gòu)建構(gòu)造函數(shù)。假設(shè)我們想創(chuàng)建一個(gè)具有額外方法的特殊數(shù)組。由于不能直接修改Array構(gòu)造函數(shù),因此可以使用這個(gè)模式。

function SpecialArray(){
  var newArr = new Array();
  newArr.push.apply(newArr,arguments);
  newArr.toPipedString = function(){
    return this.join('|')
  }
  return newArr;
}

var colors = new SpecialArray('red','pink','greey');
console.log(colors.toPipedString());  //"red|pink|greey"

關(guān)于寄生構(gòu)造函數(shù)模式,有一點(diǎn)需要說明:首先,返回的對象與構(gòu)造函數(shù)或者構(gòu)造函數(shù)的原型屬性之間沒有關(guān)系;也就是說,構(gòu)造函數(shù)返回的對象與在構(gòu)造函數(shù)外部創(chuàng)建的對象沒有什么不同。

6.穩(wěn)妥構(gòu)造函數(shù)模式

所謂穩(wěn)妥對象,指的是沒有公共屬性,而且其方法也不引用this的對象。
穩(wěn)妥構(gòu)造函數(shù)遵循與寄生構(gòu)造函數(shù)類似的模式,但有兩點(diǎn)不同:一是新創(chuàng)建對象的實(shí)例方法不引用this;二是不使用new操作符調(diào)用構(gòu)造函數(shù)。

function Person(name,age,job){
  var obj = new Object();
  obj.name = name;
  obj.age = age;
  obj.job = job;
  obj.sayName = function(){
    console.log(name);
  }
  return obj;
}

var person1 = Person('Grey',20,'Teacher');
person1.sayName();
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容