ในฐานะนักพัฒนา JS นี่คือสิ่งที่ทำให้ฉันนอนไม่หลับ

เผยแพร่แล้ว: 2022-03-11

JavaScript เป็นคี่บอลของภาษา แม้ว่าจะได้รับแรงบันดาลใจจาก Smalltalk แต่ก็ใช้รูปแบบ C-like มันรวมแง่มุมของกระบวนทัศน์การเขียนโปรแกรมเชิงขั้นตอน การทำงาน และเชิงวัตถุ (OOP) มีวิธีการมากมายที่มักจะซ้ำซากจำเจในการแก้ปัญหาการเขียนโปรแกรมที่เป็นไปได้เกือบทุกอย่าง และไม่มีความคิดเห็นอย่างแรงกล้าเกี่ยวกับสิ่งที่ต้องการ เป็นการพิมพ์ที่อ่อนแอและมีพลัง ด้วยวิธีการแบบเขาวงกตในการพิมพ์การบังคับขู่เข็ญที่ทำให้นักพัฒนาที่มีประสบการณ์ต้องสะดุดล้ม

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

ปัญหากับคลาส ES6

ผู้กระทำผิดที่เลวร้ายที่สุดยังใหม่กับเวอร์ชันล่าสุดของ JavaScript ECMAScript 6 (ES6): class การพูดคุยรอบชั้นเรียนบางส่วนเป็นเรื่องที่น่าตกใจและเผยให้เห็นถึงความเข้าใจผิดอย่างลึกซึ้งว่าภาษาทำงานอย่างไร:

“ในที่สุด JavaScript ก็เป็นภาษาเชิงวัตถุ จริง ๆ ที่มีคลาส!”

หรือ:

“ชั้นเรียนทำให้เราไม่ต้องคิดเกี่ยวกับโมเดลการสืบทอดที่เสียหายของ JavaScript”

หรือแม้กระทั่ง:

“คลาสเป็นวิธีที่ปลอดภัยและง่ายกว่าในการสร้างประเภทใน JavaScript”

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

JavaScript Pop Quiz #1: อะไรคือความแตกต่างที่สำคัญระหว่าง Code Blocks เหล่านี้?

 function PrototypicalGreeting(greeting = "Hello", name = "World") { this.greeting = greeting this.name = name } PrototypicalGreeting.prototype.greet = function() { return `${this.greeting}, ${this.name}!` } const greetProto = new PrototypicalGreeting("Hey", "folks") console.log(greetProto.greet())
 class ClassicalGreeting { constructor(greeting = "Hello", name = "World") { this.greeting = greeting this.name = name } greet() { return `${this.greeting}, ${this.name}!` } } const classyGreeting = new ClassicalGreeting("Hey", "folks") console.log(classyGreeting.greet())

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

จริง ตัวอย่างที่สองมีความหมายมากกว่า ด้วยเหตุผลนั้นเพียงอย่างเดียว คุณอาจโต้แย้งว่า class เป็นส่วนเสริมที่ดีของภาษา น่าเสียดายที่ปัญหานั้นซับซ้อนกว่าเล็กน้อย

JavaScript Pop Quiz #2: รหัสต่อไปนี้ทำอะไร?

 function Proto() { this.name = 'Proto' return this; } Proto.prototype.getName = function() { return this.name } class MyClass extends Proto { constructor() { super() this.name = 'MyClass' } } const instance = new MyClass() console.log(instance.getName()) Proto.prototype.getName = function() { return 'Overridden in Proto' } console.log(instance.getName()) MyClass.prototype.getName = function() { return 'Overridden in MyClass' } console.log(instance.getName()) instance.getName = function() { return 'Overridden in instance' } console.log(instance.getName())

คำตอบที่ถูกต้องคือพิมพ์ไปที่คอนโซล:

 > MyClass > Overridden in Proto > Overridden in MyClass > Overridden in instance

ถ้าคุณตอบผิด แสดงว่าคุณไม่เข้าใจจริงๆ ว่า class คืออะไร นี่ไม่ใช่ความผิดของคุณ เช่นเดียวกับ Array ที่ class ไม่ใช่คุณลักษณะของภาษา แต่เป็น syntax obscurantism มันพยายามที่จะซ่อนโมเดลการสืบทอดต้นแบบและสำนวนที่เงอะงะที่มาพร้อมกับมัน และมันบอกเป็นนัยว่า JavaScript กำลังทำสิ่งที่มันไม่ใช่

