原型和原型鏈的深入探索_台中搬家公司

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

前言

原型和原型鏈這方面的底層原理知識,一直以來都是面試市場上的一塊的肥肉,也是每一位前端開發人員不得不掌握的內功心法。一直以來,我都想要搞懂弄明白的這部分知識,所以,就借這次重學前端將這方面的成果和自己的一些拙見整理一下,分享給大家。現在就從編程思想開始講起吧。

本文篇幅較長,如果只想了解原型和原型鏈的核心知識,建議可以直接從第三部分看起。

一.編程思想

提起編程思想,這個概念在我的腦海中一直是一個非常模糊的概念,我所知道的是作為一名開發人員,每個人都應當具備這種能力,並且要不斷地去探索到底怎麼才能提高編程思想。那什麼是編程思想呢?

我覺得這個問題沒有固定的答案,如果非要給一個定義,那大概就是用計算機來解決人們實際問題的思維方式,即編程思想。目前我所了解的編程思想有面向過程編程,結構化編程以及面向對象編程。

1.面向過程編程

面向過程:POP(Process-oriented programming)就是分析出解決問題所需要的的步驟,然後用函數把這些步驟一步一步實現,使用的時候再一個一個的一次調用就可以了。這裏舉個栗子:將一隻大象裝進冰箱,就可以看做是面向過程的做法。

面向過程:就是按照我們分析好了的步驟,按照這個步驟解決問題。

2.結構化編程

結構化編程(Structured programming):在程序設計的早期,程序採用流程圖和自上而下的方式進行設計。這種設計方法的主要思路是將一個大問題分解為多個小問題進行解決,再針對每個小問題編寫方法。總體上來說,是先構建一個主過程來啟動程序流程,隨後根據程序走向來調用相關的其他過程,這種程序設計思想就是結構化編程。

舉個經典的栗子:需要編寫不同的工資計算方法,社保計算方法以及個人所得稅計算方法。而如果從另一個角度看這個程序,則可以從判斷判斷該程序中的對象入手。該程序中的對象,最明顯的就是“員工”!(小玲至今沒弄懂工資咋算的,所以就隨意畫了一個工資計算的流程圖,大家將就看看吧)

3.面向對象編程

面向對象是把事物分解成一個個對象,然後由對象之間分工合作。

舉個栗子:將大象裝進冰箱,面向對象的做法。(突然有點可憐這隻大象了,老是被裝進冰箱)

先找出對象,並寫出這些對象的功能

(1)大象對象

  • 進去

(2)冰箱對象

  • 打開

  • 關閉

(3)使用大象和冰箱的功能

面向對象是以對象功能來劃分問題,而不是步驟。

4.三大編程思想的對比

面向過程:

優點:性能比面向對象高,適合跟硬件聯繫很緊密的東西,例如單片機就採用的面向過程編程。

缺點:沒有面向對象易維護

面向對象:

優點:易維護,易復用,易擴展,由於面向對象有封裝,繼承,多態性的特性,可以設計出低耦合的系統,使系統更加靈活,更加易於維護。

缺點:性能比面向過程低。

結構化編程:

優點:程序易於閱讀、理解和維護,能將一個複雜的程序分解成若干個子結構,便於控制、降低程序的複雜性;提高了編程工作的效率,降低了軟件開發成本

缺點:

  • 用戶要求難以在系統分析階段準確定義,致使系統在交付使用時產生許多回問題。

  • 用系統開發每個階段的成果來進行控制,不能適應事物變化的要求。

  • 系統的開發周期長。

某大佬總結的:用面向過程的方式寫出來的程序是一份蛋炒飯,而用面向對象寫出來的程序是一份蓋澆飯。(覺得有點意思,拿來用用)

5.深入面向對象

面向對象更貼近我們的實際生活,可以使用面向對象的思想來描述世界事物,但是事物分為具體的事物和抽象的事物。

面向對象編程:

程序中先用對象結構保存現實中一個事物的屬性和功能,然後再按需使用事物的屬性和功能,這種編程方法,就叫面向對象編程

使用面向對象的原因:

便於大量數據的管理和維護

面向對象的思維特點:

