เหตุใดฉันจึงสามารถเข้าถึงสมาชิกส่วนตัวของ TypeScript ในเมื่อฉันไม่สามารถทำได้


108

ฉันกำลังดูการใช้งานสมาชิกส่วนตัวใน TypeScript และฉันพบว่ามันสับสนเล็กน้อย Intellisense ไม่อนุญาตให้เข้าถึงสมาชิกส่วนตัว แต่ใน JavaScript บริสุทธิ์ทุกอย่างอยู่ที่นั่น ทำให้ฉันคิดว่า TS ไม่ได้ใช้สมาชิกส่วนตัวอย่างถูกต้อง ความคิดใด ๆ ?

class Test{
  private member: any = "private member";
}
alert(new Test().member);

คุณสงสัยว่าทำไม IntelliSense ไม่ให้สมาชิกส่วนตัวในแถวที่มีการแจ้งเตือน ()?
esrange

7
ไม่ ฉันสงสัยว่าทำไม TS ถึงมีไพรเวตในเมื่อนั่นเป็นเพียงน้ำตาลสำหรับ intellisense เท่านั้นและไม่ใช่สำหรับ JavaScript ที่คอมไพล์ รหัสนี้ดำเนินการในtypescriptlang.org/Playgroundแจ้งเตือนค่าสมาชิกส่วนตัว
Sean Feldman

ดังที่ได้กล่าวไว้คุณต้องประกาศรายการเป็นตัวแปรในบริบทส่วนตัวเพื่อให้เป็นส่วนตัว ฉันเดาว่า typescript ไม่ทำเช่นนี้เพราะอาจไม่มีประสิทธิภาพเทียบกับการเพิ่มลงในต้นแบบ นอกจากนี้ยังยุ่งกับการกำหนดประเภท (สมาชิกส่วนตัวไม่ได้เป็นส่วนหนึ่งของชั้นเรียนจริงๆ)
เชน

หากคุณต้องการตัวแปรภาคเอกชนที่แท้จริงที่มีอยู่บนต้นแบบมันจะใช้ค่าใช้จ่ายบางส่วน แต่ผมเคยเขียนห้องสมุดที่เรียกว่า ClassJS ว่าไม่เพียงแค่นั้นบน GitHub: github.com/KthProg/ClassJS
KthProg

คำตอบ:


97

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

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

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


25
ไม่ใช่เรื่องแปลกที่โปรแกรมเมอร์จาวาสคริปต์จะใส่ตัวแปรโลคัลในตัวสร้างอ็อบเจ็กต์และใช้เป็นฟิลด์ส่วนตัว ฉันแปลกใจที่พวกเขาไม่สนับสนุนอะไรแบบนี้
Eric

2
@Eric: เนื่องจาก TypeScript ใช้ต้นแบบสำหรับวิธีการแทนที่จะเพิ่มวิธีการเป็นต้นแบบภายในตัวสร้างตัวแปรท้องถิ่นในตัวสร้างจะไม่สามารถเข้าถึงได้จากวิธีการ อาจเป็นไปได้ที่จะสร้างตัวแปรท้องถิ่นภายในฟังก์ชัน wrapper สำหรับคลาส แต่ฉันยังไม่พบวิธีการทำเช่นนั้น อย่างไรก็ตามนั่นจะยังคงเป็นตัวแปรเฉพาะที่ไม่ใช่สมาชิกส่วนตัว
Guffa

40
นี่คือสิ่งที่ฉันให้ข้อเสนอแนะ ฉันเชื่อว่าควรมีตัวเลือกในการสร้างรูปแบบโมดูลการเปิดเผยดังนั้นสมาชิกส่วนตัวจึงยังคงเป็นส่วนตัวและบุคคลสาธารณะสามารถเข้าถึงได้ใน JavaScript นี่เป็นรูปแบบทั่วไปและจะให้การเข้าถึงเดียวกันใน TS และ JS
John Papa

มีวิธีแก้ปัญหาที่คุณสามารถใช้สำหรับสมาชิกแบบคงที่ส่วนตัว: basarat.com/2013/03/real-private-static-class-members-in.html
basarat

