ไวยากรณ์ ES2015 (ES6) `class` ให้ประโยชน์อะไรบ้าง?


87

ฉันมีคำถามมากมายเกี่ยวกับคลาส ES6

ประโยชน์ของการใช้classไวยากรณ์คืออะไร? ฉันอ่านว่า public / private / static จะเป็นส่วนหนึ่งของ ES7 นั่นคือเหตุผลหรือไม่?

ยิ่งไปกว่านั้นclassOOP ประเภทอื่นหรือยังคงเป็นมรดกต้นแบบของ JavaScript? ฉันสามารถแก้ไขโดยใช้.prototype? หรือเป็นเพียงวัตถุชิ้นเดียวกัน แต่มีสองวิธีในการประกาศ

มีประโยชน์ด้านความเร็วหรือไม่? อาจจะง่ายกว่าในการดูแลรักษา / ทำความเข้าใจหากคุณมีแอปพลิเคชันขนาดใหญ่เช่นแอปใหญ่


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

คำตอบ:


120

classไวยากรณ์ใหม่ในตอนนี้ส่วนใหญ่เป็นน้ำตาลที่เป็นไวยากรณ์ ( แต่คุณรู้ที่ดีชนิดของน้ำตาล.) มีอะไรใน ES2015 ES2020-ที่classสามารถทำเช่นนั้นคุณจะไม่สามารถทำอะไรกับฟังก์ชั่นคอนสตรัคและReflect.construct(รวม subclassing ErrorและArray¹) (มันเป็นแนวโน้มที่จะมีบางสิ่งบางอย่างใน ES2021 ที่คุณสามารถทำอะไรกับclassที่คุณไม่สามารถทำอย่างอื่น: เขตข้อมูลส่วนตัว , วิธีเอกชนและเขตข้อมูลคงที่ / วิธีการคงส่วนตัว .)

ยิ่งไปกว่านั้นclassOOP ประเภทอื่นหรือยังคงเป็นมรดกต้นแบบของ JavaScript?

มันเป็นการสืบทอดต้นแบบเดียวกันกับที่เรามีเสมอเพียงแค่มีไวยากรณ์ที่สะอาดและสะดวกกว่าหากคุณชอบใช้ฟังก์ชันตัวสร้าง ( new Fooฯลฯ ) (โดยเฉพาะอย่างยิ่งในกรณีที่ได้มาจากArrayหรือErrorซึ่งคุณไม่สามารถทำได้ใน ES5 และรุ่นก่อนหน้าขณะนี้คุณสามารถใช้Reflect.construct[ spec , MDN ] ได้ แต่ไม่ใช่กับ ES5 แบบเก่า)

ฉันสามารถแก้ไขโดยใช้.prototype?

ได้คุณยังคงสามารถปรับเปลี่ยนprototypeออบเจ็กต์บนตัวสร้างของคลาสได้เมื่อคุณสร้างคลาสแล้ว เช่นสิ่งนี้ถูกกฎหมายอย่างสมบูรณ์:

class Foo {
    constructor(name) {
        this.name = name;
    }
    
    test1() {
        console.log("test1: name = " + this.name);
    }
}
Foo.prototype.test2 = function() {
    console.log("test2: name = " + this.name);
};

มีประโยชน์ด้านความเร็วหรือไม่?

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

classไวยากรณ์ES2015 (ES6) ให้ประโยชน์อะไรบ้าง?

โดยสังเขป: หากคุณไม่ได้ใช้ฟังก์ชันตัวสร้างตั้งแต่แรกเลือกObject.createหรือคล้ายกันclassก็ไม่มีประโยชน์สำหรับคุณ

หากคุณใช้ฟังก์ชันตัวสร้างจะมีประโยชน์บางประการclassดังนี้

  • ไวยากรณ์ง่ายกว่าและมีข้อผิดพลาดน้อยกว่า

  • มันมากได้ง่ายขึ้น (และอีกครั้งข้อผิดพลาดน้อย) การตั้งค่าลำดับชั้นมรดกใช้ไวยากรณ์ใหม่กว่าเก่า

  • classปกป้องคุณจากข้อผิดพลาดทั่วไปของการล้มเหลวในการใช้newกับฟังก์ชันตัวสร้าง (โดยให้ตัวสร้างโยนข้อยกเว้นหากthisไม่ใช่วัตถุที่ถูกต้องสำหรับตัวสร้าง)

  • การเรียกเมธอดเวอร์ชันต้นแบบของพาเรนต์นั้นง่ายกว่ามากด้วยไวยากรณ์ใหม่กว่าแบบเก่า ( super.method()แทนที่จะเป็นParentConstructor.prototype.method.call(this)หรือObject.getPrototypeOf(Object.getPrototypeOf(this)).method.call(this))

นี่คือการเปรียบเทียบไวยากรณ์สำหรับลำดับชั้น:

// ***ES2015+**
class Person {
    constructor(first, last) {
        this.first = first;
        this.last = last;
    }

    personMethod() {
        // ...
    }
}

class Employee extends Person {
    constructor(first, last, position) {
        super(first, last);
        this.position = position;
    }

    employeeMethod() {
        // ...
    }
}

class Manager extends Employee {
    constructor(first, last, position, department) {
        super(first, last, position);
        this.department = department;
    }

    personMethod() {
        const result = super.personMethod();
        // ...use `result` for something...
        return result;
    }

    managerMethod() {
        // ...
    }
}

ตัวอย่าง:

เทียบกับ

// **ES5**
var Person = function(first, last) {
    if (!(this instanceof Person)) {
        throw new Error("Person is a constructor function, use new with it");
    }
    this.first = first;
    this.last = last;
};