(1)抽取(抽象)對象共用的屬性和行為組織(封裝)成一個類(模板)

(2)對類進行實例化,獲取類的對象

面向對象編程我們考慮的是有哪些對象;按照面向對象的思維特點,是不斷的創建對象,使用對象,指揮對象做事情。

面向對象的三大特點:

封裝、繼承和多態

想要對編程思想有進一步了解的話,可以看看一位前輩寫的一位十年軟件工程師告訴你什麼是編程思想,相信你會有新的收穫哦。

二.對象

1.什麼是對象?

廣義來講,都說萬物皆對象。對象是一個具體的事物,看的見摸得着的實物,例如:一本書,一支筆,一個人可以是”對象”,一個數據庫、一張網頁等也可以是“對象”。

在JavaScript中,對象是一組無序的相關屬性和方法的集合,所有的實物都是對象,例如字符串,數值,數組,函數等。

許多人都以為“JavaScript 中萬物都是對象”,這是錯誤的。對象是 6 個(或者是 7 個,取 決於你的觀點)基礎類型之一。對象有包括 function 在內的子類型,不同子類型具有不同 的行為,比如內部標籤 [object Array] 表示這是對象的子類型數組。 對象就是鍵 / 值對的集合。可以通過 .propName 或者 [“propName”] 語法來獲取屬性值。

——選自《你不知道的JavaScript(上卷)》

2.組成

對象是由屬性和方法組成的:

  • 屬性:事物的特徵,在對象中用屬性來表示(常用名詞)

  • 方法:事物的行為,在對象中用方法來表示(常用動詞)

3.何時使用對象?

今後只要使用面向對象的編程方式,都要先創建所需的所有對象,作為備用。

4.創建對象的幾種方式

看到前輩們寫的文章中一共提到了有以下五種方式:

  • 工廠模式(對象字面量):用{}創建一個對象

  • 構造函數模式:用new創建

  • 原型對象模式:利用prototype創建

  • 組合使用構造函數模式和原型模式 :構造函數模式用於定義實例屬性,原型模式用於定義方法和共享的屬性(可以看成是重寫原型對象)。

  • 動態原型模式

可以看看javascript中創建對象的幾種方式 或者 JavaScript創建對象的幾種方式,講解的非常詳細。在這裏我就只簡單介紹其中三種

4.1 工廠模式(對象字面量)

今天上班第一天,先把小玲自己的信息記錄一下~

      
1   var person = {
2             uname:"xiaoling",
3             age:18,
4             intr:function(){
5                  console.log('我是小玲')}
6    };
7    console.log(person);
8    person.intr() //調用方法 

 

現在我們來看一下,假如今天老闆開會說,公司要再來三個新同事,讓你記錄一下她們的信息。

然後你按照這種方式一會就完成了

    
 1         var person1 = {
 2             uname:"小張",
 3             age:20,
 4             intr:function(){
 5                  console.log('我是小張1111')}
 6         };
 7         var person2 = {
 8             uname:"小劉",
 9             age:23,
10             intr:function(){
11                  console.log('我是小劉')}
12         };
13         var person3 = {
14             uname:"小蘇",
15             age:24,
16             intr:function(){
17                  console.log('我是小蘇')}
18         };

 

如果按照這種字面量的方式去創建,不會感覺太傻瓜式了嗎?假如老闆說要增加一個特長 Specialty 的信息,你是否對要這些創建好的每一個對象都要進行添加修改呢?另外,請注意 person1 這個對象,因為小玲粗心了一下,不小心記錄錯誤了。導致兩個地方的屬性 name 和 intr 方法中的打印的名字內容是不一致的。那麼,這種問題能否在以後開發工作中避免呢?

對於第二個問題,我們可以用this.屬性名來代替方法中引用的對象屬性即

1 console.log('我是'+this.uname)
2 //或者
3 console.log(`我是${this.uname}`)

 

關於this,每個函數都會自動攜帶(可以想象成天生就有),可以直接使用,指向正在調用函數的對象(誰調用,this就指向誰),所以person.intr(),intr中的this就指向person。

對於第一個問題,我們可以用下面講的構造函數來解決~