1
@BasaratAli: นั่นคือตัวแปรคงที่ที่มีอยู่ในเมธอดของคลาส แต่ไม่ใช่สมาชิกของคลาสกล่าวคือคุณไม่ได้เข้าถึงโดยใช้thisคีย์เวิร์ด
Guffa

37

JavaScript รองรับตัวแปรส่วนตัว

function MyClass() {
    var myPrivateVar = 3;

    this.doSomething = function() {
        return myPrivateVar++;        
    }
}

ใน TypeScript สิ่งนี้จะแสดงออกดังนี้:

class MyClass {

    doSomething: () => number;

    constructor() {
        var myPrivateVar = 3;

        this.doSomething = function () {
            return myPrivateVar++;
        }
    }
}

แก้ไข

ควรใช้แนวทางนี้เฉพาะในกรณีที่จำเป็นอย่างยิ่งเท่านั้น ตัวอย่างเช่นหากคุณต้องการแคชรหัสผ่านชั่วคราว

รูปแบบนี้มีค่าใช้จ่ายด้านประสิทธิภาพ (ไม่เกี่ยวข้องกับ Javascript หรือ typescript) และควรใช้เฉพาะในกรณีที่จำเป็นจริงๆเท่านั้น


typescript ไม่ทำสิ่งนี้ตลอดเวลาโดยการตั้งค่าvar _thisเพื่อใช้ในฟังก์ชันที่กำหนดขอบเขตหรือไม่? เหตุใดคุณจึงมีคุณสมบัติที่จะทำในขอบเขตชั้นเรียน
DrSammyD

ไม่ var _this เป็นเพียงข้อมูลอ้างอิงเท่านั้น
Martin

2
ถูกต้องมากขึ้นในการเรียกตัวแปรตัวสร้างไม่ใช่ส่วนตัว สิ่งเหล่านี้ไม่ปรากฏในวิธีการต้นแบบ
Roman M. Koss

1
โอ้ใช่ขออภัยปัญหาเป็นปัญหาอื่นแทนความจริงที่ว่าสำหรับทุกอินสแตนซ์ที่คุณสร้าง doSomething จะถูกสร้างขึ้นอีกครั้งเนื่องจากไม่ใช่ส่วนหนึ่งของห่วงโซ่ต้นแบบ
Barbu Barbu

1
@BarbuBarbu ใช่ฉันเห็นด้วย นี่เป็นปัญหาใหญ่สำหรับแนวทางนี้และเป็นหนึ่งในเหตุผลที่ควรหลีกเลี่ยง
Martin

11

เมื่อการสนับสนุนสำหรับWeakMapที่ใช้ได้อย่างกว้างขวางมีเทคนิคที่น่าสนใจรายละเอียดในตัวอย่าง # 3 ที่นี่

อนุญาตให้ใช้ข้อมูลส่วนตัวและหลีกเลี่ยงค่าใช้จ่ายด้านประสิทธิภาพของตัวอย่าง Jason Evans โดยอนุญาตให้เข้าถึงข้อมูลได้จากวิธีการต้นแบบแทนที่จะเป็นวิธีการอินสแตนซ์เท่านั้น

หน้า MDN WeakMap ที่เชื่อมโยงแสดงการรองรับเบราว์เซอร์ที่ Chrome 36, Firefox 6.0, IE 11, Opera 23 และ Safari 7.1

let _counter = new WeakMap();
let _action = new WeakMap();
class Countdown {
  constructor(counter, action) {
    _counter.set(this, counter);
    _action.set(this, action);
  }
  decrement() {
    let counter = _counter.get(this);
    if (counter < 1) return;
    counter--;
    _counter.set(this, counter);
    if (counter === 0) {
      _action.get(this)();
    }
  }
}

ฉันชอบมัน! โดยทั่วไปหมายถึงซ่อนคุณสมบัติส่วนตัวลงในคลาสรวม ความสนุกที่สุดจะเป็น ... แล้วเพิ่มการสนับสนุนprotectedพารามิเตอร์ได้อย่างไร? : D
Roman M. Koss

2
@RamtinSoltani สถิติบทความที่เชื่อมโยงซึ่งเกิดจากการทำงานของจุดอ่อนนี้จะไม่ป้องกันการรวบรวมขยะ หากมีคนต้องการความปลอดภัยเป็นพิเศษในขณะที่ใช้เทคนิคนี้พวกเขาสามารถใช้รหัสการกำจัดของตนเองซึ่งจะลบคีย์อินสแตนซ์คลาสออกจากจุดอ่อนแต่ละรายการ
Ryan Thomas

