# JavaScript的原型、原型鏈、原型繼承 ## [[Prototype]] 又是什麼? 和 __proto__ 的差別是什麼? [[Prototype]] 是在 JavaScript 中物件的特殊隱藏屬性,這個屬性對應到的就是該物件的原型,但因[[Prototype]] 為內部屬性無法直接訪問到,因此可以透過__proto__、Object.getPrototypeOf()的訪問原型 ```javascript //Person 是一個建構函式 function Person(name) { this.name = name; console.log(this); } //透過 Person 建構函式,創建了一個 ming 物件 const ming = new Person("小明"); //ming物件可以透過 __proto__ 方法訪問到它的原型 console.log(ming.__proto__); //建構函式透過prototype 方法與實例指向同個原型 console.log(Person.prototype); //答案true,建構函式透過prototype 方法與實例指向同個原型 console.log(Person.prototype === ming.__proto__); console.log(Object.getPrototypeOf( Person ) === Function.prototype); // true //答案true,或是使用 Object.getPrototypeOf(ming) console.log(Person.prototype === Object.getPrototypeOf(ming)); console.log(Person.__proto__ === Function.prototype); //答案false console.log(ming.__proto__ === Function.prototype ); console.log(Person.prototype === Function.prototype); ``` ## __proto__ 屬性和 prototype 屬性的差別是什麼? - __proto__是每個物件的一個隱藏屬性,每個物件可以由__proto__訪問到它的原型 - 每一個「房子(實例)」都有一條查看上層工具箱的路徑 - 在 JS 裡,「所有函式都是 Function 的實例」 - prototype 是存在於所有構造函式中的一個屬性,構造函式可藉由這個屬性訪問原型,但這並 "不" 代表這個 prototype 屬性是Person本身的原型,請看下面範例~ - 給「藍圖(建構函式)」附上的工具箱 - prototype 是建構函式才有的屬性,普通物件沒有這個屬性 ## Person.prototype與Function.prototype指向同個原型嗎? Person的prototype屬性不等於Function的prototype屬性,在這Person.prototype所指向的是實例的原型 ```javascript //false console.log(Person.prototype === Function.prototype); ``` 但如果將Peson屬性改為__proto__,此時結果為true,由此可知Person函式繼承自Function原型,因此它的__proto__才會指向Function.prototype ```javascript //true console.log(Person.__proto__ === Function.prototype); ``` ## 原型鏈 (prototype chain) 是什麼 ? 當你在 JavaScript 中使用一個物件來存取屬性或方法時,如果這個物件本身沒有該屬性,它就會沿著一條「路徑」往上找,這條路徑就是原型鏈 一句話解釋原型鏈: >物件訪問 [[Prototype]](也就是__proto__)往上一層查找屬性或方法的機制。 ```javascript //true //原型鏈的終點會是null console.log(ming.__proto__.__proto__.__proto__ === null); ``` ## 什麼是原型繼承 (Prototypal inheritance)? ming 本身並沒有 run 方法這個屬性,但它可以透過原型鏈(__proto__指向 Person.prototype)繼承來使用這個方法,當我們在 Person.prototype 上定義了 run 方法後,ming 雖然沒有自己的 run,但仍然可以呼叫,這就是透過原型繼承來取得方法的實例 ```javascript // Person 是一個構造函式 function Person(name) { this.name = name; } // 透過 Person 建構函式,創建了一個 ming 對象 const ming = new Person("小明"); // 在 Person.prototype 上定義 run 方法 Person.prototype.run = function () { console.log(`${this.name}可以跑`); }; // 查看 Person 的共享原型 console.dir(Person.prototype); // ming 可使用這個方法 ming.run(); ``` ## 建構函式的本身是誰的實例?它的原型是誰? Dog.prototype(用來給實例繼承的原型)是個物件,它的原型是 Object.prototype ```javascript // Dog.prototype 本身也是一個物件,所以它的原型是 Object.prototype console.log(typeof Dog.prototype); // 'object' console.log(Dog.prototype.__proto__ === Object.prototype); // true ``` Dog(建構函式)的原型是 Function.prototype ```javascript console.log(Dog.__proto__ === Function.prototype); // true ``` ## 建構函式 **函式建構子** - 這是一個「函式建構子」的寫法,慣例上函式名稱會大寫(Person),用來表示它是拿來創建物件用的 - new Person("小明") 開始執行,創建一個新的空物件 - 新物件綁定到 this - 執行 this.name = name - 最後自動 return obj ```javascript function Person(name) { this.name = name; console.log(this); } const ming = new Person("小明"); console.log(ming); ``` **Dog 函式是一份藍圖或設計圖** 👉 就像建築的設計圖,定義了每個實體該有哪些屬性(如:name、color)。 **new Dog(...) 是根據這份藍圖打造出來的具體東西** 👉 每次使用 `new` 都會產生一個新的「物件實體」,就像依照設計圖蓋出一棟新房子或養出一隻新狗。 **使用 new Dog(...) 建立的物件會繼承 Dog.prototype 上的所有方法** 👉 這些方法就像是工具箱裡的工具,每個物件都可以共用,避免每次都重複定義。 **Dog.prototype 就像是藍圖附帶的共用工具箱** 👉 設計圖不僅規範結構,也提供了一套通用的操作工具(如:`run()` 方法)。 👉 Dog.prototype 本身就是一個物件,所以它的 __proto__ 指向 Object.prototype **this 代表目前正在建構的那個實體(這一棟房子/這一隻狗)** 👉 當你在 `Dog` 函式裡寫 `this.name = name;`,意思就是:把 `name` 這個屬性裝到「這個實體」身上。 ```javascript function Dog(name, color) { this.name = name; this.color = color; } Dog.prototype.run = function(){ return this.name + '可以跑'; }; const 小白 = new Dog("小白", "白色"); const 小黃 = new Dog("小黃", "黃色"); console.log(小白.run()); console.log(Dog); ``` **class建構子** class建構子也可達成像上述一樣的效果 ```javascript class Dog { constructor(name, color) { this.name = name; this.color = color; } run() { console.log(`${this.name}可以跑`); } } const 小白 = new Dog("小白", "白色"); const 小黃 = new Dog("小黃", "黃色"); 小白.run(); console.log(Dog); ``` **類別繼承(class & extends)** - Animal 是一個基底類別(父類別) - 它代表所有動物共通的特性(這裡是 type 屬性 + walk 方法 ```javascript class Animal { constructor(type) { this.type = type || "都是人"; // 若沒傳 type,預設為 "都是人" } walk() { console.log(`${this.name}可以走`); } } class Dog { constructor(name, color) { this.name = name; this.color = color; } run() { console.log(`${this.name}可以跑`); } } class Cat extends Animal { constructor(name, color) { super("貓"); // 呼叫 Animal 的 constructor 並傳入 type = "貓" this.name = name; this.color = color; } meow() { console.log(`${this.name}喵喵叫`); } } ``` **Dog 是獨立的類別,沒有繼承 Animal** - 這個類別和 Animal 沒有繼承關係 - Dog 實例沒有 walk() 方法 ```javascript class Dog { constructor(name, color) { this.name = name; this.color = color; } run() { console.log(`${this.name}可以跑`); } } ``` **Cat 類別繼承 Animal** - Cat 類別透過 extends Animal 繼承了 Animal 的 type 屬性與 walk() 方法 - super("貓") 呼叫父類別的建構子,把 type 設成 "貓" - 額外設定自己的 name 和 color 屬性 ```javascript class Cat extends Animal { constructor(name, color) { super("貓"); // 呼叫 Animal 的 constructor 並傳入 type = "貓" this.name = name; this.color = color; } meow() { console.log(`${this.name}喵喵叫`); } } ``` 執行結果 ```javascript const 小白 = new Dog("小白", "白色"); 小白.run(); // 小白可以跑 const 小喵 = new Cat("小喵", "黃色"); 小喵.meow(); // 小喵喵喵叫 小喵.walk(); // 小喵可以走 console.log(小喵); // 印出 Cat 實例,含 name, color, type ``` **參考資料** {%preview https://www.explainthis.io/zh-hant/swe/most-common-js-prototype-questions %} {%preview https://javascript.alphacamp.co/prototype-prototype-chain.html %}