4.2 構造函數模式

構造函數

構造函數式是一種特殊的函數,主要用來初始化對象,及為對象成員變量賦初始值,它總是與new一起使用。我們可以把對象中一些公用的屬性和方法抽取出來,然後封裝到這個函數裏面。

new在執行時做的四件事

①在內存中創建一個新的空對象

②讓this指向這個新的對象

③執行構造函數裏面的代碼,給這個新對象添加屬性和方法

④返回這個新對象(所以構造函數裏面不需要return)

    
 1     //2.構造函數模式
 2         //創建構造函數 Person    uname/age/intr 是實例成員
 3       function Person(name,age){
 4           this.uname = name;
 5           this.age = age;
 6  7           this.intr = function(){
 8               console.log(`我是${this.uname}`);
 9           }
10       }
11         //創建實例
12        var p1 = new Person('xiaoling',18),
13            p2 = new Person('小張',20),
14            p3 = new Person('小劉',23);
15 16           console.log(p1); //Person {name: "xiaoling", age: 18, intr: ƒ}
17           console.log(p2); //Person {name: "小張", age: 20, intr: ƒ}
18           console.log(p3); //Person {name: "小劉", age: 23, intr: ƒ}
19         //調用方法
20           p1.intr(); //我是xiaoling
21           p2.intr(); //我是小張
22           p3.intr(); //我是小劉
23            console.log(Person.intr); //undefined
24 25            Person.sex = '女';   //sex是靜態成員
26            console.log(Person.sex); //
27            console.log(p1.sex);  //undefined

 

這裏提幾點:

  • 構造函數中的屬性和方法我們稱為成員,成員可以添加

  • 實例成員就是構造函數內部通過this添加的成員 ,如name,age,intr就是實例成員;實例成員只能通過實例化的對象來訪問,如p1.uname。不能通過構造函數來訪問實例成員,如Person.uname (這樣是不允許的)

  • 靜態成員是在構造函數本身添加的成員,如sex就是靜態成員。靜態成員只能通過構造函數來訪問,不能通過實例對象來訪問(不可以用p1.sex訪問)

這樣,就能愉快地解決4.1的第一個問題啦!

構造函數存在的問題

現在我們來看一下p1,p2,p3在內存中是怎樣的?

從上圖中我們可以看到,創建多個不同的對象時,會同時在內存中開闢多個空間創建多個相同的函數,這些方法都是一樣的。所以就存在浪費內存的問題。我們希望所有的對象使用同一個函數,這樣會比較節省內存,那麼這個問題改如何解決呢?

利用原型對象的方式創建對象可以解決。

4.3 原型對象模式 -prototype

這節內容比較多,就單獨在第三部分講了~

另:其他創建對象的方式請自行查閱資料了解(可以看看javascript中創建對象的幾種方式 或者 JavaScript創建對象的幾種方式),這裏我就不再講了~

三.原型

先來簡單介紹幾個概念

1.構造函數

前面已經提過了,這裏就再簡單概括一下吧。

構造函數式是一種特殊的函數,return會自動返回一個對象。使用時要搭配new使用,並且new會做4件非常重要的事。

2.原型對象

現在想想什麼是原型呢?

一個對象,我們也稱prototype為原型對象,他具有共享方法的作用。

其實在創建每個構造函數時,都會自動附贈一個空對象,名為原型對象(prototype),屬性名為prototype,這個屬性指向函數的原型對象,同時這個屬性是一個對象類型的值。通過 構造函數.prototype 屬性,可獲得這個構造函數對應的一個原型對象。

構造函數通過原型對象分配的函數是所有對象共享的。JavaScript規定,每一個構造函數都有一個prototype屬性,他指向另一個對象。請再次注意這個prototype就是一個對象,這個對象的所有屬性和方法,都會被構造函數所擁有。

借用一張圖來表示構造函數和實例原型之間的關係:

3.對象原型proto

對象都會有一個屬性proto指向構造函數的prototype原型對象,之所以我們的對象可以使用構造函數prototype原型對象的屬性和方法,就是因為對象對象有proto原型的存在,那麼對象的proto是怎麼存在的呢?

