JS基礎(chǔ)之面向?qū)ο?創(chuàng)建對象

創(chuàng)建對象

創(chuàng)建對象的方式有以下幾種:最簡單的就是對象字面量形式。

工廠模式

說起工廠模式這個稱呼的由來蚓庭,可以根據(jù)它的書寫方式來理解:

function factory(name, speed) {
  var obj = new Object()
  obj.name = name
  obj.speed = speed
  return obj
}
var car1 = factory('保時捷', 300)
var car2 = factory('保時捷', 280)
car1.constructor  // function Object() {}
car2.constructor  // function Object() {}

如上代碼所示,你可以把factory()當(dāng)成一個生產(chǎn)保時捷的工廠仅仆,你可以給他傳入一些零件(即參數(shù))器赞,工廠內(nèi)部就可以根據(jù)你傳入的零件,在一個車架的基礎(chǔ)上(即 new Object())墓拜,建造一輛汽車港柜。當(dāng)你提起這個工廠,自然而然就跟保時捷劃等號了。
不過夏醉,有可能好幾個工廠同時生產(chǎn)同一版本的車爽锥,單從外表你是不知道它到底是哪個工廠生產(chǎn)出來的,只知道都長的差不多畔柔。

模式存在的問題

這也就是工廠模式的問題氯夷,不能確定對象屬于哪一類。

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

都知道靶擦,可以用構(gòu)造函數(shù)來創(chuàng)建對象腮考。可以用原生構(gòu)造函數(shù)也可以用自定義的構(gòu)造函數(shù)玄捕。相對于工廠模式來創(chuàng)建對象的方式踩蔚,構(gòu)造函數(shù)不會顯示的創(chuàng)建一個對象,而且不會直接在對象上添加屬性和方法枚粘,而是通過this對象馅闽,最后,并沒有return語句馍迄。

function Person(name, age, sex) {
  this.name = name
  this.age = age
  this.sex = sex
  this.say = function() {
    console.log(this.name)
  }
}
var person1 = new Person('zhd', 18, 'boy')
var person2 = new Person('zy', 20, 'girl')

如上所示福也,,通過Person()這個構(gòu)造函數(shù)生成了person1person2兩個對象柬姚,也就是兩個構(gòu)造函數(shù)的實例拟杉,這兩個實例的屬性和方法都來自于 Person()構(gòu)造函數(shù)。不過量承,重點是new操作符搬设。
首先來看一下new操作符都干了些啥?

new操作符干了啥

1)最直接的就是通過new操作符創(chuàng)建了一個新對象
2)將構(gòu)造函數(shù)內(nèi)部this的作用域指向了創(chuàng)建的這個新的對象
3)將構(gòu)造函數(shù)內(nèi)部的屬性和方法添加到新對象中
4)返回這個新對象撕捍,并將這個對象當(dāng)做值賦給變量person1person2

回到工廠模式存在的問題拿穴,構(gòu)造函數(shù)創(chuàng)建的對象是否就知道是哪里一類嗎?試驗一下:

person1.constructor  // function Person() {}
person2.constructor  // function Person() {}

從上面代碼來看忧风,確實解決了工廠模式創(chuàng)建對象的問題默色。不過也可以用另一種方式實驗:

person1 instanceof Person // true
person1 instanceof Object // true
person2 instanceof Person // true
person2 instanceof Object // true

如果把構(gòu)造函數(shù)當(dāng)做正常的一個函數(shù)來調(diào)用呢,那內(nèi)部的屬性和方法都跑哪里去了狮腿?實際上腿宰,如果不通過new調(diào)用構(gòu)造函數(shù)的話,直接運(yùn)行構(gòu)造函數(shù)缘厢,會把構(gòu)造函數(shù)內(nèi)部的屬性和方法放在window上吃度。

工廠模式創(chuàng)建的對象不知道屬于哪種類型,那構(gòu)造函數(shù)有什么問題呢贴硫?
通過new確實會創(chuàng)建一個對象椿每,不過沒創(chuàng)建一個對象伊者,都會把構(gòu)造函數(shù)內(nèi)部所有的方法都添加到新對象上,這樣间护,如果基于這個構(gòu)造函數(shù)創(chuàng)建很多對象亦渗,每個方法都會在實例內(nèi)部創(chuàng)建一遍,但是每個實例內(nèi)部的方法干的都是同一件事汁尺,不僅造成了多余方法的冗余法精,也無形增加了內(nèi)存消耗。

