รับชื่อคลาสของอินสแตนซ์ของคลาส ES6


157

มีวิธีใดที่ 'สามัคคี' ในการรับชื่อคลาสจากอินสแตนซ์ของคลาส ES6 หรือไม่ นอกเหนือจากนี้

someClassInstance.constructor.name

ขณะนี้ฉันกำลังนับการใช้งาน Traceur และดูเหมือนว่าบาเบลมีพอลิฟิลFunction.nameในขณะที่เทรเกอร์ไม่ได้

เพื่อสรุปทั้งหมด: ไม่มีวิธีอื่นใน ES6 / ES2015 / Harmony และไม่มีสิ่งใดที่คาดว่าจะเป็น ATM ใน ES.Next

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

Babel ใช้core-jsสำหรับ polyfill Function.nameควรโหลดด้วยตนเองสำหรับแอปพลิเคชั่น Traceur และ TypeScript ตามความเหมาะสม


2
ฉันเจอปัญหาเดียวกัน สำหรับ Traceur ทางออกเดียวคือการแยกรหัสคลาสจริงเพื่อแยกชื่อซึ่งฉันไม่คิดว่ามีคุณสมบัติที่กลมกลืนกัน ฉันเพิ่งกลืนเม็ดยาและเปลี่ยนไปเป็นบาเบล ดูเหมือนว่าการพัฒนาของ Traceur จะค่อนข้างนิ่งและคุณสมบัติ ES6 จำนวนมากถูกนำไปใช้งานไม่ดี ในขณะที่คุณกล่าวถึงinstance.constructor.nameและclass.nameกลับชื่อชั้นใน ES6 ที่เหมาะสม
Andrew Odri

น่าจะเป็นวิธีเดียว
Frederik Krautwald

สิ่งนี้อยู่ในมาตรฐาน ES6 หรือไม่
drudru

12
เป็นมูลค่าการกล่าวขวัญว่าsomeClassInstance.constructor.nameจะได้รับ mangled หากคุณ uglify รหัสของคุณ
JamesB

stackoverflow.com/questions/2648293/...อาจต้องการที่จะดูที่นี้ควรจะทำงาน w .constructor/
Florrie

คำตอบ:


206

someClassInstance.constructor.nameเป็นวิธีที่ถูกต้องในการทำเช่นนี้ Transpilers อาจไม่รองรับสิ่งนี้ แต่เป็นวิธีมาตรฐานตามข้อกำหนด ( nameคุณสมบัติของฟังก์ชั่นประกาศผ่านการผลิต ClassDeclaration ตั้งอยู่ใน14.5.15ขั้นตอนที่ 6)


2
นั่นคือสิ่งที่ฉันกลัว คุณรู้หรือไม่ว่ามีพอลิฟิลที่เหมาะสมสำหรับสิ่งนั้น? ฉันพยายามคิดว่าบาเบลทำเช่นนั้นได้อย่างไร แต่ประสบความสำเร็จน้อยมาก
Estus Flask

ฉันไม่รู้จริงๆว่าคุณหมายถึงอะไรโดย polyfill สำหรับคุณลักษณะภาษา (ชั้นเรียน)
Domenic

ฉันหมายถึง polyfill สำหรับ constructor.name ดูเหมือนว่า Babel จะใช้งานได้ แต่ฉันไม่ประสบความสำเร็จในการทำความเข้าใจว่ามันเป็นเช่นไร
Estus Flask

2
@estus someClassInstance.constructorเป็นฟังก์ชั่น ฟังก์ชั่นทั้งหมดมีnameคุณสมบัติที่ตั้งเป็นชื่อของมัน นั่นเป็นเหตุผลที่บาเบลไม่ต้องทำอะไรเลย โปรดดูdeveloper.mozilla.org/en-US/docs/Web/JavaScript/Reference/
......

2
@Esteban ดูเหมือนว่า Babel จะส่งเสริม core-js polyfills (รวมถึง polyfill สำหรับ Function.name) นั่นคือเหตุผลที่ Babel builds อาจทำงานนอกกรอบในเบราว์เซอร์ทั้งหมด
Estus Flask

