การเข้าถึงตัวแปรสมาชิกส่วนตัวจากฟังก์ชั่นที่กำหนดต้นแบบ


187

มีวิธีใดที่จะทำให้ตัวแปร "ส่วนตัว" (ที่กำหนดไว้ในตัวสร้าง) พร้อมใช้งานสำหรับวิธีที่กำหนดต้นแบบ?

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

งานนี้:

t.nonProtoHello()

แต่นี่ไม่ใช่:

t.prototypeHello()

ฉันเคยกำหนดวิธีการของฉันภายในตัวสร้าง แต่ฉันย้ายออกไปจากที่ด้วยเหตุผลสองสาม



14
@ecampver ยกเว้นคนนี้ก็ถามว่า 2 ปีก่อนหน้านี้ ....
Pacerier

คำตอบ:


191

ไม่ไม่มีวิธีที่จะทำ นั่นคือการกำหนดขอบเขตในสิ่งที่ตรงกันข้าม

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

วิธีการที่กำหนดไว้ในต้นแบบไม่ได้ถูกกำหนดภายในขอบเขตของตัวสร้างและจะไม่สามารถเข้าถึงตัวแปรท้องถิ่นของตัวสร้างได้

คุณยังสามารถมีตัวแปรส่วนตัวได้ แต่ถ้าคุณต้องการให้วิธีที่กำหนดไว้ในต้นแบบมีการเข้าถึงคุณควรกำหนด getters และ setters บนthisวัตถุซึ่งวิธีต้นแบบ (พร้อมกับทุกอย่างอื่น) จะสามารถเข้าถึงได้ ตัวอย่างเช่น:

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
"scoping in reverse" เป็นคุณลักษณะ C ++ พร้อมคำสำคัญ "friend" ฟังก์ชั่นใด ๆ ที่สำคัญควรกำหนดต้นแบบของมันเป็นเพื่อน น่าเศร้าแนวคิดนี้คือ C ++ ไม่ใช่ JS :(
TWiStErRob

1
ฉันต้องการที่จะเพิ่มโพสต์นี้ไปยังด้านบนของรายการโปรดของฉันและเก็บไว้ที่นั่น
Donato

2
ฉันไม่เห็นประเด็นนี้ - คุณแค่เพิ่มเลเยอร์ของนามธรรมที่ไม่ทำอะไรเลย คุณอาจได้เป็นอย่างดีเพียงให้ทรัพย์สินของsecret thisJavaScript ไม่สนับสนุนตัวแปรส่วนตัวที่มีต้นแบบเนื่องจากต้นแบบถูกผูกไว้กับบริบทไซต์การโทรไม่ใช่บริบท 'ไซต์การสร้าง'
nicodemus13

1
ทำไมไม่ทำอย่างperson.getSecret()นั้น?
Fahmi

1
ทำไมถึงมี upvotes มากมาย? สิ่งนี้ไม่ทำให้ตัวแปรเป็นแบบส่วนตัว ดังกล่าวข้างต้นโดยใช้ person.getSecret () จะช่วยให้คุณเข้าถึงตัวแปรส่วนตัวจากที่ใดก็ได้
alexr101

64

อัปเดต: ด้วย ES6 มีวิธีที่ดีกว่า:

เรื่องย่อสั้น ๆ คุณสามารถใช้ใหม่Symbolเพื่อสร้างฟิลด์ส่วนตัว
นี่คือคำอธิบายที่ดี: https://curiosity-driven.org/private-properties-in-javascript

ตัวอย่าง:

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

สำหรับเบราว์เซอร์สมัยใหม่ทั้งหมดที่มี ES5:

คุณสามารถใช้เพียงแค่ปิด

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

หรือคุณสามารถใช้เพียงต้นแบบ

ใน JavaScript มรดก prototypal เป็นหลักการเพิ่มประสิทธิภาพ อนุญาตให้หลายอินสแตนซ์แบ่งปันวิธีต้นแบบมากกว่าแต่ละอินสแตนซ์ที่มีวิธีการของตนเอง
ข้อเสียเปรียบคือว่าthisเป็นเพียงสิ่งที่แตกต่างกันในแต่ละครั้งที่ฟังก์ชั่น prototypal เรียกว่า
ดังนั้นฟิลด์ส่วนตัวใด ๆ จะต้องสามารถเข้าถึงได้ผ่านthisซึ่งหมายความว่าพวกเขากำลังจะเป็นสาธารณะ ดังนั้นเราแค่ติดการตั้งชื่อแบบแผนสำหรับ_privateเขตข้อมูล

อย่ารบกวนการปิด Closures กับ Prototypes

ฉันคิดว่าคุณไม่ควรผสมตัวแปรการปิดเข้ากับวิธีต้นแบบ คุณควรใช้อันใดอันหนึ่ง

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

ฉันจะเลือกอันไหน

สำหรับวัตถุที่ง่ายจริงๆเพียงใช้วัตถุธรรมดาที่มีการปิด

หากคุณต้องการรับมรดกต้นแบบ - สำหรับการสืบทอดประสิทธิภาพและอื่น ๆ - จากนั้นทำตามแบบแผนการตั้งชื่อ "_private" และอย่าไปสนใจกับการปิด

ฉันไม่เข้าใจว่าทำไมนักพัฒนา JS จึงพยายามอย่างหนักเพื่อให้ฟิลด์เป็นส่วนตัวอย่างแท้จริง


4
น่าเศร้าที่_privateแผนการตั้งชื่อยังคงเป็นทางออกที่ดีที่สุดหากคุณต้องการใช้ประโยชน์จากการสืบทอดมรดก
บดขยี้

1
ES6 จะมีแนวคิดใหม่Symbolซึ่งเป็นวิธีที่ยอดเยี่ยมในการสร้างเขตข้อมูลส่วนตัว นี่คือคำอธิบายที่ดี: curiosity-driven.org/private-properties-in-javascript
Scott Rippey

1
ไม่ได้คุณสามารถSymbolปิดการเข้าร่วมที่ครอบคลุมทั้งชั้นเรียนของคุณได้ ด้วยวิธีดังกล่าววิธีต้นแบบทั้งหมดสามารถใช้ Symbol แต่ไม่เคยเปิดเผยนอกคลาส
Scott Rippey

2
บทความที่คุณเชื่อมโยงว่า " สัญลักษณ์ที่มีความคล้ายคลึงกับชื่อเอกชน แต่ - แตกต่างชื่อส่วนตัว - พวกเขาไม่ได้ให้ความเป็นส่วนตัวที่แท้จริง . " Object.getOwnPropertySymbolsได้อย่างมีประสิทธิภาพถ้าคุณมีอินสแตนซ์คุณจะได้รับสัญลักษณ์ด้วย ดังนั้นนี่คือความเป็นส่วนตัวเท่านั้นโดยความสับสน
Oriol

2
@Oriol ใช่ความเป็นส่วนตัวนั้นเกิดจากความสับสนอย่างหนัก toStringมันยังคงเป็นไปได้ที่จะย้ำถึงสัญลักษณ์และคุณสรุปวัตถุประสงค์ของสัญลักษณ์ผ่าน สิ่งนี้ไม่แตกต่างจาก Java หรือ C # ... สมาชิกส่วนบุคคลยังคงสามารถเข้าถึงได้ผ่านการสะท้อนกลับ แต่มักจะถูกบดบังอย่างรุนแรง ซึ่งทั้งหมดไปเสริมจุดสุดท้ายของฉัน "ฉันไม่เข้าใจว่าทำไมนักพัฒนา JS ลองอย่างหนักเพื่อให้ฟิลด์เป็นส่วนตัวอย่างแท้จริง"
Scott Rippey

31

เมื่อฉันอ่านสิ่งนี้มันฟังดูเหมือนเป็นความท้าทายที่ยากลำบากดังนั้นฉันจึงตัดสินใจหาวิธี สิ่งที่ฉันคิดไว้คือCRAAAAZYแต่มันใช้งานได้จริง

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

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

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

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

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

ฉันชอบความคิดเห็นจากผู้ที่เห็นข้อผิดพลาดด้วยวิธีนี้


4
ฉันเดาว่าข้อกังวลที่อาจเกิดขึ้นคืออินสแตนซ์ใด ๆ สามารถเข้าถึงอินสแตนซ์ส่วนตัว vars อื่น ๆ โดยใช้รหัสอินสแตนซ์อื่น ไม่จำเป็นต้องเป็นเรื่องเลวร้าย ...
มิมส์เอช. ไรท์

15
คุณกำหนดฟังก์ชั่นต้นแบบใหม่ทุกครั้งที่การเรียกใช้ตัวสร้าง
Lu4

10
@ Lu4 ฉันไม่แน่ใจว่าเป็นเรื่องจริง ตัวสร้างถูกส่งคืนจากภายในการปิด ครั้งเดียวที่ฟังก์ชันต้นแบบถูกกำหนดไว้เป็นครั้งแรกในการแสดงออกของฟังก์ชันที่เรียกใช้ในทันที ปัญหาความเป็นส่วนตัวที่กล่าวถึงข้างต้นนี้ดูดีสำหรับฉัน (ได้อย่างรวดเร็วก่อน)
guypursey

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

3
ฉันต้องการพูดถึงที่iได้รับการเพิ่มในทุกกรณี ดังนั้นจึงไม่ "โปร่งใส" อย่างเต็มที่และiยังสามารถแก้ไขได้
Scott Rippey

18

ดูหน้าของ Doug Crockford ในเรื่องนี้หน้าดั๊กครอกของเกี่ยวกับเรื่องนี้คุณต้องทำโดยอ้อมกับสิ่งที่สามารถเข้าถึงขอบเขตของตัวแปรส่วนตัว

ตัวอย่างอื่น:

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

ใช้กรณี:

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
ตัวอย่างนี้ดูเหมือนจะเป็นการปฏิบัติที่น่ากลัว จุดประสงค์ของการใช้วิธีต้นแบบคือเพื่อให้คุณไม่ต้องสร้างวิธีการใหม่สำหรับทุก ๆ อินสแตนซ์ คุณกำลังทำเช่นนั้น สำหรับทุกวิธีที่คุณสร้างอีกวิธีหนึ่ง
Kir

2
@ArmedMonkey แนวคิดดูดี แต่เห็นด้วยว่านี่เป็นตัวอย่างที่ไม่ดีเพราะฟังก์ชั่นต้นแบบที่แสดงนั้นไม่สำคัญ หากฟังก์ชั่นต้นแบบเป็นฟังก์ชั่นที่นานกว่านั้นที่ต้องการการเข้าถึง / ตั้งค่าตัวแปร 'ส่วนตัว' อย่างง่ายมันจะสมเหตุสมผล
แพนเค้ก

9
ทำไมถึงต้องรำคาญกับการเปิดเผย_setผ่านset? ทำไมไม่ลองตั้งชื่อsetให้เริ่มด้วยล่ะ
Scott Rippey

15

ฉันขอแนะนำว่าอาจเป็นความคิดที่ดีที่จะอธิบาย "มีการกำหนดต้นแบบในคอนสตรัคเตอร์" เป็นรูปแบบการต่อต้านจาวาสคริปต์ ลองคิดดู มันเสี่ยงเกินไป

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

ฉันพบข้อผิดพลาดใน Javascript บางตัวที่ฉันทำงานเมื่อเร็ว ๆ นี้ซึ่งเป็นเพราะรูปแบบการต่อต้านที่แน่นอน กำลังพยายามตั้งค่าตัวจัดการการลากและวางบนวัตถุเฉพาะที่กำลังสร้าง แต่แทนที่จะทำสำหรับทุกกรณี ไม่ดี.

ทางออกของ Doug Crockford นั้นดีที่สุด


10

@Kai

นั่นไม่ได้ผล ถ้าคุณทำ

var t2 = new TestClass();

จากนั้นt2.prototypeHelloจะเข้าถึงส่วนส่วนตัวของ t

@AnglesCrimes

โค้ดตัวอย่างทำงานได้ดี แต่จริง ๆ แล้วสร้างสมาชิกส่วนตัว "คงที่" ที่ใช้ร่วมกันโดยทุกกรณี มันอาจจะไม่ใช่วิธีการแก้ปัญหา morgancodes มองหา

จนถึงตอนนี้ฉันยังไม่พบวิธีที่ง่ายและสะอาดในการทำเช่นนี้โดยไม่ได้แนะนำแฮชส่วนตัวและฟังก์ชั่นการล้างข้อมูลเพิ่มเติม ฟังก์ชั่นสมาชิกส่วนตัวสามารถจำลองในระดับหนึ่ง:

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

ทำความเข้าใจกับคะแนนของคุณอย่างชัดเจน แต่คุณสามารถโปรดอธิบายว่ารหัสข้อมูลของคุณพยายามทำอะไร
Vishwanath

privateFoonew Foo()จะสมบูรณ์ส่วนตัวและมองไม่เห็นจึงเมื่อได้รับ เพียง แต่เป็นวิธีการของประชาชนที่นี่ซึ่งมีการเข้าถึงbar() privateFooคุณสามารถใช้กลไกเดียวกันสำหรับตัวแปรและวัตถุอย่างง่ายอย่างไรก็ตามคุณต้องจำไว้เสมอว่าสิ่งเหล่านั้นprivatesเป็นแบบคงที่และจะถูกใช้ร่วมกันโดยวัตถุทั้งหมดที่คุณสร้าง
Philzen

6

ใช่มันเป็นไปได้ รูปแบบการออกแบบ PPF เพียงแค่แก้ปัญหานี้

PPF ย่อมาจากฟังก์ชั่นต้นแบบส่วนตัว PPF พื้นฐานแก้ปัญหาเหล่านี้:

  1. ฟังก์ชั่นต้นแบบได้รับการเข้าถึงข้อมูลอินสแตนซ์ส่วนตัว
  2. ฟังก์ชั่นต้นแบบสามารถทำให้เป็นส่วนตัว

สำหรับคนแรกเพียง:

  1. ใส่ตัวแปรอินสแตนซ์ส่วนตัวทั้งหมดที่คุณต้องการให้เข้าถึงได้จากฟังก์ชั่นต้นแบบภายในที่เก็บข้อมูลแยกต่างหาก
  2. ผ่านการอ้างอิงไปยังที่เก็บข้อมูลไปยังฟังก์ชั่นต้นแบบทั้งหมดเป็นพารามิเตอร์

มันง่ายมาก ตัวอย่างเช่น:

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

...

อ่านเรื่องเต็มได้ที่นี่:

รูปแบบการออกแบบ PPF


4
คำตอบสำหรับลิงก์เท่านั้นโดยทั่วไปจะขมวดคิ้วอยู่บน SO กรุณาแสดงตัวอย่าง
Corey Adler

บทความนี้มีตัวอย่างอยู่ข้างในดังนั้นโปรดดูที่นั่น
Edward

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

3
@Edward ลิงค์ของคุณน่าสนใจอ่าน! อย่างไรก็ตามสำหรับฉันแล้วเหตุผลหลักในการเข้าถึงข้อมูลส่วนตัวโดยใช้ฟังก์ชั่นต้นแบบคือเพื่อป้องกันไม่ให้วัตถุทุกชิ้นทำให้หน่วยความจำสูญเสียด้วยฟังก์ชันสาธารณะที่เหมือนกัน วิธีที่คุณอธิบายไม่สามารถแก้ปัญหานี้ได้เนื่องจากการใช้สาธารณะต้องมีการห่อหุ้มฟังก์ชันต้นแบบในฟังก์ชันสาธารณะทั่วไป ฉันเดาว่ารูปแบบอาจมีประโยชน์สำหรับการบันทึกหน่วยความจำหากคุณมี ppf จำนวนมากซึ่งรวมกันในฟังก์ชั่นสาธารณะเดียว คุณใช้มันเพื่อสิ่งอื่นหรือไม่?
นักปราชญ์รับประทานอาหาร

@DiningPhilosofer ขอขอบคุณที่ชื่นชมบทความของฉัน ใช่คุณพูดถูกเรายังคงใช้ฟังก์ชั่นอินสแตนซ์ แต่แนวคิดก็คือการทำให้พวกเขามีน้ำหนักเบาที่สุดเท่าที่จะเป็นไปได้โดยเพียงแค่เรียก PPF มาใช้ซึ่งทำงานหนักทั้งหมด ในที่สุดอินสแตนซ์ทั้งหมดเรียก PPFs เดียวกัน (ผ่านทางโปรแกรมเสริมของหลักสูตร) ​​ดังนั้นอาจมีการบันทึกหน่วยความจำบางอย่าง คำถามคือเท่าไหร่ ฉันคาดหวังการประหยัดอย่างมาก
Edward

5

คุณสามารถทำได้จริงโดยใช้การยืนยันการเข้าถึง :

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

ตัวอย่างนี้มาจากโพสต์ของฉันเกี่ยวกับฟังก์ชั่นต้นแบบและข้อมูลส่วนตัวและมีการอธิบายในรายละเอียดเพิ่มเติมที่นั่น


1
คำตอบนี้ "ฉลาด" เกินไปที่จะเป็นประโยชน์ แต่ฉันชอบคำตอบของการใช้ตัวแปร IFFE-bound เป็น handshake ลับ การใช้งานนี้ใช้การปิดจำนวนมากเกินไปที่จะเป็นประโยชน์ จุดที่มีวิธีกำหนดต้นแบบคือการป้องกันการสร้างฟังก์ชั่นวัตถุใหม่สำหรับแต่ละวิธีในแต่ละวัตถุ
greg.kindel

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

4

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

เพื่อสรุปผล: Personคลาสมีแผนที่ที่อ่อนแอเพียงครั้งเดียวโดยที่คีย์คืออินสแตนซ์ของบุคคลและค่าเป็นวัตถุธรรมดาที่ใช้สำหรับการจัดเก็บส่วนตัว

นี่คือตัวอย่างการทำงานอย่างสมบูรณ์: (เล่นที่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];
        }
    };
}