Person.prototype.personMethod = function() {
    // ...
};

var Employee = function(first, last, position) {
    if (!(this instanceof Employee)) {
        throw new Error("Employee is a constructor function, use new with it");
    }
    Person.call(this, first, last);
    this.position = position;
};
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
Employee.prototype.employeeMethod = function() {
    // ...
};

var Manager = function(first, last, position, department) {
    if (!(this instanceof Manager)) {
        throw new Error("Manager is a constructor function, use new with it");
    }
    Employee.call(this, first, last, position);
    this.department = department;
};
Manager.prototype = Object.create(Employee.prototype);
Manager.prototype.constructor = Manager;
Manager.prototype.personMethod = function() {
    var result = Employee.prototype.personMethod.call(this);
    // ...use `result` for something...
    return result;
};
Manager.prototype.managerMethod = function() {
    // ...
};

ตัวอย่างสด:

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


¹ "ไม่มีอะไรใน ES2015-ES2018 ที่classสามารถทำได้ด้วยฟังก์ชันตัวสร้างและReflect.construct(รวมถึงคลาสย่อยErrorและArray)"

ตัวอย่าง:


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

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

3
@JasonCust: ใช่มันเป็นวิธีที่แตกต่างกัน ฉันจะไม่เรียกมันว่า "พื้นเมืองมากกว่า" (ฉันรู้ว่าไคล์ทำ แต่นั่นคือไคล์) ฟังก์ชั่นตัวสร้างเป็นพื้นฐานของ JavaScript ดูประเภทที่มีอยู่แล้วภายในทั้งหมด (ยกเว้นว่าฝ่ายต่อต้านคอนสตรัคเตอร์ได้รับการจัดการเพื่อให้Symbolเกิดความสับสน) แต่สิ่งที่ยอดเยี่ยมอีกครั้งคือถ้าคุณไม่ต้องการใช้มันคุณก็ไม่จำเป็นต้องทำ ฉันพบในงานของฉันว่าฟังก์ชันคอนสตรัคเตอร์มีความหมายในหลาย ๆ ที่และความหมายของการnewปรับปรุงความชัดเจน และมีObject.createเหตุผลในที่อื่น ๆ อีกมากมายโดยเฉพาะ สถานการณ์ด้านหน้า พวกเขาเสริมกัน :-)
TJ Crowder

4
ฉัน. ฉันไม่ซื้อความจำเป็นสำหรับสิ่งนี้ ฉันก้าวออกจาก c # เพื่อหนีจาก oop และตอนนี้ oop กำลังคืบคลานอยู่บนจาวาสคริปต์ ฉันไม่ชอบประเภท ทุกอย่างควรเป็น var / object / function แค่นั้นแหละ. ไม่มีคำหลักที่ไม่เกี่ยวข้องอีกต่อไป และมรดกเป็นการหลอกลวง หากคุณต้องการเห็นการต่อสู้ที่ดีระหว่างประเภทและไม่ใช่ประเภท ดูสบู่กับส่วนที่เหลือ และเราทุกคนรู้ว่าใครชนะ
Shai UI

3
@foreyez: classไวยากรณ์ไม่ได้ทำให้ JavaScript พิมพ์ผิดไปกว่าที่เคยเป็นมา ดังที่ฉันได้กล่าวไว้ในคำตอบส่วนใหญ่เป็นเพียงไวยากรณ์ที่ง่ายกว่า (มาก) สำหรับสิ่งที่มีอยู่แล้ว มีความจำเป็นอย่างยิ่งสำหรับสิ่งนั้นผู้คนมักเข้าใจไวยากรณ์แบบเก่าไม่ถูกต้อง นอกจากนี้ยังไม่ได้หมายความว่าคุณต้องใช้มรดกอีกต่อไปกว่าที่เคยทำมาก่อน (และแน่นอนถ้าคุณไม่เคยตั้งค่า "คลาส" ด้วยไวยากรณ์แบบเก่าก็ไม่จำเป็นต้องทำเช่นนั้นกับไวยากรณ์ใหม่เช่นกัน)
TJ Crowder

22

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

การใช้ Babel เพื่อถ่ายทอดคลาส ES6 นี้:

class Foo {
  constructor(bar) {
    this._bar = bar;
  }

  getBar() {
    return this._bar;
  }
}

จะให้สิ่งที่ชอบ:

var Foo = (function () {
  function Foo(bar) {    
    this._bar = bar;
  }

  Foo.prototype.getBar = function () {
    return this._bar;
  }

  return Foo;
})();

รุ่นที่สองไม่ซับซ้อนมากนัก แต่ต้องดูแลรักษารหัสมากกว่า เมื่อคุณได้รับมรดกเข้ามาเกี่ยวข้องรูปแบบเหล่านั้นจะซับซ้อนยิ่งขึ้น

เนื่องจากชั้นเรียนรวบรวมรูปแบบต้นแบบเดียวกับที่เราใช้อยู่คุณจึงสามารถจัดการกับต้นแบบเดียวกันได้ ซึ่งรวมถึงการเพิ่มวิธีการและสิ่งที่คล้ายกันในรันไทม์การเข้าถึงวิธีการบนFoo.prototype.getBarฯลฯ

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

const BAR_NAME = 'bar';

export default class Foo {
  static get name() {
    return BAR_NAME;
  }
}

และBAR_NAMEจะไม่มีให้โมดูลอื่นอ้างอิงโดยตรง

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

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

tl; dr: เป็นน้ำตาลสำหรับสิ่งที่เราทำอยู่แล้วและทำให้เจตนาของคุณชัดเจนในรหัส

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