53

ในฐานะที่เป็น @Domenic someClassInstance.constructor.nameกล่าวว่าการใช้งาน @Esteban กล่าวถึงในความคิดเห็นที่

someClassInstance.constructorเป็นฟังก์ชั่น ฟังก์ชั่นทั้งหมดมีnameคุณสมบัติ ...

ดังนั้นในการเข้าถึงชื่อคลาสแบบคงที่ให้ทำดังต่อไปนี้ (ใช้งานได้กับ Babel รุ่น BTW ของฉันตามความคิดเห็นที่ @Domenic ระยะทางของคุณอาจแตกต่างกันไป)

class SomeClass {
  constructor() {}
}

var someClassInstance = new SomeClass();
someClassInstance.constructor.name;      // === 'SomeClass'
SomeClass.name                           // === 'SomeClass'

ปรับปรุง

บาเบลไม่เป็นไร แต่ uglify / minification ทำให้ฉันมีปัญหา ฉันกำลังสร้างเกมและกำลังสร้างแฮชของทรัพยากร Sprite ที่รวมอยู่ด้วยกัน (โดยที่คีย์คือชื่อฟังก์ชัน) หลังจาก minification ทุกฟังก์ชั่น / tชั้นถูกเสนอชื่อ นี่จะเป็นการฆ่ากัญชา ฉันกำลังใช้Gulpในโครงการนี้และหลังจากอ่านเอกสารอึก - uglifyฉันพบว่ามีพารามิเตอร์เพื่อป้องกันไม่ให้ชื่อตัวแปร / ฟังก์ชั่นในท้องถิ่นนี้เกิดปัญหาขึ้น ดังนั้นใน gulpfile ของฉันฉันเปลี่ยน

.pipe($.uglify()) ถึง .pipe($.uglify({ mangle: false }))

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

ปรับปรุง

หลังจากการอภิปรายในความคิดเห็นดูเหมือนว่าการหลีกเลี่ยงการ.nameประชุมเพื่อกำหนดหน้าที่เหล่านั้นเป็นกระบวนทัศน์ที่ดี ใช้เวลาเพียงไม่กี่บรรทัดของโค้ดและจะอนุญาตให้ใช้การย่อขนาดและความสมบูรณ์ของโค้ดของคุณ (หากใช้ในไลบรารี) ดังนั้นฉันคิดว่าฉันเปลี่ยนใจและจะกำหนดgetClassNameในชั้นเรียนของฉันด้วยตนเอง ขอบคุณ@estus! . Getter / Setters เป็นความคิดที่ดีเมื่อเทียบกับการเข้าถึงตัวแปรโดยตรงอย่างไรก็ตามโดยเฉพาะอย่างยิ่งในแอปพลิเคชันที่ใช้ไคลเอ็นต์

class SomeClass {
  constructor() {}
  static getClassName(){ return 'SomeClass'; }
  getClassName(){ return SomeClass.getClassName(); }
}
var someClassInstance = new SomeClass();
someClassInstance.constructor.getClassName();      // === 'SomeClass' (static fn)
someClassInstance.getClassName();                  // === 'SomeClass' (instance fn)
SomeClass.getClassName()                           // === 'SomeClass' (static fn)

3
การปิดใช้งาน mangling อย่างสมบูรณ์นั้นไม่ใช่ความคิดที่ดีมากเพราะ mangling มีส่วนช่วยในการลดจำนวนมาก มันไม่ใช่ความคิดที่ดีนักที่จะใช้หัวเรื่องในโค้ดฝั่งไคลเอ็นต์ในตอนแรก แต่คลาสสามารถป้องกันได้จากการ mangling ด้วยreservedตัวเลือก Uglify (รายการของคลาสสามารถรับได้ด้วย regexp หรืออะไรก็ตาม)
Estus Flask

