再和「面向對象」談戀愛 - 對象相關概念(二)

上一篇文章把對象的概念講解了一下,這篇文章要重點解釋最讓你們犯迷糊的一些概念,包括javascript

  • 構造函數
  • 實例
  • 繼承
  • 構造函數的屬性與方法(私有屬性與方法)
  • 實例的屬性與方法(共享屬性與方法)
  • prototype(原型)
  • __proto__(原型)

構造函數

構造函數依然是個函數,用來生成對象。全部的對象都是由構造函數建立的

對象哪來的?構造函數生的。而普通函數不能生成對象(不孕不育),構造函數能夠生成對象(有生育能力)。每一個對象都會有一個本身對應的構造函數,可是不表明每一個構造函數都會生(生成實例),有不會生的構造函數,例如Math對象(不孕不育)java

console.dir(Array);     //數組的構造函數
console.dir(Function); //函數的構造函數
console.dir(Object);   //對象的構造函數

new Math();     //報錯,不能生成實例
new Window();   //報錯,不能生成實例

唐僧說過一句話,用來解釋構造函數與實例再合適不過了「人是人他媽生的,妖是妖他媽生的」那這裏的人他媽與妖他媽就是構造函數。人與妖實際上是下面要說的概念「實例」segmentfault

實例

實例就是對象,由構造函數生成

平時用的實際的東西都是實例(獲取的DOM元素、聲明的數組、聲明的函數、聲明的對象),有時候須要new關鍵字生成(不是絕對的)數組

  • 實例身上有一個constructor屬性,它指向對應構造函數(生母)
  • 判斷一個對象是不是某個構造函數的實例,用instanceof運算符
//實例
[];             //不用new生成
new Array();    //用new生成

[].constructor===Array; //true
[] instanceof Array;    //true

繼承

生成的實例具備構造函數身上的屬性與方法(就像老鼠的兒子會打洞)
一個對象身上有另外一個對象身上的屬性或方法,這種具備的方式就叫繼承

說到繼承首先想到的就是遺產。老人去世了,在二環裏留了一套四合院,那這套房子歸誰呀?不可能歸我吧,歸個人話我就不用在這吭哧吭哧寫文章了。那也不能歸你吧,歸你了你也不能座這看我吭哧吭哧寫的文章。得歸人家兒子,這個就是繼承。那從這個例子中得出幾個重要信息,繼承者與被繼承者之間是有必定關係的,最簡單的就是父子或者母子關係。瀏覽器

那回到程序中,構造函數就是老人,實例就是兒子。那實例的屬性或者方法哪來的?構造函數的,不過它能夠繼承過來,這是合理合法的。可是也會有特殊狀況,假如老人膝下無子,那這房子怎麼辦?充公?不能吧,要你你幹麼?不過這個老人有一個惟一的親人,就是弟弟。那弟弟可否拿到這個房子呢,應該是能夠的。再回到程序中,剛纔這種狀況其實程序中也存在,就是一個對象能夠有另外一個對象身上的東西。函數

//arr身上是並無push方法,這個方法來自於構造函數Array,是arr繼承了構造函數身上的這個方法
const arr=[1,2];
arr.push(3);
console.log(arr);    //[1, 2, 3]

//Array對象身上並無valueOf方法,這個方法來自於Object對象,是Array對象繼承了Object對象的這個方法
Array.valueOf===Object.valueOf;   //true

構造函數的屬性與方法(私有屬性與私有方法)

構造函數身上的屬性與方法,只有構造函數能用,實例不能用

再回到這1個億的例子中來,這個老人爲啥有個四合院呢,這位老人原來是個老兵,立過一等功,戰功赫赫,得到了無數勳章。那我問你他的這些榮譽,兒子有麼?或者弟弟有麼?沒有吧。這個就是構造函數特有的屬性與方法,實例身上是沒有的spa

//構造函數的私有屬性與方法
console.log(Array.name);    //Array
console.log(Array.of(5));    //[5]

//實例不能用
const arr=[];   //實例
console.log(arr.name);        //undefined
arr.of(6);    //報錯

實例的屬性與方法(共享屬性與共享方法)

實例身上的屬性與方法,只有實例能用,構造函數不能用(放在prototype裏)

老人有兩個孩子,這倆孩子跟我學會了js,又學會了ES6,如今在百度上班作開發,這你說扯不扯,我都快編不下去了。那這些技能老人會麼,他不會。因此這個就叫實例的屬性與方法,只能實例去用,構造函數用不了prototype

//實例的方法
const arr=[1,2,3];
console.log(arr.concat(['a','b']));    //[1, 2, 3, "a", "b"]

Array.concat(['a','b']));    //報錯。你的就是你的,個人就是個人,不能互用。就跟媳婦是同樣

//但構造函數能夠間接用
console.log(Array.prototype.concat(['a','b']));    //["a", "b"]

prototype(原型)

一、構造函數身上的一個屬性,它的類型爲對象,這個屬性的值就是原型
二、這個對象裏放的屬性與方法,就是構造函數的共享屬性與共享方法,全部實例都能用

