Các thuộc tính riêng tư trong các lớp JavaScript ES6


444

Có thể tạo các thuộc tính riêng tư trong các lớp ES6 không?

Đây là một ví dụ. Làm thế nào tôi có thể ngăn chặn truy cập instance.property?

class Something {
  constructor(){
    this.property = "test";
  }
}

var instance = new Something();
console.log(instance.property); //=> "test"

5
Thực sự có đề xuất giai đoạn 3 cho tính năng này - tc39.github.io/proposed- class - field github.com/tc39/proposed-
arty

@arty Tôi đã cung cấp câu trả lời cho điều này với các ví dụ: stackoverflow.com/a/52237988/1432509
Alister

Câu trả lời:


165

Các trường riêng (và phương thức) đang được triển khai trong tiêu chuẩn ECMA . Bạn có thể bắt đầu sử dụng chúng ngay hôm nay với cài đặt sẵn babel 7 và giai đoạn 3.

class Something {
  #property;

  constructor(){
    this.#property = "test";
  }

  #privateMethod() {
    return 'hello world';
  }

  getPrivateMessage() {
      return this.#privateMethod();
  }
}

const instance = new Something();
console.log(instance.property); //=> undefined
console.log(instance.privateMethod); //=> undefined
console.log(instance.getPrivateMessage()); //=> hello world

Tôi đang tự hỏi làm thế nào những lĩnh vực lớp học có thể làm việc. Bạn hiện không thể sử dụng thistrong constructor trước khi bạn gọi super(). Tuy nhiên, babel đặt chúng trước siêu.
seeker_of_bacon

Làm cách nào để định cấu hình ESLint để cho phép #privateCrapcú pháp?
Marecky

6
Còn eslint thì sao? Tôi đã nhận được lỗi trình phân tích cú pháp ở dấu bằng. Babel đang hoạt động, chỉ cần eslint không thể phân tích cú pháp js mới này.
martonx

6
Wow điều này rất xấu xí. Hashtag là một nhân vật hợp lệ. Tài sản không thực sự riêng tư, hay? .. Tôi đã kiểm tra nó trong TypeScript. Các thành viên riêng không được biên dịch ở chế độ riêng tư hoặc chỉ đọc (từ bên ngoài). Chỉ cần tuyên bố như một tài sản (công cộng) khác. (ES5).
Dominik

2
Làm thế nào để bạn viết phương pháp riêng tư với điều này? Tôi có thể làm điều này : #beep() {}; và điều này : async #bzzzt() {}?
Toàn cảnh

277

Câu trả lời ngắn, không, không có hỗ trợ riêng cho các thuộc tính riêng với các lớp ES6.

Nhưng bạn có thể bắt chước hành vi đó bằng cách không gắn các thuộc tính mới vào đối tượng, mà giữ chúng bên trong một hàm tạo của lớp, và sử dụng getters và setters để tiếp cận các thuộc tính ẩn. Lưu ý rằng các getters và setters được xác định lại trên mỗi phiên bản mới của lớp.

ES6

class Person {
    constructor(name) {
        var _name = name
        this.setName = function(name) { _name = name; }
        this.getName = function() { return _name; }
    }
}

ES5

function Person(name) {
    var _name = name
    this.setName = function(name) { _name = name; }
    this.getName = function() { return _name; }
}

1
Tôi thích giải pháp này tốt nhất. Tôi đồng ý rằng nó không nên được sử dụng để nhân rộng nhưng nó hoàn hảo cho các lớp thường sẽ chỉ được khởi tạo một lần mỗi lần bao gồm.
Blake Regalia

2
Ngoài ra, bạn đang xác định lại mọi thành phần duy nhất của lớp này mỗi khi tạo mới.
Quentin Roy

10
Điều này thật kỳ lạ! Trong ES6, bạn đang tạo ra nhiều "kim tự tháp đóng" hơn trước ES6! Xác định các hàm trong đó một hàm tạo trông xấu hơn so với trong ví dụ ES5 ở trên.
Kokodoko

1
Vì OP đặc biệt hỏi về các lớp ES6, cá nhân tôi nghĩ rằng đây là một giải pháp kém, mặc dù về mặt kỹ thuật nó hoạt động. Hạn chế chính là bây giờ mọi phương thức lớp sử dụng các biến riêng tư phải được khai báo bên trong hàm tạo, làm suy yếu nghiêm trọng các lợi thế của việc có classcú pháp ở vị trí đầu tiên.
NanoWizard

10
Tất cả điều này không được giới thiệu. Bây giờ làm thế nào để bạn làm cho các tài sản getNamesetNametư nhân?
aij

195

Để mở rộng câu trả lời của @ loganfsmyth:

Dữ liệu thực sự riêng tư duy nhất trong JavaScript vẫn là các biến có phạm vi. Bạn không thể có các thuộc tính riêng theo nghĩa các thuộc tính được truy cập bên trong giống như các thuộc tính công cộng, nhưng bạn có thể sử dụng các biến có phạm vi để lưu trữ dữ liệu riêng tư.

Biến phạm vi

Cách tiếp cận ở đây là sử dụng phạm vi của hàm xây dựng, là riêng tư, để lưu trữ dữ liệu riêng tư. Để các phương thức có quyền truy cập vào dữ liệu riêng tư này, chúng cũng phải được tạo trong hàm tạo, nghĩa là bạn đang tạo lại chúng với mọi trường hợp. Đây là một hình phạt hiệu suất và bộ nhớ, nhưng một số người tin rằng hình phạt là chấp nhận được. Có thể tránh bị phạt đối với các phương thức không cần truy cập vào dữ liệu riêng tư bằng cách thêm chúng vào nguyên mẫu như bình thường.

Thí dụ:

function Person(name) {
  let age = 20; // this is private
  this.name = name; // this is public

  this.greet = function () {
    // here we can access both name and age
    console.log(`name: ${this.name}, age: ${age}`);
  };
}

let joe = new Person('Joe');
joe.greet();

// here we can access name but not age

WeakMap phạm vi

WeakMap có thể được sử dụng để tránh hiệu suất và hình phạt bộ nhớ của phương pháp tiếp cận trước đó. WeakMaps liên kết dữ liệu với các Đối tượng (ở đây, ví dụ) theo cách mà nó chỉ có thể được truy cập bằng WeakMap đó. Vì vậy, chúng tôi sử dụng phương thức biến phạm vi để tạo WeakMap riêng tư, sau đó sử dụng WeakMap đó để truy xuất dữ liệu riêng tư được liên kết với this. Phương pháp này nhanh hơn phương thức biến trong phạm vi bởi vì tất cả các phiên bản của bạn có thể chia sẻ một WeakMap duy nhất, vì vậy bạn không cần phải tạo lại các phương thức chỉ để chúng truy cập vào WeakMaps của riêng chúng.

Thí dụ:

let Person = (function () {
  let privateProps = new WeakMap();

  class Person {
    constructor(name) {
      this.name = name; // this is public
      privateProps.set(this, {age: 20}); // this is private
    }

    greet() {
      // Here we can access both name and age
      console.log(`name: ${this.name}, age: ${privateProps.get(this).age}`);
    }
  }

  return Person;
})();

let joe = new Person('Joe');
joe.greet();

// here we can access joe's name but not age

Ví dụ này sử dụng một Object để sử dụng một WeakMap cho nhiều thuộc tính riêng tư; bạn cũng có thể sử dụng nhiều WeakMaps và sử dụng chúng như age.set(this, 20), hoặc viết một trình bao bọc nhỏ và sử dụng nó theo cách khác, như thế nào privateProps.set(this, 'age', 0).