จริงแท้แน่นอน. การแลกเปลี่ยนไฟล์มีขนาดใหญ่ขึ้น ลักษณะเช่นนี้เป็นวิธีที่คุณสามารถใช้เพื่อป้องกันไม่ให้ RegEx mangling เพียงเลือกรายการ คุณหมายความว่าอย่างไร "ไม่ใช่ความคิดที่ดีที่จะใช้หัวเรื่องในโค้ดฝั่งไคลเอ็นต์" มันจะมีความเสี่ยงด้านความปลอดภัยในบางสถานการณ์หรือไม่?
James L.

1
ไม่สิ่งที่พูดไปแล้ว เป็นเรื่องปกติที่ JS ลูกค้าฝั่งจะถูกทำให้เล็กลงดังนั้นจึงเป็นที่ทราบกันดีว่ารูปแบบนี้จะทำให้เกิดปัญหากับแอพ โค้ดพิเศษหนึ่งบรรทัดสำหรับตัวระบุสตริงคลาสแทนnameรูปแบบที่เรียบร้อยอาจเป็นเพียง win-win สิ่งเดียวกันอาจถูกนำไปใช้กับโหนด แต่ในระดับที่น้อยกว่า (เช่นแอปอิเล็กตรอนที่สับสน) โดยทั่วไปฉันจะเชื่อnameในรหัสเซิร์ฟเวอร์ แต่ไม่ใช่ในรหัสเบราว์เซอร์และไม่ใช่ในไลบรารีทั่วไป
Estus Flask

ตกลงดังนั้นคุณแนะนำให้กำหนด 2 ฟังก์ชัน getClassName (คงที่ & อินสแตนซ์) ด้วยตนเองเพื่อหลีกเลี่ยง mangling hell & เพื่ออนุญาตการลดขนาดเต็ม (โดยไม่มี RegEx ที่น่ารำคาญ) จุดนั้นเกี่ยวกับห้องสมุดทำให้รู้สึกมาก ทำให้รู้สึกมาก สำหรับฉันโครงการของฉันเป็นแอพ Cordova ขนาดเล็กที่สร้างขึ้นเองดังนั้นสิ่งเหล่านี้จึงไม่เป็นปัญหา อะไรก็ตามที่ฉันสามารถเห็นปัญหาเหล่านี้เพิ่มขึ้น ขอบคุณสำหรับการสนทนา! หากคุณสามารถนึกถึงการปรับปรุงใด ๆ ของโพสต์รู้สึกอิสระที่จะแก้ไข
James L.

1
ใช่ฉันเริ่มใช้nameรหัส DRYer เพื่อแยกชื่อของเอนทิตีที่ใช้คลาส (บริการ, ปลั๊กอินและอื่น ๆ ) จากชื่อคลาส แต่ในท้ายที่สุดฉันพบว่ามันซ้ำซ้อนอย่างชัดเจนด้วย prop แบบคงที่ ( id, _name) เป็นวิธีที่มั่นคงที่สุด ทางเลือกที่ดีคือไม่สนใจชื่อคลาสใช้คลาสตัวเอง (ฟังก์ชันวัตถุ) เป็นตัวระบุสำหรับเอนทิตีที่แมปกับคลาสนี้และimportทุกที่ที่ต้องการ (วิธีที่ใช้โดย Angular 2 DI)
Estus Flask

8

รับชื่อคลาสโดยตรงจากคลาส

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

typeof YourClass === "function"

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

YourClass.name

ต่อไปนี้เป็นตัวอย่างที่ดีว่าทำไมสิ่งนี้ถึงมีประโยชน์

กำลังโหลดส่วนประกอบของเว็บ

ในขณะที่เอกสาร MDNสอนเรานี่คือวิธีที่คุณโหลดองค์ประกอบของเว็บ:

customElements.define("your-component", YourComponent);

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

function registerComponent(componentClass) {
    const componentName = upperCamelCaseToSnakeCase(componentClass.name);
    customElements.define(componentName, componentClass);
}

ดังนั้นสิ่งที่คุณต้องทำคือ:

registerComponent(YourComponent);

