摘要:如果你已經對機制已有了解,但是由于兩者對象機制的巨大本質差異,對它和構造函數,實例對象的關系仍有疑惑,本文或許可以解答你的問題。所有的原型對象都會自動獲得一個屬性,這個屬性的值是指向原型所在的構造函數的指針。
幫助面向對象開發者理解關于JavaScript對象機制
本文是以一個熟悉OO語言的開發者視角,來解釋JavaScript中的對象。
對于不了解JavaScript 語言,尤其是習慣了OO語言的開發者來說,由于語法上些許的相似會讓人產生心理預期,JavaScript中的原型繼承機制和class語法糖是讓人迷惑的。
如果你已經對prototype機制已有了解,但是由于兩者對象機制的巨大(本質)差異,對它和構造函數,實例對象的關系仍有疑惑,本文或許可以解答你的問題。
我們看下面的代碼,可以看出和OO語言相比,語法上也有很大分別:
// 定義一個類 class Foo { constructor() { this.a = "a"; } } //實例化對象 const foo = new Foo(); //定義原型的屬性 Foo.prototype.b = "b"; //實例可以訪問屬性 foo.b // "b" //修改原型的屬性 Foo.prototype.b= "B"; //實例屬性值沒有被修改 foo.b // "b"
類已經定義了怎么還能修改呢?prototype又是什么?
不存在面向對象對于熟悉了面向對象的開發者而言JS中種種非預期操作的存在,都是因為JavaScript中根本沒有面向對象的概念,只有對象,沒有類。
即使ES6新添了class語法,不意味著JS引入了面向對象,只是原型繼承的語法糖。
原型是什么什么是原型?如果說類是面向對象語言中對象的模版,原型就是 JS中創造對象的模版。
在面向類的語言中,實例化類,就像用模具制作東西一樣。實例化一個類就意味著“把類的形態復制到物理對象中”,對于每一個新實例來說都會重復這個過程。
但是在JavaScript中,并沒有類似的復制機制。你不能創建一個類的多個實例,只能創建多個對象,它們[[Prototype]]關聯的是同一個對象。
//構造函數 function Foo(){ } //在函數的原型上添加屬性 Foo.prototype.prototypeAttribute0 = {status: "initial"}; const foo0 = new Foo(); const foo1 = new Foo(); foo0.prototypeAttribute0 === foo1.prototypeAttribute0 //true對象、構造函數和原型的關系
當我們創建一個新對象的時候,發生了什么,對象、構造函數和原型到底什么。
先簡單地概括:
原型用于定義共享的屬性和方法。
構造函數用于定義實例屬性和方法,僅負責創造對象,與對象不存在直接的引用關系。
我們先不用class語法糖,這樣便于讀者理解和暴露出他們之間真正的關系。
// 先創建一個構造函數 定義原型的屬性和方法 function Foo() { this.attribute0 = "attribute0"; }
當創建了一個函數,就會為該函數創建一個prototype屬性,它指向函數原型。
所有的原型對象都會自動獲得一個constructor屬性,這個屬性的值是指向原型所在的構造函數的指針。
現在定義原型的屬性和方法
Foo.prototype.prototypeMethod0 = function() { console.log("this is prototypeMethod0"); } Foo.prototype.prototypeAttribute0 = "prototypeAttribute0";
好了,現在,新建一個對象,
const foo = new Foo(); foo.attribute0 // "attribute0" foo.prototypeAttribute0 //"prototypeAttribute0" foo.prototypeMethod0() // this is prototypeMethod0
它擁有自己的實例屬性attribute0,并且可以訪問在原型上定義的屬性和方法,他們之間的引用關系如圖所示。
當調用構造函數創建實例后,該實例的內部會包含一個指針(內部對象),指向構造函數的原型對象。
當讀取實例對象的屬性時,會在實例中先搜尋,沒有找到,就會去原型鏈中搜索,且總是會選擇原型鏈中最底層的屬性進行訪問。
對象的原型可以通過__proto__在chrome等瀏覽器上訪問。
__proto__是對象的原型指針,prototype是構造函數所對應的原型指針。
語法糖做了什么ES6推出了class語法,為定義構造函數和原型增加了便利性和可讀性。
class Foo { constructor(){ this.attribute0 = "attribute0"; } prototypeMethod0(){ console.log("this is prototypeMethod0") } } /* 相當于下面的聲明*/ function Foo() { this.attribute0 = "attribute0"; } Foo.prototype.prototypeMethod0 = function() { console.log("this is prototypeMethod0") }
class中的constractor相當于構造函數,而class中的方法相當于原型上的方法。、
值得注意的特性 屬性屏蔽 —— 避免實例對象無意修改原型看這段代碼,思考輸出的結果。
class Foo { prototypeMethod0(){ console.log("this is prototypeMethod0") } } const foo0 = new Foo(); const foo1 = new Foo(); foo0.prototypeMethod0 === foo0.__proto__.prototypeMethod0 // true foo0.prototypeMethod0 = () => console.log("foo0 method"); foo0.prototypeMethod0(); //?? foo1.prototypeMethod0(); //?? foo0.prototypeMethod0 === foo0.__proto__.prototypeMethod0 // ??
輸出的結果是
foo0.prototypeMethod0(); // foo0 method foo1.prototypeMethod0(); // this is prototypeMethod0 foo0.prototypeMethod0 === foo0.__proto__.prototypeMethod0 // false
我們知道對象(即便是原型對象),都是運行時的。
創建之初,foo本身沒有prototypeMethod0這個屬性,訪問foo0.prototypeMethod0將會讀取foo0.__proto__.prototypeMethod0。
直接修改foo0.prototypeMethod0沒有改變__proto__上的方法原因是存在屬性屏蔽。
現在的情況是:想要修改foo0.prototypeMethod0,prototypeMethod0在foo中不存在而在上層(即foo.__proto__中存在),并且這不是一個特殊屬性(如只讀)。
那么會在foo中添加一個新的屬性。
這便是為什么直接修改卻沒有影響__proto__的原因。
小結再溫習一遍這些定義:
原型用于定義共享的屬性和方法。
構造函數用于定義實例屬性和方法,僅負責創造對象,與對象不存在直接的引用關系。
__proto__是對象的原型指針,prototype是構造函數的原型指針。
在解釋原型作用的文章或書籍中,我們會聽到繼承這樣的術語,其實更準確地,委托對于JavaScript中的對象模型來說,是一個更合適的術語。
委托行為意味著某些對象在找不到屬性或者方法引用時會把這個請求委托給另一個對象。對象之間的關系不是復制而是委托。
參考
《JavaScript高級程序設計》
《你不知道的JavaScript》
本文僅供解惑,要在腦袋里形成系統的概念,還是要看書呀。
有疑問歡迎大家一起討論。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/108193.html
摘要:主線程在啟動程序時被創建,用于執行函數。用戶自主創建的若干進程相對于主線程而言就是子線程。子線程和主線程都是獨立的運行單元,各自的執行互不影響,因此能夠并發執行。這就是的異步機制了。 某天突然寫了個方法要從后臺調用數據,顯示在前臺頁面,但是輸出結果總是空undefined,得不到數據。多方找資料才發現,原來是入了JS異步的坑。 我們常常聽到單線程、多線程、同步、異步這些概念,那么這些東...
摘要:轉行前端有哪些疑慮在人生的抉擇處,尋求一些別人的經驗和總結,無可厚非,但是決定了就一定要堅定的走下去,謹慎是為了更好的堅持,而不是放棄的理由。寫在前面這里前后端指的是開發的前后端。 轉行前端有哪些疑慮? 在人生的抉擇處,尋求一些別人的經驗和總結,無可厚非,但是決定了就一定要堅定的走下去,謹慎是為了更好的堅持,而不是放棄的理由。寫在前面:這里前后端指的是web開發的前后端。1、前端崗位需...
摘要:轉行前端有哪些疑慮在人生的抉擇處,尋求一些別人的經驗和總結,無可厚非,但是決定了就一定要堅定的走下去,謹慎是為了更好的堅持,而不是放棄的理由。寫在前面這里前后端指的是開發的前后端。 轉行前端有哪些疑慮? 在人生的抉擇處,尋求一些別人的經驗和總結,無可厚非,但是決定了就一定要堅定的走下去,謹慎是為了更好的堅持,而不是放棄的理由。寫在前面:這里前后端指的是web開發的前后端。1、前端崗位需...
閱讀 2797·2023-04-25 23:08
閱讀 1583·2021-11-23 09:51
閱讀 1564·2021-10-27 14:18
閱讀 3115·2019-08-29 13:25
閱讀 2831·2019-08-29 13:14
閱讀 2895·2019-08-26 18:36
閱讀 2193·2019-08-26 12:11
閱讀 811·2019-08-26 11:29