Kodokon kodokon.com

Overload, this ที่กำหนด type และ variance

เข้าใจการแก้ปัญหา overload, pseudo-parameter this และกฎ variance ที่ควบคุมการกำหนดค่าได้ (assignability) ของฟังก์ชัน

10 นาที · 3 คำถาม

เปิดบทเรียนนี้ใน Kodokon

ฟังก์ชันแบบ overload เปิดเผยรายการ signature สาธารณะตามด้วย signature ของ implementation เพียงตัวเดียว จุดสำคัญในสเปก: ตัวหลังนั้น มองไม่เห็นจากภายนอก; เรียกใช้ได้เฉพาะ overload ที่ประกาศไว้เท่านั้น การแก้ปัญหาจะไล่ไปตาม signature จากบนลงล่าง และเก็บตัวแรกที่เข้ากันได้: จงวาง signature ที่เฉพาะเจาะจงที่สุดไว้ก่อนเสมอ ไม่เช่นนั้นมันจะไม่มีวันถูกเลือก

TYPESCRIPT
function parse(input: string): number;
function parse(input: string[]): number[];
function parse(input: string | string[]) {
  return Array.isArray(input)
    ? input.map((s) => s.length)
    : input.length;
}

const single = parse("abc");      // number
const many = parse(["a", "bb"]);  // number[]
การเรียกแต่ละครั้งแก้ไปยัง overload ที่แม่นยำ

TypeScript ให้คุณประกาศ pseudo-parameter this ในตำแหน่งแรกได้ มันถูก ลบทิ้งทั้งหมด จาก JavaScript ที่ปล่อยออกมา: มันเป็นข้อจำกัดแบบ static ล้วน ๆ บนบริบทการเรียก (call context) ซึ่งจะถูกตรวจสอบเมื่อเปิดใช้งาน noImplicitThis ยูทิลิตี ThisParameterType และ OmitThisParameter ให้คุณสกัดหรือลบมันออกได้ ซึ่งจำเป็นอย่างยิ่งสำหรับการกำหนด type ให้กับฟังก์ชันที่ตั้งใจจะใช้กับ call, apply หรือ bind

TYPESCRIPT
function greet(this: { name: string }, msg: string) {
  return `${msg}, ${this.name}`;
}

type Ctx = ThisParameterType<typeof greet>;
// { name: string }
type Fn = OmitThisParameter<typeof greet>;
// (msg: string) => string

const user = { name: "Ada", greet };
user.greet("Hello"); // "Hello, Ada"

// greet("Hi"); // error: missing this context
พารามิเตอร์ this จำกัดจุดที่เรียก ไม่ใช่ JS ที่ปล่อยออกมา

Variance อธิบายว่าความสามารถในการกำหนดค่า (assignability) ส่งผ่านตัวสร้าง type (type constructor) อย่างไร สำหรับฟังก์ชัน return type เป็น covariant และเมื่อเปิด strictFunctionTypes พารามิเตอร์เป็น contravariant: ฟังก์ชันที่รับ type กว้างกว่าสามารถแทนที่ฟังก์ชันที่รับ type แคบกว่าได้ แต่ไม่ใช่ในทางกลับกัน มีข้อยกเว้นที่จงใจอย่างหนึ่ง: พารามิเตอร์ของ method ที่ประกาศด้วยไวยากรณ์แบบย่อยังคงเป็น bivariant ซึ่งเป็นการประนีประนอมเชิงประวัติศาสตร์ ที่ถ้าไม่มีมัน Array<Dog> ก็จะกำหนดค่าให้ Array<Animal> ไม่ได้ ตั้งแต่ 4.7 คุณสามารถระบุ variance ที่คาดหวังด้วย in และ out เช่น interface Producer<out T> และคอมไพเลอร์จะตรวจสอบมัน

TYPESCRIPT
interface Animal { name: string }
interface Dog extends Animal { bark(): void }

declare let f: (a: Dog) => void;
declare let g: (a: Animal) => void;

f = g; // OK: g accepts a wider type, that is safe
// g = f; // error with strictFunctionTypes

interface Sink { accept(a: Animal): void }
interface DogSink { accept(a: Dog): void }
declare let s: Sink;
declare let d: DogSink;

d = s; s = d; // both compile: bivariance!
property ฟังก์ชันแบบเข้มงวด, method แบบ bivariant

ทดสอบความรู้

ตรวจสอบว่าคุณจำประเด็นสำคัญของบทเรียนนี้ได้ครบถ้วน

  1. เมื่อเรียกฟังก์ชันแบบ overload คอมไพเลอร์เลือก signature ตัวใด?
    • signature ที่กว้างที่สุด
    • signature ตัวแรกที่เข้ากันได้ตามลำดับการประกาศ
    • signature ของ implementation
    • union ของทุก signature
  2. เกิดอะไรขึ้นกับพารามิเตอร์ this ของฟังก์ชันใน JavaScript ที่ปล่อยออกมา?
    • มันกลายเป็น argument ตัวแรกที่แท้จริง
    • มันหายไป: มันเป็น annotation แบบ static ล้วน ๆ
    • มันถูกแทนที่ด้วยการเรียก bind
    • มันทำให้เกิด runtime error หากขาดหายไป
  3. ด้วย strictFunctionTypes ไวยากรณ์แบบใดได้ประโยชน์จากการตรวจสอบพารามิเตอร์แบบ contravariant ที่เข้มงวด?
    • method แบบไวยากรณ์ย่อ เช่น m(a: X): void
    • property ที่กำหนด type เป็นฟังก์ชัน เช่น m: (a: X) => void
    • ทั้งสองไวยากรณ์
    • ไม่มีทั้งสอง