อย่างที่ฉันบอกไปนี่เป็นวิธีเดียวที่จะบรรลุทั้ง 3 ส่วนได้

อย่างไรก็ตามมีสองประการ ก่อนอื่นค่าใช้จ่ายนี้มีผลต่อประสิทธิภาพ - ทุกครั้งที่คุณเข้าถึงข้อมูลส่วนตัวมันเป็นการO(n)ดำเนินการที่nมีจำนวนอินสแตนซ์ ดังนั้นคุณจะไม่ต้องการทำสิ่งนี้หากคุณมีอินสแตนซ์จำนวนมาก ประการที่สองเมื่อคุณทำกับอินสแตนซ์คุณต้องโทรdestroy ; มิฉะนั้นอินสแตนซ์และข้อมูลจะไม่ถูกรวบรวมขยะและคุณจะจบลงด้วยการรั่วไหลของหน่วยความจำ

และนั่นคือสาเหตุที่คำตอบดั้งเดิมของฉัน"คุณไม่ควร"เป็นสิ่งที่ฉันอยากทำ


หากคุณไม่ได้ทำลายอินสแตนซ์ของ Person อย่างชัดเจนก่อนที่มันจะไม่อยู่ในขอบเขตขอบเขตที่อ่อนแอจะไม่อ้างอิงถึงดังนั้นคุณจะมีหน่วยความจำรั่วหรือไม่ ฉันมาพร้อมกับรูปแบบการป้องกันตามที่อินสแตนซ์อื่นของบุคคลสามารถเข้าถึงตัวแปรและสิ่งที่สืบทอดจากบุคคลสามารถ เพียงแค่เล่นมันออกมาดังนั้นไม่แน่ใจว่ามีข้อได้เปรียบใด ๆ นอกเหนือจากการประมวลผลพิเศษ (ดูไม่มากเท่ากับการเข้าถึงของเอกชน) stackoverflow.com/a/21800194/1641941 การส่งคืนวัตถุส่วนตัว / ได้รับการป้องกันเป็นความเจ็บปวดนับตั้งแต่การเรียกรหัส จากนั้นสามารถกลายพันธุ์ส่วนตัว / ป้องกันของคุณ
HMR

