JavaScript 創(chuàng)建對象的四種常見模式(附《高程3》下載地址)

寫在前面

因為本人想回顧一遍JavaScript基于原型繼承的相關(guān)知識桐智,所以開始翻查過去的筆記和相關(guān)博客佑女,想來想去還是先從創(chuàng)建對象這一塊入手奥秆,這個地方講的比較清楚的應該首推《JavaScript高級編程指南》。下面的內(nèi)容80%出自這本書的第五章傅是,如果大家不想翻書的話搅吁,姑且可以先看看我的這篇抄書筆記~

后文附贈了這本圣經(jīng)的高清第三版下載地址,希望能節(jié)省各位查詢下載的時間落午。下面開始介紹創(chuàng)建對象的幾種常見模式(寄生構(gòu)造函數(shù)模式和穩(wěn)妥構(gòu)造函數(shù)模式等不常見的模式暫且忽略谎懦,等后面關(guān)于原型鏈的文章繼續(xù)填坑~)

工廠模式

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

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

  return o;
}

var person1 = createPerson('feng', 25, 'enginner');
var person2 = createPerson('yun', 52, 'doctor');

可以無數(shù)次調(diào)用createPerson(), 每次都會返回一個包含三個屬性一個方法的對象梗劫。工廠模式雖然解決了創(chuàng)建多個相似對象的問題享甸,但是無法解決對象識別的問題(類型都是Object,我們希望能返回類型為function并且具有特征name的對象),于是就有了下面的構(gòu)造函數(shù)模式。

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

構(gòu)造函數(shù)可以用來創(chuàng)建特定類型的對象

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('feng', 25, 'enginner');
var person2 = new Person('yun', 52, 'doctor');

在本方法中我們使用到了new操作符來達到目的梳侨。以這種方式調(diào)用構(gòu)造函數(shù)實際上會經(jīng)歷一下四個步驟

  • 創(chuàng)建一個新對象(讓空對象的'_proto'屬性指向Person.prototype,詳見下文)
  • 將構(gòu)造函數(shù)的作用域賦值給新對象(因此this指向了這個新對象)
  • 執(zhí)行構(gòu)造函數(shù)中的代碼(為這個新對象添加屬性)
  • 返回新對象

創(chuàng)建自定義的構(gòu)造函數(shù)意味著將來可以將它的實例標志為一個特定的類型蛉威;而這正是構(gòu)造函數(shù)模式勝過工廠模式的地方。

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

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ù)

構(gòu)造函數(shù)與其他函數(shù)唯一的區(qū)別走哺,就在于調(diào)用他們的方式不同蚯嫌。不過,構(gòu)造函數(shù)畢竟也是函數(shù)丙躏,不存在定義構(gòu)造函數(shù)的特殊語法择示。任何函數(shù),只要通過new操作符來調(diào)用晒旅,那他就可以當做構(gòu)造函數(shù)栅盲;不通過new來調(diào)用,則就是普通函數(shù)废恋。上面構(gòu)造器模式中的Person()函數(shù)可以通過下面任何一種方式來調(diào)用:

var person = new Person('feng', 25, 'enginner');
console.log(person.sayName());//feng

Person('duang', 101, 'god');//函數(shù)直接調(diào)用谈秫,this綁定到window
window.sayName();//duang

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

每個方法都要在每個實例上重新創(chuàng)建一遍扒寄。在前面的例子中,person1person2都有一個名為sayName()的方法拟烫,但那兩個方法不是同一個Function的實例(ECMAScript中旗们,函數(shù)就是對象,因此每定義一個函數(shù)构灸,也就實例化了一個對象)上渴。從邏輯上講,此時的構(gòu)造函數(shù)也可以這樣定義:

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

  this.sayName = new Function("console.log(this.name)")
}

因此不同實例上的同名函數(shù)實不相等的

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

這樣做浪費內(nèi)存喜颁,我們希望得到一種能把共享的屬性和方法放到同一個容器的模式稠氮,于是就有了下文的原型模式。

原型模式

原型模式初步

