鑒別一個人是否 js 入門的標準是對 js 原型的理解

姓名:房小慧

學號:17101223361

專業(yè):軟件工程

【嵌牛導讀】:學Web前端的人都會用到JavaScript缎玫,本文是對js原型的一個簡單總結(jié)民轴,希望能夠幫到大家

【嵌牛鼻子】:js原型

【嵌牛提問】:js原型是什么?

【嵌牛正文】:

對象

JavaScript 是一種基于對象的編程語言踊谋,但它與一般面向?qū)ο蟮木幊陶Z言不同蝉仇,因為他沒有類(class)的概念。

對象是什么?ECMA-262 把對象定義為:「無序?qū)傩缘募辖蜗危鋵傩钥梢园局党良!ο蠡蛘吆瘮?shù)『裕」簡單來說鞭呕,對象就是一系列的鍵值對(key-value),我習慣把鍵值對分為兩種宛官,屬性(property)和方法(method)葫松。

面向?qū)ο缶幊蹋谖业睦斫饫锸且环N編程思想底洗。這種思想的核心就是把萬物都抽象成一個個對象进宝,它并不在乎數(shù)據(jù)的類型以及內(nèi)容,它在乎的是某個或者某種數(shù)據(jù)能夠做什么枷恕,并且把數(shù)據(jù)和數(shù)據(jù)的行為封裝在一起党晋,構(gòu)建出一個對象,而程序世界就是由這樣的一個個對象構(gòu)成徐块。而類是一種設計模式未玻,用來更好地創(chuàng)建對象。

舉個例子胡控,把我自己封裝成一個簡單的對象扳剿,這個對象擁有我的一些屬性和方法。

//構(gòu)造函數(shù)創(chuàng)建

varklaus =newObject();

klaus.name ='Klaus';

klaus.age =22;

klaus.job ='developer';

klaus.introduce =function(){

console.log('My name is '+this.name +', I\'m '+this.age +' years old.');

};

//字面量語法創(chuàng)建昼激,與上面效果相同

varklaus = {

name:'Klaus',

age:22,

job:'developer',

introduce:function(){

console.log('My name is '+this.name +', I\'m '+this.age +' years old.');

}

};

這個對象中庇绽,name、age 和 job 是數(shù)據(jù)部分橙困,introduce 是數(shù)據(jù)行為部分瞧掺,把這些東西都封裝在一起就構(gòu)成了一個完整的對象。這種思想不在乎數(shù)據(jù)(name凡傅、age 和 job)是什么辟狈,它只在乎這些數(shù)據(jù)能做什么(introduce),并且把它們封裝在了一起(klaus 對象)夏跷。

跑一下題哼转,與面向?qū)ο缶幊滔鄬木幊趟枷胧敲嫦蜻^程編程,它把數(shù)據(jù)和數(shù)據(jù)行為分離槽华,分別封裝成數(shù)據(jù)庫和方法庫壹蔓。方法用來操作數(shù)據(jù),根據(jù)輸入的不同返回不同的結(jié)果猫态,并且不會對輸入數(shù)據(jù)之外的內(nèi)容產(chǎn)生影響佣蓉。與之相對應的設計模式就是函數(shù)式編程披摄。

工廠模式創(chuàng)建對象

如果創(chuàng)建一個簡單的對象,像上面用到的兩種方法就已經(jīng)夠了偏螺。但是如果想要創(chuàng)建一系列相似的對象行疏,這種方法就太過麻煩了。所以套像,就順勢產(chǎn)生了工廠模式酿联。

functioncreatePerson(name, age, job){

varo =newObject();

o.name = name;

o.age = age;

o.job = job;

o.introduce =function(){

console.log('My name is '+this.name +', I\'m '+this.age +' years old.');

};

returno;

}

varklaus = createPerson('Klaus',22,'developer');

隨著 JavaScript 的發(fā)展,這種模式漸漸被更簡潔的構(gòu)造函數(shù)模式取代了夺巩。(高程三中提到工廠模式無法解決對象識別問題贞让,我覺得完全可以加一個_type 屬性來標記對象類型)

構(gòu)造函數(shù)模式創(chuàng)建對象

我們可以通過創(chuàng)建自定義的構(gòu)造函數(shù),然后利用構(gòu)造函數(shù)來創(chuàng)建相似的對象柳譬。

functionPerson(name, age, job){

this.name = name;

this.age = age;

this.job = job;

this.introduce =function(){

console.log('My name is '+this.name +', I\'m '+this.age +' years old.');

};

}