還得回到那一個億的例子裏,咱們說兒子能繼承老子的遺產,爲何呢?由於他有個神器叫戶口本,國家給發的,能證實他們是父子關係。你去辦手續的時候確定要拿着戶口本。如今都得要證件,固然有的時候你可能須要到派出所開個證實,證實你就是你。雖然有點扯哈,可是有真實發生過,若是派出所不給你證實,那你就不是你。回到程序中,雖然你知道實例是構造函數生的,那實例就能有構造函數身上的方法,爲何呢?其實他們也有證,跟戶口本同樣,這個證就是prototypecode

//prototype 原型
console.log(Array.prototype);   //數組的原型對象

//若是把原型上的方法刪除了,那實例就不能用了。證實原型裏放的屬性與方法都是實例的屬性與方法
const arr=[];
Array.prototype.push=null;
arr.push(6);    //報錯

__proto__(原型)

一、這個屬性是瀏覽器本身部署的,到了ES6也沒有正式寫入標準裏,建議你們不要用它,用 Object.getPrototypeOf()方法替代
二、它也是指原型對象,與 prototype同樣。可是有區別:歸屬不一樣, prototype是函數身上的屬性, __proto__是對象身上的屬性

這個屬性與prototype屬性每每讓大部分人都百思不得其解,看得是一頭霧水,腦殼擰成了麻花,網上的資料是一堆一堆,但每每你們看得是一愣一愣。其實這倆東西很簡單,是一道推算題,首先,你要明白原型的結果只有一個,就是構造函數的prototype屬性的值。那爲何瀏覽器看熱鬧不嫌事大,給咱們找麻煩,又部署了一個__proto__呢?其實瀏覽器也是好心,但沒成想辦了壞事。在沒有這個屬性之前,只能從函數身上找到原型。爲了能從實例對象身上也能找到原型,瀏覽器就部署了這個屬性。以字符串爲例推算以下:對象

字符串構造函數的原型放在String.prototype裏。那如今我可否經過實例找到這個原型呢?也就是str ? ===String.prototype

const str=new String('kaivon');
console.dir(str);    //打印出實例,點開後看到__proto__

要經過實例找到原型的話,首先要經過實例找到構造函數(由於原型在構造函數身上)。前面有說過,實例身上都有一個屬性叫constructor,這個就指向構造函數

console.log(str.constructor===String);    //true 經過實例找到了構造函數

那找到了構造函數,原型不就放在構造函數身上麼?因此變成了這樣

console.log(str.constructor.prototype===String.prototype);    //true

到這裏就推出來如何經過實例找到原型,可是這麼寫是否是有點長呢?天空一聲巨響,__proto__閃亮登場,瀏覽器爲了簡化操做,就讓__proto__等於constructor.prototype,也就是變成下面這樣

console.log(__proto__===constructor.prototype);    //true

因此整個語句其實就能夠變成這樣

console.log(str.__proto__===String.prototype);    //true

到這裏就明白了吧,再總結一下

  • prototype屬性是構造函數身上的,指向原型
  • __proto__屬性是對象身上的,指向原型
  • 實例的.__proto__===構造函數.prototype

如今是否是恍然大悟、如夢初醒、豁然開朗,可是我不得不澆你一頭冷水啊,尚未完,看下面

console.dir(String);//打印出構造函數,點開也看到了__proto__

插入圖1

剛纔不是說這玩意是實例身上的麼,如今怎麼跑到構造函數身上了?童話裏都是騙人的嗎?別急,構造函數是函數麼?函數是對象麼?是吧,這就對了。其實上面的推理題都好說,都簡單。你們弄不懂的是在這。網上的文章就在這裏讓你的麻花越擰越緊。構造函數是函數,是函數它就是對象,__proto__既然是對象身上的,那這個構造函數身上就必定會有。點開__proto__看一下
插入圖2
裏面的內容不該該是String原型的內容麼?好像不是哎,這就傻逼了,剛搞清楚的東西如今全亂了。別急,記住那句話__proto__永遠指向實例對象對應的構造函數的prototype,那就先看實例對象是誰。咱們點開的這個String它是什麼?它是構造函數,它的類型是個函數。雖然它是一個構造函數,但在這裏它就是一個實例對象,而且它的類型是函數,因此它是Function構造函數的實例,那Function的實例對象身上的__proto__不該該指向Functionprototype麼?因此這裏面的內容爲Function對象的原型

console.log(String.__proto__===Function.prototype);    //true

接着看,這裏還有一個__proto__
插入圖3
真是懼怕什麼來什麼呀!這是__proto__來自於Functionprototype,它是個原型對象,原型對象的數據類型固然爲對象了,因此它是Object構造函數的實例,那Object的實例對象身上的__proto__不該該指向Objectprototype麼,因此點開這個__proto__裏面的內容是Object.prototype的值
插入圖4

console.log(String.__proto__.__proto__===Object.prototype);    //true

由於Object已是頂層對象了,因此在它的prototype裏不會再出現__proto__了,可是有人說Object其實還有繼承,繼承於null。可是我不太認同這種說法,__proto__的值是個對象類型數據,而Object已是頂層對象了,它原型對象的__proto__確定沒有值了,在ECMAScriptnull的數據類型又爲對象,因此就呼應上了,而不是繼承於null

Object.prototype.__proto__===null;  //true

到這裏我把面向對象當中關鍵的一些概念算是說清楚了,下一篇文章來講一下真正的面向對象概念!

相關文章
相關標籤/搜索