Tại sao tôi có thể truy cập các thành viên riêng tư của TypeScript khi tôi không thể truy cập?


108

Tôi đang xem xét việc triển khai các thành viên riêng trong TypeScript và tôi thấy nó hơi khó hiểu. Intellisense không cho phép truy cập thành viên riêng tư, nhưng trong JavaScript thuần túy, tất cả đều ở đó. Điều này làm cho tôi nghĩ rằng TS không thực hiện các thành viên riêng tư một cách chính xác. Có suy nghĩ gì không?

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

Bạn đang thắc mắc tại sao IntelliSense không cung cấp cho bạn thành viên riêng trên hàng có cảnh báo ()?
esrange

7
Không. Tôi đang tự hỏi tại sao TS lại có một private khi đó chỉ là một đường cho intellisense, và không thực sự dành cho JavaScript mà nó biên dịch. Mã này được thực thi trong stylescriptlang.org/Playground cảnh báo giá trị thành viên riêng.
Sean Feldman

Như đã đề cập, bạn phải khai báo các mục dưới dạng một biến trong ngữ cảnh riêng tư để đặt chúng ở chế độ riêng tư. Tôi đoán rằng chỉ định kiểu không làm điều này vì nó có thể không hiệu quả so với việc thêm vào nguyên mẫu. Nó cũng messes với định nghĩa kiểu (các thành viên cá nhân không phải là thực sự là một phần của lớp)
Shane

Nếu bạn muốn các biến riêng tư thực sự tồn tại trên nguyên mẫu, nó sẽ mất một số chi phí, nhưng tôi đã viết một thư viện có tên ClassJS thực hiện điều đó trên GitHub: github.com/KthProg/ClassJS .
KthProg

Câu trả lời:


97

Cũng giống như kiểm tra kiểu, quyền riêng tư của các thành viên chỉ được thực thi trong trình biên dịch.

Thuộc tính private được thực hiện như một thuộc tính thông thường và mã bên ngoài lớp không được phép truy cập vào nó.

Để tạo một cái gì đó thực sự riêng tư bên trong lớp, nó không thể là thành viên của lớp, nó sẽ là một biến cục bộ được tạo bên trong một phạm vi hàm bên trong mã tạo đối tượng. Điều đó có nghĩa là bạn không thể truy cập nó như một thành viên của lớp, tức là sử dụng thistừ khóa.


25
Không có gì lạ khi một lập trình viên javascript đặt một biến cục bộ vào một phương thức khởi tạo đối tượng và sử dụng nó như một trường riêng tư. Tôi ngạc nhiên khi họ không ủng hộ những thứ như thế này.
Eric

2
@Eric: Vì TypeScript sử dụng nguyên mẫu cho các phương thức thay vì thêm các phương thức làm nguyên mẫu bên trong phương thức khởi tạo, một biến cục bộ trong phương thức khởi tạo không thể truy cập được từ các phương thức. Có thể tạo một biến cục bộ bên trong trình bao bọc hàm cho lớp, nhưng tôi chưa tìm ra cách để làm điều đó. Tuy nhiên, đó vẫn sẽ là một biến cục bộ và không phải là một thành viên riêng.
Guffa

40
Đây là điều mà tôi đã và đang cung cấp phản hồi. Tôi tin rằng nó sẽ cung cấp tùy chọn tạo Mẫu mô-đun tiết lộ, vì vậy các thành viên riêng tư có thể vẫn ở chế độ riêng tư và các thành viên công khai có thể truy cập được bằng JavaScript. Đây là một mẫu phổ biến và sẽ cung cấp khả năng truy cập giống nhau trong TS và JS.
John Papa

Có một giải pháp bạn có thể sử dụng cho các thành viên tĩnh tin: basarat.com/2013/03/real-private-static-class-members-in.html
basarat

1
@BasaratAli: Đó là một biến tĩnh có sẵn bên trong các phương thức của lớp, nhưng nó không phải là thành viên của lớp, tức là bạn không truy cập nó bằng thistừ khóa.
Guffa

37

JavaScript không hỗ trợ các biến riêng tư.

