คุณสมบัติส่วนตัวในคลาส JavaScript ES6


444

เป็นไปได้หรือไม่ที่จะสร้างคุณสมบัติส่วนตัวในคลาส ES6

นี่คือตัวอย่าง ฉันจะป้องกันการเข้าถึงได้instance.propertyอย่างไร

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

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

5
มีจริงขั้นตอนที่ 3 ข้อเสนอสำหรับคุณลักษณะนี้ - tc39.github.io/proposal-class-fields github.com/tc39/proposal-class-fields
อาร์ตี้

@arty ฉันได้ให้คำตอบกับตัวอย่างนี้: stackoverflow.com/a/52237988/1432509
Alister

คำตอบ:


165

เขตภาคเอกชน (และวิธีการ) มีการดำเนินการในมาตรฐาน ECMA คุณสามารถเริ่มใช้มันได้แล้ววันนี้ด้วยการตั้งค่าล่วงหน้าแบบบาเบล 7และ 3

class Something {
  #property;

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

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

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

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

ฉันสงสัยว่าชั้นเรียนเหล่านั้นสามารถทำงานได้อย่างไร คุณไม่สามารถใช้งานthisใน constructor ก่อนที่คุณจะโทรsuper()ได้ ถึงแม้ว่าบาเบลจะนำพวกเขามาก่อนสุดยอด
seeker_of_bacon

วิธีกำหนดค่า ESLint เพื่อให้สามารถ#privateCrapใช้งานไวยากรณ์ได้
Marecky

6
แล้ว eslint ล่ะ? ฉันพบข้อผิดพลาดในการแยกวิเคราะห์ที่เครื่องหมายเท่ากับ บาเบลทำงานได้เพียง eslint ไม่สามารถแยกวิเคราะห์ไวยากรณ์ js ใหม่นี้
martonx

6
ว้าวนี่มันน่าเกลียดมาก Hashtag เป็นอักขระที่ถูกต้อง สถานที่ไม่ได้เป็นส่วนตัวจริงๆหรือ? .. ฉันตรวจสอบใน TypeScript สมาชิกส่วนตัวไม่ได้รวบรวมเป็นแบบส่วนตัวหรือแบบอ่านอย่างเดียว (จากภายนอก) เพิ่งประกาศเช่นเดียวกับทรัพย์สินอื่น (สาธารณะ) (ES5)
Dominik

2
คุณจะเขียนวิธีส่วนตัวด้วยวิธีนี้ได้อย่างไร ฉันสามารถทำสิ่งนี้: #beep() {}; และนี่: async #bzzzt() {}?
КонстантинВан

277

คำตอบสั้น ๆ ไม่ไม่มีการสนับสนุนดั้งเดิมสำหรับคุณสมบัติส่วนตัวที่มีคลาส ES6

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

ES6

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

ES5

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

1
ฉันชอบวิธีนี้ดีที่สุด ฉันเห็นด้วยว่าไม่ควรใช้สำหรับการปรับขนาด แต่มันเหมาะสำหรับชั้นเรียนที่มักจะถูกสร้างอินสแตนซ์เพียงครั้งเดียวต่อการรวม
Blake Regalia

2
นอกจากนี้คุณจะต้องนิยามองค์ประกอบทุกส่วนของคลาสนี้ใหม่ทุกครั้งที่มีการสร้างใหม่
Quentin Roy

10
นี่มันแปลกมาก! ใน ES6 คุณกำลังสร้าง "ปิรามิดปิด" มากกว่า ES6 ก่อนหน้า! การกำหนดฟังก์ชั่นภายใน Constructor นั้นดูน่าเกลียดกว่าที่เคยทำในตัวอย่าง ES5 ด้านบน
Kokodoko

1
เนื่องจาก OP ถามเฉพาะเกี่ยวกับคลาส ES6 โดยส่วนตัวฉันคิดว่านี่เป็นวิธีการแก้ปัญหาที่ไม่ดี ข้อ จำกัด ที่สำคัญคือตอนนี้วิธีการเรียนทุกครั้งที่ใช้ตัวแปรส่วนตัวจะต้องประกาศภายในตัวสร้างทำลายข้อได้เปรียบอย่างรุนแรงของการมีclassไวยากรณ์ในสถานที่แรก
NanoWizard

10
ทั้งหมดนี้จะแนะนำทางอ้อม ตอนนี้คุณจะทำให้getNameและsetNameคุณสมบัติเป็นส่วนตัวได้อย่างไร
aij

195

หากต้องการขยายคำตอบของ @ loganfsmyth:

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

ตัวแปรที่กำหนดขอบเขต

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

ตัวอย่าง:

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

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

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

// here we can access name but not age

แผนที่อ่อนแอ

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

ตัวอย่าง:

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

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

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

  return Person;
})();

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

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

ตัวอย่างนี้ใช้วัตถุเพื่อใช้ WeakMap เดียวสำหรับคุณสมบัติส่วนตัวหลายรายการ คุณยังสามารถใช้ WeakMaps หลายและใช้พวกเขาชอบหรือเขียนกระดาษห่อขนาดเล็กและใช้วิธีอื่นเช่นage.set(this, 20)privateProps.set(this, 'age', 0)

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