模式存在的問題

每個方法都要在實例上重新創(chuàng)建一遍

那有什么解決的辦法呢均函?實際上亿虽,創(chuàng)建的對象內(nèi)部的方法菱涤,對于實例來說干的都是同一件事苞也,那為什么不寫成公用的一個方法呢?如下:

function Person(name, age, sex) {
  this.name = name
  this.age = age
  this.sex = sex
  this.say = say
}
function say () {
  console.log(this.name)
}

但是這種解決方案帶來了一個無法讓人接受的問題:那就是在做封裝的時候粘秆,通常會定義很多方法如迟,如果這個對象需要定義很多方法究西,也就是定義很多全局變量堵漱,這樣既沒有封裝性可言戈鲁,而且增加了全局變量细卧。

原型模式

上面提到的構(gòu)造函數(shù)的問題瑞躺,可以通過第三種模式树叽,原型模式解決它镐捧。

function Person() {
 
}
Person.prototype.name = 'zhd'
Person.prototype.age = 18
Person.prototype.sex = 'boy'
Person.prototype.say = function() {
  console.log(this.name)
}
var person1 = new Person()
var person2 = new Person()
person1.say() // 'zhd'
person2.say() // 'zy'

通過以上的方式就可以看出支救,將所有實例共用的方法添加到構(gòu)造函數(shù)的原型上摘符,這樣既減少了定義函數(shù)的次數(shù)贤斜,又沒有增加全局變量。

理解原型

這里只簡單說幾點
1)除了函數(shù)逛裤,其他所有對象都沒有prototype屬性瘩绒。
2)所有對象都有一個_proto_屬性,指向構(gòu)造函數(shù)的原型带族。
3)除了null和undefined锁荔,其他所有對象都有constructor屬性。

相關(guān)的方法

isPrototypeOf():通過這個方法可以確定一個對象是否是另一個對象的原型蝙砌。
例如:
Object.prototype.isPrototypeOf(person1)
// true

hasOwnProperty():通過這個方法可以檢測一個屬性是否存在于實例中還是原型中阳堕。這個方法接收一個屬性名作為參數(shù),由所要查找屬性所在的對象調(diào)用择克。
例如:
person1.__proto__.hasOwnProperty('name')
// true
person1.hasOwnProperty('name')
// false

Object.getPrototypeOf():通過這個方法可以返回一個對象的原型對象恬总。
例如:
Object.getPrototypeOf(person1)
// {name:'zhd', age:18, sex:'boy', say:function(){...}}

Object.keys():這個方法返回的一個對象上所有可枚舉實例屬性,這個方法接收一個對象作為參數(shù)祠饺,返回一個包含所有屬性的字符串?dāng)?shù)組越驻。
例如:
Object.keys(Person.prototype)
// ["name", "age", "sex", "say"]

Object.getOwnPropertyNames():上面說的Object.keys()方法返回的是所有可枚舉的實例屬性,相反,這個方法更全面缀旁。返回的是所有屬性记劈,不管屬性是否可枚舉
例如:
Object.getOwnPropertyNames(Person.prototype)
// ["constructor", "name", "age", "sex", "say"]

這里有一點需要提醒的是,如果實例和原型存在相同的一個屬性或者方法并巍,實例的屬性或方法會覆蓋原型上的屬性或方法目木。

更簡單的原型語法

上面提到的原型模式,如果每添加一個屬性或方法懊渡,就要寫一遍Person.prototype,為了更優(yōu)雅的封裝功能刽射,我們可以改變Personprototype
例如:

function Person() {}
Person.prototype = {
  name: 'zhd',
  age: 18,
  sex: 'boy',
  say: function() {
    console.log(this.name)
  }
}

這樣的寫法是不是就輕松多了剃执。不過這樣方式修改了Person的原型對象誓禁,能帶來什么問題嗎?如下:

var person1 = new Person()
person1.constructor == Person // false

為什么會是false呢肾档?
之前介紹過摹恰,一旦創(chuàng)建一個函數(shù)怒见,就會同時創(chuàng)建它的prototype對象,這個對象也會自動獲得一個constructor屬性遣耍。就像上面一樣闺阱,一旦聲明一個Person構(gòu)造函數(shù),就同時創(chuàng)建了一個它的原型對象({constructor:function() {}}),同時這個原型對象也會獲得一個constructor屬性酣溃。
所以這種寫法,聲明一個構(gòu)造函數(shù)以后棋傍,又修改了它的原型對象救拉,因此瘫拣,修改后的原型對象內(nèi)部的constructor也不再指向Person了,而是指向了原生構(gòu)造函數(shù)function Object() {}麸拄。
為了解決這種寫法帶來的問題派昧,我們可以在改變構(gòu)造函數(shù)的原型對象后,手動的在原型對象中添加一個constructor屬性拢切,其屬性值為Person蒂萎。如下:

Person.prototype = {
  constuctor: Person,
  name: 'zhd',
  age: 18,
  sex: 'boy',
  say: function() {
    console.log(this.name)
  }
}

我們這樣手動添加constructor屬性確實解決了寫法帶來的問題。不過constructor這個屬性本身是不可枚舉的淮椰,我們可以用Object.defineProperty()方法來改變它的特性五慈。如下:

Object.definePropety(Person.prototype, 'constructor', {
  enumerable: false,
  value: Person
})

in 操作符的使用

有兩種方式使用in操作符:
1)單獨使用纳寂,如propertyName in object

  1. for-in循環(huán)中使用

不過單獨使用in操作符,有一個缺點泻拦,如下:

function Person () {}
Person.prototype.name = 'zhd'
console.log('name' in Person) // true

所以毙芜,當(dāng)單獨用in操作符檢測一個屬性是否在目標(biāo)對象上,結(jié)果并不準(zhǔn)確争拐,如果屬性在目標(biāo)對象的原型上腋粥,也會返回true
所以架曹,搭配hasOwnProperty()方法一起使用隘冲,就可以確定要檢測的屬性是否在對象上還是在原型上。

模式存在的問題

首先绑雄,從原型模式的寫法上可以看出展辞,省略了構(gòu)造函數(shù)的傳參和初始化這一環(huán)節(jié),結(jié)果是所有實例會默認(rèn)取得相同的屬性和方法绳慎,增加了冗余纵竖。
但是,原型模式最大的問題是在共享的本質(zhì)杏愤。尤其是對于包含引用類型值得屬性。
如下:

function Person () {}
Person.prototype = {
  constructor: Person,
  name: ['person1', 'person2']
}
let person1 = new Person()
let person2 = new Person()
person1.name.push('person3')
console.log(person2.name) // ['person1', 'person2', 'person3']

開發(fā)者的意思只是修改一下person1這個實例的name屬性已脓,無意修改person2這個實例的name屬性珊楼。如果說這兩個實例共享的是一個name,那就沒問題度液,如果是每個實例獨享一個name屬性厕宗,并做相應(yīng)的業(yè)務(wù)邏輯處理,那豈不是翻天了堕担。為了避免這方面的問題已慢,建議構(gòu)造函數(shù)模式和原型模式組合使用。

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

function Person(name, age, sex,fn) {
  this.name = name
  this.age = age
  this.sex = sex
  this.fn = fn
}
Person.prototype.sayName = function() {
  console.log(this.name)
}
let person1 = new Person('zhd','18','boy')

這種組合模式的使用霹购,既照顧到了每個實例私有的屬性和方法,又把實例共享的方法或?qū)傩苑旁诹嗽蜕厦嬗踊荩慌e兩得。在這先提一下:也推薦借用構(gòu)造函數(shù)和原型鏈組合的繼承方式齐疙。這一點會在后面說出膜楷。

動態(tài)原型模式

其實,動態(tài)原型模式很簡單贞奋,基本上相當(dāng)于構(gòu)造函數(shù)模式和原型模式的組合使用赌厅,區(qū)別就是加了一個if判斷,而且把原型模式寫在了構(gòu)造函數(shù)內(nèi)部轿塔。如下:

function Person(name) {
  this.name = name
  if(typeof this.sayName != 'function') {
    Person.prototype.sayName = function() {
      console.log(this.name)
    }
  }
}

之前提到了構(gòu)造函數(shù)模式和原型模式的組合模式的使用特愿,而動態(tài)原型模式其實也是一樣仲墨,只是判斷了一下構(gòu)造函數(shù)內(nèi)部有沒有對象的方法,沒有的話直接把方法放到原型上揍障。

其實宗收,講到這里,項目中常用的方法就已經(jīng)說的差不多了亚兄。創(chuàng)建對象無非就是以上的幾種方法混稽。不過還有兩種,不常用审胚,但咱們一起來了解一下匈勋。

寄生構(gòu)造函數(shù)模式(工廠構(gòu)造模式)