function MyClass() {
    var myPrivateVar = 3;

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

Trong TypeScript, điều này sẽ được thể hiện như sau:

class MyClass {

    doSomething: () => number;

    constructor() {
        var myPrivateVar = 3;

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

BIÊN TẬP

Phương pháp này chỉ nên được sử dụng RIÊNG khi thực sự cần thiết. Ví dụ, nếu bạn cần lưu mật khẩu tạm thời.

Có chi phí hiệu suất để sử dụng mẫu này (không liên quan đến Javascript hoặc Typescript) và chỉ nên được sử dụng khi thực sự cần thiết.


Không phải tất cả thời gian đều có thể thực hiện việc này bằng cách cài đặt var _thisđể sử dụng trong các hàm có phạm vi? Tại sao bạn phải lo lắng khi làm điều đó ở phạm vi lớp học?
DrSammyD

Không. Var _ đây chỉ là một tham chiếu đến điều này.
Martin,

2
Chính xác hơn là gọi chúng là các biến hàm tạo, không phải là private. Chúng không thể nhìn thấy trong các phương pháp nguyên mẫu.
Roman M. Koss

1
oh yeah, xin lỗi thay vì vấn đề là một vấn đề khác, thực tế là đối với mọi trường hợp bạn tạo, doSomething sẽ được tạo lại, vì nó không phải là một phần của chuỗi nguyên mẫu.
Barbu Barbu

1
@BarbuBarbu Vâng, tôi đồng ý. Đây là một vấn đề LỚN với cách tiếp cận này và một trong những lý do nên tránh nó.
Martin

11

Sau khi hỗ trợ cho WeakMap được phổ biến rộng rãi hơn, sẽ có một kỹ thuật thú vị được nêu chi tiết trong ví dụ # 3 ở đây .

Nó cho phép dữ liệu riêng tư VÀ tránh chi phí hiệu suất của ví dụ Jason Evans bằng cách cho phép dữ liệu có thể truy cập được từ các phương thức nguyên mẫu thay vì chỉ các phương thức phiên bản.

Trang MDN WeakMap được liên kết liệt kê trình duyệt hỗ trợ Chrome 36, Firefox 6.0, IE 11, Opera 23 và 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)();
    }
  }
}

Tôi thích nó! Về cơ bản, nó có nghĩa là ẩn các thuộc tính private vào lớp tổng hợp. Điều thú vị nhất sẽ là ... Làm thế nào về việc thêm hỗ trợ cho protectedcác thông số? : D
Roman M. Koss

2
@RamtinSoltani Bài viết được liên kết thống kê rằng do bản đồ yếu hoạt động như thế nào, điều này sẽ không ngăn chặn việc thu gom rác. Nếu ai đó muốn an toàn hơn trong khi sử dụng kỹ thuật này, họ có thể triển khai mã loại bỏ của riêng họ để xóa khóa cá thể lớp khỏi mỗi bản đồ yếu.
Ryan Thomas

1
Từ trang MDN: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… . Ngược lại, các WeakMaps bản địa giữ các tham chiếu "yếu" đến các đối tượng chính, có nghĩa là chúng không ngăn chặn việc thu gom rác trong trường hợp không có tham chiếu nào khác đến đối tượng chính. Điều này cũng tránh ngăn chặn việc thu thập rác các giá trị trong bản đồ.
Ryan Thomas

@RyanThomas Đúng, đó là một nhận xét cũ mà tôi đã để lại cách đây một thời gian. WeakMaps, trái ngược với Maps, sẽ không gây rò rỉ bộ nhớ. Vì vậy, nó là an toàn để sử dụng kỹ thuật này.
Ramtin Soltani

@RamtinSoltani Vậy xóa bình luận cũ của bạn đi?>
ErikE

10

Vì TypeScript 3.8 sẽ được phát hành, bạn sẽ có thể khai báo trường riêng tư không thể truy cập hoặc thậm chí bị phát hiện bên ngoài lớp chứa .

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.

Các trường riêng tư bắt đầu bằng #ký tự

Xin lưu ý rằng các trường riêng tư này sẽ khác với các trường được đánh dấu bằng privatetừ khóa

Tham khảo https://devblogs.microsoft.com/typescript/annocting-typescript-3-8-beta/


4

Cảm ơn Sean Feldman về liên kết đến cuộc thảo luận chính thức về vấn đề này - hãy xem câu trả lời của anh ấy cho liên kết.

Tôi đã đọc cuộc thảo luận mà anh ấy đã liên kết và đây là bản tóm tắt các điểm chính:

  • Gợi ý: thuộc tính private trong constructor
    • vấn đề: không thể truy cập từ các chức năng nguyên mẫu
  • Gợi ý: các phương thức private trong constructor
    • vấn đề: giống như với các thuộc tính, cộng với việc bạn mất lợi ích về hiệu suất của việc tạo một hàm một lần cho mỗi lớp trong nguyên mẫu; thay vào đó, bạn tạo một bản sao của hàm cho mỗi phiên bản
  • Gợi ý: thêm bản soạn sẵn để truy cập thuộc tính trừu tượng và thực thi chế độ hiển thị
    • các vấn đề: chi phí hiệu suất chính; TypeScript được thiết kế cho các ứng dụng lớn
  • Gợi ý: TypeScript đã bao bọc các định nghĩa phương thức khởi tạo và nguyên mẫu trong một bao đóng; đặt các phương thức và thuộc tính private ở đó
    • các vấn đề với việc đặt các tài sản riêng trong việc đóng đó: chúng trở thành các biến tĩnh; không có một cái cho mỗi trường hợp
    • vấn đề với việc đặt các phương thức riêng tư trong việc đóng đó: chúng không có quyền truy cập thismà không có một số loại giải pháp
  • Gợi ý: tự động xử lý các tên biến riêng tư
    • đối số phản đối: đó là quy ước đặt tên, không phải là cấu trúc ngôn ngữ. Mang nó cho mình
  • Đề xuất: Chú thích các phương pháp riêng tư với@private để nhận ra rằng chú thích có thể thu nhỏ tên phương thức một cách hiệu quả
    • Không có đối số phản đối quan trọng nào cho cái này