1
จากหน้า MDN: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... ในทางตรงกันข้าม WeakMaps ดั้งเดิมจะมีการอ้างอิง "อ่อนแอ" ไปยังวัตถุสำคัญซึ่งหมายความว่าพวกเขาไม่ได้ป้องกันการรวบรวมขยะในกรณีที่จะไม่มีการอ้างอิงอื่น ๆ ไปยังวัตถุหลัก นอกจากนี้ยังหลีกเลี่ยงการป้องกันการรวบรวมขยะของค่าในแผนที่
Ryan Thomas

@RyanThomas True นั่นเป็นความคิดเห็นเก่าที่ฉันทิ้งไว้เมื่อครู่ WeakMaps ซึ่งตรงข้ามกับแผนที่จะไม่ทำให้หน่วยความจำรั่วไหล ดังนั้นจึงปลอดภัยที่จะใช้เทคนิคนี้
Ramtin Soltani

@RamtinSoltani ดังนั้นลบความคิดเห็นเก่าของคุณ?>
ErikE

10

ตั้งแต่ typescript 3.8 จะได้รับการปล่อยตัวออกมาคุณจะสามารถที่จะประกาศข้อมูลส่วนตัวซึ่ง ไม่สามารถเข้าถึงหรือภายนอกตรวจพบแม้กระทั่งของชั้นที่มี

class Person {
    #name: string

    constructor(name: string) {
        this.#name = name;
    }

    greet() {
        console.log(`Hello, my name is ${this.#name}!`);
    }
}

let jeremy = new Person("Jeremy Bearimy");

jeremy.#name
//     ~~~~~
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.

ฟิลด์ส่วนตัวเริ่มต้นด้วย#อักขระ

โปรดทราบว่าฟิลด์ส่วนตัวเหล่านี้จะแตกต่างจากฟิลด์ที่มีprivateคำหลัก

อ้างอิง https://devblogs.microsoft.com/typescript/announcing-typescript-3-8-beta/


4

ขอบคุณ Sean Feldman สำหรับลิงก์ไปยังการสนทนาอย่างเป็นทางการเกี่ยวกับปัญหานี้ - ดูคำตอบของเขาสำหรับลิงก์

ฉันอ่านการสนทนาที่เขาเชื่อมโยงและนี่คือสรุปประเด็นสำคัญ:

  • คำแนะนำ:คุณสมบัติส่วนตัวในตัวสร้าง
    • ปัญหา:ไม่สามารถเข้าถึงจากฟังก์ชันต้นแบบ
  • คำแนะนำ:วิธีการส่วนตัวในตัวสร้าง
    • ปัญหา:เช่นเดียวกับคุณสมบัติและคุณสูญเสียประโยชน์ด้านประสิทธิภาพในการสร้างฟังก์ชันหนึ่งครั้งต่อคลาสในต้นแบบ แทนที่จะสร้างสำเนาของฟังก์ชันสำหรับแต่ละอินสแตนซ์
  • คำแนะนำ:เพิ่มสำเร็จรูปในการเข้าถึงคุณสมบัตินามธรรมและบังคับใช้การมองเห็น
    • ปัญหา:ค่าใช้จ่ายด้านประสิทธิภาพที่สำคัญ TypeScript ออกแบบมาสำหรับแอปพลิเคชันขนาดใหญ่
  • คำแนะนำ: TypeScript ได้รวมตัวสร้างและนิยามเมธอดต้นแบบไว้แล้วในการปิด ใส่วิธีการและคุณสมบัติส่วนตัวไว้ที่นั่น
    • ปัญหาเกี่ยวกับการใส่คุณสมบัติส่วนตัวในการปิดนั้นพวกมันกลายเป็นตัวแปรคงที่ ไม่มีหนึ่งรายการต่ออินสแตนซ์
    • ปัญหาเกี่ยวกับการใส่เมธอดส่วนตัวในการปิดนั้น:พวกเขาไม่สามารถเข้าถึงได้thisหากไม่มีวิธีแก้ปัญหาบางอย่าง
  • คำแนะนำ:ทำลายชื่อตัวแปรส่วนตัวโดยอัตโนมัติ
    • อาร์กิวเมนต์ตอบโต้:นั่นเป็นหลักการตั้งชื่อไม่ใช่โครงสร้างภาษา ยุ่งเหยิงด้วยตัวคุณเอง
  • คำแนะนำ:ใส่คำอธิบายประกอบเมธอดส่วนตัวด้วยตัว@privateย่อที่รับรู้ว่าคำอธิบายประกอบสามารถย่อชื่อเมธอดได้อย่างมีประสิทธิภาพ
    • ไม่มีข้อโต้แย้งที่สำคัญสำหรับข้อโต้แย้งนี้