我們來看一下這段話:

JavaScript 中的對象有一個特殊的 [[Prototype]] 內置屬性,其實就是對於其他對象的引用。幾乎所有的對象在創建時[[Prototype]] 屬性都會被賦予一個非空的值。 

注意:很快我們就可以看到,對象的 [[Prototype]] 鏈接可以為空,雖然很少見。

                                                    ——選自《你不知道的JavaScript(上卷)》 第五章

這段話的意思其實就是在告訴我們:每個對象都有“proro”屬性,這個屬性會通過指針的方式指向其他對象,這個“其他對象”就是我們說的原型對象。當然除了頂級Object.prototype.proto為null外,幾乎所有的”proto“都會指向一個非空對象。

當我們用構造函數創建對象時,new的第二步自動為新對象添加”_ proto “屬性,將” _ proto_ _”屬性指向當前構造函數的原型對象。

比如: 如果var p1=new Person(“xiaoling”,18)

則new會自動: p1._ proto _=Person.prototype。

然後會有以下 結果:

① 凡是這個構造函數創建出的新對象,都是原型對象的孩子(子對象)

②放在原型對象中的屬性值或方法,所有子對象無需重複創建,就可直接使用。

看到這裏,是否有點懵圈呢?別急,我們來畫個圖理一理

如上圖所示:構造函數的prorotype屬性 和proto對象原型指向的是同一個對象。

來證明一下吧在控制台打印一下這段代碼:

1 console.log(Person.prototype === p1.__proto__);//true
2 console.log(Person.prototype === p2.__proto__);//true
3 console.log(Person.prototype === p3.__proto__);//true

 

我們發現結果都是true,那麼說明proto對象原型和原型對象prototype是等價的。或者說他們就是同一個對象,即:

構造函數.prototype === 對應實例對象.proto

4.原型對象模式

前面介紹了很多概念,現在來介紹一下原型對象prorotype集體是怎麼實現的,其實很簡單。

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

構造函數.prototype.方法 = function(){ … }

具體看代碼:

 1       //原型對象方式
 2          function Person(name,age){
 3           this.uname = name;
 4           this.age = age;
 5       }
 6       Person.prototype.intr = function(){
 7               console.log(`我是${this.uname}`);
 8           }
 9         //創建實例
10        var p1 = new Person('xiaoling',18),
11            p2 = new Person('小張',20),
12            p3 = new Person('小劉',23);
13            console.log(p1); //Person {name: "xiaoling", age: 18}
14           console.log(p2); //Person {name: "小張", age: 20}
15           console.log(p3); //Person {name: "小劉", age: 23}

 

我們來看控制台打印:

我們會發現,打印結果並沒有出現intr方法,那我們直接來調用一下試試吧

//調用intr方法
p1.intr()
p2.intr()
p3.intr()

 

控制台結果:

我們發現,居然可以直接調用,這是怎麼回事呢?我們現在把p1,p2,p3在控制台打印的結果展開

我們可以看到每個實例對象都有一個對象原型proto,將它展開就能看到intr方法,難道實例對象調用成功的intr()是proto上的嗎?。我們知道實例對象的proto就是構造函數的prototype,然而我們的intr方法就是定義在Person.prototype上的,所以,實例對象p1,p2,p3能夠成功調用intr方法。

我們來看一下,他們在內存中是怎麼樣的(簡單畫了一下)?

我們可以看到,p1,p2,p3的__proto__通過指針指向原型對象。我們知道原型對象是一個對象,所以intr在原型對象中存儲的只是一個地址,這個地址會通過指針指向intr方法存儲的位置。所以p1,p2,p3調用的intr方法,其實就是原型對象上的intr,他們共享同一個方法,這也正是前面提到的原型對象的作用:共享方法。

這樣4.2提到的浪費內存的問題就完美的解決啦。雖然創建了三個實例對象,但是他們用的都是同一個方法(只佔用了一份內存),就算咱們再創建3000,30000個對象,他們用的還是同一個方法,佔用的內存依然只有一份,內存資源得到了大大的改善和節省,perfect!

