อะไรคือความแตกต่างระหว่างคีย์เวิร์ดส่วนตัวและฟิลด์ส่วนตัวใน TypeScript?


27

ใน TypeScript 3.8+ ความแตกต่างระหว่างการใช้ privateคำหลักเพื่อทำเครื่องหมายสมาชิกส่วนตัวคืออะไร:

class PrivateKeywordClass {
    private value = 1;
}

และใช้#ฟิลด์ส่วนตัวที่เสนอสำหรับ JavaScript :

class PrivateFieldClass {
    #value = 1;
}

ฉันควรจะเลือกอันไหนมากกว่ากัน?


คำตอบ:


43

คำหลักส่วนตัว

คำหลักส่วนตัวใน typescript เป็นเวลารวบรวมคำอธิบายประกอบ มันบอกคอมไพเลอร์ว่าทรัพย์สินควรจะสามารถเข้าถึงได้ภายในชั้นเรียนนั้น:

class PrivateKeywordClass {
    private value = 1;
}

const obj = new PrivateKeywordClass();
obj.value // compiler error: Property 'value' is private and only accessible within class 'PrivateKeywordClass'.

อย่างไรก็ตามการตรวจสอบเวลาการคอมไพล์สามารถข้ามได้ง่ายตัวอย่างเช่นโดยการโยนข้อมูลประเภท:

const obj = new PrivateKeywordClass();
(obj as any).value // no compile error

privateคำหลักก็ยังไม่ได้มีการบังคับใช้ที่รันไทม์

JavaScript ที่ปล่อยออกมา

เมื่อทำการรวบรวม TypeScript ไปยัง JavaScript privateคำหลักนั้นจะถูกลบอย่างง่ายดาย:

class PrivateKeywordClass {
    private value = 1;
}

กลายเป็น:

class PrivateKeywordClass {
    constructor() {
        this.value = 1;
    }
}

จากสิ่งนี้คุณสามารถดูได้ว่าทำไมprivateคำหลักไม่มีการป้องกันรันไทม์ใด ๆ : ใน JavaScript ที่สร้างขึ้นมันเป็นเพียงคุณสมบัติ JavaScript ปกติ

ฟิลด์ส่วนตัว

ฟิลด์ส่วนตัวทำให้มั่นใจได้ว่าคุณสมบัติถูกเก็บเป็นส่วนตัวเมื่อรันไทม์ :

class PrivateFieldClass {
    #value = 1;

    getValue() { return this.#value; }
}

const obj = new PrivateFieldClass();

// You can't access '#value' outside of class like this
obj.value === undefined // This is not the field you are looking for.
obj.getValue() === 1 // But the class itself can access the private field!

// Meanwhile, using a private field outside a class is a runtime syntax error:
obj.#value

// While trying to access the private fields of another class is 
// a runtime type error:
class Other {
    #value;

    getValue(obj) {
        return obj.#value // TypeError: Read of private field #value from an object which did not contain the field
    }
}

new Other().getValue(new PrivateKeywordClass());

TypeScript จะส่งออกข้อผิดพลาดเวลาการคอมไพล์ถ้าคุณลองใช้เขตข้อมูลส่วนตัวนอกคลาส:

เกิดข้อผิดพลาดในการเข้าถึงฟิลด์ส่วนตัว

ฟิลด์ส่วนตัวมาจากข้อเสนอ JavaScriptและทำงานใน JavaScript ปกติ

JavaScript ที่ปล่อยออกมา

หากคุณใช้ฟิลด์ส่วนบุคคลใน TypeScript และกำหนดเป้าหมาย JavaScript รุ่นเก่าสำหรับเอาต์พุตของคุณเช่นes6หรือes2018TypeScript จะพยายามสร้างรหัสที่เลียนแบบพฤติกรรมการทำงานของรันไทม์ของฟิลด์ส่วนตัว

class PrivateFieldClass {
    constructor() {
        _x.set(this, 1);
    }
}
_x = new WeakMap();

หากคุณกำหนดเป้าหมายesnextไว้ TypeScript จะปล่อยฟิลด์ส่วนตัว:

class PrivateFieldClass {
    constructor() {
        this.#x = 1;
    }
    #x;
}

ฉันควรใช้อันไหนดี

ขึ้นอยู่กับสิ่งที่คุณพยายามทำ

privateคำหลักคือการเริ่มต้นที่ดี มันบรรลุสิ่งที่ออกแบบมาเพื่อให้สำเร็จและมีการใช้งานอย่างประสบความสำเร็จโดยนักพัฒนา TypeScript มานานหลายปี และถ้าคุณมี codebase อยู่คุณไม่จำเป็นต้องสลับรหัสทั้งหมดของคุณเพื่อใช้เขตข้อมูลส่วนตัว นี่เป็นเรื่องจริงโดยเฉพาะถ้าคุณไม่ได้กำหนดเป้าหมายesnextเนื่องจาก JS ที่ TS ส่งเสียงสำหรับฟิลด์ส่วนตัวอาจส่งผลกระทบต่อประสิทธิภาพ โปรดทราบว่าเขตข้อมูลส่วนตัวมีความแตกต่างที่ลึกซึ้ง แต่สำคัญอื่น ๆ จากprivateคำหลัก

อย่างไรก็ตามหากคุณต้องการบังคับใช้ความเป็นส่วนตัวของรันไทม์หรือกำลังแสดงผลesnextJavaScript มากกว่าที่คุณควรใช้ฟิลด์ส่วนตัว

นอกจากนี้โปรดทราบว่าข้อตกลงองค์กร / ชุมชนเกี่ยวกับการใช้อย่างใดอย่างหนึ่งจะกลายเป็นวิวัฒนาการเมื่อเขตข้อมูลส่วนบุคคลแพร่หลายมากขึ้นในระบบนิเวศ JavaScript / TypeScript

ความแตกต่างอื่น ๆ ของโน้ต