อันไหนดีเพราะมันเกิดข้อผิดพลาดน้อยกว่าการเขียนแท็กองค์ประกอบด้วยตัวเอง เพื่อสรุปมันนี่คือupperCamelCaseToSnakeCase()ฟังก์ชั่น:

// converts `YourString` into `your-string`
function upperCamelCaseToSnakeCase(value) {
    return value
        // first char to lower case
        .replace(/^([A-Z])/, $1 => $1.toLowerCase())
        // following upper chars get preceded with a dash
        .replace(/([A-Z])/g, $1 => "-" + $1.toLowerCase());
}

ขอบคุณ ตัวอย่างคือฝั่งไคลเอ็นต์ ตามที่ได้กล่าวไปแล้วมีปัญหาบางอย่างกับการใช้ชื่อฟังก์ชั่นในเบราว์เซอร์ เกือบทุกชิ้นส่วนของรหัสเบราว์เซอร์คาดว่าจะลดขนาดลง แต่สิ่งนี้จะทำลายรหัสที่อาศัยชื่อฟังก์ชั่น
Estus Flask

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

1

สำหรับการส่ง babel (ก่อนการลดขนาด)

หากคุณใช้ Babel ด้วย@babel/preset-envอาจเป็นไปได้ที่จะเก็บคำจำกัดความของคลาสโดยไม่แปลงเป็นฟังก์ชัน (ซึ่งจะเอาconstructorคุณสมบัติออก)

คุณสามารถวางความเข้ากันได้ของเบราว์เซอร์เก่ากับการกำหนดค่านี้ในbabel.config / babelrc:

{
  "presets": [
    ["@babel/preset-env", {"targets": {"browsers": ["> 2%"]}}]
  ]
}

ข้อมูลเพิ่มเติมเกี่ยวกับtargets: https://babeljs.io/docs/en/babel-preset-env#targets

สำหรับการเบาบาง minification (หลังจาก transpilation)

ดูเหมือนว่าจะไม่มีวิธีแก้ปัญหาง่าย ๆ ในตอนนี้ ... เราจำเป็นต้องดูที่การแยกตัวออกเป็น mangling


คุณช่วยอธิบายเพิ่มเติมได้ไหมว่ามันควรจะช่วยเรื่องการลดขนาดได้อย่างไร class Foo {}จะย่อเล็กสุดให้คล้ายclass a{}กับเป้าหมายใด ๆ จะไม่มีFooคำใดในซอร์สโค้ดแบบย่อ
Estus Flask

สุจริตฉันไม่ได้ขุดมากกว่าเอกสารประกอบและความจริงมันช่วยให้ฉันใช้การกำหนดค่านี้ ... ฉันใช้ ECSY ในโครงการ transpiled ของ Babel และต้องใช้พารามิเตอร์นี้เพื่อรับชื่อคลาสที่ถูกต้อง: github.com/MozillaReality/ecsy/issues/ 119
Ifnot

ฉันเห็น. นี่เป็นรหัสเฉพาะที่คุณจัดการ เช่นชื่ออาจถูกเก็บรักษาไว้สำหรับโมดูล ES และ ES6 เพราะexport class Foo{}ไม่สามารถอัปเกรดได้อย่างมีประสิทธิภาพ แต่อาจแตกต่างกันในที่อื่น ๆ เราไม่สามารถรู้ได้อย่างแน่นอนว่าไม่มีความคิดที่ดีว่าเกิดอะไรขึ้นกับชิ้นส่วนของรหัสในเวลาสร้าง อย่างไรก็ตามมันไม่ได้เปลี่ยนไปตั้งแต่ปี 2015 มันเป็นไปได้เสมอสำหรับการกำหนดค่าการรวบรวมและรหัสบางอย่าง และฉันบอกว่าความเป็นไปได้นี้ยังคงเปราะบางเกินไปที่จะใช้ชื่อคลาสสำหรับตรรกะของแอป ชื่อคลาสที่คุณไว้ใจอาจกลายเป็นขยะหลังจากการเปลี่ยนแปลงหนึ่งครั้งในรหัสที่มาโดยไม่ตั้งใจ
Estus Flask

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