(วิธีนี้สามารถทำได้ด้วยMapแต่WeakMapจะดีกว่าเพราะMapจะสร้างการรั่วไหลของหน่วยความจำเว้นแต่คุณจะระมัดระวังและเพื่อจุดประสงค์นี้ทั้งสองจะไม่แตกต่างกัน)

ครึ่งคำตอบ: สัญลักษณ์ที่กำหนดขอบเขต

Symbol เป็นประเภทของค่าดั้งเดิมที่สามารถใช้เป็นชื่อคุณสมบัติ this[mySymbol]คุณสามารถใช้วิธีการกำหนดขอบเขตตัวแปรในการสร้างสัญลักษณ์ส่วนตัวแล้วเก็บข้อมูลส่วนตัว

ความเป็นส่วนตัวของวิธีนี้อาจถูกละเมิดโดยใช้Object.getOwnPropertySymbolsแต่ค่อนข้างจะน่าอึดอัดใจที่จะทำ

ตัวอย่าง:

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

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

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

  return Person;
})();

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

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

ครึ่งคำตอบ: ขีดล่าง

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

ตัวอย่าง:

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

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

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

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

ข้อสรุป

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


7
ตัวอย่างแรกตัวอย่าง ("ตัวแปรที่กำหนดขอบเขต") คือตัวรวมทั้งหมด - แต่ละอ็อบเจ็กต์ที่ส่งคืนจะมีคลาสที่แตกต่างกัน อย่าทำอย่างนั้น หากคุณต้องการวิธีการที่มีสิทธิพิเศษให้สร้างพวกเขาในตัวสร้าง
Bergi

1
การตัดคลาสในฟังก์ชั่นดูเหมือนจะเอาชนะจุดประสงค์ทั้งหมดของการใช้คลาสในตอนแรก หากคุณใช้ฟังก์ชั่นนี้เพื่อสร้างตัวอย่างคุณอาจวางสมาชิกส่วนตัว / สาธารณะทั้งหมดของคุณไว้ในฟังก์ชั่นนั้นเช่นกันและลืมคำสำคัญทั้งหมดของคลาส
Kokodoko

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

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

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

117

อัปเดต: ข้อเสนอที่มีไวยากรณ์ดีกว่ากำลังจะมา ผลงานยินดีต้อนรับ


ใช่มี - สำหรับการเข้าถึงขอบเขตในวัตถุ - ES6 แนะนำSymbol s

สัญลักษณ์ไม่เหมือนใครคุณไม่สามารถเข้าถึงสิ่งใดสิ่งหนึ่งได้จากภายนอกยกเว้นการสะท้อนกลับ (เช่นแบบส่วนตัวใน Java / C #) แต่ทุกคนที่มีการเข้าถึงสัญลักษณ์ด้านในสามารถใช้เพื่อการเข้าถึงที่สำคัญ:

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

var instance = new Something();

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

6
คุณใช้Object.getOwnPropertySymbolsไม่ได้เหรอ ;)
แควนตัส 94 Heavy

41
@BenjaminGruenbaum: สัญลักษณ์ดูเหมือนจะไม่รับประกันความเป็นส่วนตัวที่แท้จริงอีกต่อไป: stackoverflow.com/a/22280202/1282216
d13

28
@trusktr ผ่านคีย์ thre หรือไม่ ไม่เลยผ่านสัญลักษณ์เหรอ? ใช่. เป็นอย่างมากเหมือนกับวิธีที่คุณสามารถใช้การสะท้อนในภาษาเช่น C # และ Java เพื่อเข้าถึงฟิลด์ส่วนตัว ตัวดัดแปลงการเข้าถึงไม่ได้เกี่ยวกับความปลอดภัย แต่มันเกี่ยวกับเจตนาที่ชัดเจน
Benjamin Gruenbaum

9
ดูเหมือนว่าการใช้ Symbols นั้นคล้ายกับการทำแบบconst myPrivateMethod = Math.random(); Something.prototype[''+myPrivateMethod] = function () { ... } new Something()[''+myPrivateMethod]();นี้มันไม่ได้เป็นความเป็นส่วนตัวจริงๆมันคลุมเครือในแง่ของจาวาสคริปต์แบบดั้งเดิม ฉันจะพิจารณาจาวาสคริปต์ "ส่วนตัว" เพื่อหมายถึงการใช้การปิดเพื่อห่อหุ้มตัวแปร ดังนั้นตัวแปรเหล่านี้จึงไม่สามารถเข้าถึงได้ผ่านการสะท้อนกลับ
trusktr

13
นอกจากนี้ผมรู้สึกว่าการใช้privateและprotectedคำหลักที่จะเป็นมากสะอาดกว่าหรือSymbol Nameฉันชอบเครื่องหมายจุดมากกว่าเครื่องหมายวงเล็บ ฉันต้องการที่จะใช้จุดสำหรับสิ่งส่วนตัว this.privateVar
trusktr

33