Phản đối tổng thể để thêm hỗ trợ khả năng hiển thị trong mã được phát ra:

  • vấn đề là bản thân JavaScript không có công cụ sửa đổi khả năng hiển thị - đây không phải là vấn đề của TypeScript
  • đã có một khuôn mẫu được thiết lập sẵn trong cộng đồng JavaScript: đặt tiền tố các thuộc tính và phương thức riêng tư bằng dấu gạch dưới, có nội dung "tự chịu rủi ro khi tiến hành"
  • khi các nhà thiết kế TypeScript nói rằng các thuộc tính và phương thức riêng tư thực sự không "khả thi", thì họ có nghĩa là "không thể thực hiện theo các ràng buộc thiết kế của chúng tôi", cụ thể là:
    • JS phát ra là thành ngữ
    • Boilerplate là tối thiểu
    • Không có chi phí bổ sung so với JS OOP thông thường

Nếu câu trả lời này là từ cuộc trò chuyện này: typescript.codeplex.com/discussions/397651 -, vui lòng cung cấp một liên kết: D
La Mã M. Koss

1
Vâng, đó là cuộc trò chuyện - nhưng tôi đã liên kết với câu trả lời của Sean Feldman cho câu hỏi này , nơi anh ấy cung cấp liên kết. Vì anh ấy đã làm công việc tìm kiếm liên kết, tôi muốn ghi công cho anh ấy.
alexanderbird

2

Trong TypeScript, các chức năng Private chỉ có thể truy cập được bên trong lớp. Giống

nhập mô tả hình ảnh ở đây

Và nó sẽ hiển thị lỗi khi bạn cố gắng truy cập vào một thành viên riêng tư. Đây là ví dụ:

nhập mô tả hình ảnh ở đây

Lưu ý: Nó sẽ ổn với javascript và cả hai chức năng đều có thể truy cập bên ngoài.


4
OP: "nhưng trong tinh khiết JavaScript, đó là tất cả những gì" - Tôi không nghĩ rằng bạn giải quyết vấn đề rằng JavaScript được tạo ra cho thấy nhiều chức năng "private" công khai
alexanderbird

1
@alexanderbird Tôi nghĩ rằng anh ấy muốn nói rằng TypeScript thường là đủ. Khi chúng tôi đang phát triển TypeScript, chúng tôi vẫn tiếp tục với nó trong phạm vi dự án, vì vậy quyền riêng tư từ JavaScript không phải là vấn đề lớn. Bởi vì trước hết, mã gốc quan trọng đối với nhà phát triển, không phải mã chuyển đổi (JavaScript).
Roman M. Koss

1
Trừ khi bạn viết và xuất bản một thư viện JavaScript đang, sau đó mã transpiled không thành vấn đề
alexanderbird

câu trả lời của bạn là lạc đề.
canbax

1

Tôi nhận thấy đây là một cuộc thảo luận cũ hơn nhưng vẫn có thể hữu ích khi chia sẻ giải pháp của tôi cho vấn đề các biến và phương thức được cho là riêng tư trong TypeScript "rò rỉ" ra giao diện công khai của lớp JavaScript đã biên dịch.

Đối với tôi, vấn đề này hoàn toàn là vấn đề thẩm mỹ, tức là tất cả về sự lộn xộn trực quan khi một biến thể hiện được xem trong DevTools. Cách khắc phục của tôi là nhóm các khai báo riêng tư lại với nhau bên trong một lớp khác sau đó được khởi tạo trong lớp chính và được gán cho một biến private(nhưng vẫn hiển thị công khai trong JS) với tên như __(dấu gạch dưới kép).

Thí dụ:

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);

Khi myClassphiên bản được xem trong DevTools, thay vì nhìn thấy tất cả các thành viên "riêng tư" của nó được trộn lẫn với các thành viên thực sự công khai (có thể trở nên lộn xộn về mặt hình ảnh trong mã đời thực được cấu trúc lại đúng cách), bạn thấy chúng được nhóm gọn gàng bên trong thuộc tính thu gọn __:

nhập mô tả hình ảnh ở đây


1
Tôi thích nó. Trông sạch sẽ.

0

Đây là cách tiếp cận có thể sử dụng lại để thêm các thuộc tính riêng tư thích hợp:

/**
 * 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);
    }
}

Giả sử bạn có lớp Clientở đâu đó cần hai thuộc tính riêng:

  • prop1: string
  • prop2: number

Dưới đây là cách bạn triển khai nó:

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

Và nếu tất cả những gì bạn cần là một tài sản riêng, thì nó thậm chí còn đơn giản hơn, bởi vì bạn sẽ không cần phải xác định bất kỳ ClientPrivate trong trường hợp đó.

Đáng chú ý, phần lớn, lớp Privatechỉ cung cấp một chữ ký dễ đọc, trong khi việc sử dụng trực tiếp WeakMapthì không.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.