อาร์กิวเมนต์โดยรวมเพื่อเพิ่มการสนับสนุนการมองเห็นในโค้ดที่ปล่อยออกมา:

  • ปัญหาคือ JavaScript เองไม่มีตัวปรับการมองเห็น - นี่ไม่ใช่ปัญหาของ TypeScript
  • มีรูปแบบที่กำหนดไว้แล้วในชุมชน JavaScript: คำนำหน้าคุณสมบัติส่วนตัวและวิธีการที่มีขีดล่างซึ่งระบุว่า "ดำเนินการด้วยความเสี่ยงของคุณเอง"
  • เมื่อนักออกแบบ TypeScript กล่าวว่าคุณสมบัติและวิธีการส่วนตัวอย่างแท้จริงไม่ "เป็นไปได้" นั่นหมายความว่า "เป็นไปไม่ได้ภายใต้ข้อ จำกัด ในการออกแบบของเรา" โดยเฉพาะ:
    • JS ที่ปล่อยออกมาเป็นสำนวน
    • หม้อไอน้ำมีน้อย
    • ไม่มีค่าใช้จ่ายเพิ่มเติมเมื่อเทียบกับ JS OOP ปกติ

หากคำตอบนี้มาจากบทสนทนานี้: typescript.codeplex.com/discussions/397651 - โปรดระบุลิงค์: D
Roman M. Koss

1
ใช่นั่นคือบทสนทนา - แต่ฉันเชื่อมโยงกับคำตอบของฌอนเฟลด์แมนสำหรับคำถามนี้ซึ่งเขาให้ลิงก์ เนื่องจากเขาทำงานในการค้นหาลิงค์ฉันจึงต้องการให้เครดิตกับเขา
alexanderbird

1

ในฟังก์ชัน TypeScript Private สามารถเข้าถึงได้ภายในคลาสเท่านั้น ชอบ

ใส่คำอธิบายภาพที่นี่

และจะแสดงข้อผิดพลาดเมื่อคุณพยายามเข้าถึงสมาชิกส่วนตัว นี่คือตัวอย่าง:

ใส่คำอธิบายภาพที่นี่

หมายเหตุ: ใช้จาวาสคริปต์ได้ดีและทั้งสองฟังก์ชันสามารถเข้าถึงได้จากภายนอก


4
OP: "แต่ใน JavaScript บริสุทธิ์ทุกอย่างอยู่ที่นั่น" - ฉันไม่คิดว่าคุณจะแก้ปัญหาที่ JavaScript ที่สร้างขึ้นเปิดเผยฟังก์ชัน "ส่วนตัว" แบบสาธารณะ
alexanderbird

1
@alexanderbird ฉันคิดว่าเขาอยากจะบอกว่าปกติแล้ว TypeScript ก็เพียงพอแล้ว เมื่อเราพัฒนาใน TypeScript เราจะอยู่กับมันในขอบเขตโครงการดังนั้นความเป็นส่วนตัวจาก JavaScript จึงไม่ใช่เรื่องใหญ่ เนื่องจากประการแรกรหัสดั้งเดิมมีความสำคัญสำหรับนักพัฒนาไม่ใช่รหัสที่ถ่ายทอด (JavaScript)
Roman M. Koss

1
เว้นแต่คุณจะเขียนและเผยแพร่ไลบรารี JavaScript รหัสที่โปร่งใสก็มีความสำคัญ
alexanderbird

คำตอบของคุณไม่ตรงประเด็น
canbax

1