Sự riêng tư của phương pháp này về mặt lý thuyết có thể bị phá vỡ bằng cách giả mạo WeakMapđối tượng toàn cầu . Điều đó nói rằng, tất cả JavaScript có thể bị phá vỡ bởi toàn cầu. Mã của chúng tôi đã được xây dựng dựa trên giả định rằng điều này không xảy ra.

(Phương pháp này cũng có thể được thực hiện với Map, nhưng WeakMaptốt hơn vì Mapsẽ tạo ra rò rỉ bộ nhớ trừ khi bạn rất cẩn thận và vì mục đích này, hai phương thức này không khác nhau.)

Nửa câu trả lời: Biểu tượng có phạm vi

Biểu tượng là một loại giá trị nguyên thủy có thể dùng làm tên thuộc tính. Bạn có thể sử dụng phương pháp biến phạm vi để tạo Biểu tượng riêng, sau đó lưu trữ dữ liệu riêng tư tại this[mySymbol].

Quyền riêng tư của phương pháp này có thể bị vi phạm bằng cách sử dụng Object.getOwnPropertySymbols, nhưng hơi khó thực hiện.

Thí dụ:

let Person = (function () {
  let ageKey = Symbol();

  class Person {
    constructor(name) {
      this.name = name; // this is public
      this[ageKey] = 20; // this is intended to be private
    }

    greet() {
      // Here we can access both name and age
      console.log(`name: ${this.name}, age: ${this[ageKey]}`);
    }
  }

  return Person;
})();

let joe = new Person('Joe');
joe.greet();

// Here we can access joe's name and, with a little effort, age. ageKey is
// not in scope, but we can obtain it by listing all Symbol properties on
// joe with `Object.getOwnPropertySymbols(joe)`.

Nửa câu trả lời: Dấu gạch dưới

Mặc định cũ, chỉ sử dụng một tài sản công cộng có tiền tố gạch dưới. Mặc dù không phải là một tài sản riêng dưới bất kỳ hình thức nào, nhưng quy ước này đủ phổ biến để thực hiện tốt công việc truyền thông rằng độc giả nên coi tài sản là riêng tư, thường hoàn thành công việc. Để đổi lấy sự sai sót này, chúng ta có một cách tiếp cận dễ đọc hơn, dễ gõ hơn và nhanh hơn.

Thí dụ:

class Person {
  constructor(name) {
    this.name = name; // this is public
    this._age = 20; // this is intended to be private
  }

  greet() {
    // Here we can access both name and age
    console.log(`name: ${this.name}, age: ${this._age}`);
  }
}

let joe = new Person('Joe');
joe.greet();

// Here we can access both joe's name and age. But we know we aren't
// supposed to access his age, which just might stop us.

Phần kết luận

Kể từ ES2017, vẫn không có cách hoàn hảo để làm tài sản riêng. Phương pháp khác nhau có ưu và nhược điểm. Các biến phạm vi là thực sự riêng tư; WeakMaps có phạm vi rất riêng tư và thực tế hơn các biến có phạm vi; Biểu tượng phạm vi là hợp lý riêng tư và thực tế hợp lý; dấu gạch dưới thường đủ riêng tư và rất thực tế.


7
Đoạn mã ví dụ đầu tiên ("biến có phạm vi") là một antipotype tổng - mỗi đối tượng được trả về sẽ có một lớp khác nhau. Đừng làm vậy. Nếu bạn muốn các phương thức đặc quyền, tạo chúng trong hàm tạo.
Bergi

1
Việc bao bọc một lớp bên trong một hàm dường như đánh bại toàn bộ mục đích sử dụng các lớp ở vị trí đầu tiên. Nếu bạn đã sử dụng hàm để tạo một thể hiện, bạn cũng có thể đặt tất cả các thành viên riêng tư / công khai của mình bên trong chức năng đó và quên đi toàn bộ từ khóa của lớp.
Kokodoko

2
@Bergi @Kokodoko Tôi đã chỉnh sửa cách tiếp cận biến phạm vi để nhanh hơn một chút và không bị hỏng instanceof. Tôi thừa nhận tôi đã nghĩ đến cách tiếp cận đó chỉ bao gồm vì sự hoàn thiện và đáng lẽ nên suy nghĩ nhiều hơn về khả năng thực sự của nó.
tristan

1
Giải thích tuyệt vời! Tôi vẫn ngạc nhiên khi ES6 thực sự khiến việc mô phỏng một biến riêng tư trở nên khó khăn hơn, trong ES5 bạn chỉ có thể sử dụng var và điều này bên trong một chức năng để mô phỏng riêng tư và công khai.
Kokodoko

2
@Kokodoko Nếu bạn phân phối với lớp và chỉ cần đặt mọi thứ vào hàm, bạn cũng sẽ phải hoàn nguyên để thực hiện kế thừa bằng phương thức nguyên mẫu. Sử dụng phần mở rộng trên các lớp là một cách tiếp cận sạch hơn, vì vậy sử dụng một lớp bên trong một hàm là hoàn toàn chấp nhận được.
AndroidDev

117

Cập nhật: Một đề xuất với cú pháp đẹp hơn đang được thực hiện. Đóng góp được chào đón.


Có, có - để truy cập phạm vi trong các đối tượng - ES6 giới thiệu Symbols .

Các biểu tượng là duy nhất, bạn không thể có quyền truy cập từ bên ngoài ngoại trừ sự phản chiếu (như tư nhân trong Java / C #) nhưng bất kỳ ai có quyền truy cập vào một biểu tượng ở bên trong đều có thể sử dụng nó để truy cập khóa:

var property = Symbol();
class Something {
    constructor(){
        this[property] = "test";
    }
}

var instance = new Something();

console.log(instance.property); //=> undefined, can only access with access to the Symbol

6
Bạn không thể sử dụng Object.getOwnPropertySymbols? ;)
Qantas 94 Nặng

41
@BenjaminGruenbaum: Rõ ràng Biểu tượng không còn đảm bảo quyền riêng tư thực sự: stackoverflow.com/a/22280202/1282216
d13

28
@trusktr qua khóa thre? Không thông qua các biểu tượng? Đúng. Rất giống như cách bạn có thể sử dụng sự phản chiếu trong các ngôn ngữ như C # và Java để truy cập các trường riêng. Công cụ sửa đổi truy cập không phải là về bảo mật - chúng là về sự rõ ràng của ý định.
Benjamin Gruenbaum

9
Có vẻ như việc sử dụng Biểu tượng tương tự như thực hiện const myPrivateMethod = Math.random(); Something.prototype[''+myPrivateMethod] = function () { ... } new Something()[''+myPrivateMethod]();. Đây thực sự không phải là quyền riêng tư, nó tối nghĩa, theo nghĩa của JavaScript truyền thống. Tôi sẽ xem xét JavaScript "riêng tư" có nghĩa là sử dụng các bao đóng để đóng gói các biến. Những biến số này do đó không thể truy cập thông qua sự phản ánh.
trusktr

13
Ngoài ra, tôi cảm thấy rằng việc sử dụng privateprotectedtừ khóa sẽ sạch hơn rất nhiều so với Symbolhoặc Name. Tôi thích ký hiệu chấm hơn là ký hiệu khung. Tôi muốn tiếp tục sử dụng một dấu chấm cho những thứ riêng tư. this.privateVar
trusktr

33

Câu trả lời là không". Nhưng bạn có thể tạo quyền truy cập riêng vào các thuộc tính như thế này:

(Đề xuất rằng các Biểu tượng có thể được sử dụng để đảm bảo quyền riêng tư là đúng trong phiên bản trước của thông số ES6 nhưng không còn như vậy nữa: https://mail.mozilla.org/pipermail/es-discuss/2014-Janftime/035604. htmlhttps://stackoverflow.com/a/22280202/1282216 . Để thảo luận lâu hơn về Biểu tượng và quyền riêng tư, hãy xem: https://curiosity-driven.org/private-properies-in-javascript )


6
-1, điều này không trả lời câu hỏi của bạn thực sự. (Bạn cũng có thể sử dụng các bao đóng với IIFE trong ES5). Các thuộc tính riêng tư được liệt kê thông qua sự phản ánh trong hầu hết các ngôn ngữ (Java, C #, v.v.). Quan điểm của tài sản tư nhân là truyền đạt ý định cho những người lập kế hoạch khác và không thực thi bảo mật.
Benjamin Gruenbaum

1
@BenjaminGruenbaum, tôi biết, tôi ước mình có câu trả lời tốt hơn, tôi cũng không hài lòng với nó.
d13

Tôi nghĩ các biểu tượng vẫn là một cách hợp lệ để đạt được các thành viên không thể tiếp cận khi ở trong môi trường lập trình. Vâng, chúng vẫn có thể được tìm thấy nếu bạn thực sự muốn, nhưng đó không phải là vấn đề phải không? Bạn không nên lưu trữ thông tin nhạy cảm trong đó, nhưng dù sao bạn cũng không nên làm điều đó trong mã phía máy khách. Nhưng nó hoạt động cho mục đích che giấu một tài sản hoặc phương thức từ một lớp bên ngoài.
Kokodoko

Sử dụng các biến trong phạm vi của một mô-đun để thay thế cho các thuộc tính riêng tư trong một lớp sẽ dẫn đến một singleton.behavior hoặc hành vi similart thành các thuộc tính statitc. Các tính năng của vars sẽ được chia sẻ.
Adrian Moisa

30

Cách duy nhất để có được quyền riêng tư thực sự trong JS là thông qua phạm vi, vì vậy không có cách nào để có một thuộc tính là thành viên thissẽ chỉ có thể truy cập được trong thành phần. Cách tốt nhất để lưu trữ dữ liệu thực sự riêng tư trong ES6 là với WeakMap.

const privateProp1 = new WeakMap();
const privateProp2 = new WeakMap();

class SomeClass {
  constructor() {
    privateProp1.set(this, "I am Private1");
    privateProp2.set(this, "I am Private2");

    this.publicVar = "I am public";
    this.publicMethod = () => {
      console.log(privateProp1.get(this), privateProp2.get(this))
    };        
  }

  printPrivate() {
    console.log(privateProp1.get(this));
  }
}

Rõ ràng đây là một thứ có lẽ chậm và chắc chắn xấu, nhưng nó mang lại sự riêng tư.

Hãy nhớ rằng NGAY CẢ NÀY không hoàn hảo, vì Javascript rất năng động. Ai đó vẫn có thể làm

var oldSet = WeakMap.prototype.set;
WeakMap.prototype.set = function(key, value){
    // Store 'this', 'key', and 'value'
    return oldSet.call(this, key, value);
};

để bắt các giá trị khi chúng được lưu trữ, vì vậy nếu bạn muốn cẩn thận hơn, bạn cần nắm bắt một tham chiếu cục bộ .set.getsử dụng rõ ràng thay vì dựa vào nguyên mẫu có thể ghi đè.

const {set: WMSet, get: WMGet} = WeakMap.prototype;

const privateProp1 = new WeakMap();
const privateProp2 = new WeakMap();

class SomeClass {
  constructor() {
    WMSet.call(privateProp1, this, "I am Private1");
    WMSet.call(privateProp2, this, "I am Private2");

    this.publicVar = "I am public";
    this.publicMethod = () => {
      console.log(WMGet.call(privateProp1, this), WMGet.call(privateProp2, this))
    };        
  }

  printPrivate() {
    console.log(WMGet.call(privateProp1, this));
  }
}

3
Theo đề xuất, bạn có thể tránh sử dụng một bản đồ yếu cho mỗi thuộc tính bằng cách sử dụng một đối tượng làm giá trị. Bằng cách này, bạn cũng có thể giảm số lượng bản đồ getxuống còn một cho mỗi phương thức (ví dụ const _ = privates.get(this); console.log(_.privateProp1);).
Quentin Roy

Yup, đó hoàn toàn là một lựa chọn. Tôi chủ yếu đi với điều này vì nó ánh xạ trực tiếp hơn đến những gì người dùng sẽ viết khi sử dụng các thuộc tính thực.
loganfsmyth

@loganfsmyth const myObj = new SomeClass(); console.log(privateProp1.get(myObj)) // "I am Private1"có nghĩa là tài sản của bạn là riêng tư hay không?
Barbu Barbu

2
Để làm việc đó, mã truy cập vào thuộc tính sẽ cần quyền truy cập vào đối tượng WeakMap, thường sẽ nằm trong phạm vi của một mô-đun và không thể truy cập
loganfsmyth

22

Để tham khảo trong tương lai của những người tìm kiếm khác, tôi hiện đang nghe rằng khuyến nghị là sử dụng WeakMaps để giữ dữ liệu riêng tư.

Dưới đây là một ví dụ rõ ràng hơn, làm việc:

function storePrivateProperties(a, b, c, d) {
  let privateData = new WeakMap;
  // unique object as key, weak map can only accept object as key, when key is no longer referened, garbage collector claims the key-value 
  let keyA = {}, keyB = {}, keyC = {}, keyD = {};

  privateData.set(keyA, a);
  privateData.set(keyB, b);
  privateData.set(keyC, c);
  privateData.set(keyD, d);

  return {
    logPrivateKey(key) {
      switch(key) {
      case "a":
        console.log(privateData.get(keyA));
        break;
      case "b":
        console.log(privateData.get(keyB));
        break;
      case "c":
        console.log(privateData.get(keyC));
        break;
      case "d":
        console.log(privateData.set(keyD));
        break;
      default:
        console.log(`There is no value for ${key}`)
      }
    }
  }
}

20
Hãy lưu ý rằng các tính chất này là tĩnh.
Michael Theriot

8
Tôi đã không đánh giá thấp bạn nhưng ví dụ sơ đồ yếu của bạn là hoàn toàn sai.
Benjamin Gruenbaum

4
Cụ thể - Bạn đang chia sẻ dữ liệu giữa tất cả các phiên bản lớp và không phải mỗi phiên bản - ít nhất tôi có thể sửa nó không?
Benjamin Gruenbaum

1
Thật vậy, sơ đồ yếu cần được đính kèm với một thể hiện nhất định. Xem fitzgeraldnick.com/weblog/53 để biết ví dụ.
mở rộng

2
Theo MDN, các loại dữ liệu nguyên thủy như Biểu tượng không được phép làm khóa WeakMap. Tài liệu WeakMap MDN
leepowell

12

Phụ thuộc vào người bạn hỏi :-)

Không có privatesửa đổi thuộc tính được bao gồm trong đề xuất các lớp tối thiểu mà dường như đã đưa nó vào dự thảo hiện tại .

Tuy nhiên, có thể có sự hỗ trợ cho các tên riêng tư , cho phép các thuộc tính riêng - và có lẽ chúng cũng có thể được sử dụng trong các định nghĩa lớp.


3
Rất khó khả năng các tên riêng tư sẽ biến nó thành ES6, mặc dù họ đang nghĩ đến một dạng riêng tư nào đó cho ES7.
Qantas 94 Nặng

@ Qantas94Heavy cả tên riêng và giá trị chuỗi duy nhất đã được thay thế bởi các Biểu tượng từ những gì tôi hiểu.
Benjamin Gruenbaum

Vâng, nó có thể sẽ trở thành Biểu tượng. Tuy nhiên, afaik các "biểu tượng" hiện có trong thông số kỹ thuật chỉ được sử dụng để mô tả các thuộc tính bên trong như [[nguyên mẫu]], và không có cách nào để tạo và sử dụng chúng trong mã người dùng. Bạn có biết một số tài liệu?
Bergi

Tôi chỉ nhận ra rằng các mô-đun có thể được sử dụng để thiết lập quyền riêng tư. Kết hợp với các Biểu tượng có thể là tất cả những gì bạn cần ...?
d13

1
@Cody: Toàn bộ mã mô-đun của bạn dù sao cũng có phạm vi riêng trong ES6, không cần IEFE. Và vâng, các biểu tượng được dành cho mục đích duy nhất (tránh va chạm), không phải sự riêng tư.
Bergi

10

Sử dụng các mô-đun ES6 (ban đầu được đề xuất bởi @ d13) hoạt động tốt với tôi. Nó không bắt chước các thuộc tính riêng tư một cách hoàn hảo, nhưng ít nhất bạn có thể tin tưởng rằng các thuộc tính riêng tư sẽ không bị rò rỉ bên ngoài lớp học của bạn. Đây là một ví dụ:

một cái gì đó

let _message = null;
const _greet = name => {
  console.log('Hello ' + name);
};

export default class Something {
  constructor(message) {
    _message = message;
  }

  say() {
    console.log(_message);
    _greet('Bob');
  }
};

Sau đó, mã tiêu thụ có thể trông như thế này:

import Something from './something.js';

const something = new Something('Sunny day!');
something.say();
something._message; // undefined
something._greet(); // exception

Cập nhật (Quan trọng):

Như @DanyalAytekin đã nêu trong các nhận xét, các thuộc tính riêng tư này là tĩnh, do đó có phạm vi toàn cầu. Chúng sẽ hoạt động tốt khi làm việc với Singletons, nhưng phải cẩn thận cho các đối tượng thoáng qua. Mở rộng ví dụ trên:

import Something from './something.js';
import Something2 from './something.js';

const a = new Something('a');
a.say(); // a

const b = new Something('b');
b.say(); // b

const c = new Something2('c');
c.say(); // c

a.say(); // c
b.say(); // c
c.say(); // c

4
Tốt cho private static.
Danyal Aytekin

@DanyalAytekin: đó là một điểm rất tốt. Các thuộc tính riêng tư là tĩnh nên toàn cầu trong phạm vi. Tôi đã cập nhật câu trả lời của mình để phản ánh điều này.
Johnny Oshika

Càng tìm hiểu về lập trình chức năng (đặc biệt là Elm và Haskell), tôi càng tin rằng các lập trình viên JS sẽ được hưởng lợi từ cách tiếp cận dựa trên mô-đun đến "mô đun hóa" thay vì dựa trên lớp OOP. Nếu chúng ta nghĩ về các mô-đun ES6 là nền tảng để xây dựng các ứng dụng và quên hoàn toàn các lớp, tôi tin rằng chúng ta có thể kết thúc với các ứng dụng tốt hơn nhiều về tổng thể. Bất kỳ người dùng Elm hoặc Haskell có kinh nghiệm có thể nhận xét về phương pháp này?
d13

1
Trong bản cập nhật, thứ hai a.say(); // anên làb.say(); // b
Grokky

let _message = nullCách thử , không hay lắm, khi gọi hàm tạo nhiều lần, nó rối tung lên.
Littlee

9

Hoàn thành @ d13 và các nhận xét của @ johnny-oshika và @DanyalAytekin:

Tôi đoán trong ví dụ được cung cấp bởi @ johnny-oshika, chúng ta có thể sử dụng các hàm bình thường thay vì các hàm mũi tên và sau đó .bindchúng với đối tượng hiện tại cộng với một _privatesđối tượng làm tham số được xử lý:

một cái gì đó

function _greet(_privates) {
  return 'Hello ' + _privates.message;
}

function _updateMessage(_privates, newMessage) {
  _privates.message = newMessage;
}

export default class Something {
  constructor(message) {
    const _privates = {
      message
    };

    this.say = _greet.bind(this, _privates);
    this.updateMessage = _updateMessage.bind(this, _privates);
  }
}

main.js

import Something from './something.js';

const something = new Something('Sunny day!');

const message1 = something.say();
something.updateMessage('Cloudy day!');
const message2 = something.say();

console.log(message1 === 'Hello Sunny day!');  // true
console.log(message2 === 'Hello Cloudy day!');  // true

// the followings are not public
console.log(something._greet === undefined);  // true
console.log(something._privates === undefined);  // true
console.log(something._updateMessage === undefined);  // true

// another instance which doesn't share the _privates
const something2 = new Something('another Sunny day!');

const message3 = something2.say();

console.log(message3 === 'Hello another Sunny day!'); // true

Lợi ích tôi có thể nghĩ đến:

  • chúng ta có thể có các phương thức riêng tư ( _greet_updateMessagehoạt động như các phương thức riêng tư miễn là chúng ta không exporttham khảo)
  • Mặc dù chúng không có trên nguyên mẫu, các phương thức được đề cập ở trên sẽ tiết kiệm bộ nhớ vì các thể hiện được tạo một lần, bên ngoài lớp (trái ngược với việc xác định chúng trong hàm tạo)
  • chúng tôi không rò rỉ bất kỳ toàn cầu vì chúng tôi đang ở trong một mô-đun
  • chúng ta cũng có thể có các thuộc tính riêng bằng cách sử dụng _privatesđối tượng bị ràng buộc

Một số nhược điểm tôi có thể nghĩ đến:

Một đoạn mã đang chạy có thể được tìm thấy ở đây: http://www.webpackbin.com/NJgI5J8lZ


8

Có - bạn có thể tạo thuộc tính đóng gói , nhưng điều đó không được thực hiện với các công cụ sửa đổi truy cập (công khai | riêng tư) ít nhất là không với ES6.

Đây là một ví dụ đơn giản về cách thực hiện với ES6:

1 Tạo lớp bằng từ lớp

2 Bên trong hàm tạo của nó khai báo biến có phạm vi khối bằng cách sử dụng let OR const từ dành riêng -> vì chúng là phạm vi khối nên chúng không thể được truy cập từ bên ngoài (được đóng gói)

3 Để cho phép một số điều khiển truy cập (setters | getters) cho các biến đó, bạn có thể khai báo phương thức cá thể bên trong hàm tạo của nó bằng cách sử dụng: this.methodName=function(){}cú pháp

"use strict";
    class Something{
        constructor(){
            //private property
            let property="test";
            //private final (immutable) property
            const property2="test2";
            //public getter
            this.getProperty2=function(){
                return property2;
            }
            //public getter
            this.getProperty=function(){
                return property;
            }
            //public setter
            this.setProperty=function(prop){
                property=prop;
            }
        }
    }

Bây giờ hãy kiểm tra nó:

var s=new Something();
    console.log(typeof s.property);//undefined 
    s.setProperty("another");//set to encapsulated `property`
    console.log(s.getProperty());//get encapsulated `property` value
    console.log(s.getProperty2());//get encapsulated immutable `property2` value

1
Đây là (hiện tại) là giải pháp duy nhất cho vấn đề này mặc dù thực tế là tất cả các phương thức được khai báo trong hàm tạo đều được phân phối lại cho mỗi phiên bản của lớp. Đây là một ý tưởng tồi liên quan đến hiệu suất và việc sử dụng bộ nhớ. Các phương thức lớp nên được khai báo bên ngoài phạm vi hàm tạo.
Freezystem

@Freezystem Đầu tiên: Đầu tiên là các phương thức cá thể (không phải phương thức Class). Câu hỏi thứ hai của OP là: _ Làm thế nào tôi có thể ngăn truy cập vào instance.property?_ và câu trả lời của tôi là: một ví dụ về cách ... Thứ ba nếu bạn có ý tưởng nào tốt hơn - hãy nghe nó
Nikita Kurtin

1
Tôi không nói rằng bạn đã sai, tôi đã nói rằng giải pháp của bạn là sự thỏa hiệp tốt nhất để đạt được biến riêng tư mặc dù thực tế là một bản sao của mỗi phương thức cá thể được tạo ra mỗi khi bạn gọi new Something();vì các phương thức của bạn được khai báo trong hàm tạo để có quyền truy cập vào các phương thức này biến riêng. Điều đó có thể gây ra tiêu thụ nhiều bộ nhớ nếu bạn tạo nhiều thể hiện của lớp, do đó, vấn đề về hiệu năng. Các phương thức nên được khai báo bên ngoài phạm vi hàm tạo. Nhận xét của tôi là một lời giải thích về nhược điểm giải pháp của bạn hơn là một lời chỉ trích.
Freezystem

1
Nhưng nó không phải là thực tiễn xấu để xác định toàn bộ lớp của bạn bên trong hàm tạo? Bây giờ chúng ta không "hack" javascript phải không? Chỉ cần nhìn vào bất kỳ ngôn ngữ lập trình OOP nào khác và bạn sẽ thấy rằng hàm tạo không có nghĩa là xác định một lớp.
Kokodoko

1
Vâng, đó là những gì tôi muốn nói, và giải pháp của bạn hoạt động! Tôi chỉ nói rằng nói chung tôi rất ngạc nhiên khi ES6 đã thêm một từ khóa 'lớp', nhưng đã loại bỏ giải pháp tinh tế khi làm việc với var và điều này, để đạt được sự đóng gói.
Kokodoko

8

Một cách tiếp cận khác nhau để "riêng tư"

Thay vì đấu tranh chống lại thực tế là khả năng hiển thị riêng tư hiện không có sẵn trong ES6, tôi đã quyết định thực hiện một cách tiếp cận thực tế hơn, chỉ tốt nếu IDE của bạn hỗ trợ JSDoc (ví dụ: Webstorm). Ý tưởng là sử dụng @privatethẻ . Theo như sự phát triển, IDE sẽ ngăn bạn truy cập bất kỳ thành viên tư nhân nào từ bên ngoài lớp của nó. Hoạt động khá tốt đối với tôi và nó thực sự hữu ích trong việc ẩn các phương thức nội bộ, vì vậy tính năng tự động hoàn thành chỉ cho tôi biết lớp học thực sự có ý nghĩa gì để phơi bày. Đây là một ví dụ:

tự động hoàn tất chỉ hiển thị công cụ


1
Vấn đề là, chúng tôi sẽ không truy cập các biến riêng tư trên Trình chỉnh sửa, chúng tôi sẽ không bảo vệ các biến riêng tư từ bên ngoài - Và đó là, những gì công khai / riêng tư làm. Nếu mã của bạn kết thúc, bạn có thể truy cập (và suy nghĩ quan trọng: ghi đè ) các biến này từ bên ngoài lớp. @privateNhận xét của bạn không thể ngăn chặn những điều này, nó chỉ là một Tính năng để tạo tài liệu và IDE của bạn.
Adrian Preuss

Vâng, tôi nhận thức được điều đó. Nó chỉ đủ với tôi và có thể đủ cho những người khác ở ngoài kia. Tôi biết nó không thực sự làm cho các biến của tôi riêng tư; nó chỉ cảnh báo tôi không nên thử và truy cập nó từ bên ngoài (tất nhiên, chỉ khi tất cả nhóm của tôi và tôi đang sử dụng một IDE hỗ trợ tính năng này). Javascript (và các ngôn ngữ khác, như Python) không được thiết kế với các cấp độ truy cập. Mọi người làm tất cả mọi thứ để bằng cách nào đó thực hiện chức năng đó, nhưng cuối cùng chúng tôi cuối cùng chỉ hack ngôn ngữ để đạt được điều đó. Tôi quyết định đi theo cách tiếp cận "tự nhiên" hơn, nếu bạn muốn.
Lucio Paiva

6

Bản đồ yếu

  • được hỗ trợ trong IE11 (Biểu tượng thì không)
  • hard-private (đạo cụ sử dụng Symbols là soft-private do Object.getOwnPropertySymbols)
  • có thể trông thực sự sạch sẽ (không giống như các bao đóng yêu cầu tất cả các đạo cụ và phương thức trong hàm tạo)

Đầu tiên, xác định hàm để bọc WeakMap:

function Private() {
  const map = new WeakMap();
  return obj => {
    let props = map.get(obj);
    if (!props) {
      props = {};
      map.set(obj, props);
    }
    return props;
  };
}

Sau đó, xây dựng một tài liệu tham khảo bên ngoài lớp học của bạn:

const p = new Private();

class Person {
  constructor(name, age) {
    this.name = name;
    p(this).age = age; // it's easy to set a private variable
  }

  getAge() {
    return p(this).age; // and get a private variable
  }
}

Lưu ý: lớp không được IE11 hỗ trợ, nhưng nó có vẻ sạch hơn trong ví dụ.


6

Ồ, rất nhiều giải pháp kỳ lạ! Tôi thường không quan tâm đến quyền riêng tư nên tôi sử dụng "quyền riêng tư giả" như đã nói ở đây . Nhưng nếu quan tâm (nếu có một số yêu cầu đặc biệt cho điều đó) tôi sử dụng một cái gì đó như trong ví dụ này:

class jobImpl{
  // public
  constructor(name){
    this.name = name;
  }
  // public
  do(time){
    console.log(`${this.name} started at ${time}`);
    this.prepare();
    this.execute();
  }
  //public
  stop(time){
    this.finish();
    console.log(`${this.name} finished at ${time}`);
  }
  // private
  prepare(){ console.log('prepare..'); }
  // private
  execute(){ console.log('execute..'); }
  // private
  finish(){ console.log('finish..'); }
}

function Job(name){
  var impl = new jobImpl(name);
  return {
    do: time => impl.do(time),
    stop: time => impl.stop(time)
  };
}

// Test:
// create class "Job"
var j = new Job("Digging a ditch");
// call public members..
j.do("08:00am");
j.stop("06:00pm");

// try to call private members or fields..
console.log(j.name); // undefined
j.execute(); // error

Một cách thực hiện chức năng khác (hàm tạo) Job:

function Job(name){
  var impl = new jobImpl(name);
  this.do = time => impl.do(time),
  this.stop = time => impl.stop(time)
}

5

Cá nhân tôi thích đề xuất của toán tử liên kết :: và sau đó sẽ kết hợp nó với giải pháp @ d13 đã đề cập nhưng bây giờ hãy gắn bó với câu trả lời của @ d13 nơi bạn sử dụng exporttừ khóa cho lớp của mình và đặt các hàm riêng tư trong mô-đun.

có một giải pháp khó khăn hơn chưa được đề cập ở đây, đó là cách tiếp cận nhiều chức năng hơn và sẽ cho phép nó có tất cả các đạo cụ / phương thức riêng trong lớp.

Private.js

export const get = state => key => state[key];
export const set = state => (key,value) => { state[key] = value; }

Test.js

import { get, set } from './utils/Private'
export default class Test {
  constructor(initialState = {}) {
    const _set = this.set = set(initialState);
    const _get = this.get = get(initialState);

    this.set('privateMethod', () => _get('propValue'));
  }

  showProp() {
    return this.get('privateMethod')();
  }
}

let one = new Test({ propValue: 5});
let two = new Test({ propValue: 8});
two.showProp(); // 8
one.showProp(); // 5

ý kiến ​​về nó sẽ được đánh giá cao.


Nói chung tôi thích cách tiếp cận. Phản hồi: 1. bạn sẽ cần một mô-đun private.js khác nhau cho mỗi lớp để ngăn ngừa xung đột. 2. Tôi không thích tiềm năng làm cho hàm tạo thực sự dài bằng cách xác định nội tuyến từng phương thức riêng tư của bạn. 3. Sẽ thật tuyệt nếu tất cả các phương thức lớp nằm trong một tệp.
Doug Coburn

5

Tôi đã xem qua bài đăng này khi tìm kiếm thực tiễn tốt nhất cho "dữ liệu riêng tư cho các lớp". Nó đã được đề cập rằng một vài trong số các mẫu sẽ có vấn đề về hiệu suất.

Tôi tập hợp một vài bài kiểm tra jsperf dựa trên 4 mẫu chính từ cuốn sách trực tuyến "Khám phá ES6":

http://exploringjs.com/es6/ch_groupes.html#sec_private-data-for-groupes

Các xét nghiệm có thể được tìm thấy ở đây:

https://jsperf.com/private-data-for- classes

Trong Chrome 63.0.3239 / Mac OS X 10.11.6, các mẫu hoạt động tốt nhất là "Dữ liệu riêng tư thông qua môi trường của nhà xây dựng" và "Dữ liệu riêng tư thông qua quy ước đặt tên". Đối với tôi Safari hoạt động tốt cho WeakMap nhưng Chrome không tốt lắm.

Tôi không biết tác động của bộ nhớ, nhưng mô hình cho "môi trường xây dựng" mà một số người đã cảnh báo sẽ là một vấn đề về hiệu năng rất hiệu quả.

4 mẫu cơ bản là:

Dữ liệu riêng tư thông qua môi trường xây dựng

class Countdown {
    constructor(counter, action) {
        Object.assign(this, {
            dec() {
                if (counter < 1) return;
                counter--;
                if (counter === 0) {
                    action();
                }
            }
        });
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

Dữ liệu riêng tư thông qua môi trường constructor 2

class Countdown {
    constructor(counter, action) {
        this.dec = function dec() {
            if (counter < 1) return;
            counter--;
            if (counter === 0) {
                action();
            }
        }
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

Dữ liệu riêng tư thông qua quy ước đặt tên

class Countdown {
    constructor(counter, action) {
        this._counter = counter;
        this._action = action;
    }
    dec() {
        if (this._counter < 1) return;
        this._counter--;
        if (this._counter === 0) {
            this._action();
        }
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

Dữ liệu riêng tư qua WeakMaps

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

Dữ liệu riêng tư thông qua các ký hiệu

const _counter = Symbol('counter');
const _action = Symbol('action');

class Countdown {
    constructor(counter, action) {
        this[_counter] = counter;
        this[_action] = action;
    }
    dec() {
        if (this[_counter] < 1) return;
        this[_counter]--;
        if (this[_counter] === 0) {
            this[_action]();
        }
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

4

Tôi tin rằng có thể có được 'tốt nhất của cả hai thế giới' bằng cách sử dụng các bao đóng bên trong các nhà xây dựng. Có hai biến thể:

Tất cả các thành viên dữ liệu là riêng tư

function myFunc() {
   console.log('Value of x: ' + this.x);
   this.myPrivateFunc();
}

function myPrivateFunc() {
   console.log('Enhanced value of x: ' + (this.x + 1));
}

class Test {
   constructor() {

      let internal = {
         x : 2,
      };
      
      internal.myPrivateFunc = myPrivateFunc.bind(internal);
      
      this.myFunc = myFunc.bind(internal);
   }
};

Một số thành viên là riêng tư

LƯU Ý: Điều này được thừa nhận là xấu xí. Nếu bạn biết một giải pháp tốt hơn, xin vui lòng chỉnh sửa phản hồi này.

function myFunc(priv, pub) {
   pub.y = 3; // The Test object now gets a member 'y' with value 3.
   console.log('Value of x: ' + priv.x);
   this.myPrivateFunc();
}

function myPrivateFunc() {
   pub.z = 5; // The Test object now gets a member 'z' with value 3.
   console.log('Enhanced value of x: ' + (priv.x + 1));
}

class Test {
   constructor() {
      
      let self = this;

      let internal = {
         x : 2,
      };
      
      internal.myPrivateFunc = myPrivateFunc.bind(null, internal, self);
      
      this.myFunc = myFunc.bind(null, internal, self);
   }
};


4

Trong thực tế, có thể sử dụng Symbols và Proxy. Bạn sử dụng các ký hiệu trong phạm vi lớp và đặt hai bẫy trong proxy: một bẫy cho nguyên mẫu lớp để Reflect.ownKeys (dụ) hoặc Object.getOwnPropertySymbols không cung cấp cho các ký hiệu của bạn, một cái khác dành cho chính hàm tạo vì vậy khi new ClassName(attrs)được gọi, thể hiện được trả về sẽ bị chặn và có các ký hiệu thuộc tính riêng bị chặn. Đây là mã:

const Human = (function() {
  const pet = Symbol();
  const greet = Symbol();

  const Human = privatizeSymbolsInFn(function(name) {
    this.name = name; // public
    this[pet] = 'dog'; // private 
  });

  Human.prototype = privatizeSymbolsInObj({
    [greet]() { // private
      return 'Hi there!';
    },
    revealSecrets() {
      console.log(this[greet]() + ` The pet is a ${this[pet]}`);
    }
  });

  return Human;
})();

const bob = new Human('Bob');

console.assert(bob instanceof Human);
console.assert(Reflect.ownKeys(bob).length === 1) // only ['name']
console.assert(Reflect.ownKeys(Human.prototype).length === 1 ) // only ['revealSecrets']


// Setting up the traps inside proxies:
function privatizeSymbolsInObj(target) { 
  return new Proxy(target, { ownKeys: Object.getOwnPropertyNames });
}

function privatizeSymbolsInFn(Class) {
  function construct(TargetClass, argsList) {
    const instance = new TargetClass(...argsList);
    return privatizeSymbolsInObj(instance);
  }
  return new Proxy(Class, { construct });
}

Reflect.ownKeys()hoạt động như vậy: Object.getOwnPropertyNames(myObj).concat(Object.getOwnPropertySymbols(myObj))đó là lý do tại sao chúng ta cần một cái bẫy cho các đối tượng này.


Cảm ơn, tôi sẽ thử các biểu tượng :) Từ tất cả các câu trả lời ở trên, có vẻ như cách đơn giản nhất để tạo một thành viên lớp không thể tiếp cận :)
Kokodoko

4

Ngay cả bản in cũng không thể làm được. Từ tài liệu của họ :

Khi một thành viên được đánh dấu là private, nó không thể được truy cập từ bên ngoài lớp chứa nó. Ví dụ:

class Animal {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}

new Animal("Cat").name; // Error: 'name' is private;

Nhưng được dịch trên sân chơi của họ, điều này mang lại:

var Animal = (function () {
    function Animal(theName) {
        this.name = theName;
    }
    return Animal;
}());
console.log(new Animal("Cat").name);

Vì vậy, từ khóa "riêng tư" của họ là không hiệu quả.


2
Chà, nó vẫn hiệu quả vì nó ngăn chặn lập trình "xấu", trong khi ở IDE. Nó cho bạn thấy những thành viên bạn nên và không nên sử dụng. Tôi nghĩ đó là lý do chính để sử dụng riêng tư và công cộng. (Ví dụ: khi bạn biên dịch C # thành mã máy, thì chế độ riêng tư vẫn ở chế độ riêng tư? Ai biết?). Khi đọc các câu trả lời khác, dường như việc sử dụng @Symbol cũng có thể khiến thành viên không thể truy cập được. Nhưng ngay cả Symbols vẫn có thể được tìm thấy từ bảng điều khiển.
Kokodoko

Có phải lỗi TypeScript xảy ra trong quá trình chuyển mã của TypeScript sang JavaScript không? (Giống như việc kiểm tra kiểu xảy ra vào thời gian chuyển đổi. Thay vì một số cơ chế riêng tư thời gian chạy.)
Eljay

4

Đến bữa tiệc này rất muộn nhưng tôi gặp phải câu hỏi OP trong một tìm kiếm vì vậy ... Vâng, bạn có thể có các thuộc tính riêng tư bằng cách gói khai báo lớp trong một bao đóng

Có một ví dụ về cách tôi có các phương thức riêng tư trong codepen này . Trong đoạn trích bên dưới, lớp Đăng ký có hai hàm 'riêng tư' processprocessCallbacks. Bất kỳ thuộc tính nào cũng có thể được thêm vào theo cách này và chúng được giữ kín thông qua việc sử dụng bao đóng. IMO Quyền riêng tư là một nhu cầu hiếm hoi nếu các mối quan tâm được phân tách rõ ràng và Javascript không cần phải trở nên cồng kềnh bằng cách thêm nhiều cú pháp khi đóng cửa gọn gàng thực hiện công việc.

const Subscribable = (function(){

  const process = (self, eventName, args) => {
    self.processing.set(eventName, setTimeout(() => processCallbacks(self, eventName, args)))};

  const processCallbacks = (self, eventName, args) => {
    if (self.callingBack.get(eventName).length > 0){
      const [nextCallback, ...callingBack] = self.callingBack.get(eventName);
      self.callingBack.set(eventName, callingBack);
      process(self, eventName, args);
      nextCallback(...args)}
    else {
      delete self.processing.delete(eventName)}};

  return class {
    constructor(){
      this.callingBack = new Map();
      this.processing = new Map();
      this.toCallbacks = new Map()}

    subscribe(eventName, callback){
      const callbacks = this.unsubscribe(eventName, callback);
      this.toCallbacks.set(eventName,  [...callbacks, callback]);
      return () => this.unsubscribe(eventName, callback)}  // callable to unsubscribe for convenience

    unsubscribe(eventName, callback){
      let callbacks = this.toCallbacks.get(eventName) || [];
      callbacks = callbacks.filter(subscribedCallback => subscribedCallback !== callback);
      if (callbacks.length > 0) {
        this.toCallbacks.set(eventName, callbacks)}
      else {
        this.toCallbacks.delete(eventName)}
      return callbacks}

    emit(eventName, ...args){
      this.callingBack.set(eventName, this.toCallbacks.get(eventName) || []);
      if (!this.processing.has(eventName)){
        process(this, eventName, args)}}}})();

Tôi thích cách tiếp cận này vì nó tách biệt mối quan tâm độc đáo và giữ mọi thứ thực sự riêng tư. Nhược điểm duy nhất là nhu cầu sử dụng 'bản thân' (hoặc một cái gì đó tương tự) để đề cập đến 'cái này' trong nội dung riêng tư.


4

Tôi nghĩ rằng câu trả lời của Benjamin có lẽ là tốt nhất cho hầu hết các trường hợp cho đến khi ngôn ngữ thực sự hỗ trợ các biến riêng tư rõ ràng.

Tuy nhiên, nếu vì lý do nào đó bạn cần ngăn chặn truy cập Object.getOwnPropertySymbols(), một phương pháp tôi đã xem xét sử dụng là đính kèm một thuộc tính duy nhất, không thể cấu hình, không thể đếm được, không thể ghi, có thể được sử dụng làm định danh thuộc tính cho từng đối tượng khi xây dựng (chẳng hạn như duy nhất Symbol, nếu bạn chưa có một số tài sản duy nhất khác như một id). Sau đó, chỉ cần giữ một bản đồ của các biến 'riêng tư' của từng đối tượng bằng cách sử dụng định danh đó.

const privateVars = {};

class Something {
    constructor(){
        Object.defineProperty(this, '_sym', {
            configurable: false,
            enumerable: false,
            writable: false,
            value: Symbol()
        });

        var myPrivateVars = {
            privateProperty: "I'm hidden"
        };

        privateVars[this._sym] = myPrivateVars;

        this.property = "I'm public";
    }

    getPrivateProperty() {
        return privateVars[this._sym].privateProperty;
    }

    // A clean up method of some kind is necessary since the
    // variables won't be cleaned up from memory automatically
    // when the object is garbage collected
    destroy() {
        delete privateVars[this._sym];
    }
}

var instance = new Something();
console.log(instance.property); //=> "I'm public"
console.log(instance.privateProperty); //=> undefined
console.log(instance.getPrivateProperty()); //=> "I'm hidden"

Ưu điểm tiềm năng của phương pháp này so với sử dụng WeakMapthời gian truy cập nhanh hơn nếu hiệu suất trở thành mối quan tâm.


1
Sửa lỗi cho tôi nếu tôi sai, nhưng mã này có bị rò rỉ bộ nhớ không vì privateVars vẫn sẽ lưu trữ các biến riêng tư của đối tượng ngay cả khi đối tượng đã bị hủy?
Russell Santos

@RussellSantos bạn đúng, giả sử các đối tượng sẽ cần phải được thu gom rác tại một số điểm. Cảm ơn bạn đã chỉ ra rằng. Trong ví dụ của tôi, tôi đã thêm một destroy()phương thức nên được gọi bằng mã sử dụng bất cứ khi nào một đối tượng cần được loại bỏ.
NanoWizard

4

Có hoàn toàn có thể, và khá dễ dàng quá. Điều này được thực hiện bằng cách hiển thị các biến và hàm riêng của bạn bằng cách trả về biểu đồ đối tượng nguyên mẫu trong hàm tạo. Điều này không có gì mới, nhưng hãy lấy một chút js foo để hiểu được sự thanh lịch của nó. Cách này không sử dụng phạm vi toàn cầu, hoặc yếu. Nó là một hình thức phản ánh được xây dựng trong ngôn ngữ. Tùy thuộc vào cách bạn tận dụng điều này; người ta có thể buộc một ngoại lệ làm gián đoạn ngăn xếp cuộc gọi hoặc chôn ngoại lệ đó thành một ngoại lệ undefined. Điều này được trình bày dưới đây và có thể đọc thêm về các tính năng này tại đây

class Clazz {
  constructor() {
    var _level = 1

    function _private(x) {
      return _level * x;
    }
    return {
      level: _level,
      public: this.private,
      public2: function(x) {
        return _private(x);
      },
      public3: function(x) {
        return _private(x) * this.public(x);
      },
    };
  }

  private(x) {
    return x * x;
  }
}

var clazz = new Clazz();

console.log(clazz._level); //undefined
console.log(clazz._private); // undefined
console.log(clazz.level); // 1
console.log(clazz.public(1)); //1
console.log(clazz.public2(2)); //2
console.log(clazz.public3(3)); //27
console.log(clazz.private(0)); //error


3
class Something {
  constructor(){
    var _property = "test";
    Object.defineProperty(this, "property", {
        get: function(){ return _property}
    });
  }
}

var instance = new Something();
console.log(instance.property); //=> "test"
instance.property = "can read from outside, but can't write";
console.log(instance.property); //=> "test"

2
Tốt nhất là tránh mã chỉ trả lời. Sẽ tốt hơn nếu bạn có thể giải thích cách mã của bạn trả lời câu hỏi của OP
Stewart_R

Đây thực sự là cách làm cho một biến chỉ đọc nhiều hơn một biến riêng tư. Một biến riêng không nên truy cập ra bên ngoài. console.log(instance.property)nên ném hoặc đưa cho bạn không xác định, không cho bạn "kiểm tra" lại.
oooyaya

3

Một cách khác tương tự như hai bài trước

class Example {
  constructor(foo) {

    // privates
    const self = this;
    this.foo = foo;

    // public interface
    return self.public;
  }

  public = {
    // empty data
    nodata: { data: [] },
    // noop
    noop: () => {},
  }

  // everything else private
  bar = 10
}

const test = new Example('FOO');
console.log(test.foo); // undefined
console.log(test.noop); // { data: [] }
console.log(test.bar); // undefined

2

Hầu hết các câu trả lời đều nói là không thể hoặc yêu cầu bạn sử dụng WeakMap hoặc Biểu tượng, đó là các tính năng ES6 có thể sẽ yêu cầu polyfill. Tuy nhiên có một cách khác! Kiểm tra này:

// 1. Create closure
var SomeClass = function() {
  // 2. Create `key` inside a closure
  var key = {};
  // Function to create private storage
  var private = function() {
    var obj = {};
    // return Function to access private storage using `key`
    return function(testkey) {
      if(key === testkey) return obj;
      // If `key` is wrong, then storage cannot be accessed
      console.error('Cannot access private properties');
      return undefined;
    };
  };
  var SomeClass = function() {
    // 3. Create private storage
    this._ = private();
    // 4. Access private storage using the `key`
    this._(key).priv_prop = 200;
  };
  SomeClass.prototype.test = function() {
    console.log(this._(key).priv_prop); // Using property from prototype
  };
  return SomeClass;
}();

// Can access private property from within prototype
var instance = new SomeClass();
instance.test(); // `200` logged

// Cannot access private property from outside of the closure
var wrong_key = {};
instance._(wrong_key); // undefined; error logged

Tôi gọi phương thức này là mẫu accessor . Ý tưởng thiết yếu là chúng ta có một bao đóng , một khóa bên trong bao đóng và chúng ta tạo một đối tượng riêng (trong hàm tạo) chỉ có thể được truy cập nếu bạn có khóa .

Nếu bạn quan tâm, bạn có thể đọc thêm về điều này trong bài viết của tôi . Sử dụng phương pháp này, bạn có thể tạo mỗi thuộc tính đối tượng không thể truy cập bên ngoài bao đóng. Do đó, bạn có thể sử dụng chúng trong constructor hoặc nguyên mẫu, nhưng không phải nơi nào khác. Tôi chưa thấy phương pháp này được sử dụng ở bất cứ đâu, nhưng tôi nghĩ nó thực sự mạnh mẽ.


Câu hỏi là về cách đạt được điều này trong các lớp ES6.
Michael Franzl

Bạn có thể sử dụng cùng một phương thức trong các lớp ES6. Các lớp ES6 chủ yếu chỉ là đường trên các chức năng như tôi đã trình bày trong ví dụ của mình. Rất có thể người đăng ban đầu đang sử dụng bộ chuyển mã, trong trường hợp đó, WeakMaps hoặc Symbols vẫn sẽ yêu cầu polyfill. Câu trả lời của tôi là hợp lệ bất kể.
guitarino


2

Tôi tìm thấy một giải pháp rất đơn giản, chỉ cần sử dụng Object.freeze(). Tất nhiên vấn đề là bạn không thể thêm gì để đối tượng sau đó.

class Cat {
    constructor(name ,age) {
        this.name = name
        this.age = age
        Object.freeze(this)
    }
}

let cat = new Cat('Garfield', 5)
cat.age = 6 // doesn't work, even throws an error in strict mode

này cũng sẽ vô hiệu hóa phương pháp setter nhưsetName(name) { this.name = name; }
ngakak

2

Tôi sử dụng mô hình này và nó luôn luôn làm việc cho tôi

class Test {
    constructor(data) {
        class Public {
            constructor(prv) {

                // public function (must be in constructor on order to access "prv" variable)
                connectToDb(ip) {
                    prv._db(ip, prv._err);
                } 
            }

            // public function w/o access to "prv" variable
            log() {
                console.log("I'm logging");
            }
        }

        // private variables
        this._data = data;
        this._err = function(ip) {
            console.log("could not connect to "+ip);
        }
    }

    // private function
    _db(ip, err) {
        if(!!ip) {
		    console.log("connected to "+ip+", sending data '"+this.data+"'");
			return true;
		}
        else err(ip);
    }
}



var test = new Test(10),
		ip = "185.167.210.49";
test.connectToDb(ip); // true
test.log(); // I'm logging
test._err(ip); // undefined
test._db(ip, function() { console.log("You have got hacked!"); }); // undefined


2

Trên thực tế có thể.
1. Đầu tiên, tạo lớp và trong hàm tạo trả về _publichàm được gọi .
2. Trong _publichàm được gọi, chuyển thistham chiếu (để có quyền truy cập vào tất cả các phương thức và đạo cụ riêng) và tất cả các đối số từ constructor (sẽ được truyền vào new Names())
3. Trong _publicphạm vi hàm cũng có Nameslớp có quyền truy cập vào this(_this ) tài liệu tham khảo của Nameslớp tư nhân

class Names {
  constructor() {
    this.privateProperty = 'John';
    return _public(this, arguments);
  }
  privateMethod() { }
}

const names = new Names(1,2,3);
console.log(names.somePublicMethod); //[Function]
console.log(names.publicProperty); //'Jasmine'
console.log(names.privateMethod); //undefined
console.log(names.privateProperty); //undefind

function _public(_this, _arguments) {
  class Names {
    constructor() {
      this.publicProperty = 'Jasmine';
      _this.privateProperty; //"John";
      _this.privateMethod; //[Function]
    }

    somePublicMethod() {
      _this.privateProperty; //"John";
      _this.privateMethod; //[Function]
    }

  }
  return new Names(..._arguments);
}

2

Bạn có thể thử https://www.npmjs.com/package/private-members

Gói này sẽ lưu các thành viên theo ví dụ.

const pvt = require('private-members');
const _ = pvt();

let Exemplo = (function () {    
    function Exemplo() {
        _(this).msg = "Minha Mensagem";
    }

    _().mensagem = function() {
        return _(this).msg;
    }

    Exemplo.prototype.showMsg = function () {
        let msg = _(this).mensagem();
        console.log(msg);
    };

    return Exemplo;
})();

module.exports = Exemplo;
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.