前言
JavaScript 中的原型一直是我很懼怕的一個(gè)主題忿项,理由很簡單,因?yàn)檎娴牟缓美斫猓_實(shí)是 JavaScript 中很重要的一部分榨为,而且是面試的必考題,就算現(xiàn)在不懂,以后遲早有一天要把它弄懂绰精,不然的話永遠(yuǎn)都沒辦法把自己的技術(shù)能力往上提高一個(gè)層次,所以今天就來講講 JavaScript 中的原型硫椰。
本文是這系列的第四篇蹄胰,往期文章:
什么是原型
首先要說一下為什么會(huì)有原型這個(gè)東西派继,那是因?yàn)樵?JavaScript 中并沒有 “類” 的概念庆猫,它是靠原型和原型鏈實(shí)現(xiàn)對象屬性的繼承,即便在 ES6 中新出了class
的語法,但那也只是一個(gè)語法糖,它的底層依然是原型。
要理解原型(原型鏈)牙丽,最重要的是理解兩個(gè)屬性以及它們之間的關(guān)系:
__proto__
prototype
__proto__
JavaScript中构罗,萬物皆對象,所有的對象都有__proto__
屬性(null
和undefined
除外)盖彭,而且指向創(chuàng)造這個(gè)對象的函數(shù)對象的prototype
屬性。
var obj = {};
console.log( obj.__proto__ === Object.prototype ); // true
var arr = [];
console.log( arr.__proto__ === Array.prototype ); // true
var fn = function(){};
console.log( fn.__proto__ === Function.prototype ); // true
var str = "";
console.log( str.__proto__ === String.prototype ); // true
var num = 1;
console.log( num.__proto__ === Number.prototype ); // true
前面說了片挂,在 JavaScript 中邻悬,一切皆對象(可以理解為它們都是從對象那里繼承過來的),所以:
console.log( Function.prototype.__proto__ === Object.prototype ); // true
console.log( Array.prototype.__proto__ === Object.prototype ); // true
console.log( String.prototype.__proto__ === Object.prototype ); // true
而因?yàn)?code>Object.prototype的__proto__
已經(jīng)是終點(diǎn)了攘烛,所以它的指向是:
console.log( Object.prototype.__proto__ === null ); // true
注意更哄,雖然大多數(shù)瀏覽器都支持通過__proto__
來訪問,但它并不是ECMAScript
的標(biāo)準(zhǔn),在 ES5 中可以通過Object.getPrototypeOf()
來獲取這個(gè)屬性。
var obj = {};
console.log( Object.getPrototypeOf(obj) === Object.prototype ); // true
prototype
prototype
是每個(gè)函數(shù)對象都具有的屬性(它也有__proto__
鱼喉,因?yàn)楹瘮?shù)也是對象)皱坛,實(shí)例化創(chuàng)建出來的對象會(huì)共享此prototype
里的屬性和方法(通過__proto__
)贩猎。
在上面的例子中已經(jīng)看到過prototype
的身影,下面通過一個(gè)例子來講述它的作用。
現(xiàn)在我們有一個(gè)構(gòu)造函數(shù)Person
桩匪,并且對它進(jìn)行實(shí)例化:
function Person(name){
this.name = name;
this.sayName = function(){
console.log("我的名字是:" + this.name);
}
}
var a = new Person("小明");
var b = new Person("小紅");
a.sayName(); // 我的名字是:小明
b.sayName(); // 我的名字是:小紅
new運(yùn)算符的缺點(diǎn)
但是葛碧,用構(gòu)造函數(shù)生成實(shí)例對象,有一個(gè)缺點(diǎn),那就是無法共享屬性和方法菠发。
例如上面例子中的a
和b
糜俗,它們都有sayName
方法楔敌,雖然做的事相同氛谜,但它們卻是獨(dú)立的杨何,這就會(huì)造成極大的資源浪費(fèi),因?yàn)槊恳粋€(gè)實(shí)例對象埃跷,都有自己的屬性和方法的副本。
prototype屬性的引入
考慮到這一點(diǎn)酱固,Brendan Eich 決定為構(gòu)造函數(shù)設(shè)置一個(gè)prototype
屬性。
這個(gè)屬性包含一個(gè)對象,所有實(shí)例對象需要共享的屬性和方法恃鞋,都放在這個(gè)對象里面荠呐,而不需要共享屬性和方法媚创,就放在構(gòu)造函數(shù)里面,這個(gè)對象就是prototype
對象宝磨。
實(shí)例對象一旦創(chuàng)建,將自動(dòng)引用prototype
對象的屬性和方法。也就是說贝奇,實(shí)例對象的屬性和方法,分成兩種,一種是本地的塌忽,另一種是引用的棉圈。
現(xiàn)在對上面的例子進(jìn)行改寫:
function Person(name){
this.name = name;
}
Person.prototype = {
sayName : function(){
console.log("我的名字是:" + this.name);
}
}
var a = new Person("小明");
var b = new Person("小紅");
a.sayName() // 我的名字是:小明
b.sayName() // 我的名字是:小紅
現(xiàn)在無論Person
被實(shí)例化多少次上岗,它的實(shí)例對象都共享同一個(gè)sayName
方法,這就是prototype
最大的用處。
原型鏈
講原型一個(gè)不可避免的概念就是原型鏈始绍,原型鏈?zhǔn)峭ㄟ^__proto__
來實(shí)現(xiàn)的。
現(xiàn)在我們以Person
的例子來講整個(gè)原型鏈芽狗。
var a = new Person("小明");
// 實(shí)例化對象的 __proto__ 指針指向構(gòu)造函數(shù)的原型
console.log( a.__proto__ === Person.prototype )
// 構(gòu)造函數(shù)的原型是一個(gè)對象鲁捏,它的 __proto__ 指向?qū)ο蟮脑?console.log( Person.prototype.__proto__ === Object.prototype )
// 函數(shù)也是一個(gè)對象,它的 __proto__ 指向 函數(shù)的原型
console.log( Person.__proto__ === Function.prototype )
// 函數(shù)的原型是一個(gè)對象虎谢,它的 __proto__ 指向?qū)ο蟮脑?console.log( Function.prototype.__proto__ === Object.prototype )
// 對象的原型的__proto__ 指向 null
console.log( Object.prototype.__proto__ === null )
以上就是a
對象的整個(gè)原型鏈婴噩。
屬性查找機(jī)制
當(dāng)訪問一個(gè)對象的屬性時(shí),Javascript 會(huì)從對象本身開始往上遍歷整個(gè)原型鏈,直到找到對應(yīng)屬性為止峭沦。如果此時(shí)到達(dá)了原型鏈的頂部,也就是上例中的 Object.prototype
矛辕,仍然未發(fā)現(xiàn)需要查找的屬性刽宪,那么 Javascript 就會(huì)返回 undefined
值。
注:此文為原創(chuàng)文章界酒,如需轉(zhuǎn)載圣拄,請注明出處。