2
@HMR ใช่คุณต้องทำลายข้อมูลส่วนตัวอย่างชัดเจน ฉันจะเพิ่มคำเตือนนี้เข้าไปในคำตอบของฉัน
Scott Rippey

3

มีวิธีที่ง่ายกว่าด้วยการใช้ประโยชน์จากbindและcallวิธีการ

โดยการตั้งค่าตัวแปรส่วนตัวให้กับวัตถุคุณสามารถใช้ประโยชน์จากขอบเขตของวัตถุนั้น

ตัวอย่าง

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

วิธีนี้ไม่ได้ไม่มีข้อเสีย เนื่องจากบริบทขอบเขตถูกแทนที่อย่างมีประสิทธิภาพคุณจึงไม่สามารถเข้าถึงภายนอก_privateวัตถุได้ อย่างไรก็ตามมันเป็นไปไม่ได้ที่จะยังคงให้การเข้าถึงขอบเขตของวัตถุตัวอย่าง คุณสามารถผ่านในบริบทของวัตถุ ( this) เป็นอาร์กิวเมนต์ที่สองไปยังbindหรือcallยังคงมีการเข้าถึงค่าสาธารณะในฟังก์ชั่นต้นแบบ

การเข้าถึงค่าสาธารณะ

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
ทำไมบางคนถึงสร้างสำเนาของวิธีต้นแบบแทนที่จะสร้างวิธีที่ไม่ได้มาตรฐานในตอนแรก
บดขยี้

