理解 JavaScript 原型對(duì)象及應(yīng)用

理解原型對(duì)象

創(chuàng)建一個(gè)函數(shù)剥懒,就會(huì)根據(jù)一組規(guī)則為該函數(shù)創(chuàng)建一個(gè) prototype 屬性奖地,這個(gè)屬性指向函數(shù)的原型對(duì)象。

function Person() {
}
Person.prototype.name = 'Zhang san';
Person.prototype.sayName = function() {
    console.log(this.name);
}

console.log(Person.prototype); // Person { name: 'Zhang san' }
console.log(Person.prototype.constructor === Person); // true

const person = new Person();
console.log(person.__proto__); // Person { name: 'prototype' }

在默認(rèn)情況下赎瞎,所有原型對(duì)象都會(huì)自動(dòng)獲得一個(gè) constructor 屬性,這個(gè)屬性指向構(gòu)造函數(shù)巧号。自定義構(gòu)造函數(shù)的原型對(duì)象默認(rèn)只會(huì)獲得 constructor 屬性族奢,其他的方法和屬性都是從 Object 繼承而來(lái)。

當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)實(shí)例時(shí)丹鸿,實(shí)例內(nèi)部會(huì)包含一個(gè) [[Prototype]]屬性越走,指向構(gòu)造函數(shù)的原型對(duì)象,在瀏覽器中以 __proto__表示靠欢。
[image:48EDB027-8752-4216-94D9-8AC4BA23B791-30221-00025A8850310CC5/WechatIMG245.png]

對(duì)象和原型之間的關(guān)系可以通過(guò) isPrototypeOf() 方法來(lái)檢測(cè)廊敌。

 console.log(Person.prototype.isPrototypeOf(person)); // true

對(duì)象有兩種方式可以獲取到它的原型。

console.log(Object.getPrototypeOf(person) === Person.prototype); // true
console.log(person.__proto__ === Person.prototype); // true, 官方不推薦使用這種方式

當(dāng)讀取一個(gè)對(duì)象的屬性或方法時(shí)门怪,會(huì)先在對(duì)象實(shí)例上搜索骡澈,如果實(shí)例具有給定名稱的屬性,則返回該屬性值掷空。如果沒(méi)有找到肋殴,則繼續(xù)從原型中搜索。

雖然實(shí)例可以訪問(wèn)保存在原型中的值坦弟,但卻不能通過(guò)對(duì)象實(shí)例重寫(xiě)原型中的值护锤。如果我們?cè)趯?shí)例中添加一個(gè)屬性,而該屬性與實(shí)例原型中的一個(gè)屬性同名酿傍,那么該實(shí)例的屬性會(huì)屏蔽原型中的對(duì)應(yīng)的屬性烙懦。

function Person() {
}
Person.prototype.name = 'Zhang san';

let person1 = new Person();
let person2 = new Person();
person2.name = 'Li si';

console.log(person1.name); // Zhang san
console.log(person2.name); // Li si

person2 重寫(xiě) name 屬性后原型的 name 屬性值被覆蓋。person1 的 name 屬性不受影響赤炒。

通過(guò) delete 操作符能夠使得 person2 重新獲得原型上的值氯析。

delete person2.name;
console.log(person2.name); // Zhang san

使用對(duì)象的 hasOwnProperty() 方法檢測(cè)實(shí)例中的屬性。

function Person() {
}
Person.prototype.name = 'Zhang san';

let person1 = new Person();
let person2 = new Person();
person2.name = 'Li si';

console.log(person1.hasOwnProperty('name')); // false
console.log(person2.hasOwnProperty('name')); // true

person2 因?yàn)橹貙?xiě)了 name 屬性莺褒,所以返回 true掩缓;而 person1 沒(méi)有該實(shí)例屬性,所以返回 false.

原型與 in 操作符

有兩種方式使用 in 操作符:

  • 單獨(dú)使用
  • 在 for-in 循環(huán)中使用

單獨(dú)使用 in 操作符時(shí)遵岩,用于檢測(cè)對(duì)象能夠訪問(wèn)的屬性拾因,不管是在實(shí)例中還是原型中。

console.log('name' in person1); // true
console.log('name' in person2); // true

前面的例子中 person1 的 name 屬性在原型上旷余,person2 的 name 屬性在實(shí)例上,兩者都返回 true扁达。

使用 for-in 循環(huán)時(shí)正卧,返回的是所有能夠通過(guò)對(duì)象訪問(wèn)的、可枚舉的屬性跪解,其中包括在實(shí)例中和原型中的屬性炉旷。