  • ฟิลด์ส่วนตัวจะไม่ถูกส่งคืนโดยObject.getOwnPropertyNamesและวิธีการที่คล้ายกัน

  • ฟิลด์ส่วนตัวไม่ได้ต่อเนื่องกัน JSON.stringify

  • มีกรณีขอบสำคัญรอบมรดก

    TypeScript เช่นห้ามประกาศคุณสมบัติส่วนตัวในคลาสย่อยที่มีชื่อเดียวกันกับทรัพย์สินส่วนตัวในซูเปอร์คลาส

    class Base {
        private value = 1;
    }
    
    class Sub extends Base {
        private value = 2; // Compile error:
    }

    นี่ไม่เป็นความจริงสำหรับฟิลด์ส่วนตัว:

    class Base {
        #value = 1;
    }
    
    class Sub extends Base {
        #value = 2; // Not an error
    }
  • privateคำหลักทรัพย์สินส่วนตัวโดยไม่ต้องมีการเริ่มต้นจะไม่สร้างการประกาศทรัพย์สินใน JavaScript ที่ปล่อยออกมา:

    class PrivateKeywordClass {
        private value?: string;
        getValue() { return this.value; }
    }

    รวบรวมเพื่อ:

    class PrivateKeywordClass {
        getValue() { return this.value; }
    }

    ในขณะที่ฟิลด์ส่วนตัวสร้างการประกาศคุณสมบัติเสมอ:

    class PrivateKeywordClass {
        #value?: string;
        getValue() { return this.#value; }
    }

    คอมไพล์เป็น (เมื่อกำหนดเป้าหมายesnext):

