Truy cập các biến thành viên riêng từ các hàm do nguyên mẫu xác định


187

Có cách nào để tạo các biến riêng tư của người dùng (các định nghĩa trong hàm tạo), có sẵn cho các phương thức xác định nguyên mẫu không?

TestClass = function(){
    var privateField = "hello";
    this.nonProtoHello = function(){alert(privateField)};
};
TestClass.prototype.prototypeHello = function(){alert(privateField)};

Những công việc này:

t.nonProtoHello()

Nhưng điều này không:

t.prototypeHello()

Tôi đã quen với việc xác định các phương thức của mình bên trong hàm tạo, nhưng tôi đang di chuyển ra khỏi đó vì một vài lý do.



14
@ecampver, Ngoại trừ cái này đã được hỏi 2 năm trước ....
Pacerier

Câu trả lời:


191

Không, không có cách nào để làm điều đó. Điều đó về cơ bản sẽ được phạm vi ngược lại.

Các phương thức được xác định bên trong hàm tạo có quyền truy cập vào các biến riêng vì tất cả các hàm đều có quyền truy cập vào phạm vi mà chúng được xác định.

Các phương thức được xác định trên một nguyên mẫu không được xác định trong phạm vi của hàm tạo và sẽ không có quyền truy cập vào các biến cục bộ của hàm tạo.

Bạn vẫn có thể có các biến riêng tư, nhưng nếu bạn muốn các phương thức được xác định trên nguyên mẫu có quyền truy cập vào chúng, bạn nên xác định getters và setters trên this đối tượng, mà các phương thức nguyên mẫu (cùng với mọi thứ khác) sẽ có quyền truy cập. Ví dụ:

function Person(name, secret) {
    // public
    this.name = name;

    // private
    var secret = secret;

    // public methods have access to private members
    this.setSecret = function(s) {
        secret = s;
    }

    this.getSecret = function() {
        return secret;
    }
}

// Must use getters/setters 
Person.prototype.spillSecret = function() { alert(this.getSecret()); };

