做爲JS新手,JS原型和原型鏈的概念一直是莫測高深,好似那霧山上的一朵嬌花,很美的樣子,但又看不清,摸不着。本文並不能帶你深刻理解JS原型,卻能帶着你和它來一次親密接觸。JS原型啊,即便我還不懂你,但我至少切切實實看到你,摸到你了。node
原型的英文爲prototype,意思其實就是,批量製造一種產品時,做爲母版的那個東西。數組
咱們用js建立的每個對象都擁有原型,每個函數也擁有原型,經代碼驗證,函數的原型體如今其prototype屬性裏,對象的原型體如今其__proto__屬性裏。固然,函數也是對象,在這裏,咱們把函數對象簡稱爲函數,而除函數外的其它對象簡稱爲對象。瀏覽器
這兩個屬性是不可枚舉的,在使用node的console打印對象或函數時,它們是不會顯示的,同時,Object.keys()也沒法獲取它們。但在瀏覽器的console裏可以顯示。bash
那麼這兩個屬性是怎麼出如今咱們建立的對象和函數中的呢?我只能說,是JS自動建立的,由於不用寫半句代碼,建立了對象和函數,它們本身就存在了。app
首先來感覺一下吧函數
建立一個對象ui
var person1 = {
name: 'person no.1',
age: 1,
sayName: function () {
console.log(this.name)
}
}
person1.sayName()
複製代碼
這個對象建立的同時,js在對象內部建立了__proto__,即原型,在如今的node版本,能夠訪問到它,它就是JS中原始對象的構造函數Object的原型this
console.log( person1.__proto__);//{}
console.log(person1.__proto__ === Object.prototype);//true
複製代碼
看,__proto__本身就存在在那裏了,不須要敲打任何代碼,打印結果不是undefined,而是空對象{}。spa
node打印的這個對象的__proto__,在這裏也就是Object.prototype,是個空對象{},實際上若是在瀏覽器的console裏查看Object.prototype,會發現不少東西,其中包括咱們比較熟悉的toString()等方法,感興趣的能夠看一下,這裏不貼圖了。實際上Object.prototype,就是js所說萬物皆對象的那個根對象。prototype
那麼Object.prototype這個東西,它的原型是什麼呢,畢竟萬物皆有原型啊!
console.log(Object.prototype.__proto__);//null
複製代碼
頗有意思吧,首先,它有原型,由於打印結果並非undefined,其次,它的原型是null。
原來,JS世界裏,也是由無生有啊。這麼看來,Object.prototype是萬物之母,也就是原型鏈最頂端了,由於它的原型是null。或者說null是萬物之母。無生一,一輩子萬物。
咱們再來看看函數,建立一個函數,而後打印它的prototype:
function xxxx(){
console.log('我是xxxx函數');
}
console.log(xxxx.prototype);//node打印顯示不全,你們能夠去瀏覽器打印查看
console.log(xxxx.prototype.__proto__===Object.prototype);//true
複製代碼
咱們會發現,雖然咱們沒有去寫代碼,但這個函數的prototype是存在的,它並非undefined。只能解釋它是JS自動生成的了。實際上它有兩個屬性,constructor指向函數自己,__proto__指向Object.prototype,在瀏覽器中的console中能夠看到。
那麼,到目前爲止,咱們能夠得出幾個結論:
那麼全部建立的對象的原型都是Object.prototype嗎?固然不可能,若是萬物皆一母,則何來原型鏈一說? 只能說,用字面量建立的,以及使用new Object()建立的對象,其原型爲Object.prototype。
你們能夠本身試一下使用new Object()建立對象,而後查看一下它的__proto__是否與Object.prototype相等。
關於這個問題,我必須很慚愧地說,做爲一個新手,並不懂得它具體有多少巧妙用處,只知道兩點,一是實現了面嚮對象語言的繼承,二是實現了共享屬性和方法,即其它面嚮對象語言中相似於類的靜態成員和方法。關於這兩點的演示,結合構造函數和Object.create()這兩樣東西一塊兒講會更清楚一點。
構造函數和原型有關係嗎?固然,關係密切,由於構造函數建立的對象改變了原型,由一個構造函數建立的全部對象,它們的原型都是構造函數本身的原型,而不萬物之母Object.prototype,換句話說,由構造函數建立的全部對象,它們與Object.prototype還隔了一層,這一層就是構造函數的原型,但也由於有了這一層,能夠搞不少事情。
那麼先聲明一個構造函數,而後用它建立一個對象:
function Person(name, age) {
this.name = name
this.age = age
this.sayName = function () {
console.log( this.name);
}
}
var person2=new Person('person no.2', 2)
console.log(person5.__proto__ === Person.prototype);//true
console.log(person5.__proto__.__proto__===Object.prototype);//true
複製代碼
咱們以前說過,每一個函數都有prototype,構造函數也是函數,所以看到Person.prototype不該該再困惑。 最後兩句代碼表達的含義很清楚了,Person構造函數製造的對象,其原型是Person的prototype,其原型的原型是萬物之母。new Person()作了些什麼呢?其實咱們能夠模擬一下:
var person3=new Object()
//call爲函數內部方法,每個js函數都有,它與apply、bind均可以改變函數內部this的指向
Person.call(person3,'person no.3',3)
person3.constructor=Person
person3.__proto__=Person.prototype
console.log('模擬的是否爲Person:',Person.prototype.isPrototypeOf(person6));//true
person3.sayName()
複製代碼
第一步,建立新對象
第二步,將構造函數的做用域指向所建立的新對象(this指向新對象),並執行,至關於給person6添加了屬性和方法
第三步,將新對象的構造器指向構造函數,與本文無關
第四步,將新對象的原型指向構造函數的prototype
如今,不去管是否模擬,咱們把person3和person2當作同樣,都是由Person構造的對象,它們倆都有一個sayName方法,但是它們是同一個方法嗎?顯然不是,儘管代碼如出一轍,但它們各自是獨立的方法,在內存中開闢了獨立的空間。能夠比較一下:
console.log(person2.sayName===person3.sayName);//false
複製代碼
那麼如何解決這個問題呢?難道若是建立一萬個Person,就必須同時建立一萬個相同的sayName方法嗎?此時加的那層Person.prototype就能夠派到用場了。咱們用另外一個方法sayAge()來演示,在Person的prototype上加入sayAge方法:
Person.prototype.sayAge=function(){
console.log(this.age)
}
person2.sayAge()//2
person3.sayAge()//3
複製代碼
很顯然,加在原型上的sayAge方法person2和person3都能使用,而且能打印出它們age屬性各自的值,怎麼作到的呢?
這裏須要注意的是,上面的代碼段若是改爲下面,則會出錯:
Person.prototype={
sayAge:function(){
console.log(this.age)
}
}
person2.sayAge()//報錯,sayAge undefined
person3.sayAge()
複製代碼
由於給Person.prototype從新賦值的話,以前建立的person2及person3的原型仍然指向原來的原型,因此找不到sayAge這個方法,除非是使用Person從新建立的對象,才能調用sayAge(),感興趣的能夠本身試一下。
講到這裏,你們會不會問,爲何要多這一層,直接把sayAge()加在Object.prototype不是更方便嗎?若是是這樣,不要忘了,Object.prototype是萬物之母,加在這上面,全部的對象都會有sayAge這個方法了,這可不是咱們想看到的。
好了,原型的共享屬性和方法也很清楚了,後面演示使用原型實現繼承。
Object.create()的做用是以其參數爲原型,建立一個對象,也就是說,新建立的對象的__proto__指向提供的參數。咱們使用以前的person2來建立:
var person4=Object.create(person2)
console.log(person4.__proto__===person2);//true
複製代碼
能夠看到,person4的原型爲person2。
而這時候,person4本身沒有任何屬性和方法:
console.log(person8);//{}
Object.keys(person8).map((v,i)=>{
console.log('key',i,v);//不會調用,由於keys數組爲空
})
複製代碼
打印結果爲空對象,Object.keys()也獲取不到任何屬性。
可是,它仍是能訪問到name這個屬於person2的屬性,由於person2是它的原型,而js在person4自己找不到name屬性時,會順着原型鏈向上查找,看下面代碼:
console.log('name' in person4);//true
console.log(person4.name);//person no.2
複製代碼
in操做符的打印結果爲true,而且直接訪問name輸出的值爲person2的name值,這就是繼承。
事實上,Person構造函數的原型的sayAge()也能被person4訪問到,由於Person.prototype爲person2的原型,因而也在person4的原型鏈之上:
person4.sayAge();//2
複製代碼
好,到這裏,原型的繼承功能應該也講清楚了,咱們和原型的第一次親密接觸到這裏也就結束了,不知道此次接觸可否爲你們之後更深刻的接觸帶來幫助,謝謝你們閱讀!
這篇文章講了一些對於JS原型及原型鏈的粗淺理解,若是有錯誤的地方,請在評論區多多指正。無論如何,把本身理解下的原型給講清楚了,但願能給你們帶來一點幫助。 這篇文章是我在掘金的處女做,但願你們喜歡,若是點贊多的話,必定會再接再勵。固然,若是沒讚的話,應該也仍是會寫,畢竟,寫完以後感受收穫最大的是本身。