varklaus =newPerson('Klaus',22,'developer');

console.log(klausinstanceofPerson);//true

console.log(klausinstanceofObject);//true

現(xiàn)在我們來看一下構(gòu)造函數(shù)模式與工廠模式對比有什么不同:

函數(shù)名首字母大寫:這只是一種約定喳张,寫小寫也完全沒問題,但是為了區(qū)別構(gòu)造函數(shù)和一般函數(shù)美澳,默認構(gòu)造函數(shù)首字母都是大寫销部。

不需要創(chuàng)建對象,函數(shù)最后也不需要返回創(chuàng)建的對象:new 操作符幫你創(chuàng)建對象并返回制跟。

添加屬性和方法的時候用 this:new 操作符幫你把 this 指向創(chuàng)建的對象舅桩。

創(chuàng)建的時候需要用 new 操作符來調(diào)用構(gòu)造函數(shù)。

可以獲取原型上的屬性和方法雨膨。(下面會說)

可以用 instanceof 判斷創(chuàng)建出的對象的類型擂涛。

new

這么看來,構(gòu)造函數(shù)模式的精髓就在于這個 new 操作符上聊记,所以這個 new 到底做了些什么呢撒妈?

創(chuàng)建一個空對象。

在這個空對象上調(diào)用構(gòu)造函數(shù)排监。(所以 this 指向這個空對象)

將創(chuàng)建對象的內(nèi)部屬性__proto__指向構(gòu)造函數(shù)的原型(原型狰右,后面講到原型會解釋)。

檢測調(diào)用構(gòu)造函數(shù)后的返回值社露,如果返回值為對象(不包括 null)則 new 返回該對象挟阻,否則返回這個新創(chuàng)建的對象。

用代碼來模仿大概是這樣的:

function_new(fn){

returnfunction(){

varo =newObject();

varresult = fn.apply(o,arguments);

o.__proto__ = fn.prototype;

if(result && (typeofresult ==='object'||typeofresult ==='function')){

returnresult;

}else{

returno;

}

}

}

varklaus = _new(Person)('Klaus',22,'developer');

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

構(gòu)造函數(shù)雖然很好峭弟,但是他有一個問題,那就是創(chuàng)建出的每個實例對象里的方法都是一個獨立的函數(shù)脱拼,哪怕他們的內(nèi)容完全相同瞒瘸,這就違背了函數(shù)的復用原則,而且不能統(tǒng)一修改已創(chuàng)建實例對象里的方法熄浓,所以情臭,原型模式應運而生省撑。

functionPerson(name, age, job){

this.name = name;

this.age = age;

this.job = job;

this.introduce =function(){

console.log('My name is '+this.name +', I\'m '+this.age +' years old.');

};

}

varklaus1 =newPerson('Klaus',22,'developer');

varklaus2 =newPerson('Klaus',22,'developer');

console.log(klaus1.introduce === klaus2.introduce);//false

什么是原型?我們每創(chuàng)建一個函數(shù)俯在,他就會自帶一個原型對象竟秫,這個原型對象你可以理解為函數(shù)的一個屬性(函數(shù)也是對象),這個屬性的 key 為 prototype跷乐,所以你可以通過 fn.prototype 來訪問它肥败。這個原型對象除了自帶一個不可枚舉的指向函數(shù)本身的 constructor 屬性外,和其他空對象并無不同愕提。

那這個原型對象到底有什么用呢馒稍?我們知道構(gòu)造函數(shù)也是一個函數(shù),既然是函數(shù)那它也就有自己的原型對象浅侨,既然是對象你也就可以給它添加一些屬性和方法纽谒,而這個原型對象是被該構(gòu)造函數(shù)所有實例所共享的,所以你就可以把這個原型對象當做一個共享倉庫如输。下面來說說他具體是如何共享的鼓黔。

上面講 new 操作符的時候講過有一步,將創(chuàng)建對象的內(nèi)部屬性__proto__指向構(gòu)造函數(shù)的原型不见,這一步才是原型共享的關(guān)鍵澳化。這樣你就可以在新建的實例對象里訪問構(gòu)造函數(shù)原型對象里的數(shù)據(jù)。

functionPerson(name, age, job){

this.name = name;

this.age = age;

this.job = job;

this.introduce =this.__proto__.introduce;//這句可以省略脖祈,后面會介紹

}

Person.prototype.introduce =function(){

console.log('My name is '+this.name +', I\'m '+this.age +' years old.');

};