我們創(chuàng)建的每個函數(shù)在生成的一瞬間半开,都有一個prototype(原型)屬性隔披,這個屬性是一個指針,指向一個對象寂拆,而這個對象的用途是包含可由特定類型的所有實例共享的實例和方法奢米,這個對象一般稱為這個函數(shù)的 原型對象(prototype)。所有原型對象都會自動獲得一個constructor(構(gòu)造函數(shù))屬性纠永,這個屬性包含一個指回構(gòu)造函數(shù)鬓长。

使用原型對象的好處就是可以讓所有的實例對象共享原型所包含的屬性和方法。

function Person(){}

Person.prototype.name = 'Nicholas';
Person.prototype.age = '29';
Person.prototype.job = 'Software Engineer';
Person.prototype.sayName = function(){
  console.log(this.name);
}

var person1 = new Person();
person1.sayName();//feng

var person2 = new Person();
person.sayName();//feng

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

與構(gòu)造函數(shù)模式不同的是尝江,新對象的屬性和方法是有所有實例所共享的涉波。

理解原型

默認情況下,具體的關(guān)系如下所示

原型鏈示意圖

注:
(2)中炭序,如果手動分配了原型指針(重寫了原型對象啤覆,比如將原型對象重新聲明為一個字面量對象),則需要手動為原型添加constructor屬性惭聂,重新建立構(gòu)造函數(shù)和原型對象之間的關(guān)系
(5)中窗声,Chrome 中可以通過_proto_訪問到該屬性,而在IE中是不可見的辜纲。

上面那段代碼參考上圖的關(guān)系如下:

從上圖可見笨觅,構(gòu)造函數(shù)和實例對象沒有直接關(guān)系!當解釋器讀取某個對象的某個屬性的時候侨歉,都會按照上圖中的實現(xiàn)執(zhí)行一遍搜索屋摇,目標是具有給定名字的屬性。搜索首先從對象實例開始幽邓,如果在實例中找到該屬性則返回,如果沒有則繼續(xù)搜索指針指向的原型對象(prototype)火脉,如果還是沒有找到則繼續(xù)遞歸prototypeprototype對象牵舵,直到找到為止柒啤,如果遞歸到object(原型鏈最頂端)仍然沒有則返回錯誤。

可以通過對象事例訪問保存在原型中的值畸颅,但卻不能通過對象事例重寫原型中的值担巩。如果在實例中定義和原型中同名的屬性或函數(shù),則會事例中的屬性會覆蓋原型中的同名屬性没炒。

重寫原型

上面例子中涛癌,每添加一個屬性和方法都要敲一遍Person.prototype。為了減少不必要的輸入送火,更常見的寫法是用一個包含所有屬性和方法的字面量對象來從斜原型對象拳话,代碼如下:

function Person(){}

Person.prototype = {
    name: 'Nicholas',
    age: '29',
    job: 'Software Engineer',
    sayName: function(){
      console.log(this.name);
    }
}

但是這么寫有一個例外:constructor屬性不再指向Person了。因為這樣寫种吸,本質(zhì)上重寫了prototype對象弃衍,因此constructor屬性也就變成了新的對象的constructor(指向Object構(gòu)造函數(shù)),不再指向Person函數(shù)坚俗。此時盡管instanceOf操作符還能返回正確結(jié)果镜盯,但是通過constructor已經(jīng)無法確定對象的類型了。

var friend = new Person();

console.log(friend instanceof Object);//true
console.log(friend.instanceof Person);//true
console.log(friend.constructor == Person);//false
console.log(friend.constructor == Object);//true

所以如果constructor屬性真的很重要的話猖败,可以向下面的方式特意將它設(shè)置為適當?shù)闹担?/p>

function Person(){}

Person.prototype = {
    constructor: Person,
    name: 'Nicholas',
    age: '29',
    job: 'Software Engineer',
    sayName: function(){
      console.log(this.name);
    }
}    

in && hasOwnProperty

如何確認屬性是保存在原型中還是保存在實例對象中呢速缆?組合使用inhasOwnProperty

  • in操作符,如果value in object 返回true則表示這個value屬性要么能在實例中訪問恩闻,要么保存在原型中激涤,反正能通過原型鏈訪問到

  • hasOwnProperty只在屬性存在于實例中時才返回true

    function hasPrototypeProperty(object, name) {
    return !object.hasOwnProperty && (name in object)
    }