3

ลองมัน!

    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
สิ่งนี้ขึ้นอยู่กับcallerซึ่งเป็นส่วนขยายที่ขึ้นอยู่กับการใช้งานไม่ได้รับอนุญาตในโหมดเข้มงวด
Oriol

1

นี่คือสิ่งที่ฉันมาด้วย

(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

ปัญหาหลักของการใช้งานนี้คือการกำหนดต้นแบบใหม่ในทุก ๆ การติดตั้ง


น่าสนใจฉันชอบความพยายามและคิดถึงสิ่งเดียวกัน แต่คุณพูดถูกว่าการกำหนดฟังก์ชั่นต้นแบบใหม่ในทุกการสร้างอินสแตนซ์นั้นเป็นข้อ จำกัด ที่ค่อนข้างใหญ่ นี่ไม่ใช่เพียงเพราะมันสูญเสียรอบ CPU แต่ถ้าคุณเปลี่ยน prototoype ในภายหลังมันจะได้รับ "รีเซ็ต" กลับสู่สถานะดั้งเดิมตามที่กำหนดไว้ในตัวสร้างเมื่ออินสแตนซ์ถัดไป: /
Niko Bellic

1
ต้นแบบที่ไม่เพียง แต่นิยามใหม่นี้จะกำหนดตัวสร้างใหม่สำหรับแต่ละอินสแตนซ์ ดังนั้น "อินสแตนซ์" จึงไม่ใช่อินสแตนซ์ของคลาสเดียวกันอีกต่อไป
Oriol

1

มีวิธีง่าย ๆ ในการทำเช่นนี้

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._

จาวาสคริปต์ต้นแบบนั้นมีสีทอง


2
ฉันเชื่อว่ามันจะดีกว่าที่จะไม่ใช้ต้นแบบในฟังก์ชั่นคอนสตรัคเตอร์เพราะมันจะสร้างฟังก์ชั่นใหม่ทุกครั้งที่มีการสร้างอินสแตนซ์ใหม่
whamsicore

@whamsicore ใช่จริง แต่ในกรณีนี้มันสำคัญมากสำหรับวัตถุทุกชิ้นที่เราได้ทำการปิดการแบ่งปัน นั่นคือเหตุผลที่นิยามฟังก์ชั่นอยู่ภายในตัวสร้างและเราต้องอ้างถึงSharedPrivate.prototypeว่าthis.constructor.prototypeมันไม่ใช่เรื่องใหญ่ที่จะกำหนด getP และ setP ซ้ำหลายครั้ง ...
Redu

1

ฉันไปงานปาร์ตี้สาย แต่ฉันคิดว่าฉันสามารถมีส่วนร่วมได้ ที่นี่ตรวจสอบนี้:

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

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

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


0

คุณไม่สามารถวางตัวแปรในขอบเขตที่สูงขึ้นได้หรือไม่

(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
จากนั้นตัวแปรจะถูกแชร์ระหว่างอินสแตนซ์ทั้งหมดของ MyClass
บดขยี้

0

คุณสามารถลองเพิ่มเมธอดไม่ได้โดยตรงบนต้นแบบ แต่บนฟังก์ชันตัวสร้างเช่นนี้:

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

นี่คือสิ่งที่ฉันคิดขึ้นมาในขณะที่พยายามค้นหาวิธีแก้ปัญหาที่ง่ายที่สุดสำหรับปัญหานี้บางทีอาจเป็นประโยชน์กับใครบางคน ฉันใหม่กับ javascript ดังนั้นอาจมีปัญหาบางอย่างเกี่ยวกับรหัส

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

ฉันเผชิญหน้ากับคำถามเดียวกันทุกวันนี้และหลังจากที่อธิบายอย่างละเอียดเกี่ยวกับการตอบรับชั้นหนึ่งของ Scott Rippey ฉันได้พบกับวิธีแก้ปัญหาที่ง่ายมาก (IMHO) ที่เข้ากันได้กับ ES5 และมีประสิทธิภาพและยังเป็นชื่อที่ปลอดภัย .

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

ทดสอบกับ ringojs และ nodejs ฉันอยากอ่านความคิดเห็นของคุณ


นี่คือการอ้างอิง: ตรวจสอบส่วน 'ขั้นตอนที่ใกล้ชิด' philipwalton.com/articles/…
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()

เป็นไงบ้าง ใช้ accessor ส่วนตัว อนุญาตให้คุณรับตัวแปรโดยไม่ต้องตั้งค่าขึ้นอยู่กับกรณีการใช้งาน


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

เขาต้องการวิธีในการเข้าถึงตัวแปรส่วนตัวที่ซ่อนอยู่ของ Class ผ่านทางต้นแบบโดยไม่ต้องสร้างตัวแปรที่ซ่อนอยู่ในทุก ๆ อินสแตนซ์ของคลาส โค้ดด้านบนเป็นตัวอย่างวิธีการทำเช่นนั้น นั่นไม่ใช่คำตอบของคำถาม?
dylan0150

ฉันไม่ได้บอกว่ามันไม่ใช่คำตอบสำหรับคำถาม ฉันบอกว่ามันไม่ใช่คำตอบที่มีประโยชน์เพราะมันไม่ได้ช่วยให้ใครเรียนรู้ คุณควรอธิบายโค้ดของคุณว่าทำไมมันถึงใช้ได้ทำไมมันถึงเป็นวิธีที่ถูกต้อง ถ้าฉันเป็นผู้เขียนคำถามฉันจะไม่ยอมรับคำตอบของคุณเพราะมันไม่สนับสนุนการเรียนรู้มันไม่ได้สอนฉันว่าฉันทำอะไรผิดหรือรหัสที่กำหนดนั้นกำลังทำอยู่
GrumpyCrouton

0

ฉันมีวิธีแก้ไขปัญหาเดียว แต่ฉันไม่แน่ใจว่าไม่มีข้อบกพร่อง

เพื่อให้ใช้งานได้คุณต้องใช้โครงสร้างต่อไปนี้:

  1. ใช้ 1 วัตถุส่วนตัวที่มีตัวแปรส่วนตัวทั้งหมด
  2. ใช้ 1 ฟังก์ชันอินสแตนซ์
  3. ใช้การปิดตัวสร้างและฟังก์ชั่นต้นแบบทั้งหมด
  4. อินสแตนซ์ใด ๆ ที่สร้างจะทำนอกกำหนดปิด

นี่คือรหัส:

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

วิธีการทำงานคือให้ฟังก์ชั่นอินสแตนซ์ "this.getPrivateFields" เพื่อเข้าถึงวัตถุตัวแปรส่วนตัว "privateFields" แต่ฟังก์ชั่นนี้จะส่งคืนเฉพาะวัตถุ "privateFields" ภายในการปิดหลักที่กำหนดไว้ ต้องกำหนดไว้ในการปิดนี้)

แฮชที่สร้างขึ้นระหว่างรันไทม์และยากต่อการเดาถูกใช้เป็นพารามิเตอร์เพื่อให้แน่ใจว่าแม้ว่าจะเรียกว่า "getPrivateFields" นอกขอบเขตของการปิดจะไม่ส่งคืนวัตถุ "privateFields"

ข้อเสียเปรียบคือเราไม่สามารถขยาย TestClass ด้วยฟังก์ชั่นต้นแบบเพิ่มเติมนอกการปิด

นี่คือรหัสทดสอบบางส่วน:

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

แก้ไข: ใช้วิธีนี้มันเป็นไปได้ที่จะ "กำหนด" ฟังก์ชั่นส่วนตัว

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

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

0

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

วิธีการแก้ปัญหาขึ้นอยู่กับโหลดโมดูลที่ผลิตเองซึ่งโดยทั่วไปจะกลายเป็นสื่อกลางสำหรับแคชหน่วยเก็บข้อมูลส่วนตัว (โดยใช้แผนที่อ่อนแอ)

   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

ฉันรู้ว่ามันถูกใช้มานานกว่า 1 ทศวรรษแล้วตั้งแต่มีการถามคำถามนี้ แต่ฉันแค่คิดถึงสิ่งนี้เป็นครั้งที่ n ในชีวิตนักเขียนโปรแกรมของฉันและพบวิธีแก้ปัญหาที่เป็นไปได้ที่ฉันไม่รู้ . ฉันไม่ได้เห็นวิธีการนี้เอกสารก่อนดังนั้นผมจะตั้งชื่อมันว่า "รัฐ / เอกชนรูปแบบดอลล่า" หรือ_ $ / $ รูปแบบ

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

แนวคิดใช้ฟังก์ชันClassDefinitionที่ส่งกลับฟังก์ชันตัวสร้างที่ส่งคืนวัตถุอินเทอร์เฟซ วิธีการเฉพาะของอินเตอร์เฟซ$ที่ได้รับการnameโต้แย้งเพื่อเรียกใช้ฟังก์ชั่นที่สอดคล้องกันในวัตถุคอนสตรัค, ข้อโต้แย้งเพิ่มเติมใด ๆ ที่ผ่านหลังจากnameจะถูกส่งผ่านในการภาวนา

ทั่วโลกที่กำหนดไว้ผู้ช่วยฟังก์ชั่นClassValuesร้านค้าทุกสาขาในวัตถุตามความจำเป็น มันกำหนดฟังก์ชั่นในการเข้าถึงพวกเขาโดย นี่เป็นไปตามรูปแบบ get / set แบบสั้นดังนั้นหากผ่านไปแล้วจะถูกใช้เป็นค่าตัวแปรใหม่_$namevalue

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

      return values[name];
    }
  };
};