那我們再來思考一個問題:p1,p2,p3,自己沒有uname,age,intr這些方法和屬性,為什麼可以使用呢?這就涉及到我們後面會講的繼承以及JavaScript的查找機制規則了.我們後面會說。

5.三者關係

在上文中,我們總是提到構造函數,原型(原型對象),實例,那麼這三者之間到底有什麼樣的關係呢?

目前我們已經知道的關係是:

  • 構造函數.prototype 指向的是原型對象

  • 實例對象的proto指向的也是原型對象

我們再來看一下這三個實例對象的打印結果,上圖

有看到什麼額外的東西嗎?請再仔細看一下!

我們可以看到每個實例對象展開,他裏面都有一個proto,這個不是重點,重點是,每個proto屬性展開,他不僅僅有我們自己定義的方法intr,還有一個淺粉色的constructor屬性,重點是我們可以看到這個這個constructor的值正好是我們的構造函數Person.

咱們來驗證一下,在控制台輸出以下代碼:

1 console.log(Person.prototype.constructor === Person); //true
2 console.log(p1.__proto__.constructor === Person);//true
3 console.log(p2.__proto__.constructor === Person);//true console.log(p3.__proto__.constructor === Person);//true

 

我們得到的結果是4個true,說明我們上文的猜想是正確的,也就是說構造函數也有一個屬性constructor,這個constructor屬性指向的是構造函數。既然構造函數能夠出生的時候就有prototype屬性–創建每個構造函數時,都會自動附贈一個空對象,名為原型對象(prototype),那我們是不是也可以把原型對象上憑空多出來的constructor當成是它天生就有的呢(哎!有些人註定了,出生就是不平凡的,自帶光環)

好了,我們現在再來總結一下:

  • 創建構造函數時,構造函數會附贈一個prototype屬性,這個prototype屬性以指針的方式指向一個對象,這個對象我們稱為原型對象。

  • 原型對象存在的同時,也會自動附贈一個constructor屬性,這個屬性以指針的方式指向他的構造函數。

  • 用構造函數實例化對象時,會通過new這個動作做4件事。

    ①在內存中創建一個新的空對象

    ②讓this指向這個新的對象(自動設置新對象的_ proto _指向構造函數的原型對象——繼承)

    ③執行構造函數裏面的代碼,給這個新對象添加屬性和方法

    ④返回這個新對象(所以構造函數裏面不需要return)

  • 實例對象創建時會自帶一個內置屬性proto,這個proto性會通過指針的方式指向原型對象(我們可以稱為父類)

    再來畫一個圖看看他們之間的關係

再來看一張更詳細的圖

看完這張圖,你是不是又懵圈了呢?沒關係,請跟我一樣拿起筆自己也在紙上畫一畫吧,如果你能畫明白,說明你已經動了一大半了。如果還沒有明白,我們一起再來捋一捋:

  1. 構造函數Person生成的時候會自動存在一個prototype屬性,即Person.prototype,我們稱為原型對象。

  2. 原型對象是一個對象,它存在的同時會自動生成一個constrctor屬性,這個屬性會自動指向他的構造函數,即Person。

  3. 用new生成實例對象時,這個實力對象同時會攜帶一個proto屬性,這個屬性會指向構造函數的原型對象,通過這個proto,實例對象繼承了原型對象上的所有方法,就是可以用原型對象上的方法。

其實不僅僅是實例對象,任何一個對象數據類型都存在這樣的關係。

再來看一張圖,或許你會更加明白

(本圖來自《JavaScript 高級程序設計》的圖 6-1)

好了,構造函數,原型對象,實例對象三者之間的關係就講完了,如果還沒有弄明白,建議多看幾遍,看的同時自己動手在紙上畫一畫哦。

四.JavaScript的成員查找機制

先直接講一下規則:

①當訪問一個對象的屬性(包括方法)時,首先查找這個對象有沒有該屬性。

②如果沒有就查找他的原型(也就是proto指向的prototype原型對象)。

③如果還沒有就查找原型對象的原型(Object的原型對象)。

④以此類推一直到Object為止(null).

proto對象原型的意義就在於為對象成員查找機制提供了一個方向,或者說一條路線。

這一部分看不明白沒關係,先大概知道這個規則就行。