คุณอาจได้รับแจ้งว่า class ได้รับการแนะนำให้รู้จักกับ JavaScript เพื่อทำให้นักพัฒนา OOP แบบคลาสสิกมาจากภาษาต่างๆ เช่น Java สะดวกสบายยิ่งขึ้นด้วยโมเดลการสืบทอดคลาส ES6 หากคุณ เป็น หนึ่งในนักพัฒนาเหล่านั้น ตัวอย่างนั้นอาจทำให้คุณตกใจ มันควรจะ. มันแสดงให้เห็นว่าคีย์เวิร์ด class ของ JavaScript ไม่ได้มาพร้อมกับการรับประกันใดๆ ว่าคลาสนั้นมีไว้เพื่อให้ นอกจากนี้ยังแสดงให้เห็นความแตกต่างที่สำคัญอย่างหนึ่งในแบบจำลองการสืบทอดต้นแบบ: ต้นแบบคือ อินสแตนซ์ของวัตถุ ไม่ใช่ ประเภท

ต้นแบบกับคลาส

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

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

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

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

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

พิจารณาสิ่งต่อไปนี้:

 let parent = { foo: 'foo' } let child = { } Object.setPrototypeOf(child, parent) console.log(child.foo) // 'foo' child.foo = 'bar' console.log(child.foo) // 'bar' console.log(parent.foo) // 'foo' delete child.foo console.log(child.foo) // 'foo' parent.foo = 'baz' console.log(child.foo) // 'baz'
หมายเหตุ: คุณแทบจะไม่เคยเขียนโค้ดแบบนี้เลยในชีวิตจริง—เป็นการฝึกฝนที่แย่มาก—แต่แสดงให้เห็นหลักการอย่างรัดกุม

ในตัวอย่างก่อนหน้านี้ ในขณะที่ child.foo undefined จะอ้างอิง parent.foo ทันทีที่เรากำหนด foo ใน child นั้น child.foo มีค่า 'bar' แต่ parent.foo คงค่าเดิมไว้ เมื่อเรา delete child.foo มันจะอ้างอิงถึง parent.foo อีกครั้ง ซึ่งหมายความว่าเมื่อเราเปลี่ยนค่าของ child.foo จะอ้างอิงถึงค่าใหม่

มาดูกันว่าเกิดอะไรขึ้น (เพื่อให้เห็นภาพชัดเจนขึ้น เราจะแสร้งทำเป็นว่าสิ่งเหล่านี้คือ Strings ไม่ใช่ตัวอักษรสตริง ความแตกต่างไม่สำคัญที่นี่):

เดินผ่านห่วงโซ่ต้นแบบเพื่อแสดงให้เห็นว่ามีการจัดการการอ้างอิงที่ขาดหายไปใน JavaScript อย่างไร

วิธีทำงานภายใต้ประทุน และโดยเฉพาะอย่างยิ่งลักษณะเฉพาะของ new และ this เป็นหัวข้อสำหรับวันอื่น แต่ Mozilla มีบทความโดยละเอียดเกี่ยวกับห่วงโซ่การสืบทอดต้นแบบของ JavaScript หากคุณต้องการอ่านเพิ่มเติม

ประเด็นสำคัญคือต้นแบบไม่ได้กำหนด type พวกมันคือ instances และพวกมันสามารถเปลี่ยนแปลงได้ในขณะใช้งานจริง โดยทั้งหมดนั้นมีความหมายและเกี่ยวข้อง

ยังอยู่กับฉัน? กลับไปที่การแยกคลาส JavaScript

JavaScript Pop Quiz #3: คุณใช้ความเป็นส่วนตัวในชั้นเรียนอย่างไร?

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

ไม่มีตัวอย่างโค้ดที่นี่ คำตอบคือคุณไม่สามารถ