上面這段代碼返回true則表示,屬性在原型中而不在實例對象中

原型模式的問題

首先判呕,他省略了為構(gòu)造函數(shù)傳遞參數(shù)這個環(huán)節(jié)倦踢,結(jié)果所有屬性默認情況下都獲得了相同的屬性值。還有最重要的問題是由其共享的本質(zhì)導致的侠草。

原型中所有屬性被很多實力共享辱挥,這種共享對函數(shù)很適合,對那些事基本值的屬性也還說得過去,然而咪橙,對那些包含引用值得屬性來說韭山,問題就比較大了:

function Person(){}

Person.prototype = {
    constructor: Person,
    name: 'Nicholas',
    age: '29',
    job: 'Software Engineer',
    friends:['aa', 'bb', 'cc'],
    sayName: function(){
      console.log(this.name);
    }
}

var person1 = new Person();
var person2 = new Person();

person1.friends.push('dd');

console.log(person1.friends);//aa,bb,cc,dd
console.log(person2.friends);//aa,bb,cc,dd
console.log(person1.friends === person2.friends);//true

可見,一個事例修改原型中的某個引用類型屬性园爷,其他事例都會受到影響。正是這個原因?qū)е铝撕苌儆腥藛为毷褂迷湍J绞胶常谑蔷陀辛讼挛牡幕旌夏J健?/p>

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

組合使用原型模式和構(gòu)造函數(shù)模式是最常見童社,應用最廣泛的創(chuàng)建自定義類型的方式:構(gòu)造函數(shù)模式用來定義實力屬性,原型模式用來定義方法和共享的屬性著隆。結(jié)果扰楼,每個實例都會有自己的一份實例屬性的副本呀癣,同時共享著對方法的引用。另外這種混合模式弦赖,還支持想構(gòu)造函數(shù)傳遞參數(shù)项栏。

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

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

var person1 = new Person("Nicholas","29","Software Engineer");
var person2 = new Person("Grep","27","Doctoer");

person1.friends.push('cc');

console.log(person1.friends);//aa,bb,cc
console.log(person2.friends);//aa,bb
console.log(person1.friends === person2.friends);//false
console.log(person1.sayName === person2.sayName);//true

原文推薦

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蹬竖,隨后出現(xiàn)的幾起案子沼沈,更是在濱河造成了極大的恐慌,老刑警劉巖币厕,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件列另,死亡現(xiàn)場離奇詭異,居然都是意外死亡劈榨,警方通過查閱死者的電腦和手機访递,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來同辣,“玉大人拷姿,你說我怎么就攤上這事『岛” “怎么了响巢?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長棒妨。 經(jīng)常有香客問我踪古,道長,這世上最難降的妖魔是什么券腔? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任伏穆,我火速辦了婚禮,結(jié)果婚禮上纷纫,老公的妹妹穿的比我還像新娘枕扫。我一直安慰自己,他們只是感情好辱魁,可當我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布烟瞧。 她就那樣靜靜地躺著,像睡著了一般染簇。 火紅的嫁衣襯著肌膚如雪参滴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天锻弓,我揣著相機與錄音砾赔,去河邊找鬼。 笑死,一個胖子當著我的面吹牛过蹂,可吹牛的內(nèi)容都是我干的十绑。 我是一名探鬼主播聚至,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼酷勺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了扳躬?” 一聲冷哼從身側(cè)響起脆诉,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贷币,沒想到半個月后击胜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡役纹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年偶摔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片促脉。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡辰斋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瘸味,到底是詐尸還是另有隱情宫仗,我是刑警寧澤,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布旁仿,位于F島的核電站藕夫,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏枯冈。R本人自食惡果不足惜毅贮,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望尘奏。 院中可真熱鬧滩褥,春花似錦、人聲如沸罪既。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽琢感。三九已至丢间,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間驹针,已是汗流浹背烘挫。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饮六。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓其垄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親卤橄。 傳聞我的和親對象是個殘疾皇子绿满,可洞房花燭夜當晚...
    茶點故事閱讀 44,678評論 2 354

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