varklaus1 =newPerson('Klaus',22,'developer');

varklaus2 =newPerson('Klaus',22,'developer');

console.log(klaus1.introduce === klaus2.introduce);//true

這樣肆捕,我們就達到了函數(shù)復用的目的,而且如果你修改了原型對象里的 introduce 函數(shù)后盖高,所有實例的 introduce 方法都會同時更新慎陵,是不是很方便呢?但是原型絕對不止是為了這么簡單的目的所創(chuàng)建的喻奥。

我們首先明確一點席纽,當創(chuàng)建一個最簡單的對象的時候,其實默認用 new 調(diào)用了 JavaScript 內(nèi)置的 Objcet 構(gòu)造函數(shù)撞蚕,所以每個對象都是 Object 的一個實例(用 Object.create(null) 等特殊方法創(chuàng)建的暫不討論)润梯。所以根據(jù)上面的介紹,每個對象都有一個__proto__的屬性指向 Object.prototype甥厦。這是理解下面屬性查找機制的前提纺铭。

varklaus = {

name:'Klaus',

age:22,

job:'developer',

introduce:function(){

console.log('My name is '+this.name +', I\'m '+this.age +' years old.');

}

};

console.log(klaus.friend);//undefined

console.log(klaus.toString);//? toString() { [native code] }

上面代碼可以看出,如果我們訪問 klaus 對象上沒有定義的屬性 friend刀疙,結(jié)果返回 undefined舶赔,這個可以理解。但是同樣訪問沒定義的 toString 方法卻返回了一個函數(shù)谦秧,這是不是很奇怪呢竟纳?其實一點不奇怪撵溃,這就是 JavaScript 對象的屬性查找機制。

屬性查找機制:當訪問某對象的某個屬性的時候锥累,如果存在該屬性缘挑,則返回該屬性的值,如果該對象不存在該屬性桶略,則自動查找該對象的__proto__指向的對象的此屬性语淘。如果在這個對象上找到此屬性,則返回此屬性的值删性,如果__proto__指向的對象也不存在此屬性亏娜,則繼續(xù)尋找__proto__指向的對象的__proto__指向的對象的此屬性。這樣一直查下去蹬挺,直到找到 Object.prototype 對象维贺,如果還沒找到此屬性,則返回 undefined巴帮。(原型鏈查找溯泣,講繼承時會詳細講)

理解了上面的查找機制以后,也就不難理解 klaus.toString 其實也就是 klaus.__proto__.toString榕茧,也就是 Object.prototype.toString垃沦,所以就算你沒有定義依然也可以拿到一個函數(shù)。

理解了這一點以后用押,也就理解了上面 Person 構(gòu)造函數(shù)里的那一句我為什么注釋了可以省略肢簿,因為訪問實例的 introduce 找不到時會自動找到實例__proto__指向的對象的 introduce,也就是 Person.prototype.introduce蜻拨。

這也就是原型模式的強大之處池充,因為你可以在每個實例上訪問到構(gòu)造函數(shù)的原型對象上的屬性和方法,而且可以實時修改缎讼,是不是很方便呢收夸。

除了給原型對象添加屬性和方法之外,也可以直接重寫原型對象(因為原型對象本質(zhì)也是一個對象)血崭,只是別忘記添加 constructor 屬性卧惜。

還需要注意一點,如果原型對象共享的某屬性是個引用類型值夹纫,一個實例修改該屬性后咽瓷,其他實例也會因此受到影響。

以及舰讹,如果用 for-in 循環(huán)來遍歷屬性的 key 的時候忱详,會遍歷到原型對象里的可枚舉屬性。

functionPerson(name, age, job){

this.name = name;

this.age = age;

this.job = job;

}

Person.prototype = {

introduce:function(){

console.log('My name is '+this.name +', I\'m '+this.age +' years old.');

},

friends: ['person0','person1','person2']

};

Object.defineProperty(Person.prototype,'constructor', {

enumerable:false,

value: Person

});

varklaus1 =newPerson('Klaus',22,'developer');

varklaus2 =newPerson('Klaus',22,'developer');

console.log(klaus1.friends);//['person0', 'person1', 'person2']

klaus1.friends.push('person3');

console.log(klaus1.friends);//['person0', 'person1', 'person2', 'person3']

console.log(klaus2.friends);//['person0', 'person1', 'person2', 'person3']

for(varkeyinklaus1){

console.log(key);//name, age, job, introduce, friends

}

ES6 class