    class PrivateKeywordClass {
        #value;
        getValue() { return this.#value; }
    }

อ่านเพิ่มเติม:


4

ใช้กรณี: - #เขตข้อมูลส่วนบุคคล

คำนำ:

ความเป็นส่วนตัวรวบรวมและเวลาทำงาน

#- เขตข้อมูลส่วนบุคคลให้ความเป็นส่วนตัวเวลารวบรวมและเวลาทำงานซึ่งไม่ใช่ "แฮ็ก" มันเป็นกลไกในการป้องกันการเข้าถึงสมาชิกออกจากนอกร่างกายชั้นเรียนในทางใด ๆ โดยตรง

class A {
    #a: number;
    constructor(a: number) {
        this.#a = a;
    }
}

let foo: A = new A(42);
foo.#a; // error, not allowed outside class bodies
(foo as any).#bar; // still nope.

การสืบทอดคลาสที่ปลอดภัย

#เขตข้อมูลส่วนตัวได้รับขอบเขตที่ไม่ซ้ำกัน ลำดับชั้นของคลาสสามารถนำไปใช้ได้โดยไม่ต้องเขียนทับคุณสมบัติส่วนตัวด้วยชื่อที่เท่าเทียมกันโดยไม่ได้ตั้งใจ

class A { 
    #a = "a";
    fnA() { return this.#a; }
}

class B extends A {
    #a = "b"; 
    fnB() { return this.#a; }
}

const b = new B();
b.fnA(); // returns "a" ; unique property #a in A is still retained
b.fnB(); // returns "b"

ผู้แปล TS โชคดีที่ปล่อยข้อผิดพลาดเมื่อprivateคุณสมบัติอยู่ในอันตรายที่จะถูกเขียนทับ (ดูตัวอย่างนี้ ) แต่เนื่องจากลักษณะของการรวบรวมเวลาคุณสมบัติทุกอย่างยังคงเป็นไปได้ที่เวลาทำงานข้อผิดพลาดการรวบรวมที่กำหนดจะถูกละเว้นและ / หรือปล่อยรหัส JS ใช้

ห้องสมุดภายนอก

ผู้เขียนไลบรารีสามารถ refactor #-private identifier โดยไม่ทำให้การเปลี่ยนแปลงที่เกิดขึ้นกับลูกค้า ผู้ใช้ห้องสมุดในอีกด้านหนึ่งได้รับการปกป้องจากการเข้าถึงฟิลด์ภายใน

JS API ละเว้น#ช่อง -private

ฟังก์ชั่น JS ในตัวและเมธอดละเว้น#ฟิลด์ -private ซึ่งอาจส่งผลให้การเลือกคุณสมบัติที่คาดการณ์ได้มากขึ้นในขณะใช้งาน ตัวอย่าง: Object.keys, Object.entries, JSON.stringify, for..inห่วงและอื่น ๆ ( ตัวอย่างรหัสดูยังแมตต์ Bierner ของคำตอบ ):

class Foo {
    #bar = 42;
    baz = "huhu";
}

Object.keys(new Foo()); // [ "baz" ]

ใช้กรณี: privateคำหลัก

คำนำ:

การเข้าถึง API คลาสภายในและสถานะ (ความเป็นส่วนตัวเวลารวบรวมเท่านั้น)

privateสมาชิกของคลาสเป็นคุณสมบัติทั่วไป ณ รันไทม์ เราสามารถใช้ความยืดหยุ่นนี้เพื่อเข้าถึง API ภายในหรือสถานะของคลาสจากภายนอก เพื่อตอบสนองการตรวจสอบคอมไพเลอร์กลไกเช่นประเภทยืนยันการเข้าถึงสถานที่ให้บริการแบบไดนามิกหรือ@ts-ignoreอาจจะใช้ในหมู่คนอื่น ๆ

ตัวอย่างที่มีการยืนยันประเภท ( as/ <>) และการanyกำหนดตัวแปรที่พิมพ์:

class A { 
    constructor(private a: number) { }
}

const a = new A(10);
a.a; // TS compile error
(a as any).a; // works
const casted: any = a; casted.a // works

TS อนุญาตให้เข้าถึงคุณสมบัติแบบไดนามิกของprivateสมาชิกด้วยescape-hatch :

class C {
  private foo = 10;
}

const res = new C()["foo"]; // 10, res has type number

การเข้าถึงแบบส่วนตัวสามารถทำให้รู้สึกได้ที่ไหน (1) การทดสอบหน่วย (2) สถานการณ์การดีบัก / การบันทึกหรือ (3) สถานการณ์กรณีขั้นสูงอื่น ๆ ที่มีคลาสโครงการภายใน (รายการปลายเปิด)

การเข้าถึงตัวแปรภายในเป็นสิ่งที่ขัดแย้งกันไม่เช่นนั้นคุณจะไม่ทำให้มัน privateเป็นที่แรก เพื่อให้เป็นตัวอย่างการทดสอบหน่วยควรจะเป็นกล่องดำ / เทาที่มีช่องส่วนตัวซ่อนอยู่ตามรายละเอียดการใช้งาน แม้ว่าในทางปฏิบัติอาจมีวิธีการที่ถูกต้องในแต่ละกรณี

พร้อมใช้งานในสภาพแวดล้อม ES ทั้งหมด

privateตัวดัดแปลงTS สามารถใช้กับเป้าหมาย ES ทั้งหมดได้ #- เขตข้อมูลส่วนบุคคลใช้ได้เฉพาะสำหรับtarget ES2015/ ES6หรือสูงกว่า ใน ES6 + WeakMapจะใช้เป็นการดำเนินการระดับล่าง(ดูที่นี่ ) พื้นเมืองเขตข้อมูลส่วนตัวในปัจจุบันจำเป็นต้องมี#target esnext

ความสอดคล้องและความเข้ากันได้

ทีมงานอาจใช้แนวทางการเข้ารหัสและกฎ linter เพื่อบังคับให้ใช้privateเป็นตัวดัดแปลงการเข้าถึงเท่านั้น ข้อ จำกัด นี้สามารถช่วยในเรื่องความสอดคล้องและหลีกเลี่ยงความสับสนกับ#สัญลักษณ์ข้อมูลส่วนบุคคลในลักษณะที่เข้ากันได้ย้อนหลัง

หากจำเป็นต้องใช้คุณสมบัติของพารามิเตอร์ (ชวเลขการกำหนดตัวสร้าง) เป็นตัวหยุดการแสดง สามารถใช้ได้กับprivateคำหลักเท่านั้นและยังไม่มีแผนในการนำไปใช้กับ#ฟิลด์ -private

เหตุผลอื่น ๆ

  • privateอาจให้ประสิทธิภาพการใช้งานดีขึ้นในบางกรณีที่มีการปรับระดับ (ดูที่นี่ )
  • ไม่มีวิธีการเรียนแบบส่วนตัวที่ยากใน TS จนถึงปัจจุบัน
  • บางคนชอบprivateสัญกรณ์คำหลักดีกว่า😊

หมายเหตุทั้งสอง

ทั้งสองวิธีสร้างประเภทที่ระบุหรือตราสินค้าบางประเภทในเวลารวบรวม

class A1 { private a = 0; }
class A2 { private a = 42; }

const a: A1 = new A2(); 
// error: "separate declarations of a private property 'a'"
// same with hard private fields

นอกจากนี้ทั้งสองอนุญาตการเข้าถึงข้ามอินสแตนซ์: อินสแตนซ์ของคลาสAสามารถเข้าถึงสมาชิกส่วนตัวของAอินสแตนซ์อื่น:

class A {
    private a = 0;
    method(arg: A) {
        console.log(arg.a); // works
    }
}

แหล่งที่มา

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.