ฟังก์ชั่นที่กำหนดไว้ทั่วโลกInterfaceใช้วัตถุและValuesวัตถุที่จะส่งกลับ_interfaceด้วยฟังก์ชั่นเดียวเดียว$ที่ตรวจสอบobjเพื่อหาฟังก์ชั่นตั้งชื่อตามพารามิเตอร์nameและเรียกมันด้วยvaluesเป็นวัตถุที่กำหนดขอบเขต อาร์กิวเมนต์เพิ่มเติมที่ส่งไปยัง$จะถูกส่งผ่านไปยังการเรียกใช้ฟังก์ชัน

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

ในตัวอย่างด้านล่างClassXถูกกำหนดให้กับผลลัพธ์ของClassDefinitionซึ่งเป็นConstructorฟังก์ชัน Constructorอาจได้รับข้อโต้แย้งจำนวนเท่าใดก็ได้ Interfaceคือสิ่งที่รหัสภายนอกได้รับหลังจากเรียกตัวสร้าง

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

ไม่มีจุดในการมีฟังก์ชั่นที่ไม่ใช่ต้นแบบConstructorแม้ว่าคุณจะสามารถกำหนดได้ในฟังก์ชั่นร่างกายของตัวสร้าง ฟังก์ชั่นทั้งหมดจะถูกเรียกว่ามีรูปแบบเงินดอลลาร์สาธารณะ this.$("functionName"[, param1[, param2 ...]])ค่าส่วนตัวมีการเข้าถึงที่มีรูปแบบเงินดอลลาร์ส่วนตัว this._$("valueName"[, replacingValue]);เนื่องจากInterfaceไม่มีคำจำกัดความของ_$วัตถุภายนอกจึงไม่สามารถเข้าถึงค่าได้ เนื่องจากแต่ละตัวของฟังก์ชั่นต้นแบบthisถูกตั้งค่าเป็นvaluesวัตถุในฟังก์ชั่น$คุณจะได้รับข้อยกเว้นหากคุณเรียกใช้ฟังก์ชันพี่น้องของคอนสตรัคเตอร์โดยตรง _ $ / $ รูปแบบความต้องการที่จะตามมาในร่างกายของฟังก์ชั่นเป็นต้นแบบเกินไป การใช้งานด้านล่างตัวอย่าง

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

