您好,登錄后才能下訂單哦!
小編給大家分享一下JavaScript的繼承和原型鏈是什么,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
JavaScript的繼承和原型鏈是我在學習前端過程中遇到的較為少有的難以理解的部分,這里便將我所有知道和了解到的東西記錄了下來
構造函數由實例成員和靜態成員二者組成,其中實例成員是在函數內部通過this關鍵字添加的成員;只能通過實例化對象以后通過實例化對象進行訪問;而靜態成員是函數本身上添加的成員,只能通過構造函數來訪問。
//創造一個構造函數let Father = function(name,age){ //實例成員 this.name = name; this.age = age; this.method = "我是一個實例成員";} //靜態成員Father.like = "mother"; //檢驗實例對象是否能夠被構造函數直接訪問console.log(Father.method); //undefinedconsole.log(Father.like); //mother //實例化一個對象let father = new Father("小王",27); //檢驗靜態對象是否能夠被實例化對象訪問console.log(father.name); //小王console.log(father.age); //27console.log(father.like); //undefined
通過new關鍵字可以通過構造函數實現一個實例化對象,那么在具體實例化的過程中發生了什么呢?大致可以劃分為以下幾個步驟:
(1) 創建一個空對象 son {}
(2) 為 son 準備原型鏈連接 son.__proto__ = Father.prototype
(3) 重新綁定this,使構造函數的this指向新對象 Father.call(this)
(4) 為新對象屬性賦值 son.name
(5) 返回this return this
,此時的新對象就擁有了構造函數的方法和屬性了
構造函數的方法分為兩種,第一種為在函數內部直接定義的方法,第二種為通過原型添加的方法;
//函數內部直接定義的方法let Father = function(){ this.read = function(){ console.log("我是內部定義的read方法!"); }}//通過原型鏈添加的方法Father.prototype.look = function(){ console.log("我是通過原型鏈定義的look方法!");} //實例化對象進行檢驗let father1 = new Father();let father2 = new Father(); father1.read(); //我是內部定義的read方法!father2.read(); //我是內部定義的read方法!console.log(father1.read === father2.read); //falsefather1.look(); //我是通過原型鏈定義的look方法!father2.look(); //我是通過原型鏈定義的look方法!console.log(father1.look === father2.look); /true
可以發現,函數內部直接定義的方法在每實例化一個新的對象以后,都會給這個方法分配一個新的內存空間,而通過原型添加的方法便會共享一個空間。
不存在內存空間的問題,判斷時看其值是否相同;
let Father = function(name){ this.name = name;}let father1 = new Father("小王"); let father2 = new Father("小紅"); console.log(father1.name === father2.name); //falselet father1 = new Father("小王"); let father2 = new Father("小王"); console.log(father1.name === father2.name); //true
因此我們可以總結一下定義構造函數的基本規則,即公共屬性定義到構造函數里面,公共方法我們放到原型對象身上。
Father.prototype 就是原型,它是一個對象,也可以稱為原型對象。
原型的作用,就是共享方法。
我們通過 Father.prototype.method
可以共享方法,不會反應開辟空間存儲方法。
原型中this的指向是實例。
原型鏈本人感覺是一個對于初學者或者說是部分前端菜雞(例如本人)來說特別難以理解的東西,為了讓下面的部分更容易理解,這里強行先記住以下幾點:
__proto__
是每個對象都有的屬性,prototype是每個函數特有的方法;
每個對象的__proto__
屬性都會指向自身構造函數的prototype;
constructor屬性始終指向創建當前對象的構造函數;
Function.__proto__
=== Function.prototype;
Object.prototype.__proto__
=== null 也就是原型鏈的終點;
原型與原型層層相鏈接的過程即為原型鏈。
對象可以使用構造函數prototype原型對象的屬性和方法,就是因為對象有__proto__
原型的存在每個對象都有__proto__
原型的存在
let Father = function(name){ this.name = name;}let father = new Father("老王");console.log(father.__proto__ === Father.prototype); //true //驗證上述說法中的第二條
結合寫在最前面的幾點,理解上圖應該問題不大了,圖中圈起來的部分就是駭人聽聞的原型鏈。
function Star(name) { this.name = name; //(1)首先看obj對象身上是否有dance方法,如果有,則執行對象身上的方法 this.dance = function () { console.log(this.name + '1'); }}//(2)如果沒有dance方法,就去構造函數原型對象prototype身上去查找dance這個方法。Star.prototype.dance = function () { console.log(this.name + '2');}; //(3)如果再沒有dance方法,就去Object原型對象prototype身上去查找dance這個方法。Object.prototype.dance = function () { console.log(this.name + '3');}; //(4)如果再沒有,則會報錯。let obj = new Star('小紅');obj.dance();
(1)首先看obj對象身上是否有dance方法,如果有,則執行對象身上的方法。
(2)如果沒有dance方法,就去構造函數原型對象prototype身上去查找dance這個方法。
(3)如果再沒有dance方法,就去Object原型對象prototype身上去查找dance這個方法。
(4)如果再沒有,則會報錯。
有兩種添加方法,第一種為上面的寫法,直接通過 構造函數.prototype.方法名 進行添加;第二種為重定義構造函數的prototype,但是此種情況會丟失掉原有的constructor構造器,所以一定要再連接回去,例子如下:
function Star(name) { this.name = name;}Star.prototype = { dance:function(){ console.log("重定義prototype"); }}Star.prototype.constructor = Star;
另外,類似于Array、String這些內置的類是不能這么處理的。
這里就長話短說,首先我們要明確繼承需要繼承哪些東西,在前文中我們提到了定義構造函數的基本規則,即**公共屬性定義到構造函數里面,公共方法我們放到原型對象身上。**我們所需要繼承的東西也不外乎就這二者,公共屬性的繼承可以通過call()或者apply()進行this的指向定義,而公共方法可以通過原型對象的賦值進行處理,因此我們很容易想到如下的方法:
//定義一個父類function Father(name) { this.name = name;}Father.prototype.dance = function () { console.log('I am dancing');};//定義一個子類function Son(name, age) { Father.call(this, name); this.age = age;}//通過賦值的方法連接Son.prototype = Father.prototype;//為子類添加方法Son.prototype.sing = function () { console.log('I am singing');}; let son = new Son('小紅', 100); //此時父類也被影響了console.log(Father.prototype) //{dance: ?, sing: ?, constructor: ?}
很顯然,當我們只想修改子類里面的方法時,顯然上述方法不太合適;因此 我們可以嘗試new一個新的父類出來,代碼如下:
function Father(name) { this.name = name;}Father.prototype.dance = function () { console.log('I am dancing');};function Son(name, age) { Father.call(this, name); this.age = age;}Son.prototype = new Father();Son.prototype.sing = function () { console.log('I am singing');};let son = new Son('小紅', 100);console.log(Father.prototype) //{dance: ?, constructor: ?}
對于以前了解過面向對象編程的程序員來講,上述關于繼承的寫法屬實讓人有些難以接受,因此在es6里面新增了一個語法糖來更方便更便捷地書寫繼承,這里就直接上代碼了;
class Father { constructor(name) { this.name = name; } dance() { console.log("我是" + this.name + ",我今年" + this.age + "歲," + "我在跳舞"); }}class Son extends Father { constructor(name, age) { super(name); this.age = age; } sing() { console.log("我是" + this.name + ",我今年" + this.age + "歲," + "我在唱歌"); }}let obj = new Son('小紅', 19); obj.sing();obj.dance();
分析一下上面代碼,首先一個類(構造函數)里面依舊為兩部分,即公共屬性和公共方法,constructor() 里面存放了該構造函數的公共屬性,后面接著的便是公共方法,extends 關鍵字表示繼承的是哪個類,super() 便是將里面父類里面相應的公共屬性拿出來,這樣看下來便可以將代碼規整許多。
以上是“JavaScript的繼承和原型鏈是什么”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。