摘要:從原型對象指向構造函數畫一條帶箭頭的線。線上標注,表示該原型對象的構造函數等于。但除此之外,若構造函數所指的顯示原型對象存在于的原型鏈上,結果也都會為。執行構造函數,并將指針綁定到新創建的對象上。
做前端開發有段時間了,遇到過很多坎,若是要排出個先后順序,那么JavaScript的原型與對象絕對逃不出TOP3。
如果說前端是海,JavaScript就是海里的水
一直以來都想寫篇文章梳理一下這塊,為了加深自己的理解,也為了幫助后來者盡快出坑,但總覺缺少恰當的切入點,使讀者能看到清晰的路徑而非生硬的教科書。最近看到句話“好的問題如庖丁之刃,能幫你輕松剖開現象直達本質”,所以本文以層層探問解答的方式,試圖提供一個易于理解的角度。
現在的軟件開發,很少有不是面向對象的,那么JavaScript如何創建對象?一、 創建對象的方法
在傳統的面向對象編程語言(如:C++,Java等)中,都用定義類的關鍵字class,首先聲明一個類,然后再通過類實例化出對象實例。但在JavaScript中若實現這樣邏輯的對象創建,需要先定義一個代表類的構造函數,再通過new運算符執行構造函數實例化出對象。
對象字面量
var object1 = { name: "object1" }
構造函數法
var ClassMethod = function() { this.name = "Class" } var object2 = new ClassMethod() // 這種方式創建的對象字面量 var object3 = new Object({ name: "object3" })
這里提到的new運算符,后面會詳述
Object.create(proto)
創建一個新對象,使用入參proto對象來提供新創建的對象的__proto__,也就入參對象時新創建對象的原型對象。
var Parent = { name: "Parent" } var object4 = Object.create(Parent)
想要明白JavaScript原型繼承的幺蛾子,勢必要搞清楚原型對象、實例對象、構造函數以及原型鏈的概念和關系,接下來我盡量做到表述地結構清晰,言簡意賅。二、原型繼承
暫時擱置一下原型鏈,我先講清楚其余三個概念的門門道道,如果你手邊有紙筆最好,沒有在腦中想象也不復雜。
畫一個等邊三角形,從頂點順時針為每個角編號(1)、(2)、(3)
其中(1)旁邊標注“原型對象”,(2)構造函數,(3)實例對象
從(2)構造函數(如上節例中的ClassMethod)指向(3)實例對象(上節例中的object2)畫一條帶箭頭的線。線上注明new運算符,表示var object2 = new ClassName()。
從(2)構造函數指向(1)原型對象畫一條帶箭頭的線。線上標注prototype,表示該構造函數的原型對象等于ClassName.prototype。(函數都有prototype屬性,指向它的原型對象)
從(3)實例對象指向(1)原型對象畫一條帶箭頭的線。線上標注__proto__,表示該實例對象的原型對象等于object2.__proto__,結合第4步,便有ClassName.prototype === object2.__proto__。
從(1)原型對象指向(2)構造函數畫一條帶箭頭的線。線上標注constructor,表示該原型對象的構造函數等于ClassName === object2.__proto__.constructor。
關于JavaScript函數與對象自帶的屬性有一句需要畫重點的話:所有的對象都有一個__proto__屬性指向其原型對象,所有的函數都有prototype屬性,指向它的原型對象。函數其實也是一種對象,那么函數便有兩個原型對象。由于平時更關注對象依據__proto__屬性,指向的原型對象所構成的原型鏈,為了區分函數的兩個原型,便將__proto__所指的原型對象稱作隱式原型,而把prototype所指向的原型對象稱作顯示原型。
三、從instanceof再看原型鏈看到這里你應該已經知道原型對象、實例對象、構造函數以及原型鏈是什么了,但是對于為什么是這樣應該還比較懵,因為我也曾如此,用以往類與對象,父類與子類的概念對照原型與實例,試圖想找出一些熟悉的關系,讓自己能夠理解。
人們總是習慣通過熟悉的事物,類比去認識陌生的事物。這或許是一種快速的方式,但這絕對不是一種有效的方式。類比總會讓我們輕視邏輯推理
語法格式為object instanceof constructor,從字面上理解instanceof,是用來判斷object是否為constructor構造函數實例化出的對象。但除此之外,若構造函數所指的顯示原型對象constructor.prototype存在于object的原型鏈上,結果也都會為true。
字面理解多少會有些偏差,請及時查閱MDN文檔
原型鏈就是JavaScript相關對象之間,由__proto__屬性依次引用形成的有向關系鏈,原型對象上的屬性和方法可以被其實例對象使用。(這種有向的父子關系鏈就具有了實現類繼承的特性)
四、new運算符new Foo()執行過程中,都發生了什么?
以下三步:
創建一個繼承自Foo.prototype的新對象。
執行構造函數Foo,并將this指針綁定到新創建的對象上。
如果構造函數返回一個對象,則這個對象就是new運算符執行的結果;如果沒返回對象,則使用第一步創建出的新對象。
為了直觀的理解,這里自定義一個函數myNew來模擬new運算符
function myNew(Foo){ var tmp = Object.create(Foo.prototype) var ret = Foo.call(tmp) if (typeof ret === "object") { return ret } else { return tmp } }五、實現繼承
在ES6中,出現了更為直觀的語法糖形式:class Child extends Parent{},但這里我們只看看之前沒有這種語法糖是怎么實現的。我一直有一個體會:要想快速的了解一個事物,就去了解它的源起流變。
首先定義一個父類Parent,以及它的一個屬性name:
function Parent() { this.name = "parent" }
接下來如何定義一個繼承自Parent的子類Child:
構造函數方式
function Child() { Parent.call(this) this.type = "subClass" // ... 這里還可定義些子類的屬性和方法 }
這種方式的缺陷是:父類原型鏈上的屬性和方法不會被子類繼承。
原型鏈方式
function Child() { this.type = "subClass" } Child.prototype = new Parent()
這種方式彌補了子類沒法繼承父類原型鏈上屬性和方法的缺陷,與此同時又引入一個新的問題:父類上的對象或數組屬性會引用傳遞給子類實例。
比如父類上有一個數組屬性arr,現通過new Child()實例化出兩個實例對象c1和c2,那么c1對其arr屬性的操作同時也會引起c2.arr的改變,這當然不是我們想要的。
組合方式(綜合1,2兩種方式)
function Child() { Parent.call(this) this.type = "subClass" } Child.prototype = new Parent()
雖然解決了上述問題,但明顯看到這里構造函數執行了兩遍,顯然有些多余。
組合優化方式
function Child() { Parent.call(this) this.type = "subClass" } Child.prototype = Parent.prototype
這種方式減少了多余的父類構造函數調用,但子類的顯示原型會被覆蓋。此例中通過子類構造函數實例化一個對象:var cObj = new Child(),可以驗證出實例對象的原型對象,是父類構造函數的顯示原型:cObj.__proto__.constructor === Parent,顯然這種方式依舊不很完美。
終極方式
function Child() { Parent.call(this) this.type = "subClass" } Child.prototype = Object.create(Parent.prototype) Child.prototype.constructor = Child
實例對象的__proto__屬性值總是該實例對象的構造函數的prototype屬性。這里關于構造函數的從屬關系存在一個易混淆的點,我多啰嗦幾句來試圖把這塊講清楚:還記的上面我們畫的那個三角形么?三個角分別代表構造函數、實例對象和原型對象,三條有向邊分別代表new,__proto__,prototype,根據__proto__有向邊串聯起來鏈便是原型鏈。
要解釋清楚構造函數的從屬關系,我們先在上面所畫的原型鏈三角形中的每個三角形中,添加一條有向邊:從原型對象指向構造函數,這表示原型對象有一個constructor屬性指向它的構造函數,而該構造函數的prototype屬性又指向這個構造函數,于是便在局部形成了一個有向環。
現在一切都協調了,唯獨還有一點,就是原型鏈末端的實例對象構造函數的指向,不論通過new運算符還是通過Object.create創建出來的實例對象的constructor屬性,都和其原型對象的constructor相同。所以為了保持一致性便有了上面那句Child.prototype.constructor = Child,為的是在你想要知道一個對象是由哪個構造函數實例化出來的,可以根據obj.__proto__.constructor獲取到。
多繼承
function Child() { Parent1.call(this) Parent2.call(this) } Child.prototype = Object.create(Parent1.prototype) Object.assign(Child.prototype, Parent2.prototype) Child.prototype.constructor = Child
利用Obejct.assign方法將Parent2原型上的方法復制到Child的原型。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/94484.html
摘要:一正則使用分類正則表達式后文簡稱為正則可劃分出兩種使用方式通過正則字面量與通過構造函數創建出來的正則對象,在不考慮訪問正則對象屬性的情況下,是等價的。匹配前一個表達式次或多次。 正則表達式在前端開發中,對于字符串處理任務來說,絕對是一件可以祭出的大殺器。同時對于前端開發人員來說也是一項基本技能,但若只是停留在能看懂,知道去哪查的階段,那距離得心應手地運用差的可能不止一步兩步。 行業總習...
摘要:一同源策略用戶瀏覽網站時難免需要將一些經常用到的信息,緩存在本地以提升交互體驗,避免一些多余的操作。無法獲得請求不能發送同源策略是必要的,但這些限制有時也會對一些合理的使用帶來不便,這便引出了跨域通信的需求。 一、同源策略 用戶瀏覽網站時難免需要將一些經常用到的信息,緩存在本地以提升交互體驗,避免一些多余的操作。那么這些信息中難免有些就會涉及用戶的隱私,怎么保證用戶的信息不在多個站點之...
摘要:用構造器模擬類的兩種方法在構造器中修改,給添加屬性修改構造器的屬性指向的對象,它是從這個構造器構造出來的所有對象的原型。 筆記說明 重學前端是程劭非(winter)【前手機淘寶前端負責人】在極客時間開的一個專欄,每天10分鐘,重構你的前端知識體系,筆者主要整理學習過程的一些要點筆記以及感悟,完整的可以加入winter的專欄學習【原文有winter的語音】,如有侵權請聯系我,郵箱:kai...
摘要:用構造器模擬類的兩種方法在構造器中修改,給添加屬性修改構造器的屬性指向的對象,它是從這個構造器構造出來的所有對象的原型。 筆記說明 重學前端是程劭非(winter)【前手機淘寶前端負責人】在極客時間開的一個專欄,每天10分鐘,重構你的前端知識體系,筆者主要整理學習過程的一些要點筆記以及感悟,完整的可以加入winter的專欄學習【原文有winter的語音】,如有侵權請聯系我,郵箱:kai...
閱讀 1808·2021-11-22 09:34
閱讀 3096·2019-08-30 15:55
閱讀 675·2019-08-30 15:53
閱讀 2061·2019-08-30 15:52
閱讀 3007·2019-08-29 18:32
閱讀 1996·2019-08-29 17:15
閱讀 2402·2019-08-29 13:14
閱讀 3564·2019-08-28 18:05