JavaScript中的原型到底該如何理解?

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JavaScript作爲一個基於原型的OOP,和我們熟知的基於類的面向對象編程語言有很大的差異。如果不理解其中的本質含義,則無法深入理解JavaScript的諸多特性,以及由此產生的諸多“坑”。"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"原型"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"概念"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在討論“原型”的概念之前,我們先來討論一下“類”,也就是Java、C++等語言所使用的概念。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在基於類的編程語言中,都要先抽象出一個“類”,用來統一表示同一種對象。然後用這個抽象類創建出一個個實例(泛化),也就是對象object。最後,類和類之間通過組合、繼承等特性共建出一個可以互動的系統,從而用這套人爲創建的系統來模擬、操縱現實中的物理世界。它的三大特性爲:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"封裝"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"繼承"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"多態"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然而,在原型概念中,有很多不同之處。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基於原型的編程範式提倡程序設計者關注實例對象的一系列行爲,然後根據行爲的不同劃分出不同的原型,而不是事先抽象出一個類,再關注具體的對象。它最大的特點是可以動態修改對象的行爲,具有高度靈活性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"如果把基於類的對象稱爲“自上而下”式的頂層設計,那麼基於原型的對象則可以被稱爲“自下而上”式的動態演化。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這就像專業人士可能喜歡在看到老虎的時候,喜歡用貓科豹屬豹亞種來描述它,但是對一些不那麼正式的場合,“大貓”可能更爲接近直觀的感受一些(插播一個冷知識:比起老虎來,美洲獅在歷史上相當長時間都被劃分爲貓科貓屬,所以性格也跟貓更相似,比較親人)。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基於原型的面向對象系統通過“複製”的方式來創建新對象,這實際上就是創建一個全新的對象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原型系統的“複製”操作有兩種實現思路:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"並不是真正的複製一個對象,而是使新對象持有一個原型的引用;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"切實的複製一個對象,複製對象和被複制對象再無任何關聯。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"zerowidth"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JavaScript選擇了前一種複製方式。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"JavaScript中的原型"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"拋開一切複雜的語法規則,JavaScript的原型系統的實質只有兩條:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果對象都有私有字段[[prototype]],那它就是對象的原型;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"讀一個屬性,如果對象自身沒有,則繼續訪問對象的原型,直到找到或者原型爲空爲止。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從 ES6 以來,JavaScript提供了一系列內置函數,以便更直接地訪問操縱原型,分別爲:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Object.create"}]},{"type":"text","text":" 根據指定的原型創建新對象,原型可以是"},{"type":"codeinline","content":[{"type":"text","text":"null"}]},{"type":"text","text":";"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Object.getPrototypeOf"}]},{"type":"text","text":" 獲得一個對象的原型;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Object.setPrototypeOf"}]},{"type":"text","text":" 設置一個對象的原型。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"利用這三個方法,我們完全可以拋開基於類的面向對象思維,用原型的概念實現抽象和複用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"// 作爲“原型”的貓\nvar cat = {\n say(){\n console.log(\"meow~\");\n },\n jump(){\n console.log(\"jump\");\n }\n}\n\n// 作爲“原型”的老虎\nvar tiger = Object.create(cat, {\n say:{\n writable:true,\n configurable:true,\n enumerable:true,\n value:function(){\n console.log(\"roar!\");\n }\n }\n})\n\n\nvar anotherCat = Object.create(cat);\n\nanotherCat.say();\n// meow~\n\nvar anotherTiger = Object.create(tiger);\n\nanotherTiger.say();\n// jump"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"早期版本中的原型"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"ES3"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在ES3之前的版本中,“類”的定義只是對象的一個私有屬性 [[class]],語言標準爲內置類型Number、String、Date等指定了[[class]]屬性,以表示它們的類。語言使用者唯一可以訪問[[class]]屬性的方式是"},{"type":"codeinline","content":[{"type":"text","text":" Object.prototype.toString"}]},{"type":"text","text":" 。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"var o = new Object;\nvar n = new Number;\nvar s = new String;\nvar b = new Boolean;\nvar d = new Date;\n\nconsole.log([o, n, s, b, d].map(v => Object.prototype.toString.call(v))); \n// 0: \"[object Object]\"\n// 1: \"[object Number]\"\n// 2: \"[object String]\"\n// 3: \"[object Boolean]\"\n// 4: \"[object Date]\""}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"ES5"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從ES5開始,[[class]] 私有屬性被 "},{"type":"codeinline","content":[{"type":"text","text":"Symbol.toStringTag"}]},{"type":"text","text":" 代替,"},{"type":"codeinline","content":[{"type":"text","text":"Object.prototype.toString"}]},{"type":"text","text":" 的意義從命名上不再跟 "},{"type":"codeinline","content":[{"type":"text","text":"class"}]},{"type":"text","text":" 相關。我們甚至可以自定義 "},{"type":"codeinline","content":[{"type":"text","text":"Object.prototype.toString"}]},{"type":"text","text":" 的行爲,以下代碼展示了使用Symbol."},{"type":"codeinline","content":[{"type":"text","text":"toStringTag"}]},{"type":"text","text":"來自定義 "},{"type":"codeinline","content":[{"type":"text","text":"Object.prototype.toString"}]},{"type":"text","text":" 的行爲:"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"var o = { [Symbol.toStringTag]: \"MyObject\" }\nconsole.log(o + \"\");\n// [object MyObject]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"new"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"new"}]},{"type":"text","text":" 運算符是JavaScript面向對象體系中非常重要的一員。在ES6之前,"},{"type":"codeinline","content":[{"type":"text","text":"new"}]},{"type":"text","text":" 和函數基本上撐起了JavaScript的對象系統。"},{"type":"codeinline","content":[{"type":"text","text":"new"}]},{"type":"text","text":" 運算接受一個構造器和一組參數,實際上做了這些事:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以構造器的 "},{"type":"codeinline","content":[{"type":"text","text":"prototype"}]},{"type":"text","text":" 屬性(注意與私有字段[[prototype]]的區分)爲原型,創建新對象;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"將 "},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":" 和調用參數傳給構造器,執行;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果構造器返回的是對象,則返回;否則返回第一步創建的對象。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實際上,它提供了兩種方式用於操作對象的屬性,其一是在構造器上添加屬性;其二實在構造器的"},{"type":"codeinline","content":[{"type":"text","text":"prototype"}]},{"type":"text","text":"上添加屬性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"function c1(){\n this.p1 = 1;\n this.p2 = function(){\n console.log(this.p1);\n }\n} \nvar o1 = new c1;\no1.p2();\n\n\nfunction c2(){\n}\nc2.prototype.p1 = 1;\nc2.prototype.p2 = function(){\n console.log(this.p1);\n}\n\nvar o2 = new c2;\no2.p2();"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在沒有"},{"type":"codeinline","content":[{"type":"text","text":"Object.create"}]},{"type":"text","text":"、 "},{"type":"codeinline","content":[{"type":"text","text":"Object.setPrototypeOf"}]},{"type":"text","text":" 的早期版本中,"},{"type":"codeinline","content":[{"type":"text","text":"new"}]},{"type":"text","text":" 運算是唯一一個可以指定[[prototype]]的方法(當時的mozilla提供了私有屬性"},{"type":"text","marks":[{"type":"strong"}],"text":"proto"},{"type":"text","text":";但在目前,大多數瀏覽器已經支持這一私有屬性__proto__)。所以,當時已經有人試圖用它來代替後來的"},{"type":"codeinline","content":[{"type":"text","text":" Object.create"}]},{"type":"text","text":",我們甚至可以用它來實現一個 "},{"type":"codeinline","content":[{"type":"text","text":"Object.create"}]},{"type":"text","text":" 的不完整的polyfill:"}]},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":"Object.create = function(prototype) {\n var cls = function(){}\n cls.prototype = prototype;\n return new cls;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這段代碼創建了一個空函數作爲類,並把傳入的原型掛在了它的 "},{"type":"codeinline","content":[{"type":"text","text":"prototype"}]},{"type":"text","text":" 上,最後創建了一個它的實例,根據 "},{"type":"codeinline","content":[{"type":"text","text":"new"}]},{"type":"text","text":" 的行爲,這將產生一個以傳入的第一個參數爲原型的對象。這個函數無法做到與原生的 "},{"type":"codeinline","content":[{"type":"text","text":"Object.create"}]},{"type":"text","text":" 一致,一個是不支持第二個參數,另一個是不支持 "},{"type":"codeinline","content":[{"type":"text","text":"null"}]},{"type":"text","text":" 作爲原型,所以放到今天意義已經不大了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"ES6中的類class"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ES6中, "},{"type":"codeinline","content":[{"type":"text","text":"class"}]},{"type":"text","text":" 成爲JavaScript官方支持的關鍵字,可以像其他語言一樣定義類,並且還支持 "},{"type":"codeinline","content":[{"type":"text","text":"extends"}]},{"type":"text","text":" 關鍵字來實現繼承,"},{"type":"codeinline","content":[{"type":"text","text":"setter"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"getter"}]},{"type":"text","text":" 也支持。至此,基於類的編程範式成爲JavaScript官方支持的編程範式。如下所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"class Rectangle {\n constructor(height, width) {\n this.height = height;\n this.width = width;\n }\n // Getter\n get area() {\n return this.calcArea();\n }\n set area(area) {\n this.area = area;\n }\n // Method\n calcArea() {\n return this.height * this.width;\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實現繼承的用法:"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"class Animal { \n constructor(name) {\n this.name = name;\n }\n \n speak() {\n console.log(this.name + ' makes a noise.');\n }\n}\n\nclass Dog extends Animal {\n constructor(name) {\n super(name); // call the super class constructor and pass in the name parameter\n }\n\n speak() {\n console.log(this.name + ' barks.');\n }\n}\n\nlet d = new Dog('Mitzie');\nd.speak(); // Mitzie barks."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以從此以後,我們都應該使用 "},{"type":"codeinline","content":[{"type":"text","text":"class"}]},{"type":"text","text":" 關鍵字正正經經地定義類,而不是用各種怪異的手法模擬對象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"原型鏈"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JavaScript的每個對象都有一個指向其原型對象的關係鏈,當試圖訪問一個屬性時,它不僅僅在對象上搜尋,而且還會在它的原型上搜尋,以及原型的原型上搜尋,直到找到屬性或者達到此鏈條的頂端,這就是JavaScript的原型鏈,用來實現"},{"type":"text","marks":[{"type":"strong"}],"text":"繼承"},{"type":"text","text":"的核心邏輯。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"函數對象和構造器對象"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除過上面對於JavaScript對象的的一般分類方法,還有另一個角度,就是用對象來模擬函數和構造器。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JavaScript中函數對象的定義是:具有[[call]]私有字段的對象;構造器對象的定義是:具有私有字段[[construct]]的對象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":">JavaScript用對象模擬函數的設計代替了一般編程語言中的函數,它們可以像其它語言的函數一樣被調用、傳參。任何宿主只要提供了“具有[[call]]私有字段的對象”,就可以被 JavaScript 函數調用語法支持。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以在JavaScript中,任何對象只要實現了[[call]],它就是一個函數對象,可以去作爲函數被調用。而如果它能實現[[construct]],它就是一個構造器對象,可以作爲構造器被調用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,還有非常重要的一點:用"},{"type":"codeinline","content":[{"type":"text","text":"function"}]},{"type":"text","text":"關鍵字創建的函數,既是函數(對象),又是構造器(對象)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於宿主和內置對象來說,[[call]](作爲函數被調用)的行爲和[[construct]](作爲構造器被調用)的行爲可能存在些許差異。而用戶使用 "},{"type":"codeinline","content":[{"type":"text","text":"function"}]},{"type":"text","text":" 語法或者"},{"type":"codeinline","content":[{"type":"text","text":"Function"}]},{"type":"text","text":"構造器創建的對象來說,[[call]]和[[construct]]行爲總是一致的。但是,在ES6之後用 "},{"type":"codeinline","content":[{"type":"text","text":"=>"}]},{"type":"text","text":" 語法創建的函數僅僅是函數,它們無法被當作構造器使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":"function f(){\n return 1;\n}\nvar v = f(); //把f作爲函數調用\nvar o = new f(); //把f作爲構造器調用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面這段代碼,它的[[construct]]的執行過程如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"以 "},{"type":"codeinline","content":[{"type":"text","text":"Object.protoype"}]},{"type":"text","text":" 爲原型創建一個新對象;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"以新對象爲 "},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":",執行函數的[[call]];"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"如果[[call]]的返回值是對象,那麼,返回這個對象;否則返回第一步創建的新對象。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"prototype(顯式原型)和_"},{"type":"text","marks":[{"type":"italic"}],"text":"proto"},{"type":"text","text":"_(隱式原型)"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"prototype"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每一個函數在創建之後都會擁有一個名爲"},{"type":"codeinline","content":[{"type":"text","text":"prototype"}]},{"type":"text","text":"的屬性,這個屬性指向函數的原型對象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Note:通過Function.prototype.bind方法構造出來的函數是個例外,它沒有prototype屬性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼,prototype的作用是什麼呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":">ECMAScript does not use classes such as those in C++, Smalltalk, or Java. Instead, objects may be created in various ways including via a literal notation or via constructors which create objects and then execute code that initializes all or part of them by assigning initial values to their properties. Each constructor is a function that has a property named “prototype” that is used to implement prototype-based inheritance and shared properties. Objects are created by using constructors in new expressions; for example, new Date(2009,11) creates a new Date object. ----"},{"type":"link","attrs":{"href":"https://link.zhihu.com/?target=http%3A//www.ecma-international.org/ecma-262/5.1/%23sec-4.2.1","title":""},"content":[{"type":"text","text":"ECMAScript Language Specification"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一句話,prototype用來實現基於原型的繼承與屬性的共享。prototype屬性只有Function對象有。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"_"},{"type":"text","marks":[{"type":"italic"}],"text":"proto"},{"type":"text","text":"_"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"遵循ECMAScript標準,someObject.[[Prototype]] 符號是用於指向 someObject 的原型。從 ECMAScript 6 開始,[[Prototype]] 可以通過 Object.getPrototypeOf() 和 Object.setPrototypeOf() 訪問器來訪問。這個等同於 JavaScript 的非標準但許多瀏覽器實現的屬性 "},{"type":"codeinline","content":[{"type":"text","text":"__proto__"}]},{"type":"text","text":"。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每個實例對象( object )都有一個私有屬性(稱之爲 "},{"type":"codeinline","content":[{"type":"text","text":"__proto__"}]},{"type":"text","text":" )指向它的構造函數的原型對象( "},{"type":"codeinline","content":[{"type":"text","text":"prototype"}]},{"type":"text","text":" )。該原型對象也有一個自己的原型對象( "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"proto"}]},{"type":"text","text":" ) ,層層向上直到一個對象的原型對象爲 "},{"type":"codeinline","content":[{"type":"text","text":"null"}]},{"type":"text","text":"。根據定義,"},{"type":"codeinline","content":[{"type":"text","text":"null"}]},{"type":"text","text":" 沒有原型,並作爲這個原型鏈中的最後一個環節。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也就是說:對每一個對象,"},{"type":"codeinline","content":[{"type":"text","text":"__proto__"}]},{"type":"text","text":"是構成JavaScript對象基於原型鏈的繼承關係的具體實現細節。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要注意的是,**JavaScript的函數function也是對象(Function的實例,也就是函數對象)。所以,function也有了"},{"type":"codeinline","content":[{"type":"text","text":"__proto__"}]},{"type":"text","text":"屬性**,指向Function.prototype。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用下面兩張圖演直觀感受一下原型鏈的真實樣子:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ab/ab4e90ce17e2595114c8974bcfc77509.jpeg","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"牢記兩點:"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - "},{"type":"codeinline","content":[{"type":"text","text":"__proto__"}]},{"type":"text","text":"屬性是對象所獨有的;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - "},{"type":"codeinline","content":[{"type":"text","text":"prototype"}]},{"type":"text","text":"屬性是函數所獨有的;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - 因爲函數也是一種對象,所以同時擁有"},{"type":"codeinline","content":[{"type":"text","text":"__proto__"}]},{"type":"text","text":"屬性和"},{"type":"codeinline","content":[{"type":"text","text":"prototype"}]},{"type":"text","text":"屬性。"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"__proto__"}]},{"type":"text","text":"屬性的作用是當訪問一個對象的屬性時,如果該對象內部不存在這個屬性,那麼就會去它的"},{"type":"codeinline","content":[{"type":"text","text":"__proto__"}]},{"type":"text","text":"所指向的那個對象裏找,一直找,直到"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"proto"}]},{"type":"text","text":"屬性爲null,再往上找就相當於在null上取值,會報錯。通過"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"proto"}]},{"type":"text","text":"屬性將對象的繼承關係連接起來的這條鏈路即原型鏈。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"prototype"}]},{"type":"text","text":"屬性的作用是讓該函數所實例化的對象們都可以找到公用的屬性和方法,即"},{"type":"codeinline","content":[{"type":"text","text":"f1.__proto__ === Foo.prototype"}]},{"type":"text","text":"。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章