และคอนโซลเอาท์พุท

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

_ $ / $ รูปแบบที่ช่วยให้ความเป็นส่วนตัวเต็มรูปแบบของค่าในการเรียนอย่างเต็มที่เป็นต้นแบบ ฉันไม่รู้ว่าฉันจะใช้สิ่งนี้หรือไม่ถ้ามีข้อบกพร่อง แต่เดี๋ยวก่อนมันเป็นปริศนาที่ดี!


0

ES6 WeakMaps

โดยใช้รูปแบบที่เรียบง่ายอยู่ในES6 WeakMapsเป็นไปได้ที่จะได้รับตัวแปรสมาชิกส่วนตัวเข้าถึงได้จากฟังก์ชั่นต้นแบบ

หมายเหตุ: การใช้ WeakMaps รับประกันความปลอดภัยต่อการรั่วไหลของหน่วยความจำโดยให้ Garbage Collector ระบุและละทิ้งอินสแตนซ์ที่ไม่ได้ใช้

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

สามารถอ่านคำอธิบายโดยละเอียดเพิ่มเติมเกี่ยวกับรูปแบบนี้ได้ที่นี่


-1

คุณต้องเปลี่ยน 3 สิ่งในรหัสของคุณ:

  1. แทนที่ด้วยvar privateField = "hello"this.privateField = "hello"
  2. ในต้นแบบแทนที่ด้วยprivateFieldthis.privateField
  3. ในที่ไม่ใช่ต้นแบบยังแทนที่ด้วยprivateFieldthis.privateField

รหัสสุดท้ายจะเป็นดังต่อไปนี้:

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.privateFieldจะไม่เป็นสนามส่วนตัว สามารถเข้าถึงได้จากด้านนอก:t.privateField
V. Rubinetti

-2

คุณสามารถใช้การกำหนดต้นแบบภายในคำจำกัดความของตัวสร้าง

ตัวแปรจะสามารถมองเห็นได้ด้วยวิธีการเพิ่มต้นแบบ แต่อินสแตนซ์ทั้งหมดของฟังก์ชั่นจะเข้าถึงตัวแปร 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();

ฉันหวังว่านี่จะเป็นประโยชน์อย่างเต็มที่

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