五.原型鏈

在JavaScript中萬物都是對象,對象和對象之間也有關係,並不是孤立存在的。對象之間的繼承關係,在JavaScript中是通過prototype對象指向父類對象,直到指向Object對象為止,這樣就形成了一個原型指向的鏈條,專業術語稱之為原型鏈

舉例說明:person → Person → Object ,普通人繼承人類,人類繼承對象類

當我們訪問對象的一個屬性或方法時,它會先在對象自身中尋找,如果有則直接使用,如果沒有則會去原型對象中尋找,如果找到則直接使用。如果沒有則去原型的原型中尋找,直到找到Object對象的原型,Object對象的原型沒有原型,如果在Object原型中依然沒有找到,則返回null。這也是實例對象p1,p2,p3能夠使用intr()方法的最重要的

上文說到“如果在Object原型中依然沒有找到,則返回null”,這個一起來驗證一下。我們先來看一下在瀏覽器執行的這些代碼:

我們知道任何一個原型對象都是一個對象,而每個對象都有proto屬性,所以Object的原型對象也是一個對象(可以看Object.prototype的打印結果就是一個對象),那麼他的proto就是上圖我們打印的結果null.所以我們得到了驗證結果

即:Object.prototype.proto = null

所以在JavaScript中,Object是一等公民!

現在我們把原型鏈畫出來!

現在再看着這張圖,重新讀一遍第四和第五部分,你就會明白原型鏈了。

六.構造器

1.定義

本文沒有構造器的定義(沒有找到對它的準確定義),真的要說它是什麼只能說:構造函數。

構造函數跟普通函數非常相似,我們已經說過構造函數時一種特殊的函數(第四部分4.2中),我可以通過new關鍵字來使用它們。主要有兩種類型的構造函數,native構造函數(Array,Object等)它們可以在執行環境中自動生成,還有自定義的構造函數,你可以定義自己的方法和屬性,比如我們自定義的Person構造函數。

2.native構造函數

JavaScript中有內置(build-in)構造器/對象共計12個(ES5中新加了JSON):

Object、Function、String、Number、Boolean、Array、RegExp、Data、Error、Math、JSON、Global

3.自定義構造函數

就是我們可以根據需要按照構造函數的方式定義自己的方法和屬性就行。

4.一個重要結論

所有構造器/函數的proto都指向Function.prototype(Function.prototype是一個空函數)

怎麼驗證這句話呢?上代碼:

 1         console.log(Boolean.__proto__ === Function.prototype); //true
 2         console.log(Number.__proto__ === Function.prototype); // true
 3         console.log(String.__proto__ === Function.prototype);  // true
 4         console.log( Object.__proto__ === Function.prototype); // true
 5         console.log(Function.__proto__ === Function.prototype);  // true
 6         console.log(Array.__proto__ === Function.prototype); // true
 7         console.log(RegExp.__proto__ === Function.prototype); // true
 8         console.log(Error.__proto__ === Function.prototype);  // true
 9         console.log(Date.__proto__ === Function.prototype);// true
10 11 12         console.log(Function.prototype);

 

結果:

再來個自定義構造器:

 1          //自定義構造器
 2          //原型對象方式
 3          function Person(name,age){
 4           this.uname = name;
 5           this.age = age;
 6         }
 7         Person.prototype.intr = function(){
 8               console.log(`我是${this.uname}`);
 9           }
10         
11         console.log(Person.__proto__ === Function.prototype);  //true

 

這說明什麼呢?

①JavaScript中的內置構造器/對象共計12個(ES5中新加了JSON).上面列舉了可訪問的9個構造器。剩下如Global不能直接訪問,Arguments僅在函數調用時由JS引擎創建,Math,JSON是以對象形式存在的,無需new。它們的proto是Object.prototype。如下

Math.__proto__ === Object.prototype // true
JSON.__proto__ === Object.prototype // true

②所有的構造器都來自於Function.prototype,甚至包括根構造器Object及Function自身。所有構造器都繼承了Function.prototype的屬性及方法。如length、call、apply、bind(ES5)。