function Person() {
}
Person.prototype.name = 'Zhang san';
Person.prototype.sayName = function() {
    console.log(this.name);
}
Object.defineProperty(Person.prototype, 'age', {enumrable: false, value: 18})

let person2 = new Person();
person2.job = 'Engineer';
for (const prop in person2) {
    console.log(prop); // job, name, sayName
}

job 存在于對(duì)象中,name 和 sayName() 存在于對(duì)象原型,都被正常的枚舉窘行,而
age 被定義為不可枚舉饥追,所以沒(méi)有返回。

既然 in 操作符可以檢測(cè)對(duì)象能夠訪問(wèn)的屬性罐盔,而前面講到 hasOwnProperty() 只能返回存在于對(duì)象實(shí)例上的屬性但绕,那我們可以自定義方法來(lái)檢測(cè)一個(gè)屬性是否存在于原型上。

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

在 ES5 中新增了 Object.keys() 方法惶看,這個(gè)方法接收一個(gè)對(duì)象作為參數(shù)捏顺,返回一個(gè)包含所有可枚舉屬性的字符串?dāng)?shù)組。

function Person() {
}
Person.prototype.name = 'Zhang san';
Person.prototype.sayName = function() {
    console.log(this.name);
}
Object.defineProperty(Person.prototype, 'age', {enumrable: false, value: 18})

let person2 = new Person();
person2.job = 'Engineer';
person2.sayName2 = function() {
    console.log(this.name);
}
const pKeys = Object.keys(person2); 
console.log(pKeys); // ['job', 'sayName2']

我們可以發(fā)現(xiàn) Object.keys() 方法只返回包含在對(duì)象上的屬性和方法纬黎,不包含原型上的幅骄,并且不可枚舉的屬性也不會(huì)被返回。

如果想要得到實(shí)例的所有屬性本今,無(wú)論是否可枚舉拆座,可以使用 Object.getOwnPropertyNames() 方法。

const keys = Object.getOwnPropertyNames(Person.prototype);
console.log(keys); // [ 'constructor', 'name', 'sayName', 'age' ]

原型的動(dòng)態(tài)性

在原型中訪問(wèn)屬性值其實(shí)是一次搜索的過(guò)程冠息,因此我們?cè)谠蛯?duì)象上所做的任何修改都能立即從實(shí)例上反映出來(lái)挪凑,即使先創(chuàng)建實(shí)例后修改原型。

const friend = new Person();
Person.prototype.sayHi = function() {
    console.log('hi'); 
}
friend.sayHi(); // hi

因?yàn)閷?shí)例與原型之間的連接只是一個(gè)引用铐达,而非是一個(gè)副本岖赋,因此實(shí)例對(duì)象可以在原型中找到新添加的 sayHi 屬性。

但是如果重寫(xiě)整個(gè)對(duì)象的原型瓮孙,情況就不一樣了唐断。使用構(gòu)造函數(shù) new 一個(gè)對(duì)象,會(huì)為該實(shí)例添加一個(gè)指向原型的指針杭抠,而把原型修改為另一個(gè)對(duì)象就會(huì)切斷構(gòu)造函數(shù)與最初原型之間的聯(lián)系脸甘。

function Person() {
}
const friend = new Person();

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

friend.sayName(); // error

原生對(duì)象的原型

原生對(duì)象(Object, Array, String, …)的方法都是在其構(gòu)造函數(shù)的原型上定義的。比如 Array 的 sort() 方法偏灿。

console.log(Array.prototype.sort); // [Function: sort]

因此我們也可以使用這種方式為原生對(duì)象添加自定義的方法丹诀。下面的代碼為 String 添加一個(gè)名為 startWith() 的方法。

String.prototype.startWith = function(text) {
    return this.indexOf(text) === 0;
}
const msg = 'Hello world';
console.log(msg.startWith('Hello')); // true

為 String.prototype 添加屬性翁垂,當(dāng)前環(huán)境下的所有字符串都可以調(diào)用铆遭。這么做會(huì)有風(fēng)險(xiǎn),如果其他地方添加了相同名稱的屬性沿猜,就會(huì)造成命名沖突枚荣。

原型對(duì)象的問(wèn)題

原型對(duì)象實(shí)現(xiàn)了實(shí)例之間屬性的共享,但也存在一個(gè)明顯的缺點(diǎn)啼肩。共享的屬性如果是一個(gè)引用類型的對(duì)象橄妆,在修改的時(shí)候會(huì)導(dǎo)致所有實(shí)例都受影響衙伶。