其實,剛開始看到這一個模式的寫法的時候膳叨,我感覺與其叫寄生構(gòu)造函數(shù)模式洽洁,不如叫工廠構(gòu)造模式更好理解,為啥這么說呢菲嘴,我們來看一看它的寫法:

function Person(name, age, sex) {
  var o = new Object()
  o.name = name
  o.age = age
  o.sex = sex
  o.joins = function() {
    this.name = '111'
  }
  return o
}
let person1 = new Person('zhd','19','boy')

對工廠模式和構(gòu)造函數(shù)模式特別熟悉的同學(xué)一看就知道饿自,這不就是工廠模式和構(gòu)造函數(shù)模式組合使用嗎×淦海或者說昭雌,在工廠模式的基礎(chǔ)上,生成對象的時候健田,在構(gòu)造函數(shù)前面加了一個new操作符烛卧,僅此而已妓局。
然后,我啥也不說好爬,咱們先看一段代碼

let person1 = new Person('zhd','19','boy')
let person2 = Person('zhd','19','boy')
console.log(person1) // {name: 'zhd', age:19,sex:'boy',joins:function(){}}
console.log(person2) // {name: 'zhd', age:19,sex:'boy',joins:function(){}}

從上面可以看出,無論是從寄生構(gòu)造函數(shù)模式創(chuàng)建的對象存炮,還是從工廠模式常見的對象,從數(shù)據(jù)結(jié)構(gòu)上來說僵蛛,沒有什么不同。也就是說充尉,構(gòu)造函數(shù)內(nèi)部返回的對象與構(gòu)造函數(shù)本身是沒有關(guān)系的,無論是否用new操作符驼侠,創(chuàng)建的對象都是一樣的谆吴,跟構(gòu)不構(gòu)造沒關(guān)系苛预。所以在此,用 instanceof來確定一個對象從哪個對象來的是不正確的热某。
如:person1 instanceof Person: // false

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

從這個模式的名稱來看腻菇,也是依托于構(gòu)造函數(shù)來創(chuàng)建對象的昔馋。具體的方式來看下面的代碼:

function Person(name, age, sex) {
  let o = new Object()
  o.sayName = function() {
    console.log(name)
  }
  return o
}
let person1 = Person('zhd', '18', 'boy')

從官方定義來看,所謂的穩(wěn)妥秘遏,是指通過參數(shù)形式傳入到模式內(nèi)部的實參,沒有方法和屬性能訪問到邦危,除了模式內(nèi)部存在的方法和屬性,你不能人為的添加屬性和方法取訪問它倦蚪。他沒有公共的屬性,而且內(nèi)部也不會通過this這個對象去訪問參數(shù)审丘。
從書寫方式上來看,同寄生構(gòu)造函數(shù)模式有兩點不同:一是不通過this訪問數(shù)據(jù)成員,二是不使用new操作符調(diào)用構(gòu)造函數(shù)來創(chuàng)建對象播急。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市桩警,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌捶枢,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烂叔,死亡現(xiàn)場離奇詭異,居然都是意外死亡蒜鸡,警方通過查閱死者的電腦和手機(jī)牢裳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門叶沛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人灰署,你說我怎么就攤上這事「然” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵约巷,是天一觀的道長。 經(jīng)常有香客問我独郎,道長,這世上最難降的妖魔是什么氓癌? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮贪婉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘疲迂。我一直安慰自己,他們只是感情好尤蒿,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著腰池,像睡著了一般。 火紅的嫁衣襯著肌膚如雪示弓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天奏属,我揣著相機(jī)與錄音,去河邊找鬼拍皮。 笑死跑杭,一個胖子當(dāng)著我的面吹牛咆耿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播萨螺,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼慰技!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起吻商,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎艾帐,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柒爸,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡准浴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了捎稚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡葡公,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出条霜,到底是詐尸還是另有隱情,我是刑警寧澤蛔外,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布夹厌,位于F島的核電站,受9級特大地震影響裆悄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜光稼,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一孩等、第九天 我趴在偏房一處隱蔽的房頂上張望采够。 院中可真熱鬧,春花似錦蹬癌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽董济。三九已至,卻和暖如春虏肾,著一層夾襖步出監(jiān)牢的瞬間廓啊,已是汗流浹背询微。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留撑毛,地道東北人书聚。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓藻雌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親胯杭。 傳聞我的和親對象是個殘疾皇子驯杜,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348

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