③Function.prototype也是唯一一個typeof XXX.prototype為 “function”的prototype。其它的構造器的prototype都是一個對象。

    
 1 console.log(typeof Function.prototype) // function
 2 console.log(typeof Object.prototype)   // object
 3 console.log(typeof Number.prototype)   // object
 4 console.log(typeof Boolean.prototype)  // object
 5 console.log(typeof String.prototype)   // object
 6 console.log(typeof Array.prototype)    // object
 7 console.log(typeof RegExp.prototype)   // object
 8 console.log(typeof Error.prototype)    // object
 9 console.log(typeof Date.prototype)     // object
10 console.log(typeof Object.prototype)   // object

 

④除了Function.prototype,所有構造函數的prototype都是一個對象

5.一等公民

前面原型鏈部分我們知道Objec是一等公民,那其實Function也是。

我們已經知道了所有構造器(含內置及自定義)的proto都是Function.prototype,那Function.prototype的proto是誰呢?

console.log(Function.prototype.__proto__ === Object.prototype) // true

這說明所有的構造器也都是一個普通JS對象,可以給構造器添加/刪除屬性等。同時它也繼承了Object.prototype上的所有方法:toString、valueOf、hasOwnProperty等。

最後再提一次:Object.prototype的proto是誰?

前面我們已經驗證過是null,到頂了

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

講到這裏,是不是又有點懵圈了呢?上圖幫助你消化吧

看圖,一起理一理:

  • 每個構造函數(不管是內置構造函數還是自定義的構造函數),他的proto都指向Function.Prototype,包括Function他自己(Math和JSON不算在裏面,因為他們的proto指向的是Object.prototype)。

  • 每個構造函數都有一個內置屬性即prototype,他們指向自己的原型對象,除了Function的原型對象是一個空函數外,所有構造函數的prototype都是一個對象。

  • 每個原型對象都有一個內置屬性constructor,屬性,constructor屬性指回原型對象的屬性

  • Object是頂級對象,Object.prototype.proto為null

  • 每個實例對象都有一個proto屬性,通過這個proto屬性,這個實例對象可以按照JavaScript的成員查找規則(本文第四部分),去使用原型鏈上的屬性和方法,也就是我們說的繼承父類的屬性和方法的本質。這也是我們隨便創建一個數組或者對象,能夠使用toString()/valueOf() pop()/filter()/sort()等方法的原因。

現在再看這張圖明白了嘛,回國頭再去看一看第四部分:JavaScript的成員查找規則 是不是也頓悟了很多。

七.一張手繪原型鏈

請忽略我醜陋的字跡,能看懂這張圖並且自己可以畫出來,說明你今天的收穫非常大哦~

 

到這裏我們就講完原型和原型鏈相關的內容了,可能還有些地方沒講到,大家就自行下去再研究了。

另外建議紅寶書和你不知道系列多看幾遍(「書讀百遍其義自見」是很有道理的)。

再推薦幾位前輩寫的不錯的文章,值得多讀幾遍:

[
​最詳盡的 JS 原型與原型鏈終極詳解,沒有「可能是」]  https://www.jianshu.com/p/dee9f8b14771  [
​javascript中構造器(函數)的__proto__與prototype初探]  https://www.cnblogs.com/webjoker/p/5319377.html 

 

後記

非常感謝大家能夠認真看完這篇文章。其實在提筆寫這篇文章之前,小玲的內心是非常緊張和忐忑的,因為害怕自己研究的不夠深入和全面,但是一想到可以和大家分享我的收穫,還是非常激動和興奮的。可能有些地方講的還不夠清楚,大家可以自己多思考一下,你自己思考想出來的東西,比別人灌輸給你的更能讓你記憶深刻。如果,若有某個地方存在問題或者不明白的,歡迎大家积極提出寶貴的建議和見解!

 

參考文章:

https://www.cnblogs.com/TRY0929/p/11870385.html

https://www.jianshu.com/p/dee9f8b14771

https://www.cnblogs.com/snandy/archive/2012/09/01/2664134.html

https://www.cnblogs.com/webjoker/p/5319377.html

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※推薦台中搬家公司優質服務,可到府估價

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

您可能也會喜歡…