如果你有關(guān)注最新的 ES6 的話跺涤,你會發(fā)現(xiàn)里面提出了一個關(guān)鍵字 class 的用法匈睁,難道 JavaScript 要有自己類的概念了嗎?

tan90°桶错,不存在的航唆,這只是一個語法糖而已,上面定義的 Person 構(gòu)造函數(shù)可以用 class 來改寫院刁。

classPerson{

constructor(name, age, job){

this.name = name;

this.age = age;

this.job = job;

}

introduce(){

console.log('My name is '+this.name +', I\'m '+this.age +' years old.');

}

}

Person.prototype.friends = ['person0','person1','person2'];

varklaus =newPerson('Klaus',22,'developer');

很遺憾糯钙,ES6 明確規(guī)定 class 里只能有方法而不能有屬性,所以像 friends 這樣的屬性可能只能在外面單獨定義了退腥。

下面簡單舉幾個差異點任岸,如果想詳細了解可以去看阮一峰的《ECMAScript 6 入門》或者 Nicholas C. Zakas 的《Understanding ECMAScript 6》。

class 里的靜態(tài)方法(類似于 introduce)是不可枚舉的狡刘,而用 prototype 定義的是可枚舉的享潜。

class 里面默認使用嚴格模式。

class 已經(jīng)不屬于普通的函數(shù)了嗅蔬,所以不使用 new 調(diào)用會報錯剑按。

class 不存在變量提升。

class 里的方法可以加 static 關(guān)鍵字定義靜態(tài)方法澜术,這種靜態(tài)方法就不是定義在 Person.prototype 上而是直接定義在 Person 上了艺蝴,只能通過 Person.method() 調(diào)用而不會被實例共享。

作用域安全的構(gòu)造函數(shù)

不管是高程還是其他的一些資料都提到過作用域安全的構(gòu)造函數(shù)這個概念鸟废,因為構(gòu)造函數(shù)如果不用 new 來調(diào)用就只是一個普通的函數(shù)而已猜敢,這樣在函數(shù)調(diào)用的時候 this 會指向全局(嚴格模式為 undefined),這樣如果錯誤調(diào)用構(gòu)造函數(shù)就會把屬性和方法定義在 window 上盒延。為了避免這種情況缩擂,可以將構(gòu)造函數(shù)稍加改造,先用 instanceof 檢測 this 然后決定調(diào)用方法兰英。

functionPerson(name, age, job){

if(thisinstanceofPerson){

this.name = name;

this.age = age;

this.job = job;

}else{

returnnewPerson(name, age, job);

}

}

varklaus1 = Person('Klaus',22,'developer');

varklaus2 =newPerson('Klaus',22,'developer');//兩種方法結(jié)果一樣

不過個人認為這種沒什么必要撇叁,構(gòu)造函數(shù)已經(jīng)首字母大寫來加以區(qū)分了,如果還錯誤調(diào)用的話那也沒啥好說的了畦贸。陨闹。。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末薄坏,一起剝皮案震驚了整個濱河市趋厉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胶坠,老刑警劉巖君账,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異沈善,居然都是意外死亡乡数,警方通過查閱死者的電腦和手機椭蹄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來净赴,“玉大人绳矩,你說我怎么就攤上這事【脸幔” “怎么了翼馆?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長金度。 經(jīng)常有香客問我应媚,道長,這世上最難降的妖魔是什么猜极? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任中姜,我火速辦了婚禮,結(jié)果婚禮上魔吐,老公的妹妹穿的比我還像新娘扎筒。我一直安慰自己,他們只是感情好酬姆,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布嗜桌。 她就那樣靜靜地躺著,像睡著了一般辞色。 火紅的嫁衣襯著肌膚如雪骨宠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天相满,我揣著相機與錄音层亿,去河邊找鬼。 笑死立美,一個胖子當著我的面吹牛匿又,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播建蹄,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼碌更,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了洞慎?” 一聲冷哼從身側(cè)響起痛单,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎劲腿,沒想到半個月后旭绒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年挥吵,在試婚紗的時候發(fā)現(xiàn)自己被綠了重父。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蔫劣,死狀恐怖坪郭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情脉幢,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布嗦锐,位于F島的核電站嫌松,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏奕污。R本人自食惡果不足惜萎羔,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望碳默。 院中可真熱鬧贾陷,春花似錦、人聲如沸嘱根。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽该抒。三九已至慌洪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間凑保,已是汗流浹背冈爹。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留欧引,地道東北人频伤。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像芝此,于是被迫代替她去往敵國和親憋肖。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354