14
"Phạm vi đảo ngược" là một tính năng C ++ với từ khóa "bạn bè". Về cơ bản, bất kỳ chức năng nào cũng nên định nghĩa nguyên mẫu của nó là bạn bè. Đáng buồn là khái niệm này là C ++ chứ không phải JS :(
TWiStErRob

1
Tôi muốn thêm bài đăng này vào đầu danh sách yêu thích của tôi và giữ nó ở đó.
Donato

2
Tôi không thấy vấn đề này - bạn chỉ thêm một lớp trừu tượng mà không làm gì cả. Bạn cũng có thể chỉ cần làm cho secretmột tài sản của this. JavaScript đơn giản là không hỗ trợ các biến riêng tư với các nguyên mẫu vì các nguyên mẫu được liên kết với bối cảnh trang web cuộc gọi, không phải bối cảnh 'trang web tạo'.
nicodemus13

1
Tại sao không chỉ làm person.getSecret()sau đó?
Fahmi

1
Tại sao điều này có rất nhiều upvote? Điều này không làm cho biến riêng tư. Như đã đề cập ở trên bằng cách sử dụng person.getSecret () sẽ cho phép bạn truy cập biến riêng tư đó từ bất cứ đâu.
alexr101

63

Cập nhật: Với ES6, có một cách tốt hơn:

Câu chuyện dài, bạn có thể sử dụng cái mới Symbolđể tạo các trường riêng.
Đây là một mô tả tuyệt vời: https://curiosity-driven.org/private-properIES-in-javascript

Thí dụ:

var Person = (function() {
    // Only Person can access nameSymbol
    var nameSymbol = Symbol('name');

    function Person(name) {
        this[nameSymbol] = name;
    }

    Person.prototype.getName = function() {
        return this[nameSymbol];
    };

    return Person;
}());

Đối với tất cả các trình duyệt hiện đại với ES5:

Bạn chỉ có thể sử dụng

Cách đơn giản nhất để xây dựng các đối tượng là tránh kế thừa nguyên mẫu hoàn toàn. Chỉ cần xác định các biến riêng tư và các hàm công khai trong bao đóng và tất cả các phương thức công khai sẽ có quyền truy cập riêng vào các biến.

Hoặc bạn chỉ có thể sử dụng Nguyên mẫu

Trong JavaScript, kế thừa nguyên mẫu chủ yếu là tối ưu hóa . Nó cho phép nhiều trường hợp chia sẻ các phương thức nguyên mẫu, thay vì mỗi trường hợp có các phương thức riêng.
Nhược điểm là thischỉ điều đó là khác nhau mỗi lần một hàm nguyên chủng được gọi.
Do đó, bất kỳ trường riêng nào cũng phải có thể truy cập được this, điều đó có nghĩa là chúng sẽ được công khai. Vì vậy, chúng tôi chỉ bám vào các quy ước đặt tên cho _privatecác lĩnh vực.

Đừng bận tâm đến việc kết hợp Đóng cửa với Nguyên mẫu

Tôi nghĩ bạn không nên trộn các biến đóng với các phương thức nguyên mẫu. Bạn nên sử dụng cái này hay cái khác

Khi bạn sử dụng bao đóng để truy cập một biến riêng, các phương thức nguyên mẫu không thể truy cập vào biến đó. Vì vậy, bạn phải phơi bày việc đóng cửathis , điều đó có nghĩa là bạn phơi bày công khai bằng cách này hay cách khác. Có rất ít để đạt được với phương pháp này.

Tôi chọn cái nào?

Đối với các đối tượng thực sự đơn giản, chỉ cần sử dụng một đối tượng đơn giản với các bao đóng.

Nếu bạn cần kế thừa nguyên mẫu - để kế thừa, thực hiện, v.v. - thì hãy tuân thủ quy ước đặt tên "_private" và đừng bận tâm đến việc đóng cửa.

Tôi không hiểu tại sao các nhà phát triển JS cố gắng hết sức để làm cho các trường thực sự riêng tư.


4
Đáng buồn thay, _privatequy ước đặt tên vẫn là giải pháp tốt nhất nếu bạn muốn tận dụng sự kế thừa nguyên mẫu.
nghiền nát

1
ES6 sẽ có một khái niệm mới Symbol, đó là một cách tuyệt vời để tạo các trường riêng. Đây là một lời giải thích tuyệt vời: tò mò
driven.org/private-properIES-in-javascript

1
Không, bạn có thể giữ Symbolmột bao đóng bao gồm cả lớp của bạn. Theo cách đó, tất cả các phương thức nguyên mẫu có thể sử dụng Biểu tượng, nhưng nó không bao giờ được đưa ra ngoài lớp.
Scott Rippey

2
Bài viết bạn liên kết nói " Biểu tượng tương tự như tên riêng tư - nhưng không giống như tên riêng tư - chúng không cung cấp quyền riêng tư thực sự . ". Thực tế, nếu bạn có ví dụ, bạn có thể lấy các ký hiệu của nó Object.getOwnPropertySymbols. Vì vậy, đây chỉ là sự riêng tư bởi tối nghĩa.
Oriol

2
@Oriol Vâng, quyền riêng tư bị che khuất nặng nề. Vẫn có thể lặp qua các biểu tượng và bạn suy ra mục đích của biểu tượng thông qua toString. Điều này không khác gì Java hay C # ... các thành viên riêng vẫn có thể truy cập thông qua sự phản chiếu, nhưng thường bị che khuất mạnh mẽ. Tất cả đều giúp củng cố quan điểm cuối cùng của tôi, "Tôi không hiểu tại sao các nhà phát triển JS cố gắng hết sức để làm cho các trường thực sự riêng tư."
Scott Rippey

31

Khi tôi đọc nó, nghe có vẻ như là một thử thách khó khăn nên tôi quyết định tìm ra cách. Những gì tôi nghĩ ra là CRAAAAZY nhưng nó hoàn toàn hoạt động.

Đầu tiên, tôi đã thử định nghĩa lớp trong một hàm ngay lập tức để bạn có quyền truy cập vào một số thuộc tính riêng của hàm đó. Điều này hoạt động và cho phép bạn có được một số dữ liệu riêng tư, tuy nhiên, nếu bạn cố gắng đặt dữ liệu riêng tư, bạn sẽ sớm thấy rằng tất cả các đối tượng sẽ chia sẻ cùng một giá trị.

var SharedPrivateClass = (function() { // use immediate function
    // our private data
    var private = "Default";

    // create the constructor
    function SharedPrivateClass() {}

    // add to the prototype
    SharedPrivateClass.prototype.getPrivate = function() {
        // It has access to private vars from the immediate function!
        return private;
    };

    SharedPrivateClass.prototype.setPrivate = function(value) {
        private = value;
    };

    return SharedPrivateClass;
})();

var a = new SharedPrivateClass();
console.log("a:", a.getPrivate()); // "a: Default"

var b = new SharedPrivateClass();
console.log("b:", b.getPrivate()); // "b: Default"

a.setPrivate("foo"); // a Sets private to "foo"
console.log("a:", a.getPrivate()); // "a: foo"
console.log("b:", b.getPrivate()); // oh no, b.getPrivate() is "foo"!

console.log(a.hasOwnProperty("getPrivate")); // false. belongs to the prototype
console.log(a.private); // undefined

// getPrivate() is only created once and instanceof still works
console.log(a.getPrivate === b.getPrivate);
console.log(a instanceof SharedPrivateClass);
console.log(b instanceof SharedPrivateClass);

Có rất nhiều trường hợp điều này sẽ đầy đủ như nếu bạn muốn có các giá trị không đổi như tên sự kiện được chia sẻ giữa các trường hợp. Nhưng về cơ bản, chúng hoạt động như các biến tĩnh riêng.

Nếu bạn thực sự cần quyền truy cập vào các biến trong một không gian tên riêng tư trong các phương thức của bạn được xác định trên nguyên mẫu, bạn có thể thử mẫu này.

var PrivateNamespaceClass = (function() { // immediate function
    var instance = 0, // counts the number of instances
        defaultName = "Default Name",  
        p = []; // an array of private objects

    // create the constructor
    function PrivateNamespaceClass() {
        // Increment the instance count and save it to the instance. 
        // This will become your key to your private space.
        this.i = instance++; 
        
        // Create a new object in the private space.
        p[this.i] = {};
        // Define properties or methods in the private space.
        p[this.i].name = defaultName;
        
        console.log("New instance " + this.i);        
    }

    PrivateNamespaceClass.prototype.getPrivateName = function() {
        // It has access to the private space and it's children!
        return p[this.i].name;
    };
    PrivateNamespaceClass.prototype.setPrivateName = function(value) {
        // Because you use the instance number assigned to the object (this.i)
        // as a key, the values set will not change in other instances.
        p[this.i].name = value;
        return "Set " + p[this.i].name;
    };

    return PrivateNamespaceClass;
})();

var a = new PrivateNamespaceClass();
console.log(a.getPrivateName()); // Default Name

var b = new PrivateNamespaceClass();
console.log(b.getPrivateName()); // Default Name

console.log(a.setPrivateName("A"));
console.log(b.setPrivateName("B"));
console.log(a.getPrivateName()); // A
console.log(b.getPrivateName()); // B

// private objects are not accessible outside the PrivateNamespaceClass function
console.log(a.p);

// the prototype functions are not re-created for each instance
// and instanceof still works
console.log(a.getPrivateName === b.getPrivateName);
console.log(a instanceof PrivateNamespaceClass);
console.log(b instanceof PrivateNamespaceClass);

Tôi thích một số phản hồi từ bất cứ ai nhìn thấy lỗi với cách làm này.


4
Tôi đoán một mối quan tâm tiềm năng là bất kỳ trường hợp nào cũng có thể truy cập vào bất kỳ trường hợp riêng nào khác bằng cách sử dụng một id cá thể khác. Không hẳn là điều xấu ...
Mims H. Wright

15
Bạn xác định lại các chức năng nguyên mẫu theo mỗi cuộc gọi của nhà xây dựng
Lu4

10
@ Lu4 Tôi không chắc đó là sự thật. Các constructor được trả lại từ trong một đóng cửa; lần duy nhất các hàm nguyên mẫu được xác định là lần đầu tiên, trong đó biểu thức hàm được gọi ngay lập tức. Các vấn đề riêng tư đã được đề cập ở trên sang một bên, điều này có vẻ tốt với tôi (thoạt nhìn).
Guypursey

1
@ MimsH.Wright các ngôn ngữ khác cho phép truy cập vào các đối tượng khác thuộc cùng một lớp , nhưng chỉ khi bạn có tham chiếu đến chúng. Để cho phép điều này, bạn có thể ẩn các đặc quyền đằng sau một hàm lấy con trỏ đối tượng làm khóa (như được gắn với ID). Bằng cách đó, bạn chỉ có quyền truy cập vào dữ liệu riêng tư của các đối tượng mà bạn biết, điều này phù hợp hơn với phạm vi trong các ngôn ngữ khác. Tuy nhiên, việc thực hiện này làm sáng tỏ một vấn đề sâu sắc hơn với điều này. Các đối tượng riêng sẽ không bao giờ được thu gom Rác cho đến khi hàm Xây dựng là.
Thomas Nadin

3
Tôi muốn đề cập rằng iđã được thêm vào tất cả các trường hợp. Vì vậy, nó không hoàn toàn "minh bạch", và ivẫn có thể bị giả mạo.
Scott Rippey

18

xem trang của Doug Crockford về điều này . Bạn phải làm điều đó một cách gián tiếp với một cái gì đó có thể truy cập phạm vi của biến riêng tư.

một vi dụ khac:

Incrementer = function(init) {
  var counter = init || 0;  // "counter" is a private variable
  this._increment = function() { return counter++; }
  this._set = function(x) { counter = x; }
}
Incrementer.prototype.increment = function() { return this._increment(); }
Incrementer.prototype.set = function(x) { return this._set(x); }

trường hợp sử dụng:

js>i = new Incrementer(100);
[object Object]
js>i.increment()
100
js>i.increment()
101
js>i.increment()
102
js>i.increment()
103
js>i.set(-44)
js>i.increment()
-44
js>i.increment()
-43
js>i.increment()
-42

47
Ví dụ này có vẻ là thực hành khủng khiếp. Điểm quan trọng của việc sử dụng các phương thức nguyên mẫu là để bạn không phải tạo một phương thức mới cho mọi trường hợp. Dù sao thì bạn cũng đang làm điều đó. Đối với mọi phương pháp bạn đang tạo một phương thức khác.
Kir

2
@ArmedMonkey Khái niệm này nghe có vẻ hợp lý, nhưng đồng ý đây là một ví dụ tồi vì các chức năng nguyên mẫu được hiển thị là tầm thường. Nếu các hàm nguyên mẫu là các hàm dài hơn nhiều yêu cầu truy cập get / set đơn giản vào các biến 'riêng tư' thì nó sẽ có ý nghĩa.
bánh kếp

9
Tại sao thậm chí bận tâm phơi bày _setqua set? Tại sao không chỉ đặt tên cho nó setđể bắt đầu?
Scott Rippey

15

Tôi đề nghị có lẽ sẽ là một ý tưởng tốt để mô tả "có một sự phân công nguyên mẫu trong một hàm tạo" như là một mô hình chống Javascript. Hãy suy nghĩ về nó. Đó là cách quá rủi ro.

Những gì bạn đang thực sự làm ở đó khi tạo đối tượng thứ hai (tức là b) đang xác định lại chức năng nguyên mẫu đó cho tất cả các đối tượng sử dụng nguyên mẫu đó. Điều này sẽ thiết lập lại hiệu quả giá trị cho đối tượng a trong ví dụ của bạn. Nó sẽ hoạt động nếu bạn muốn một biến được chia sẻ và nếu bạn tình cờ tạo ra tất cả các trường hợp đối tượng lên phía trước, nhưng nó cảm thấy quá rủi ro.

Tôi đã tìm thấy một lỗi trong một số Javascript mà tôi đang làm việc gần đây là do kiểu chống chính xác này. Nó đã cố gắng thiết lập trình xử lý kéo và thả trên đối tượng cụ thể được tạo nhưng thay vào đó, nó lại thực hiện nó cho tất cả các trường hợp. Không tốt.

Giải pháp của Doug Crockford là tốt nhất.


10

@Kai

Điều đó sẽ không làm việc. Nếu bạn làm

var t2 = new TestClass();

sau đó t2.prototypeHellosẽ truy cập vào phần riêng tư của t.

@AnglesCrimes

Mã mẫu hoạt động tốt, nhưng nó thực sự tạo ra một thành viên riêng "tĩnh" được chia sẻ bởi tất cả các trường hợp. Nó có thể không phải là morgancodes giải pháp tìm kiếm.

Cho đến nay tôi vẫn chưa tìm thấy một cách dễ dàng và sạch sẽ để làm điều này mà không cần giới thiệu hàm băm riêng và các chức năng dọn dẹp bổ sung. Một chức năng thành viên tư nhân có thể được mô phỏng ở một mức độ nhất định:

(function() {
    function Foo() { ... }
    Foo.prototype.bar = function() {
       privateFoo.call(this, blah);
    };
    function privateFoo(blah) { 
        // scoped to the instance by passing this to call 
    }

    window.Foo = Foo;
}());

Hiểu rõ điểm của bạn, nhưng bạn có thể vui lòng giải thích đoạn mã của bạn đang cố gắng làm gì không?
Vishwanath

privateFoolà hoàn toàn riêng tư và do đó vô hình khi nhận được a new Foo(). Chỉ bar()là một phương thức công khai ở đây, có quyền truy cập privateFoo. Bạn có thể sử dụng cùng một cơ chế cho các biến và đối tượng đơn giản, tuy nhiên bạn cần luôn nhớ rằng chúng privatesthực sự tĩnh và sẽ được chia sẻ bởi tất cả các đối tượng bạn tạo.
Philzen

6

Vâng nó có thể. Mẫu thiết kế PPF chỉ giải quyết điều này.

PPF là viết tắt của Hàm nguyên mẫu riêng. PPF cơ bản giải quyết các vấn đề này:

  1. Các chức năng nguyên mẫu có quyền truy cập vào dữ liệu cá nhân.
  2. Chức năng nguyên mẫu có thể được thực hiện riêng tư.

Đối với người đầu tiên, chỉ cần:

  1. Đặt tất cả các biến cá thể riêng tư mà bạn muốn có thể truy cập được từ các hàm nguyên mẫu bên trong một thùng chứa dữ liệu riêng biệt và
  2. Truyền tham chiếu đến bộ chứa dữ liệu cho tất cả các hàm nguyên mẫu dưới dạng tham số.

Nó đơn giản mà. Ví dụ:

// Helper class to store private data.
function Data() {};

// Object constructor
function Point(x, y)
{
  // container for private vars: all private vars go here
  // we want x, y be changeable via methods only
  var data = new Data;
  data.x = x;
  data.y = y;

  ...
}

// Prototype functions now have access to private instance data
Point.prototype.getX = function(data)
{
  return data.x;
}

Point.prototype.getY = function(data)
{
  return data.y;
}

...

Đọc toàn bộ câu chuyện tại đây:

Mẫu thiết kế PPF


4
Câu trả lời chỉ liên kết thường được tán thành trên SO. Hãy cho một ví dụ.
Corey Adler

Bài báo có các ví dụ bên trong, vì vậy xin vui lòng xem ở đó
Edward

5
Điều gì xảy ra, mặc dù, tại một số điểm sau đó trên trang web đó đi xuống? Làm thế nào là một người được cho là để xem một ví dụ sau đó? Chính sách này được áp dụng sao cho mọi thứ có giá trị trong một liên kết đều có thể được giữ ở đây và không phải dựa vào một trang web mà điều này không thuộc quyền kiểm soát của chúng tôi.
Corey Adler

3
@Edward, liên kết của bạn là một đọc thú vị! Tuy nhiên, dường như lý do chính để truy cập dữ liệu riêng tư bằng cách sử dụng các chức năng nguyên mẫu là để ngăn chặn mọi đối tượng lãng phí bộ nhớ với các chức năng công khai giống hệt nhau. Phương pháp bạn mô tả không giải quyết được vấn đề này, vì đối với việc sử dụng công cộng, một chức năng nguyên mẫu cần phải được bọc trong một chức năng công cộng thông thường. Tôi đoán mô hình có thể hữu ích cho việc tiết kiệm bộ nhớ nếu bạn có nhiều ppf được kết hợp trong một chức năng công cộng duy nhất. Bạn có sử dụng chúng cho bất cứ điều gì khác?
Triết gia ăn uống

@DiningPhilosofer, cảm ơn bạn đã đánh giá cao bài viết của tôi. Vâng, bạn đã đúng, chúng tôi vẫn sử dụng các chức năng ví dụ. Nhưng ý tưởng là làm cho chúng nhẹ nhất có thể bằng cách gọi lại các đối tác PPF của chúng, tất cả đều làm việc nặng. Cuối cùng, tất cả các trường hợp đều gọi các PPF giống nhau (tất nhiên thông qua các hàm bao), do đó có thể cần phải tiết kiệm bộ nhớ nhất định. Câu hỏi đặt ra là bao nhiêu. Tôi hy vọng tiết kiệm đáng kể.
Edward

5

Bạn thực sự có thể đạt được điều này bằng cách sử dụng Xác minh Accessor :

(function(key, global) {
  // Creates a private data accessor function.
  function _(pData) {
    return function(aKey) {
      return aKey === key && pData;
    };
  }

  // Private data accessor verifier.  Verifies by making sure that the string
  // version of the function looks normal and that the toString function hasn't
  // been modified.  NOTE:  Verification can be duped if the rogue code replaces
  // Function.prototype.toString before this closure executes.
  function $(me) {
    if(me._ + '' == _asString && me._.toString === _toString) {
      return me._(key);
    }
  }
  var _asString = _({}) + '', _toString = _.toString;

  // Creates a Person class.
  var PersonPrototype = (global.Person = function(firstName, lastName) {
    this._ = _({
      firstName : firstName,
      lastName : lastName
    });
  }).prototype;
  PersonPrototype.getName = function() {
    var pData = $(this);
    return pData.firstName + ' ' + pData.lastName;
  };
  PersonPrototype.setFirstName = function(firstName) {
    var pData = $(this);
    pData.firstName = firstName;
    return this;
  };
  PersonPrototype.setLastName = function(lastName) {
    var pData = $(this);
    pData.lastName = lastName;
    return this;
  };
})({}, this);

var chris = new Person('Chris', 'West');
alert(chris.setFirstName('Christopher').setLastName('Webber').getName());

Ví dụ này xuất phát từ bài viết của tôi về Chức năng Prototypal & Dữ liệu riêng tư và được giải thích chi tiết hơn ở đó.


1
Câu trả lời này quá "thông minh" không hữu ích, nhưng tôi thích câu trả lời của việc sử dụng biến ràng buộc IFFE như một cái bắt tay bí mật. Việc thực hiện này sử dụng quá nhiều lần đóng cửa là hữu ích; điểm có các phương thức xác định nguyên mẫu là để ngăn chặn việc xây dựng các đối tượng hàm mới cho mỗi phương thức trên mỗi đối tượng.
greg.kindel

Cách tiếp cận này sử dụng khóa bí mật để xác định phương thức nguyên mẫu nào đáng tin cậy và phương pháp nào không. Tuy nhiên, đó là trường hợp xác nhận khóa, vì vậy khóa phải được gửi đến thể hiện. Nhưng sau đó, mã không tin cậy có thể gọi một phương thức đáng tin cậy trên một cá thể giả, nó sẽ đánh cắp khóa. Và với khóa đó, tạo các phương thức mới sẽ được coi là đáng tin cậy bởi các thể hiện thực. Vì vậy, đây chỉ là sự riêng tư bởi tối nghĩa.
Oriol

4

Trong JavaScript hiện tại, tôi khá chắc chắn rằng có mộtchỉ một cách để có trạng thái riêng tư , có thể truy cập từ các hàm nguyên mẫu , mà không cần thêm bất cứ điều gì công khai vàothis . Câu trả lời là sử dụng mẫu "bản đồ yếu".

Tóm lại: PersonLớp có một bản đồ yếu duy nhất, trong đó các khóa là các thể hiện của Person và các giá trị là các đối tượng đơn giản được sử dụng cho lưu trữ riêng.

Dưới đây là một ví dụ đầy đủ chức năng: (chơi tại http://jsfiddle.net/ScottRippey/BLNVr/ )

var Person = (function() {
    var _ = weakMap();
    // Now, _(this) returns an object, used for private storage.
    var Person = function(first, last) {
        // Assign private storage:
        _(this).firstName = first;
        _(this).lastName = last;
    }
    Person.prototype = {
        fullName: function() {
            // Retrieve private storage:
            return _(this).firstName + _(this).lastName;
        },
        firstName: function() {
            return _(this).firstName;
        },
        destroy: function() {
            // Free up the private storage:
            _(this, true);
        }
    };
    return Person;
})();

function weakMap() {
    var instances=[], values=[];
    return function(instance, destroy) {
        var index = instances.indexOf(instance);
        if (destroy) {
            // Delete the private state:
            instances.splice(index, 1);
            return values.splice(index, 1)[0];
        } else if (index === -1) {
            // Create the private state:
            instances.push(instance);
            values.push({});
            return values[values.length - 1];
        } else {
            // Return the private state:
            return values[index];
        }
    };
}

Như tôi đã nói, đây thực sự là cách duy nhất để đạt được cả 3 phần.

Có hai hãy cẩn thận, tuy nhiên. Đầu tiên, chi phí hiệu năng này - mỗi khi bạn truy cập dữ liệu riêng tư, đó là một O(n)hoạt động, trong đó nsố lượng phiên bản. Vì vậy, bạn sẽ không muốn làm điều này nếu bạn có một số lượng lớn các trường hợp. Thứ hai, khi bạn hoàn thành một ví dụ, bạn phải gọidestroy ; nếu không, ví dụ và dữ liệu sẽ không được thu gom rác và bạn sẽ bị rò rỉ bộ nhớ.

Và đó là lý do tại sao câu trả lời ban đầu của tôi, "Bạn không nên" , là điều tôi muốn gắn bó.


Nếu bạn không tiêu diệt rõ ràng một cá thể của Người trước khi nó vượt ra khỏi phạm vi, thì sơ đồ yếu không giữ tham chiếu đến nó để bạn có bị rò rỉ bộ nhớ? Tôi đã đưa ra một mô hình để được bảo vệ vì các trường hợp khác của Person có thể truy cập vào biến và những trường hợp kế thừa từ Person có thể. Chỉ cần xử lý nó để không chắc chắn nếu có bất kỳ lợi thế nào khác ngoài xử lý bổ sung (không giống như truy cập vào phần riêng tư) stackoverflow.com/a/21800194/1641941 Trả lại một đối tượng riêng tư / được bảo vệ là một nỗi đau vì gọi mã sau đó có thể đột biến riêng tư / được bảo vệ của bạn.
HMR

2
@HMR Vâng, bạn phải hủy dữ liệu riêng tư một cách rõ ràng. Tôi sẽ thêm lời cảnh báo này vào câu trả lời của tôi.
Scott Rippey

3

Có một cách đơn giản hơn bằng cách tận dụng việc sử dụng bindcall phương pháp.

Bằng cách đặt các biến riêng tư cho một đối tượng, bạn có thể tận dụng phạm vi của đối tượng đó.

Thí dụ

function TestClass (value) {
    // The private value(s)
    var _private = {
        value: value
    };

    // `bind` creates a copy of `getValue` when the object is instantiated
    this.getValue = TestClass.prototype.getValue.bind(_private);

    // Use `call` in another function if the prototype method will possibly change
    this.getValueDynamic = function() {
        return TestClass.prototype.getValue.call(_private);
    };
};

TestClass.prototype.getValue = function() {
    return this.value;
};

Phương pháp này không có nhược điểm. Vì bối cảnh phạm vi đang bị ghi đè một cách hiệu quả, bạn không có quyền truy cập bên ngoài _privateđối tượng. Tuy nhiên, mặc dù vẫn không thể cấp quyền truy cập vào phạm vi của đối tượng thể hiện. Bạn có thể truyền vào ngữ cảnh của đối tượng ( this) làm đối số thứ hai cho bindhoặccall vẫn có quyền truy cập vào các giá trị công khai của nó trong hàm nguyên mẫu.

Tiếp cận các giá trị công cộng

function TestClass (value) {
    var _private = {
        value: value
    };

    this.message = "Hello, ";

    this.getMessage = TestClass.prototype.getMessage.bind(_private, this);

}

TestClass.prototype.getMessage = function(_public) {

    // Can still access passed in arguments
    // e.g. – test.getValues('foo'), 'foo' is the 2nd argument to the method
    console.log([].slice.call(arguments, 1));
    return _public.message + this.value;
};

var test = new TestClass("World");
test.getMessage(1, 2, 3); // [1, 2, 3]         (console.log)
                          // => "Hello, World" (return value)

test.message = "Greetings, ";
test.getMessage(); // []                    (console.log)
                   // => "Greetings, World" (return value)

2
Tại sao một người nào đó sẽ tạo một bản sao của phương thức nguyên mẫu chứ không phải chỉ tạo ra một phương thức không ổn định ở nơi đầu tiên?
nghiền nát

3

Thử nó!

    function Potatoe(size) {
    var _image = new Image();
    _image.src = 'potatoe_'+size+'.png';
    function getImage() {
        if (getImage.caller == null || getImage.caller.owner != Potatoe.prototype)
            throw new Error('This is a private property.');
        return _image;
    }
    Object.defineProperty(this,'image',{
        configurable: false,
        enumerable: false,
        get : getImage          
    });
    Object.defineProperty(this,'size',{
        writable: false,
        configurable: false,
        enumerable: true,
        value : size            
    });
}
Potatoe.prototype.draw = function(ctx,x,y) {
    //ctx.drawImage(this.image,x,y);
    console.log(this.image);
}
Potatoe.prototype.draw.owner = Potatoe.prototype;

var pot = new Potatoe(32);
console.log('Potatoe size: '+pot.size);
try {
    console.log('Potatoe image: '+pot.image);
} catch(e) {
    console.log('Oops: '+e);
}
pot.draw();

1
Điều này phụ thuộc vào caller, đó là một phần mở rộng phụ thuộc vào việc thực hiện không được phép trong chế độ nghiêm ngặt.
Oriol

1

Đây là những gì tôi nghĩ ra.

(function () {
    var staticVar = 0;
    var yrObj = function () {
        var private = {"a":1,"b":2};
        var MyObj = function () {
            private.a += staticVar;
            staticVar++;
        };
        MyObj.prototype = {
            "test" : function () {
                console.log(private.a);
            }
        };

        return new MyObj;
    };
    window.YrObj = yrObj;
}());

var obj1 = new YrObj;
var obj2 = new YrObj;
obj1.test(); // 1
obj2.test(); // 2

vấn đề chính với việc thực hiện này là nó xác định lại các nguyên mẫu trên mỗi bản in.


Thật thú vị, tôi thực sự thích sự cố gắng và đã nghĩ đến điều tương tự, nhưng bạn nói đúng rằng việc xác định lại chức năng nguyên mẫu trên mỗi lần khởi tạo là một hạn chế khá lớn. Điều này không chỉ vì nó lãng phí chu kỳ CPU, mà bởi vì nếu bạn thay đổi loại prototo sau này, nó sẽ "đặt lại" về trạng thái ban đầu như được xác định trong hàm tạo trong lần khởi tạo tiếp theo: /
Niko Bellic

1
Điều này không chỉ xác định lại các nguyên mẫu, nó xác định một hàm tạo mới cho mỗi thể hiện. Vì vậy, "các thể hiện" không còn là các thể hiện của cùng một lớp.
Oriol

1

Có một cách rất đơn giản để làm điều này

function SharedPrivate(){
  var private = "secret";
  this.constructor.prototype.getP = function(){return private}
  this.constructor.prototype.setP = function(v){ private = v;}
}

var o1 = new SharedPrivate();
var o2 = new SharedPrivate();

console.log(o1.getP()); // secret
console.log(o2.getP()); // secret
o1.setP("Pentax Full Frame K1 is on sale..!");
console.log(o1.getP()); // Pentax Full Frame K1 is on sale..!
console.log(o2.getP()); // Pentax Full Frame K1 is on sale..!
o2.setP("And it's only for $1,795._");
console.log(o1.getP()); // And it's only for $1,795._

Các nguyên mẫu JavaScript là vàng.


2
Tôi tin rằng tốt hơn hết là không sử dụng nguyên mẫu trong hàm constructor vì nó sẽ tạo ra một hàm mới mỗi khi tạo một thể hiện mới.
whamsicore

@whamsicore Đúng, nhưng trong trường hợp này, điều cần thiết là vì mỗi đối tượng duy nhất được khởi tạo, chúng tôi phải sắp xếp một đóng cửa chung. Đó là lý do tại sao các định nghĩa hàm nằm trong hàm tạo và chúng ta phải đề cập đến SharedPrivate.prototypethis.constructor.prototypeđó không phải là vấn đề lớn để xác định lại getP và setP nhiều lần ...
Redu

1

Tôi đến bữa tiệc muộn, nhưng tôi nghĩ mình có thể đóng góp. Tại đây, hãy kiểm tra điều 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ẽ.


0

Bạn không thể đặt các biến trong một phạm vi cao hơn?

(function () {
    var privateVariable = true;

    var MyClass = function () {
        if (privateVariable) console.log('readable from private scope!');
    };

    MyClass.prototype.publicMethod = function () {
        if (privateVariable) console.log('readable from public scope!');
    };
}))();

4
Sau đó, các biến được chia sẻ giữa tất cả các phiên bản của MyClass.
nghiền nát

0

Bạn cũng có thể thử thêm phương thức không trực tiếp vào nguyên mẫu, nhưng trên hàm constructor như thế này:

var MyArray = function() {
    var array = [];

    this.add = MyArray.add.bind(null, array);
    this.getAll = MyArray.getAll.bind(null, array);
}

MyArray.add = function(array, item) {
    array.push(item);
}
MyArray.getAll = function(array) {
    return array;
}

var myArray1 = new MyArray();
myArray1.add("some item 1");
console.log(myArray1.getAll()); // ['some item 1']
var myArray2 = new MyArray();
myArray2.add("some item 2");
console.log(myArray2.getAll()); // ['some item 2']
console.log(myArray1.getAll()); // ['some item 2'] - FINE!

0

Đây là một cái gì đó tôi đã nghĩ ra trong khi cố gắng tìm giải pháp đơn giản nhất cho vấn đề này, có lẽ nó có thể hữu ích cho ai đó. Tôi chưa quen với javascript, vì vậy có thể có một số vấn đề với mã.

// pseudo-class definition scope
(function () {

    // this is used to identify 'friend' functions defined within this scope,
    // while not being able to forge valid parameter for GetContext() 
    // to gain 'private' access from outside
    var _scope = new (function () { })();
    // -----------------------------------------------------------------

    // pseudo-class definition
    this.Something = function (x) {

        // 'private' members are wrapped into context object,
        // it can be also created with a function
        var _ctx = Object.seal({

            // actual private members
            Name: null,
            Number: null,

            Somefunc: function () {
                console.log('Something(' + this.Name + ').Somefunc(): number = ' + this.Number);
            }
        });
        // -----------------------------------------------------------------

        // function below needs to be defined in every class
        // to allow limited access from prototype
        this.GetContext = function (scope) {

            if (scope !== _scope) throw 'access';
            return _ctx;
        }
        // -----------------------------------------------------------------

        {
            // initialization code, if any
            _ctx.Name = (x !== 'undefined') ? x : 'default';
            _ctx.Number = 0;

            Object.freeze(this);
        }
    }
    // -----------------------------------------------------------------

    // prototype is defined only once
    this.Something.prototype = Object.freeze({

        // public accessors for 'private' field
        get Number() { return this.GetContext(_scope).Number; },
        set Number(v) { this.GetContext(_scope).Number = v; },

        // public function making use of some private fields
        Test: function () {

            var _ctx = this.GetContext(_scope);
            // access 'private' field
            console.log('Something(' + _ctx.Name + ').Test(): ' + _ctx.Number);
            // call 'private' func
            _ctx.Somefunc();
        }
    });
    // -----------------------------------------------------------------

    // wrap is used to hide _scope value and group definitions
}).call(this);

function _A(cond) { if (cond !== true) throw new Error('assert failed'); }
// -----------------------------------------------------------------

function test_smth() {

    console.clear();

    var smth1 = new Something('first'),
      smth2 = new Something('second');

    //_A(false);
    _A(smth1.Test === smth2.Test);

    smth1.Number = 3;
    smth2.Number = 5;
    console.log('smth1.Number: ' + smth1.Number + ', smth2.Number: ' + smth2.Number);

    smth1.Number = 2;
    smth2.Number = 6;

    smth1.Test();
    smth2.Test();

    try {
        var ctx = smth1.GetContext();
    } catch (err) {
        console.log('error: ' + err);
    }
}

test_smth();

0

Tôi đã đối mặt với cùng một câu hỏi ngày hôm nay và sau khi thảo luận về phản hồi hạng nhất của Scott Rippey, tôi đã đưa ra một giải pháp rất đơn giản (IMHO) vừa tương thích với ES5 và hiệu quả, đó cũng là tên an toàn (sử dụng _private có vẻ không an toàn) .

/*jslint white: true, plusplus: true */

 /*global console */

var a, TestClass = (function(){
    "use strict";
    function PrefixedCounter (prefix) {
        var counter = 0;
        this.count = function () {
            return prefix + (++counter);
        };
    }
    var TestClass = (function(){
        var cls, pc = new PrefixedCounter("_TestClass_priv_")
        , privateField = pc.count()
        ;
        cls = function(){
            this[privateField] = "hello";
            this.nonProtoHello = function(){
                console.log(this[privateField]);
            };
        };
        cls.prototype.prototypeHello = function(){
            console.log(this[privateField]);
        };
        return cls;
    }());
    return TestClass;
}());

a = new TestClass();
a.nonProtoHello();
a.prototypeHello();

Đã thử nghiệm với ringojs và nodejs. Tôi rất muốn đọc ý kiến ​​của bạn.


Đây là một tài liệu tham khảo: kiểm tra phần 'Một bước gần hơn'. philipwalton.com/articles/ từ
jimasun

0
var getParams = function(_func) {
  res = _func.toString().split('function (')[1].split(')')[0].split(',')
  return res
}

function TestClass(){

  var private = {hidden: 'secret'}
  //clever magic accessor thing goes here
  if ( !(this instanceof arguments.callee) ) {
    for (var key in arguments) {
      if (typeof arguments[key] == 'function') {
        var keys = getParams(arguments[key])
        var params = []
        for (var i = 0; i <= keys.length; i++) {
          if (private[keys[i]] != undefined) {
            params.push(private[keys[i]])
          }
        }
        arguments[key].apply(null,params)
      }
    }
  }
}


TestClass.prototype.test = function(){
  var _hidden; //variable I want to get
  TestClass(function(hidden) {_hidden = hidden}) //invoke magic to get
};

new TestClass().test()

Cái này thế nào Sử dụng một truy cập riêng. Chỉ cho phép bạn lấy các biến mặc dù không đặt chúng, tùy thuộc vào trường hợp sử dụng.


Điều này không trả lời câu hỏi một cách hữu ích. Tại sao bạn tin rằng đây là câu trả lời? cách nào? Đơn giản chỉ cần nói với ai đó để thay đổi mã của họ mà không có bất kỳ bối cảnh hoặc ý nghĩa nào không giúp họ tìm hiểu những gì họ đã làm sai.
GrumpyCrouton

Anh ta muốn có một cách để truy cập các biến riêng tư ẩn của Class thông qua các nguyên mẫu mà không phải tạo biến ẩn đó trên mỗi phiên bản duy nhất của lớp. Các mã trên là một phương pháp ví dụ để làm như vậy. Làm thế nào mà không phải là một câu trả lời cho câu hỏi?
dylan0150

Tôi không nói đó không phải là một câu trả lời cho câu hỏi. Tôi nói đó không phải là một câu trả lời hữu ích , vì nó không giúp được ai học. Bạn nên giải thích mã của bạn, tại sao nó hoạt động, tại sao đó là cách đúng để làm điều đó. Nếu tôi là tác giả câu hỏi, tôi sẽ không chấp nhận câu trả lời của bạn vì nó không khuyến khích việc học, nó không dạy tôi những gì tôi đang làm sai hoặc những gì mã đã cho đang làm hoặc cách thức hoạt động.
GrumpyCrouton

0

Tôi có một giải pháp, nhưng tôi không chắc nó không có sai sót.

Để nó hoạt động, bạn phải sử dụng cấu trúc sau:

  1. Sử dụng 1 đối tượng riêng có chứa tất cả các biến riêng tư.
  2. Sử dụng chức năng 1 dụ.
  3. Áp dụng một bao đóng cho hàm tạo và tất cả các hàm nguyên mẫu.
  4. Bất kỳ trường hợp nào được tạo ra được thực hiện bên ngoài bao đóng được xác định.

Đây là mã:

var TestClass = 
(function () {
    // difficult to be guessed.
    var hash = Math.round(Math.random() * Math.pow(10, 13) + + new Date());
    var TestClass = function () {
        var privateFields = {
            field1: 1,
            field2: 2
        };
        this.getPrivateFields = function (hashed) {
            if(hashed !== hash) {
                throw "Cannot access private fields outside of object.";
                // or return null;
            }
            return privateFields;
        };
    };

    TestClass.prototype.prototypeHello = function () {
        var privateFields = this.getPrivateFields(hash);
        privateFields.field1 = Math.round(Math.random() * 100);
        privateFields.field2 = Math.round(Math.random() * 100);
    };

    TestClass.prototype.logField1 = function () {
        var privateFields = this.getPrivateFields(hash);
        console.log(privateFields.field1);
    };

    TestClass.prototype.logField2 = function () {
        var privateFields = this.getPrivateFields(hash);
        console.log(privateFields.field2);
    };

    return TestClass;
})();

Cách thức hoạt động của nó là nó cung cấp một hàm cá thể "this.getPrivateFields" để truy cập vào đối tượng biến riêng tư "privateFields", nhưng hàm này sẽ chỉ trả về đối tượng "privateFields" bên trong bao đóng chính được xác định (cũng là các hàm nguyên mẫu sử dụng "this.getPrivateFields "Cần phải được xác định trong đóng cửa này).

Băm được tạo ra trong thời gian chạy và khó đoán được sử dụng làm tham số để đảm bảo rằng ngay cả khi "getPrivateFields" được gọi bên ngoài phạm vi đóng sẽ không trả về đối tượng "privateFields".

Hạn chế là chúng tôi không thể mở rộng TestClass với nhiều chức năng nguyên mẫu hơn bên ngoài việc đóng.

Đây là một số mã kiểm tra:

var t1 = new TestClass();
console.log('Initial t1 field1 is: ');
t1.logField1();
console.log('Initial t1 field2 is: ');
t1.logField2();
t1.prototypeHello();
console.log('t1 field1 is now: ');
t1.logField1();
console.log('t1 field2 is now: ');
t1.logField2();
var t2 = new TestClass();
console.log('Initial t2 field1 is: ');
t2.logField1();
console.log('Initial t2 field2 is: ');
t2.logField2();
t2.prototypeHello();
console.log('t2 field1 is now: ');
t2.logField1();
console.log('t2 field2 is now: ');
t2.logField2();

console.log('t1 field1 stays: ');
t1.logField1();
console.log('t1 field2 stays: ');
t1.logField2();

t1.getPrivateFields(11233);

EDIT: Sử dụng phương pháp này, cũng có thể "xác định" các chức năng riêng tư.

TestClass.prototype.privateFunction = function (hashed) {
    if(hashed !== hash) {
        throw "Cannot access private function.";
    }
};

TestClass.prototype.prototypeHello = function () {
    this.privateFunction(hash);
};

0

Đã chơi xung quanh với điều này ngày hôm nay và đây là giải pháp duy nhất tôi có thể tìm thấy mà không cần sử dụng Biểu tượng. Điều tốt nhất về điều này là nó thực sự có thể hoàn toàn riêng tư.

Giải pháp dựa trên bộ tải mô-đun nhà, về cơ bản trở thành trung gian hòa giải cho bộ đệm lưu trữ riêng (sử dụng bản đồ yếu).

   const loader = (function() {
        function ModuleLoader() {}

    //Static, accessible only if truly needed through obj.constructor.modules
    //Can also be made completely private by removing the ModuleLoader prefix.
    ModuleLoader.modulesLoaded = 0;
    ModuleLoader.modules = {}

    ModuleLoader.prototype.define = function(moduleName, dModule) {
        if (moduleName in ModuleLoader.modules) throw new Error('Error, duplicate module');

        const module = ModuleLoader.modules[moduleName] = {}

        module.context = {
            __moduleName: moduleName,
            exports: {}
        }

        //Weak map with instance as the key, when the created instance is garbage collected or goes out of scope this will be cleaned up.
        module._private = {
            private_sections: new WeakMap(),
            instances: []
        };

        function private(action, instance) {
            switch (action) {
                case "create":
                    if (module._private.private_sections.has(instance)) throw new Error('Cannot create private store twice on the same instance! check calls to create.')
                    module._private.instances.push(instance);
                    module._private.private_sections.set(instance, {});
                    break;
                case "delete":
                    const index = module._private.instances.indexOf(instance);
                    if (index == -1) throw new Error('Invalid state');
                    module._private.instances.slice(index, 1);
                    return module._private.private_sections.delete(instance);
                    break;
                case "get":
                    return module._private.private_sections.get(instance);
                    break;
                default:
                    throw new Error('Invalid action');
                    break;
            }
        }

        dModule.call(module.context, private);
        ModuleLoader.modulesLoaded++;
    }

    ModuleLoader.prototype.remove = function(moduleName) {
        if (!moduleName in (ModuleLoader.modules)) return;

        /*
            Clean up as best we can.
        */
        const module = ModuleLoader.modules[moduleName];
        module.context.__moduleName = null;
        module.context.exports = null;
        module.cotext = null;
        module._private.instances.forEach(function(instance) { module._private.private_sections.delete(instance) });
        for (let i = 0; i < module._private.instances.length; i++) {
            module._private.instances[i] = undefined;
        }
        module._private.instances = undefined;
        module._private = null;
        delete ModuleLoader.modules[moduleName];
        ModuleLoader.modulesLoaded -= 1;
    }


    ModuleLoader.prototype.require = function(moduleName) {
        if (!(moduleName in ModuleLoader.modules)) throw new Error('Module does not exist');

        return ModuleLoader.modules[moduleName].context.exports;
    }



     return new ModuleLoader();
    })();

    loader.define('MyModule', function(private_store) {
        function MyClass() {
            //Creates the private storage facility. Called once in constructor.
            private_store("create", this);


            //Retrieve the private storage object from the storage facility.
            private_store("get", this).no = 1;
        }

        MyClass.prototype.incrementPrivateVar = function() {
            private_store("get", this).no += 1;
        }

        MyClass.prototype.getPrivateVar = function() {
            return private_store("get", this).no;
        }

        this.exports = MyClass;
    })

    //Get whatever is exported from MyModule
    const MyClass = loader.require('MyModule');

    //Create a new instance of `MyClass`
    const myClass = new MyClass();

    //Create another instance of `MyClass`
    const myClass2 = new MyClass();

    //print out current private vars
    console.log('pVar = ' + myClass.getPrivateVar())
    console.log('pVar2 = ' + myClass2.getPrivateVar())

    //Increment it
    myClass.incrementPrivateVar()

    //Print out to see if one affected the other or shared
    console.log('pVar after increment = ' + myClass.getPrivateVar())
    console.log('pVar after increment on other class = ' + myClass2.getPrivateVar())

    //Clean up.
    loader.remove('MyModule')

0

Tôi biết rằng đã hơn 1 thập kỷ kể từ khi điều này được hỏi, nhưng tôi chỉ suy nghĩ về điều này lần thứ n trong cuộc đời lập trình viên của mình và tìm thấy một giải pháp khả thi mà tôi chưa biết nếu tôi hoàn toàn thích . Tôi chưa thấy phương pháp này được ghi lại trước đây, vì vậy tôi sẽ đặt tên cho nó là "mẫu đô la riêng tư / công khai" hoặc mẫu _ $ / $ .

var ownFunctionResult = this.$("functionName"[, arg1[, arg2 ...]]);
var ownFieldValue = this._$("fieldName"[, newValue]);

var objectFunctionResult = objectX.$("functionName"[, arg1[, arg2 ...]]);

//Throws an exception. objectX._$ is not defined
var objectFieldValue = objectX._$("fieldName"[, newValue]);

Khái niệm này sử dụng một ClassDefinition hàm trả về một Constructor hàm trả về một giao diện đối tượng. Phương thức duy nhất của giao diện là $nhận một nameđối số để gọi hàm tương ứng trong đối tượng hàm tạo, bất kỳ đối số bổ sung nào được truyền sau khi nameđược truyền trong lệnh gọi.

Hàm trợ giúp được xác định toàn cục ClassValueslưu trữ tất cả các trường trong một đối tượng khi cần. Nó xác định _$chức năng để truy cập chúng bằng cách name. Điều này tuân theo một mẫu get / set ngắn để nếu valueđược thông qua, nó sẽ được sử dụng làm giá trị biến mới.

var ClassValues = function (values) {
  return {
    _$: function _$(name, value) {
      if (arguments.length > 1) {
        values[name] = value;
      }

      return values[name];
    }
  };
};

Hàm được định nghĩa toàn cục Interfacesẽ lấy một đối tượng và một Valuesđối tượng để trả về một _interfacehàm duy nhất $kiểm tra objđể tìm một hàm được đặt tên theo tham số namevà gọi nó valueslà đối tượng có phạm vi . Các đối số bổ sung được truyền vào $sẽ được truyền vào lời gọi hàm.

var Interface = function (obj, values, className) {
  var _interface = {
    $: function $(name) {
      if (typeof(obj[name]) === "function") {
        return obj[name].apply(values, Array.prototype.splice.call(arguments, 1));
      }

      throw className + "." + name + " is not a function.";
    }
  };

  //Give values access to the interface.
  values.$ = _interface.$;

  return _interface;
};

Trong mẫu dưới đây, ClassXđược gán cho kết quả của ClassDefinition, đó là Constructorchức năng. Constructorcó thể nhận được bất kỳ số lượng đối số. Interfacelà những gì mã bên ngoài nhận được sau khi gọi hàm tạo.

var ClassX = (function ClassDefinition () {
  var Constructor = function Constructor (valA) {
    return Interface(this, ClassValues({ valA: valA }), "ClassX");
  };

  Constructor.prototype.getValA = function getValA() {
    //private value access pattern to get current value.
    return this._$("valA");
  };

  Constructor.prototype.setValA = function setValA(valA) {
    //private value access pattern to set new value.
    this._$("valA", valA);
  };

  Constructor.prototype.isValAValid = function isValAValid(validMessage, invalidMessage) {
    //interface access pattern to call object function.
    var valA = this.$("getValA");

    //timesAccessed was not defined in constructor but can be added later...
    var timesAccessed = this._$("timesAccessed");

    if (timesAccessed) {
      timesAccessed = timesAccessed + 1;
    } else {
      timesAccessed = 1;
    }

    this._$("timesAccessed", timesAccessed);

    if (valA) {
      return "valA is " + validMessage + ".";
    }

    return "valA is " + invalidMessage + ".";
  };

  return Constructor;
}());

Không có điểm nào trong việc có các hàm không được tạo nguyên mẫu Constructor, mặc dù bạn có thể định nghĩa chúng trong thân hàm hàm tạo. Tất cả các chức năng được gọi với mô hình đô la công khai this.$("functionName"[, param1[, param2 ...]]) . Các giá trị riêng được truy cập với mẫu đô la riêng this._$("valueName"[, replacingValue]); . Như Interfacekhông có định nghĩa cho _$, các giá trị không thể được truy cập bởi các đối tượng bên ngoài. Vì mỗi thân hàm được tạo nguyên mẫu thisđược đặt thành valuesđối tượng trong hàm $, bạn sẽ có ngoại lệ nếu bạn gọi trực tiếp các hàm anh chị em của Trình xây dựng; mẫu _ $ / $ cũng cần phải được theo dõi trong các thân hàm nguyên mẫu. Dưới đây sử dụng mẫu.

var classX1 = new ClassX();
console.log("classX1." + classX1.$("isValAValid", "valid", "invalid"));
console.log("classX1.valA: " + classX1.$("getValA"));
classX1.$("setValA", "v1");
console.log("classX1." + classX1.$("isValAValid", "valid", "invalid"));
var classX2 = new ClassX("v2");
console.log("classX1.valA: " + classX1.$("getValA"));
console.log("classX2.valA: " + classX2.$("getValA"));
//This will throw an exception
//classX1._$("valA");

Và đầu ra giao diện điều khiển.

classX1.valA is invalid.
classX1.valA: undefined
classX1.valA is valid.
classX1.valA: v1
classX2.valA: v2

Mẫu _ $ / $ cho phép bảo mật hoàn toàn các giá trị trong các lớp được tạo mẫu đầy đủ. Tôi không biết liệu tôi có bao giờ sử dụng nó hay không, nếu nó có sai sót, nhưng này, nó là một câu đố hay!


0

ES6 WeakMaps

Bằng cách sử dụng một mẫu đơn giản dựa trên ES6 WeakMaps có thể thu được các biến thành viên riêng, có thể truy cập từ các hàm nguyên mẫu .

Lưu ý: Việc sử dụng WeakMaps đảm bảo an toàn chống rò rỉ bộ nhớ , bằng cách để Garbage Collector xác định và loại bỏ các trường hợp không sử dụng.

// Create a private scope using an Immediately 
// Invoked Function Expression...
let Person = (function() {

    // Create the WeakMap that will hold each  
    // Instance collection's of private data
    let privateData = new WeakMap();
    
    // Declare the Constructor :
    function Person(name) {
        // Insert the private data in the WeakMap,
        // using 'this' as a unique acces Key
        privateData.set(this, { name: name });
    }
    
    // Declare a prototype method 
    Person.prototype.getName = function() {
        // Because 'privateData' is in the same 
        // scope, it's contents can be retrieved...
        // by using  again 'this' , as  the acces key 
        return privateData.get(this).name;
    };

    // return the Constructor
    return Person;
}());

Một lời giải thích chi tiết hơn về mẫu này có thể được tìm thấy ở đây


-1

Bạn cần thay đổi 3 điều trong mã của bạn:

  1. Thay thế var privateField = "hello"bằng this.privateField = "hello".
  2. Trong nguyên mẫu thay thế privateFieldbằng this.privateField.
  3. Trong nguyên mẫu cũng không thay thế privateFieldbằng this.privateField.

Mã cuối cùng sẽ là như sau:

TestClass = function(){
    this.privateField = "hello";
    this.nonProtoHello = function(){alert(this.privateField)};
}

TestClass.prototype.prototypeHello = function(){alert(this.privateField)};

var t = new TestClass();

t.prototypeHello()

this.privateFieldsẽ không phải là một lĩnh vực tư nhân. nó có thể truy cập từ bên ngoài:t.privateField
V. Rubinetti

-2

Bạn có thể sử dụng một gán nguyên mẫu trong định nghĩa hàm tạo.

Biến sẽ hiển thị cho phương thức được thêm vào nguyên mẫu nhưng tất cả các phiên bản của các hàm sẽ truy cập vào cùng một biến SHARED.

function A()
{
  var sharedVar = 0;
  this.local = "";

  A.prototype.increment = function(lval)
  {    
    if (lval) this.local = lval;    
    alert((++sharedVar) + " while this.p is still " + this.local);
  }
}

var a = new A();
var b = new A();    
a.increment("I belong to a");
b.increment("I belong to b");
a.increment();
b.increment();

Tôi hy vọng điều này có thể hữu ích.

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.