JavaScript ไม่มีแนวคิดเรื่องความเป็นส่วนตัว แต่มีการปิด:

 function SecretiveProto() { const secret = "The Class is a lie!" this.spillTheBeans = function() { console.log(secret) } } const blabbermouth = new SecretiveProto() try { console.log(blabbermouth.secret) } catch(e) { // TypeError: SecretiveClass.secret is not defined } blabbermouth.spillTheBeans() // "The Class is a lie!"

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

JavaScript Pop Quiz #4: อะไรคือสิ่งที่เทียบเท่ากับด้านบนโดยใช้คีย์เวิร์ดของ class ?

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

 class SecretiveClass { constructor() { const secret = "I am a lie!" this.spillTheBeans = function() { console.log(secret) } } looseLips() { console.log(secret) } } const liar = new SecretiveClass() try { console.log(liar.secret) } catch(e) { console.log(e) // TypeError: SecretiveClass.secret is not defined } liar.spillTheBeans() // "I am a lie!"

แจ้งให้เราทราบว่ามันดูง่ายหรือชัดเจนกว่าใน SecretiveProto หรือไม่ ในมุมมองส่วนตัวของฉัน มันค่อนข้างแย่—มันทำให้การใช้การประกาศ class ใน JavaScript แตกสลาย และใช้งานไม่ได้มากอย่างที่คุณคาดหวังจาก Java สิ่งนี้จะชัดเจนโดยต่อไปนี้:

JavaScript Pop Quiz #5: SecretiveClass::looseLips() ทำอะไรได้บ้าง

ลองหา:

 try { liar.looseLips() } catch(e) { // ReferenceError: secret is not defined }

อืม… ที่น่าอึดอัดใจ

JavaScript Pop Quiz #6: ผู้พัฒนา JavaScript ที่มีประสบการณ์คนไหนชอบ—ต้นแบบหรือคลาส?

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

 function secretFactory() { const secret = "Favor composition over inheritance, `new` is considered harmful, and the end is near!" const spillTheBeans = () => console.log(secret) return { spillTheBeans } } const leaker = secretFactory() leaker.spillTheBeans()

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

ประการหนึ่ง คุณสามารถทำลายโครงสร้างได้เพราะคุณไม่ต้องกังวลกับบริบทของ this :

 const { spillTheBeans } = secretFactory() spillTheBeans() // Favor composition over inheritance, (...)

นั่นเป็นสิ่งที่ดีสวย นอกจากการหลีกเลี่ยงสิ่ง new และโง่เขลา this แล้ว ยังช่วยให้เราใช้อ็อบเจ็กต์ของเราแทนกันได้กับโมดูล CommonJS และ ES6 ยังทำให้การจัดองค์ประกอบง่ายขึ้นอีกเล็กน้อย:

 function spyFactory(infiltrationTarget) { return { exfiltrate: infiltrationTarget.spillTheBeans } } const blackHat = spyFactory(leaker) blackHat.exfiltrate() // Favor composition over inheritance, (...) console.log(blackHat.infiltrationTarget) // undefined (looks like we got away with it)

ลูกค้าของ blackHat ไม่ต้องกังวลว่าการ exfiltrate มาจากไหน และ spyFactory ไม่ต้องวุ่นวายกับ Function::bind การเล่นกลบริบทหรือคุณสมบัติที่ซ้อนกันอย่างล้ำลึก โปรดทราบว่าเราไม่ต้องกังวลมากเกี่ยวกับ this ในโค้ดขั้นตอนซิงโครนัสแบบง่าย แต่มันทำให้เกิดปัญหาทุกประเภทในโค้ดแบบอะซิงโครนัสที่หลีกเลี่ยงได้ดีกว่า

ด้วยความคิดเพียงเล็กน้อย spyFactory สามารถพัฒนาให้เป็นเครื่องมือจารกรรมที่มีความซับซ้อนสูง ซึ่งสามารถจัดการกับเป้าหมายการแทรกซึมได้ทุกประเภท หรือกล่าวอีกนัยหนึ่งก็คือ หน้าอาคาร

แน่นอน คุณสามารถทำสิ่งนั้นกับคลาสได้เช่นกัน หรือมากกว่า การแบ่งประเภทของคลาส ซึ่งทั้งหมดนั้นสืบทอดมาจาก abstract class หรือ interface ... ยกเว้นว่า JavaScript ไม่มีแนวคิดของนามธรรมหรืออินเทอร์เฟซ

กลับไปที่ตัวอย่าง Greeter เพื่อดูว่าเราจะนำไปใช้กับโรงงานได้อย่างไร:

 function greeterFactory(greeting = "Hello", name = "World") { return { greet: () => `${greeting}, ${name}!` } } console.log(greeterFactory("Hey", "folks").greet()) // Hey, folks!

คุณอาจสังเกตเห็นว่าโรงงานเหล่านี้เริ่มสั้นลงเรื่อยๆ เมื่อเราเดินหน้ากัน แต่อย่ากังวล เพราะโรงงานเหล่านี้ก็ทำแบบเดียวกัน วงล้อฝึกซ้อมกำลังจะหลุดออกมา!

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

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

 // Greeting class class ClassicalGreeting { constructor(greeting = "Hello", name = "World", punctuation = "!") { this.greeting = greeting this.name = name this.punctuation = punctuation } greet() { return `${this.greeting}, ${this.name}${this.punctuation}` } } // An unhappy greeting class UnhappyGreeting extends ClassicalGreeting { constructor(greeting, name) { super(greeting, name, " :(") } } const classyUnhappyGreeting = new UnhappyGreeting("Hello", "everyone") console.log(classyUnhappyGreeting.greet()) // Hello, everyone :( // An enthusiastic greeting class EnthusiasticGreeting extends ClassicalGreeting { constructor(greeting, name) { super(greeting, name, "!!") } greet() { return super.greet().toUpperCase() } } const greetingWithEnthusiasm = new EnthusiasticGreeting() console.log(greetingWithEnthusiasm.greet()) // HELLO, WORLD!!

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

 const greeterFactory = (greeting = "Hello", name = "World", punctuation = "!") => ({ greet: () => `${greeting}, ${name}${punctuation}` }) // Makes a greeter unhappy const unhappy = (greeter) => (greeting, name) => greeter(greeting, name, ":(") console.log(unhappy(greeterFactory)("Hello", "everyone").greet()) // Hello, everyone :( // Makes a greeter enthusiastic const enthusiastic = (greeter) => (greeting, name) => ({ greet: () => greeter(greeting, name, "!!").greet().toUpperCase() }) console.log(enthusiastic(greeterFactory)().greet()) // HELLO, WORLD!!

ไม่ชัดเจนว่าโค้ดนี้ดีกว่า แม้ว่าจะสั้นกว่าเล็กน้อยก็ตาม อันที่จริง คุณอาจโต้แย้งได้ว่าการอ่านยากขึ้น และบางทีนี่อาจเป็นแนวทางที่ไม่สุภาพ เราไม่สามารถมี unhappyGreeterFactory ที่ไม่มีความสุขและ GreeterFactory ที่ enthusiasticGreeterFactory ได้หรือไม่?

จากนั้นลูกค้าของคุณก็เข้ามาและพูดว่า “ฉันต้องการคนต้อนรับคนใหม่ที่ไม่มีความสุขและต้องการให้ทั้งห้องรู้เรื่องนี้!”

 console.log(enthusiastic(unhappy(greeterFactory))().greet()) // HELLO, WORLD :(

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

 const aggressiveGreeterFactory = enthusiastic(unhappy(greeterFactory)) console.log(aggressiveGreeterFactory("You're late", "Jim").greet())

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

ประเด็นคือใน JavaScript คุณไม่ได้รับความปลอดภัยโดยอัตโนมัติ เฟรมเวิร์ก JavaScript ที่เน้นการใช้งาน class สร้าง "เวทย์มนตร์" มากมายในการเขียนปัญหาประเภทนี้และบังคับให้คลาสปฏิบัติตน ลองดูซอร์สโค้ด ElementMixin ของ Polymer สักครั้ง ฉันขอท้าให้คุณดู เป็นระดับตัวช่วยสร้างของ JavaScript arcana และฉันหมายความว่าไม่มีการประชดหรือเสียดสี

แน่นอน เราสามารถแก้ไขปัญหาบางอย่างที่กล่าวถึงข้างต้นด้วย Object.freeze หรือ Object.defineProperties เพื่อให้เกิดผลมากหรือน้อย แต่ทำไมต้องเลียนแบบแบบฟอร์มโดยไม่มีฟังก์ชัน ในขณะที่ละเลยเครื่องมือที่ JavaScript ทำให้ เราหาไม่เจอในภาษาอย่าง Java คุณจะใช้ค้อนที่เขียนว่า "ไขควง" เพื่อขันสกรูในเมื่อกล่องเครื่องมือของคุณมีไขควงจริงๆ อยู่ข้างๆ หรือไม่

ค้นหาชิ้นส่วนที่ดี

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

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

ฉันไม่ได้บอกคุณให้หลีกเลี่ยง class โดยสิ้นเชิง บางครั้งคุณต้องการการสืบทอด และ class มีไวยากรณ์ที่สะอาดขึ้นสำหรับการทำเช่นนั้น โดยเฉพาะอย่างยิ่ง class X extends Y นั้นดีกว่าวิธีการต้นแบบแบบเก่ามาก นอกจากนั้น เฟรมเวิร์กส่วนหน้ายอดนิยมจำนวนมากสนับสนุนการใช้งาน และคุณควรหลีกเลี่ยงการเขียนโค้ดแปลกๆ ที่ไม่ได้มาตรฐานบนหลักการเพียงอย่างเดียว ฉันแค่ไม่ชอบที่จะไป

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

ในที่สุด เราทุกคนก็เลิกหงุดหงิดและเริ่มสร้างสรรค์วงล้อใหม่ใน Rust, Go, Haskell หรือใครจะรู้อะไรอีก จากนั้นจึงรวบรวม Wasm สำหรับเว็บ และเฟรมเวิร์กของเว็บและไลบรารีใหม่ๆ ก็เพิ่มขึ้นอย่างรวดเร็วในหลายภาษา

มันทำให้ฉันตื่นขึ้นในตอนกลางคืน