← JavaScript Learning Path

JAVASCRIPT V2 · ADVANCED · 130

this / Prototype / Class:Object Model 不是「Class 語法背熟」

61 先讓你會用 class;這一課往下拆 model:property lookup 如何走 prototype chain、method function 如何共享、this 為什麼主要由 call-site 決定,以及 method 被拆出來後 context 為何會消失。

Learning outcomes

1. Why now:Class 很好用,但 bug 往往發生在它下面

class Student {
  summary() {
    return this.name;
  }
}

const amy =
  new Student();

amy.name = "Amy";

const fn = amy.summary;
fn();

如果你只背「class 裡 method 用 this」,你很難解釋 detached method 為何失去原 receiver。

2. Mental model:Property Lookup 沿 Prototype Chain

amy
  own:
    name = "Amy"

  [[Prototype]]
    Student.prototype
      summary()

      [[Prototype]]
        Object.prototype

當 amy.summary 在 own properties 找不到時,engine 會沿 prototype chain 往上找。

3. Worked Example A:Methods 通常共用同一 Function

class Student {
  summary() {
    return this.name;
  }
}

const a =
  new Student();

const b =
  new Student();

console.log(
  a.summary
  === b.summary
);
// true

方法通常在 Student.prototype,不是每個 instance 各複製一份 method function。

4. this 看 Call-Site

const student = {
  name: "Amy",

  show() {
    return this.name;
  }
};

student.show();
// this = student

對一般 method call,點號左側 receiver 會影響 this binding。

5. Worked Example B:Detached Method

const show =
  student.show;

show();

現在 call-site 已不再是 student.show()。在 strict/module context 中 this 可能是 undefined,因此讀 this.name 會失敗。

6. bind:建立固定 this 的新 Function

const boundShow =
  student.show.bind(
    student
  );

boundShow();
// "Amy"

bind 不會立刻執行原 function;它回傳一個新 function,之後呼叫時使用固定 context。

7. Arrow Function:沒有自己的 this

const obj = {
  name: "Amy",

  bad: () => {
    return this.name;
  }
};

Arrow function 的 this 來自 lexical surrounding context;因此它通常不適合拿來當需要 receiver-based this 的 object method。

8. Worked Example C:Arrow 在 Method 裡捕捉外層 this

const student = {
  name: "Amy",

  delayed() {
    setTimeout(
      () => {
        console.log(
          this.name
        );
      },
      0
    );
  }
};

student.delayed();

這裡 arrow callback 使用 delayed method 的 lexical this,因此能保留 student receiver。

9. prototype 不是 class 專屬

function Student(name) {
  this.name = name;
}

Student.prototype.summary =
  function () {
    return this.name;
  };

const amy =
  new Student("Amy");

class syntax 讓 prototype-based construction 更易讀,但底層 method lookup 仍建立在 prototype chain。

10. Common mistakes

A. 認為 this 永遠指向「定義 method 的 object」

一般 function 的 this 看呼叫方式。

B. 把所有 methods 改 arrow

這會改變 this model,也可能讓每個 instance 各持有 function property。

C. 修改 built-in prototype 當便利擴充

可能造成名稱衝突與全域行為污染。

D. 把 prototype chain 當 inheritance tree 的完整替代說法

先理解 property delegation,再談 class inheritance 語法。

11. Debug evidence:印 call-site 與 receiver

console.log(
  student.show
  === show
);
// same function

student.show();
// receiver exists

show();
// receiver removed

Function identity 沒變,呼叫形式變了。這是 detached method bug 的第一個重要 evidence。

12. Guided exercise:修復 callback context

const button = {
  label: "Save",

  click() {
    console.log(
      this.label
    );
  }
};

setTimeout(
  button.click,
  0
);

分別用 wrapper 與 bind 修復,並解釋兩者何時建立 function、this 從哪裡來。

13. Independent exercise:Prototype Lookup Trace

建立 class Course,instance 有 own property title,prototype 有 summary。用 Object.hasOwn 與 Object.getPrototypeOf 證明 title 與 summary 分別在哪一層。

Object.hasOwn(
  course,
  "title"
);

Object.hasOwn(
  course,
  "summary"
);

14. Challenge:Class vs Closure Factory

分別用 class 與 closure factory 做 counter。比較 state visibility、method sharing、private-like state 與 this dependency。沒有唯一勝者;重點是 trade-off。

Project checkpoint:Grade Tracker Course Model

class Course {
  constructor(
    title,
    score
  ) {
    this.title = title;
    this.score = score;
  }

  summary() {
    return (
      this.title
      + ": "
      + this.score
    );
  }
}

把 summary 傳給 array/timer/event 前,必須重新檢查 callback contract 與 this context。

15. Vocabulary / Summary

16. Further reading

Knowledge check · Mastery

  1. 畫出 instance → prototype → Object.prototype。
  2. 解釋 detached method 為何失去 receiver。
  3. 完成 bind/wrapper 修復與 prototype trace。
  4. 完成頁面下方 Executable JS this/prototype tests。