iT邦幫忙

0

JavaScript的原型、原型鏈、原型繼承

  • 分享至 

  • xImage
  •  

[[Prototype]] 又是什麼? 和 proto 的差別是什麼?

[[Prototype]] 是在 JavaScript 中物件的特殊隱藏屬性,這個屬性對應到的就是該物件的原型,但因[[Prototype]] 為內部屬性無法直接訪問到,因此可以透過__proto__、Object.getPrototypeOf()的訪問原型

//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所指向的是實例的原型

//false
console.log(Person.prototype === Function.prototype);

但如果將Peson屬性改為__proto__,此時結果為true,由此可知Person函式繼承自Function原型,因此它的__proto__才會指向Function.prototype

//true
console.log(Person.__proto__ === Function.prototype);

原型鏈 (prototype chain) 是什麼 ?

當你在 JavaScript 中使用一個物件來存取屬性或方法時,如果這個物件本身沒有該屬性,它就會沿著一條「路徑」往上找,這條路徑就是原型鏈
一句話解釋原型鏈:

物件訪問 [[Prototype]](也就是__proto__)往上一層查找屬性或方法的機制。

//true 
//原型鏈的終點會是null
console.log(ming.__proto__.__proto__.__proto__ === null);

什麼是原型繼承 (Prototypal inheritance)?

ming 本身並沒有 run 方法這個屬性,但它可以透過原型鏈(__proto__指向 Person.prototype)繼承來使用這個方法,當我們在 Person.prototype 上定義了 run 方法後,ming 雖然沒有自己的 run,但仍然可以呼叫,這就是透過原型繼承來取得方法的實例

// 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

// Dog.prototype 本身也是一個物件,所以它的原型是 Object.prototype
console.log(typeof Dog.prototype); // 'object'
console.log(Dog.prototype.__proto__ === Object.prototype); // true

Dog(建構函式)的原型是 Function.prototype

console.log(Dog.__proto__ === Function.prototype); // true 

建構函式

函式建構子

  • 這是一個「函式建構子」的寫法,慣例上函式名稱會大寫(Person),用來表示它是拿來創建物件用的
  • new Person("小明") 開始執行,創建一個新的空物件
  • 新物件綁定到 this
  • 執行 this.name = name
  • 最後自動 return obj
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 這個屬性裝到「這個實體」身上。

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建構子也可達成像上述一樣的效果

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 方法
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() 方法
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 屬性
class Cat extends Animal {
  constructor(name, color) {
    super("貓"); // 呼叫 Animal 的 constructor 並傳入 type = "貓"
    this.name = name;
    this.color = color;
  }
  meow() {
    console.log(`${this.name}喵喵叫`);
  }
}

執行結果

const 小白 = new Dog("小白", "白色");
小白.run(); // 小白可以跑

const 小喵 = new Cat("小喵", "黃色");
小喵.meow(); // 小喵喵喵叫
小喵.walk(); // 小喵可以走
console.log(小喵); // 印出 Cat 實例,含 name, color, type

參考資料
(https://www.explainthis.io/zh-hant/swe/most-common-js-prototype-questions)
(https://javascript.alphacamp.co/prototype-prototype-chain.html)


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言