ฉันตระหนักดีว่านี่เป็นการสนทนาที่เก่ากว่า แต่การแบ่งปันวิธีแก้ปัญหาของฉันเกี่ยวกับตัวแปรและวิธีการส่วนตัวที่คาดคะเนใน TypeScript "รั่ว" ในอินเทอร์เฟซสาธารณะของคลาส JavaScript ที่คอมไพล์แล้วอาจเป็นประโยชน์

สำหรับฉันแล้วปัญหานี้เป็นเรื่องเครื่องสำอางเท่านั้นกล่าวคือทั้งหมดเกี่ยวกับความยุ่งเหยิงของภาพเมื่อมีการดูตัวแปรอินสแตนซ์ใน DevTools การแก้ไขของฉันคือการจัดกลุ่มการประกาศส่วนตัวเข้าด้วยกันภายในคลาสอื่นที่ถูกสร้างอินสแตนซ์ในคลาสหลักและกำหนดให้privateกับตัวแปร (แต่ยังคงมองเห็นได้แบบสาธารณะใน JS) ด้วยชื่อเช่น__(ขีดล่างคู่)

ตัวอย่าง:

class Privates {
    readonly DEFAULT_MULTIPLIER = 2;
    foo: number;
    bar: number;

    someMethod = (multiplier: number = this.DEFAULT_MULTIPLIER) => {
        return multiplier * (this.foo + this.bar);
    }

    private _class: MyClass;

    constructor(_class: MyClass) {
        this._class = _class;
    }
}

export class MyClass {
    private __: Privates = new Privates(this);

    constructor(foo: number, bar: number, baz: number) {
        // assign private property values...
        this.__.foo = foo;
        this.__.bar = bar;

        // assign public property values...
        this.baz = baz;
    }

    baz: number;

    print = () => {
        console.log(`foo=${this.__.foo}, bar=${this.__.bar}`);
        console.log(`someMethod returns ${this.__.someMethod()}`);
    }
}

let myClass = new MyClass(1, 2, 3);

เมื่อmyClassดูอินสแตนซ์ใน DevTools แทนที่จะเห็นสมาชิก "ส่วนตัว" ทั้งหมดผสมกับสมาชิกสาธารณะอย่างแท้จริง (ซึ่งอาจดูยุ่งเหยิงอย่างมากในโค้ดชีวิตจริงที่ปรับโครงสร้างใหม่อย่างเหมาะสม) คุณจะเห็นพวกมันถูกจัดกลุ่มอย่างเรียบร้อยภายใน__คุณสมบัติที่ยุบ:

ใส่คำอธิบายภาพที่นี่


1
ฉันชอบมัน. ดูสะอาดสะอ้าน

0

ต่อไปนี้เป็นแนวทางที่ใช้ซ้ำได้สำหรับการเพิ่มคุณสมบัติส่วนตัวที่เหมาะสม:

/**
 * Implements proper private properties.
 */
export class Private<K extends object, V> {

    private propMap = new WeakMap<K, V>();

    get(obj: K): V {
        return this.propMap.get(obj)!;
    }

    set(obj: K, val: V) {
        this.propMap.set(obj, val);
    }
}

สมมติว่าคุณมีชั้นเรียนClientที่ต้องการคุณสมบัติส่วนตัวสองอย่าง:

  • prop1: string
  • prop2: number

ด้านล่างนี้คือวิธีการใช้งาน:

// our private properties:
interface ClientPrivate {
    prop1: string;
    prop2: number;
}

// private properties for all Client instances:
const pp = new Private<Client, ClientPrivate>();

class Client {
    constructor() {
        pp.set(this, {
            prop1: 'hello',
            prop2: 123
        });
    }

    someMethod() {
        const privateProps = pp.get(this);

        const prop1 = privateProps.prop1;
        const prop2 = privateProps.prop2;
    }
}

และหากสิ่งที่คุณต้องการคือทรัพย์สินส่วนตัวเพียงอย่างเดียวมันก็จะง่ายกว่าเดิมเพราะคุณไม่จำเป็นต้องกำหนดใด ๆClientPrivateในกรณีนั้น

เป็นที่น่าสังเกตว่าส่วนใหญ่แล้วชั้นเรียนPrivateจะเสนอลายเซ็นที่อ่านได้อย่างสวยงามในขณะที่WeakMapไม่ใช้โดยตรง

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