คำตอบคือ "ไม่" แต่คุณสามารถสร้างการเข้าถึงคุณสมบัติส่วนตัวเช่นนี้:

  • ใช้โมดูล ทุกอย่างในโมดูลนั้นเป็นแบบส่วนตัวเว้นแต่จะเปิดเผยต่อสาธารณะโดยใช้exportคำหลัก
  • ภายในโมดูลให้ใช้ฟังก์ชันปิด: http://www.kirupa.com/html5/closures_in_javascript.htm

(ข้อเสนอแนะที่สามารถใช้สัญลักษณ์เพื่อรับรองความเป็นส่วนตัวนั้นเป็นจริงในข้อมูลจำเพาะ ES6 รุ่นก่อนหน้า แต่ไม่เป็นเช่นนั้นอีกต่อไป: https://mail.mozilla.org/pipermail/es-discuss/2014-มกราคม/035604 htmlและhttps://stackoverflow.com/a/22280202/1282216สำหรับการสนทนาที่ยาวนานขึ้นเกี่ยวกับสัญลักษณ์และความเป็นส่วนตัวดูได้ที่: https://curiosity-driven.org/private-properties-in-javascript )


6
-1 นี่ไม่ตอบคำถามของคุณจริงๆ (คุณสามารถใช้การปิดด้วย IIFE ใน ES5 ได้เช่นกัน) คุณสมบัติส่วนบุคคลสามารถนับผ่านการสะท้อนในภาษาส่วนใหญ่ (Java, C #, ฯลฯ ) จุดสำคัญของคุณสมบัติส่วนตัวคือการสื่อความตั้งใจไปยังผู้เขียนโปรแกรมคนอื่นและไม่บังคับใช้การรักษาความปลอดภัย
Benjamin Gruenbaum

1
@BenjaminGruenbaum ฉันรู้ว่าฉันหวังว่าฉันจะได้คำตอบที่ดีกว่าฉันก็ไม่มีความสุขกับมันเช่นกัน
d13

ฉันคิดว่าสัญลักษณ์ยังคงเป็นวิธีที่ถูกต้องในการบรรลุสมาชิกที่ไม่สามารถเข้าถึงได้ในขณะที่อยู่ในสภาพแวดล้อมการเขียนโปรแกรม ใช่พวกเขายังคงสามารถพบได้ถ้าคุณต้องการ reallllyyy แต่นั่นไม่ใช่ประเด็นคืออะไร? คุณไม่ควรเก็บข้อมูลที่ละเอียดอ่อนไว้ แต่คุณไม่ควรทำเช่นนั้นในรหัสฝั่งไคลเอ็นต์ แต่มันใช้งานได้เพื่อวัตถุประสงค์ในการซ่อนคุณสมบัติหรือวิธีการจากคลาสภายนอก
Kokodoko

การใช้ตัวแปรที่อยู่ในระดับของโมดูลแทนคุณสมบัติส่วนตัวในชั้นเรียนจะนำไปสู่การเป็นโสดการช่วยเหลือหรือพฤติกรรมที่คล้ายคลึงกับคุณสมบัติของ statitc สารของ vars จะได้รับการแบ่งปัน
Adrian Moisa

30

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

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

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

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

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

เห็นได้ชัดว่านี่อาจจะช้าและน่าเกลียด แต่ก็ให้ความเป็นส่วนตัว

โปรดทราบว่าแม้สิ่งนี้จะไม่สมบูรณ์แบบเพราะ Javascript เป็นแบบไดนามิกมาก บางคนยังสามารถทำ

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

เพื่อจับค่าขณะที่ถูกจัดเก็บดังนั้นหากคุณต้องการระมัดระวังเป็นพิเศษคุณจะต้องรวบรวมการอ้างอิงในพื้นที่.setและ.getใช้อย่างชัดเจนแทนที่จะใช้ต้นแบบต้นแบบที่สามารถลบล้างได้

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

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

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

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

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

3
เป็นข้อเสนอแนะคุณสามารถหลีกเลี่ยงการใช้แผนที่อ่อนแอหนึ่งรายการต่อคุณสมบัติโดยใช้วัตถุเป็นค่า วิธีนี้คุณสามารถลดจำนวนแผนที่เป็นgetหนึ่งต่อวิธี (เช่นconst _ = privates.get(this); console.log(_.privateProp1);)
Quentin Roy

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

@loganfsmyth const myObj = new SomeClass(); console.log(privateProp1.get(myObj)) // "I am Private1"นั่นหมายความว่าทรัพย์สินของคุณเป็นส่วนตัวหรือไม่?
Barbu Barbu

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

22

สำหรับการอ้างอิงอื่น ๆ เกี่ยวกับผู้ค้นหาในอนาคตฉันได้ยินแล้วว่าคำแนะนำคือการใช้WeakMapsเพื่อเก็บข้อมูลส่วนตัว

นี่คือตัวอย่างการทำงานที่ชัดเจนยิ่งขึ้น:

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

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

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

20
ระวังว่าคุณสมบัติเหล่านี้คงที่
Michael Theriot

8
ฉันไม่ได้ลงคะแนนให้คุณ แต่ตัวอย่างจุดอ่อนของคุณผิดทั้งหมด
Benjamin Gruenbaum

4
คือ - คุณกำลังแบ่งปันข้อมูลระหว่างอินสแตนซ์ของคลาสทั้งหมดและไม่ใช่ต่ออินสแตนซ์ - อย่างน้อยฉันจะแก้ไขได้ไหม?
Benjamin Gruenbaum

1
อันที่จริง, จำเป็นต้องติดจุดอ่อนไว้กับอินสแตนซ์ที่กำหนด ดูตัวอย่างfitzgeraldnick.com/weblog/53
กว้าง

2
ตาม MDN ประเภทข้อมูลดั้งเดิมเช่นสัญลักษณ์ไม่ได้รับอนุญาตเป็นคีย์ WeakMap เอกสาร MDN WeakMap
leepowell

12

ขึ้นอยู่กับคนที่คุณถาม :-)

ไม่privateปรับปรุงสถานที่ให้บริการจะรวมอยู่ในMaximally เรียนน้อยที่สุดข้อเสนอซึ่งดูเหมือนว่าจะได้ทำมันลงไปในร่างปัจจุบัน

อย่างไรก็ตามอาจมีการสนับสนุน ชื่อส่วนตัวซึ่งอนุญาตคุณสมบัติส่วนตัว - และอาจใช้ในการกำหนดคลาสได้เช่นกัน


3
มันเป็นอย่างมากไม่น่าที่ชื่อภาคเอกชนจะทำให้มันกลายเป็น ES6 แม้ว่าพวกเขากำลังความคิดรูปแบบของสิ่งที่ส่วนตัว ES7 บาง
แควนตัส 94 Heavy

@ Qantas94 มีทั้งชื่อส่วนตัวและค่าสตริงที่ไม่ซ้ำกันถูกแทนที่โดย Symbols จากสิ่งที่ฉันเข้าใจ
Benjamin Gruenbaum

ใช่มันอาจจะกลายเป็นสัญลักษณ์ อย่างไรก็ตาม afaik "สัญลักษณ์" ที่มีอยู่ในข้อมูลจำเพาะนั้นใช้เพื่ออธิบายคุณสมบัติภายในเช่น [[prototype]] เท่านั้นและไม่มีวิธีการสร้างและใช้ในรหัสผู้ใช้ คุณรู้เอกสารบ้างไหม?
Bergi

ฉันเพิ่งรู้ว่าสามารถใช้โมดูลเพื่อตั้งค่าความเป็นส่วนตัว รวมกับสัญลักษณ์ที่อาจเป็นสิ่งที่คุณต้องการ ... ?
d13

1
@Cody: รหัสโมดูลทั้งหมดของคุณมีขอบเขตเป็นของตัวเองใน ES6 อย่างไรก็ตามไม่จำเป็นต้องใช้ IEFE และใช่มีการระบุสัญลักษณ์เพื่อความเป็นเอกลักษณ์ (หลีกเลี่ยงการชน) ไม่ใช่ความเป็นส่วนตัว
Bergi

10

การใช้โมดูล ES6 (ตอนแรกที่เสนอโดย @ d13) ทำงานได้ดีสำหรับฉัน มันไม่ได้เลียนแบบคุณสมบัติส่วนตัวอย่างสมบูรณ์แบบ แต่อย่างน้อยคุณก็มั่นใจได้ว่าคุณสมบัติที่ควรเป็นแบบส่วนตัวจะไม่รั่วไหลออกนอกชั้นเรียนของคุณ นี่คือตัวอย่าง:

something.js

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

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

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

จากนั้นรหัสการบริโภคอาจมีลักษณะเช่นนี้:

import Something from './something.js';

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

อัปเดต (สำคัญ):

ในฐานะที่เป็น @DanyalAytekin ระบุไว้ในความคิดเห็นคุณสมบัติส่วนตัวเหล่านี้เป็นแบบคงที่ดังนั้นทั่วโลกอยู่ในขอบเขต พวกเขาจะทำงานได้ดีเมื่อทำงานกับ Singletons แต่ต้องระมัดระวังสำหรับวัตถุชั่วคราว ขยายตัวอย่างด้านบน:

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

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

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

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

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

4
private staticเหมาะสำหรับ
Danyal Aytekin

@ DanyalAytekin: นั่นเป็นจุดที่ดีมาก คุณสมบัติส่วนตัวเหล่านี้เป็นแบบคงที่ดังนั้นทั่วโลกอยู่ในขอบเขต ฉันได้อัปเดตคำตอบเพื่อแสดงถึงสิ่งนี้แล้ว
Johnny Oshika

ยิ่งฉันเรียนรู้เกี่ยวกับฟังก์ชั่นการเขียนโปรแกรม (โดยเฉพาะ Elm และ Haskell) ยิ่งฉันเชื่อว่าโปรแกรมเมอร์ JS จะได้รับประโยชน์จากวิธีการที่ใช้โมดูลเป็น "modularity" แทนที่จะเป็นคลาส OOP ถ้าเราคิดว่าโมดูล ES6 เป็นรากฐานสำหรับการสร้างแอปพลิเคชันและลืมคลาสโดยสิ้นเชิงฉันเชื่อว่าเราอาจจบลงด้วยการใช้งานที่ดีขึ้นโดยรวม ผู้ใช้ Elm หรือ Haskell ที่มีประสบการณ์สามารถแสดงความคิดเห็นเกี่ยวกับวิธีการนี้ได้หรือไม่
d13

1
ในการอัปเดตอันดับที่สองa.say(); // aควรเป็นb.say(); // b
grokky

let _message = nullวิธีที่พยายามไม่เย็นนักเมื่อมีการเรียกตัวสร้างหลาย ๆ ครั้งมันจะเลอะ
Littlee

9

การเติม @ d13 และความคิดเห็นโดย @ johnny-oshika และ @DanyalAytekin:

ฉันเดาในตัวอย่างที่มีให้โดย @ johnny-oshika เราสามารถใช้ฟังก์ชั่นปกติแทนฟังก์ชั่นลูกศรจากนั้น.bindพวกเขาด้วยวัตถุปัจจุบันบวก_privatesวัตถุเป็นพารามิเตอร์ curried:

something.js

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

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

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

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

main.js

import Something from './something.js';

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

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

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

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

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

const message3 = something2.say();

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

ประโยชน์ที่ฉันจะได้รับ:

  • เราสามารถมีวิธีการส่วนตัว ( _greetและ_updateMessageทำหน้าที่เหมือนวิธีการส่วนตัวตราบใดที่เราไม่ได้exportอ้างอิง)
  • แม้ว่าพวกเขาจะไม่ได้อยู่ในต้นแบบวิธีการดังกล่าวข้างต้นจะบันทึกหน่วยความจำเพราะอินสแตนซ์ที่ถูกสร้างขึ้นครั้งเดียวนอกคลาส (ตรงข้ามกับการกำหนดไว้ในตัวสร้าง)
  • เราไม่รั่วไหลใด ๆ กลมเนื่องจากเราอยู่ในโมดูล
  • เรายังสามารถมีคุณสมบัติส่วนตัวโดยใช้_privatesวัตถุที่ถูกผูกไว้

ข้อเสียบางอย่างที่ฉันสามารถนึกได้:

สามารถดูตัวอย่างรหัสได้ที่นี่: http://www.webpackbin.com/NJgI5J8lZ


8

ใช่ - คุณสามารถสร้างสถานที่ให้บริการแบบห่อหุ้มแต่ยังไม่ได้ทำกับตัวดัดแปลงการเข้าถึง (สาธารณะ | ส่วนตัว) อย่างน้อยไม่ใช่ ES6

นี่คือตัวอย่างง่ายๆที่สามารถใช้กับ ES6 ได้:

1 สร้างคลาสโดยใช้คำของคลาส

2 ข้างในคอนสตรัคเตอร์ประกาศตัวแปรที่กำหนดขอบเขตบล็อกโดยใช้คำสั่งletหรือconst ที่สงวนไว้ -> เนื่องจากเป็นขอบเขตบล็อกจึงไม่สามารถเข้าถึงได้จากภายนอก (encapsulated)

3 ในการอนุญาตการควบคุมการเข้าถึง (setters | getters) ให้กับตัวแปรเหล่านั้นคุณสามารถประกาศวิธีอินสแตนซ์ภายในตัวสร้างโดยใช้: this.methodName=function(){}ไวยากรณ์

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

ตอนนี้ให้ตรวจสอบ:

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

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

@Freezystem First: Firstเป็นวิธีการของอินสแตนซ์ (ไม่ใช่วิธีการแบบ Class) คำถาม OP ที่สองคือ: _ ฉันจะป้องกันการเข้าถึงอินสแตนซ์ของฉันได้อย่างไรและคำตอบของฉันคือ: ตัวอย่างของวิธีการ ... ที่สามถ้าคุณมีความคิดที่ดีกว่านี้ - ลองฟังกันเลย
Nikita Kurtin

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

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

1
ใช่นั่นคือสิ่งที่ฉันหมายถึงและโซลูชันของคุณใช้งานได้! ฉันแค่บอกว่าโดยทั่วไปแล้วฉันประหลาดใจที่ ES6 เพิ่มคำหลัก 'คลาส' แต่เอาโซลูชันที่สง่างามของการทำงานกับ var และสิ่งนี้เพื่อให้ได้ encapsulation
Kokodoko

8

แนวทางที่แตกต่างในการ "ส่วนตัว"

แทนที่จะต่อสู้กับข้อเท็จจริงที่ว่าการมองเห็นส่วนบุคคลไม่สามารถใช้งานได้ใน ES6 ในปัจจุบันฉันตัดสินใจที่จะใช้วิธีการที่เป็นประโยชน์มากขึ้นซึ่งทำได้ดีถ้า IDE ของคุณรองรับ JSDoc (เช่น Webstorm) ความคิดที่จะใช้แท็ก@private เท่าที่การพัฒนาดำเนินไป IDE จะป้องกันไม่ให้คุณเข้าถึงสมาชิกส่วนตัวใด ๆ จากนอกคลาส ใช้งานได้ดีสำหรับฉันและมันมีประโยชน์อย่างมากสำหรับการซ่อนวิธีการภายในดังนั้นคุณสมบัติการเติมข้อความอัตโนมัติจะแสดงให้ฉันเห็นว่าคลาสนั้นหมายถึงอะไรจริงๆ นี่คือตัวอย่าง:

ป้อนอัตโนมัติให้สมบูรณ์แสดงเฉพาะสิ่งที่สาธารณะ


1
ปัญหาคือว่าเราไม่ต้องการเข้าถึงตัวแปรส่วนตัวบน Editor เราไม่ต้องการปกป้องตัวแปรส่วนตัวจากภายนอก - และนั่นคือสิ่งที่สาธารณะ / ส่วนตัวทำ หากรหัสของคุณเสร็จสิ้นคุณสามารถเข้าถึง (และที่สำคัญคิดว่า: แทนที่ ) ตัวแปรเหล่านี้จากนอกชั้นเรียน @privateความคิดเห็นของคุณไม่สามารถป้องกันสิ่งเหล่านี้ได้เป็นเพียงฟีเจอร์สำหรับการสร้างเอกสารและ IDE ของคุณ
Adrian Preuss

ใช่ฉันรู้แล้ว มันก็เพียงพอแล้วสำหรับฉันและอาจเพียงพอสำหรับคนอื่น ๆ ฉันรู้ว่ามันไม่ได้ทำให้ตัวแปรของฉันเป็นแบบส่วนตัว มันเป็นเพียงการเตือนฉันที่จะไม่ลองและเข้าถึงจากภายนอก (เท่านั้นแน่นอนถ้าทีมของฉันและฉันทั้งหมดกำลังใช้ IDE ที่รองรับคุณสมบัตินี้) Javascript (และภาษาอื่น ๆ เช่น Python) ไม่ได้รับการออกแบบโดยคำนึงถึงระดับการเข้าถึงข้อมูล ผู้คนทำทุกสิ่งเพื่อใช้งานฟังก์ชั่นนั้น แต่ในที่สุดเราก็แค่แฮ็คภาษาเพื่อให้บรรลุเป้าหมายนั้น ฉันตัดสินใจที่จะใช้วิธีที่ "เป็นธรรมชาติ" มากขึ้นถ้าคุณต้องการ
Lucio Paiva

6

WeakMap

  • รองรับ IE11 (ไม่มีสัญลักษณ์)
  • ฮาร์ด - ส่วนตัว (อุปกรณ์ประกอบฉากที่ใช้สัญลักษณ์นั้นอ่อนนุ่มเนื่องจากObject.getOwnPropertySymbols)
  • สามารถดูสะอาดจริงๆ (ต่างจากการปิดซึ่งต้องใช้อุปกรณ์ประกอบฉากและวิธีการทั้งหมดในตัวสร้าง)

ก่อนอื่นให้กำหนดฟังก์ชั่นเพื่อตัด WeakMap:

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

จากนั้นสร้างการอ้างอิงนอกชั้นเรียนของคุณ:

const p = new Private();

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

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

หมายเหตุ: ชั้นจะไม่ได้รับการสนับสนุนโดย IE11 แต่ดูเหมือนทำความสะอาดในตัวอย่าง


6

โอ้ทางออกที่แปลกใหม่มากมาย! ฉันมักจะไม่สนใจเกี่ยวกับความเป็นส่วนตัวดังนั้นฉันจะใช้"หลอกความเป็นส่วนตัว"เป็นของมันกล่าวว่าที่นี่ แต่ถ้าสนใจ (ถ้ามีข้อกำหนดพิเศษสำหรับเรื่องนั้น) ฉันใช้บางอย่างเช่นในตัวอย่างนี้:

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

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

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

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

การประยุกต์ใช้ฟังก์ชัน (ตัวสร้าง) ที่เป็นไปได้อื่นJob:

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

5

โดยส่วนตัวแล้วฉันชอบข้อเสนอของตัวดำเนินการเชื่อมโยง ::และจากนั้นจะรวมเข้ากับโซลูชันที่กล่าวถึง @ d13 แต่ตอนนี้ติดกับคำตอบของ @ d13 ที่คุณใช้exportคำหลักสำหรับชั้นเรียนของคุณและใส่ฟังก์ชั่นส่วนตัวในโมดูล

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

Private.js

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

test.js

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

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

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

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

ความคิดเห็นเกี่ยวกับมันจะได้รับการชื่นชม


โดยทั่วไปฉันชอบวิธีการ ข้อเสนอแนะ: 1. คุณจะต้องใช้โมดูล private.js ที่แตกต่างกันสำหรับแต่ละชั้นเรียนเพื่อป้องกันการปะทะกัน 2. ฉันไม่ชอบศักยภาพของการสร้างคอนสตรัคเตอร์จริง ๆ โดยกำหนดอินไลน์วิธีการส่วนตัวของคุณแต่ละวิธี 3. มันจะดีถ้าเมธอดคลาสทั้งหมดอยู่ในไฟล์เดียว
Doug Coburn

5

ฉันเจอโพสต์นี้เมื่อมองหาแนวปฏิบัติที่ดีที่สุดสำหรับ "ข้อมูลส่วนตัวสำหรับชั้นเรียน" มีการกล่าวถึงว่ารูปแบบบางอย่างจะมีปัญหาด้านประสิทธิภาพ

ฉันรวบรวมการทดสอบ jsperf สองสามตัวตามรูปแบบหลัก ๆ 4 ประการจากหนังสือออนไลน์ "Exploring ES6":

http://exploringjs.com/es6/ch_classes.html#sec_private-data-for-classes

การทดสอบสามารถพบได้ที่นี่:

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

ใน Chrome 63.0.3239 / Mac OS X 10.11.6 รูปแบบที่มีประสิทธิภาพดีที่สุดคือ "ข้อมูลส่วนตัวผ่านสภาพแวดล้อมของตัวสร้าง" และ "ข้อมูลส่วนตัวผ่านรูปแบบการตั้งชื่อ" สำหรับฉัน Safari ทำงานได้ดีสำหรับ WeakMap แต่ Chrome ไม่ค่อยดี

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

4 รูปแบบพื้นฐานคือ:

ข้อมูลส่วนตัวผ่านสภาพแวดล้อมของตัวสร้าง

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

ข้อมูลส่วนตัวผ่านตัวสร้างสภาวะแวดล้อม 2

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

ข้อมูลส่วนตัวผ่านระเบียบการตั้งชื่อ

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

ข้อมูลส่วนตัวผ่าน WeakMaps

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

ข้อมูลส่วนตัวผ่านสัญลักษณ์

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

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

4

ฉันเชื่อว่าเป็นไปได้ที่จะได้ 'ดีที่สุดของทั้งสองโลก' โดยใช้การปิดในตัวสร้าง มีสองรูปแบบ:

ข้อมูลสมาชิกทั้งหมดเป็นแบบส่วนตัว

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

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

class Test {
   constructor() {

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

สมาชิกบางคนเป็นส่วนตัว

หมายเหตุ: นี่เป็นที่ยอมรับอย่างน่าเกลียด หากคุณรู้วิธีแก้ปัญหาที่ดีกว่านี้โปรดแก้ไขคำตอบนี้

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

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

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

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


4

ในความเป็นจริงมันเป็นไปได้โดยใช้สัญลักษณ์และผู้รับมอบฉันทะ คุณใช้สัญลักษณ์ในขอบเขตของคลาสและตั้งค่ากับดักสองตัวในพร็อกซี: หนึ่งสำหรับต้นแบบต้นแบบเพื่อให้ Reflect.ownKeys (อินสแตนซ์) หรือ Object.getOwnPropertySymbols ไม่ให้สัญลักษณ์ของคุณออกไปอีกอันหนึ่งเป็นตัวสร้าง ดังนั้นเมื่อnew ClassName(attrs)ถูกเรียกอินสแตนซ์ที่ส่งคืนจะถูกดักและมีสัญลักษณ์คุณสมบัติของตัวเองถูกบล็อก นี่คือรหัส:

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

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

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

  return Human;
})();

const bob = new Human('Bob');

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


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

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

Reflect.ownKeys()ทำงานเป็นเช่นนั้น: Object.getOwnPropertyNames(myObj).concat(Object.getOwnPropertySymbols(myObj))นั่นเป็นสาเหตุที่เราต้องการกับดักสำหรับวัตถุเหล่านี้


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

4

แม้แต่ typescript ก็ไม่สามารถทำได้ จากเอกสารของพวกเขา:

เมื่อสมาชิกถูกทำเครื่องหมายว่าเป็นส่วนตัวสมาชิกจะไม่สามารถเข้าถึงได้จากนอกคลาสที่มีอยู่ ตัวอย่างเช่น:

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

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

แต่ transpiled บนสนามเด็กเล่นของพวกเขานี้ให้:

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

ดังนั้นคำหลัก "ส่วนตัว" จึงไม่มีประสิทธิภาพ


2
ก็ยังคงมีประสิทธิภาพเพราะป้องกันการเขียนโปรแกรม "ไม่ดี" ในขณะที่อยู่ใน IDE มันแสดงให้คุณเห็นว่าสมาชิกคนไหนที่คุณควรและไม่ควรใช้ ฉันคิดว่านั่นเป็นเหตุผลหลักในการใช้งานส่วนตัวและสาธารณะ (ตัวอย่างเช่นเมื่อคุณรวบรวม C # กับรหัสเครื่องส่วนตัวจะยังคงเป็นส่วนตัวหรือไม่ใครจะรู้?) เมื่ออ่านคำตอบอื่น ๆ ดูเหมือนว่าการใช้ @Symbol สามารถทำให้สมาชิกไม่สามารถเข้าถึงได้ แต่แม้กระทั่งสัญลักษณ์ยังสามารถพบได้จากคอนโซล
Kokodoko

ข้อผิดพลาดของ TypeScript เกิดขึ้นระหว่าง transpile ของ TypeScript ถึง JavaScript หรือไม่ (เช่นเดียวกับการตรวจสอบชนิดที่เกิดขึ้นในเวลา transpite แทนที่จะกลไกส่วนตัวรันไทม์..)
Eljay

4

มาปาร์ตี้สายนี้มาก แต่ฉันกดคำถาม OP ในการค้นหาดังนั้น ... ใช่คุณสามารถมีคุณสมบัติส่วนตัวได้โดยการปิดประกาศในชั้นเรียน

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

const Subscribable = (function(){

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

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

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

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

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

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

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


4

ฉันคิดว่าคำตอบของเบนจามินน่าจะดีที่สุดสำหรับกรณีส่วนใหญ่จนกระทั่งภาษารองรับตัวแปรส่วนตัวอย่างชัดเจน

อย่างไรก็ตามถ้าด้วยเหตุผลบางอย่างที่คุณต้องการป้องกันการเข้าถึงด้วยObject.getOwnPropertySymbols()วิธีการที่ฉันคิดว่าใช้คือการแนบคุณสมบัติที่ไม่ซ้ำกันไม่สามารถกำหนดค่าไม่นับไม่ได้เขียนไม่ได้ซึ่งสามารถใช้เป็นตัวระบุคุณสมบัติสำหรับแต่ละวัตถุในการก่อสร้าง (เช่นที่ไม่ซ้ำกันSymbolหากคุณยังไม่มีสถานที่ให้บริการที่ไม่ซ้ำใครอื่นเช่นid) จากนั้นเก็บแผนที่ของตัวแปร 'ส่วนตัว' ของแต่ละวัตถุโดยใช้ตัวระบุนั้น

const privateVars = {};

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

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

        privateVars[this._sym] = myPrivateVars;

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

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

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

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

ข้อได้เปรียบที่เป็นไปได้ของวิธีนี้มากกว่าการใช้ a WeakMapคือเวลาในการเข้าถึงที่เร็วขึ้นหากประสิทธิภาพกลายเป็นข้อกังวล


1
ถูกต้องฉันถ้าฉันผิด แต่รหัสนี้จะไม่มีการรั่วไหลของหน่วยความจำเนื่องจาก privateVars จะยังคงเก็บตัวแปรส่วนตัวของวัตถุแม้ว่าวัตถุจะถูกทำลายแล้ว?
รัสเซลซานโตส

@RussellSantos คุณถูกต้องสมมติว่าวัตถุจะต้องถูกเก็บรวบรวมขยะในบางจุด ขอบคุณสำหรับการชี้ให้เห็นว่า ในตัวอย่างของฉันฉันได้เพิ่มdestroy()วิธีการซึ่งควรเรียกใช้โดยรหัสเมื่อใดก็ตามที่วัตถุจะต้องถูกลบออก
NanoWizard

4

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

class Clazz {
  constructor() {
    var _level = 1

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

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

var clazz = new Clazz();

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


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

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

2
เป็นการดีที่สุดที่จะหลีกเลี่ยงคำตอบของรหัสเท่านั้น มันจะดีกว่านี้ถ้าคุณสามารถอธิบายได้ว่าโค้ดของคุณตอบคำถามของ OP อย่างไร
Stewart_R

นี่เป็นวิธีการสร้างตัวแปรแบบอ่านอย่างเดียวมากกว่าตัวแปรส่วนตัว ตัวแปรส่วนตัวไม่ควรเข้าถึงจากภายนอก console.log(instance.property)ควรโยนหรือให้คุณไม่ได้กำหนดไม่ให้ "การทดสอบ" กลับมาให้คุณ
oooyaya

3

อีกวิธีหนึ่งคล้ายกับสองโพสต์ล่าสุด

class Example {
  constructor(foo) {

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

    // public interface
    return self.public;
  }

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

  // everything else private
  bar = 10
}

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

2

คำตอบส่วนใหญ่บอกว่าเป็นไปไม่ได้หรือต้องการให้คุณใช้ WeakMap หรือ Symbol ซึ่งเป็นคุณสมบัติ ES6 ที่อาจต้องใช้ polyfills อย่างไรก็ตามมีวิธีอื่น! ลองดูสิ

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

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

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


คำถามคือเกี่ยวกับวิธีการประสบความสำเร็จในชั้นเรียน ES6
Michael Franzl

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

2

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


2

Object.freeze()ผมพบว่าวิธีการแก้ปัญหาที่ง่ายมากเพียงแค่ใช้ แน่นอนปัญหาคือคุณไม่สามารถเพิ่มอะไรลงในวัตถุในภายหลัง

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

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

สิ่งนี้จะปิดใช้งานเมธอด setter เช่นsetName(name) { this.name = name; }
ngakak

2

ฉันใช้รูปแบบนี้และมันก็ใช้ได้กับฉันเสมอ

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

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

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

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

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



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


2

ที่จริงมันเป็นไปได้
1. ขั้นแรกให้สร้างคลาสและใน Constructor ส่งคืน_publicฟังก์ชันที่เรียกว่า
2. ใน_publicฟังก์ชั่นที่เรียกว่าผ่านการthisอ้างอิง(ที่จะได้รับการเข้าถึงวิธีการส่วนตัวและอุปกรณ์ประกอบฉากทั้งหมด)และข้อโต้แย้งทั้งหมดจากconstructor (ที่จะถูกส่งผ่านnew Names())
3. ใน_publicขอบเขตฟังก์ชั่นยังมีNamesชั้นเรียนที่มีการเข้าถึงthis(_this ) การอ้างอิงของNamesคลาสส่วนตัว

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

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

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

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

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

2

คุณสามารถลองใช้https://www.npmjs.com/package/private-members

แพคเกจนี้จะบันทึกสมาชิกโดยอินสแตนซ์

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

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

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

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

    return Exemplo;
})();

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