function Person() {
}

Person.prototype = {
    constructor: Person,
    name: 'Li si',
    friends: ['Wang wu', 'Zhang san'],
}

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

person1.friends.push('Li li');
console.log(person1.friends); // [ 'Wang wu', 'Zhang san', 'Li li' ]
console.log(person2.friends); // [ 'Wang wu', 'Zhang san', 'Li li' ]

Person.prototype 上定義的 friends 屬性,在修改 person1 實(shí)例時(shí)害碾, person2 也被修改了矢劲。

所以,原型對(duì)象一般只用來(lái)添加方法慌随,而對(duì)象屬性直接添加在實(shí)例上芬沉。

用原型創(chuàng)建一個(gè)實(shí)例

前面講到通過(guò)構(gòu)造函數(shù) new 一個(gè)實(shí)例,該實(shí)例的 [[prototype]]屬性指向構(gòu)造函數(shù)的原型儒陨。還有一種方法是直接以一個(gè)對(duì)象原型來(lái)創(chuàng)建實(shí)例花嘶。

const person = {
  name: 'Zhang San',
  sayName: function() {
      console.log(this.name);
  }
};

const me = Object.create(person);
me.sayName(); // Zhang san

我們以 person 為原型創(chuàng)建了 me 實(shí)例,沒(méi)有顯示的用到構(gòu)造函數(shù)蹦漠。實(shí)際上內(nèi)部是創(chuàng)建了一個(gè) Object 實(shí)例颁湖,并將其 [[prototype]] 指針指向了 person善炫。

總結(jié)

  • 如何檢測(cè)對(duì)象是否存在某個(gè)屬性赶么;
  • 如何遍歷對(duì)象實(shí)例和原型中的屬性瘟仿;
  • 如何檢測(cè)是屬性在對(duì)象中還是原型中;
  • 如何為原生對(duì)象添加自定義方法研铆;
  • 如何以一個(gè)對(duì)象為原型埋同,創(chuàng)建實(shí)例對(duì)象。

本文內(nèi)容多數(shù)為《JavaScript 高級(jí)程序設(shè)計(jì)》閱讀筆記棵红。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末凶赁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子逆甜,更是在濱河造成了極大的恐慌虱肄,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,029評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件交煞,死亡現(xiàn)場(chǎng)離奇詭異咏窿,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)素征,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)集嵌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人御毅,你說(shuō)我怎么就攤上這事根欧。” “怎么了端蛆?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,570評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵咽块,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我欺税,道長(zhǎng)侈沪,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,535評(píng)論 1 284
  • 正文 為了忘掉前任晚凿,我火速辦了婚禮亭罪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘歼秽。我一直安慰自己应役,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,650評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布燥筷。 她就那樣靜靜地躺著箩祥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肆氓。 梳的紋絲不亂的頭發(fā)上袍祖,一...
    開(kāi)封第一講書(shū)人閱讀 49,850評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音谢揪,去河邊找鬼蕉陋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛拨扶,可吹牛的內(nèi)容都是我干的凳鬓。 我是一名探鬼主播,決...
    沈念sama閱讀 39,006評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼患民,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼缩举!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起匹颤,我...
    開(kāi)封第一講書(shū)人閱讀 37,747評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤仅孩,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后惋嚎,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體杠氢,經(jīng)...
    沈念sama閱讀 44,207評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,536評(píng)論 2 327
  • 正文 我和宋清朗相戀三年另伍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鼻百。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,683評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡摆尝,死狀恐怖温艇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情堕汞,我是刑警寧澤勺爱,帶...
    沈念sama閱讀 34,342評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站讯检,受9級(jí)特大地震影響琐鲁,放射性物質(zhì)發(fā)生泄漏卫旱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,964評(píng)論 3 315
  • 文/蒙蒙 一围段、第九天 我趴在偏房一處隱蔽的房頂上張望顾翼。 院中可真熱鬧,春花似錦奈泪、人聲如沸适贸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,772評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拜姿。三九已至,卻和暖如春冯遂,著一層夾襖步出監(jiān)牢的瞬間蕊肥,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,004評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工债蜜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留晴埂,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,401評(píng)論 2 360
  • 正文 我出身青樓寻定,卻偏偏與公主長(zhǎng)得像儒洛,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子狼速,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,566評(píng)論 2 349

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