เมื่อใดที่ฉันควรใช้ฟังก์ชั่นลูกศรใน ECMAScript 6


406

คำถามนี้ส่งตรงไปยังผู้ที่คิดเกี่ยวกับรูปแบบโค้ดในบริบทของ ECMAScript 6 (Harmony) ที่กำลังจะมาถึงและผู้ที่ได้ทำงานกับภาษานี้แล้ว

ด้วย() => {}และfunction () {}เราจะได้รับสองวิธีที่คล้ายกันมากในการเขียนฟังก์ชั่นใน ES6 ในภาษาอื่น ๆ ฟังก์ชั่นแลมบ์ดามักจะแยกแยะตัวเองโดยการไม่เปิดเผยตัวตน แต่ใน ECMAScript ฟังก์ชั่นใด ๆ สามารถไม่ระบุชื่อได้ แต่ละประเภทมีโดเมนการใช้งานที่ไม่ซ้ำกัน (เช่นเมื่อthisต้องถูกผูกไว้อย่างชัดเจนหรือไม่ผูกมัดอย่างชัดเจน) ระหว่างโดเมนเหล่านั้นมีหลายกรณีที่สัญกรณ์ทั้งสองจะทำ

ฟังก์ชั่นลูกศรใน ES6 มีข้อ จำกัด อย่างน้อยสองข้อ:

  • ไม่ทำงานnewและไม่สามารถใช้งานได้เมื่อสร้างprototype
  • แก้ไขthisขอบเขตกับขอบเขตเมื่อเริ่มต้น

ข้อ จำกัด ทั้งสองนี้ข้างกันฟังก์ชั่นลูกศรในทางทฤษฎีสามารถแทนที่ฟังก์ชั่นปกติได้เกือบทุกที่ วิธีการที่ถูกต้องใช้พวกเขาในทางปฏิบัติคืออะไร? ควรใช้ฟังก์ชันลูกศรเช่น:

  • "ทุกที่ที่ทำงาน" คือทุกที่ที่ฟังก์ชั่นไม่จำเป็นต้องไม่เชื่อเรื่องพระเจ้าthisและเราไม่ได้สร้างวัตถุ
  • มีเพียง "ทุกที่ที่ต้องการ" เช่นผู้ฟังเหตุการณ์, หมดเวลา, ที่ต้องเชื่อมโยงกับขอบเขตที่แน่นอน
  • ด้วยฟังก์ชั่น 'สั้น' แต่ไม่ได้มีฟังก์ชั่น 'ยาว'
  • เฉพาะกับฟังก์ชั่นที่ไม่มีฟังก์ชั่นลูกศรอื่น

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


33
คุณถือว่าFixed this bound to scope at initialisationเป็นข้อ จำกัด หรือไม่?
thefourtheye

12
มันเป็นข้อได้เปรียบ แต่ก็อาจเป็นข้อ จำกัด หากคุณวางแผนที่จะใช้ฟังก์ชันภายนอกบริบทเดิม ตัวอย่างเช่นเมื่อเพิ่มฟังก์ชั่นให้กับคลาสแบบไดนามิกผ่าน Object.prototype สิ่งที่ฉันหมายถึงโดย 'จำกัด ' คือการเปลี่ยนค่าของthisเป็นสิ่งที่คุณสามารถทำได้กับฟังก์ชั่นปกติ แต่ไม่ได้มีฟังก์ชั่นลูกศร
lyschoening

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

ฉันไม่คิดว่าFixed this bound to scope at initialisationเป็นข้อ จำกัด :) ลองดูที่บทความนี้: exploringjs.com/es6/ch_arrow-functions.html
NgaNguyenDuy

3
@thefourtheye "ข้อ จำกัด " ที่นี่หมายถึง "ข้อ จำกัด เนื่องจากตัวแปลโค้ดอัตโนมัติที่เป็นใบ้ไม่สามารถแทนที่ค่าสุ่มด้วยกันได้และคิดว่าทุกอย่างจะทำงานอย่างที่คาดหวัง"
Pacerier

คำตอบ:


322

ขณะที่ผ่านมาทีมงานของเราอพยพรหัสทั้งหมด (แอป AngularJS ขนาดกลาง) เพื่อรวบรวมโดยใช้ JavaScript Traceur บาเบล ตอนนี้ฉันใช้กฎง่ายๆสำหรับฟังก์ชั่นใน ES6 และที่อื่น ๆ :

  • ใช้functionในขอบเขตโกลบอลและสำหรับObject.prototypeคุณสมบัติ
  • ใช้classสำหรับตัวสร้างวัตถุ
  • ใช้=>ทุกที่อื่น

เหตุใดจึงใช้ฟังก์ชั่นลูกศรเกือบทุกที่

  1. ความปลอดภัยของขอบเขต: เมื่อใช้ฟังก์ชั่นลูกศรอย่างสม่ำเสมอทุกอย่างจะใช้เหมือนthisObjectรูท หากแม้แต่การเรียกกลับฟังก์ชั่นมาตรฐานเดียวผสมกับฟังก์ชั่นพวงของลูกศรก็มีโอกาสที่ขอบเขตจะเลอะ
  2. ความกะทัดรัด: ฟังก์ชั่นลูกศรง่ายต่อการอ่านและเขียน (นี่อาจดูเหมือนเป็นความเห็นดังนั้นฉันจะยกตัวอย่างเพิ่มเติมอีกเล็กน้อย)
  3. ความชัดเจน: เมื่อเกือบทุกอย่างเป็นฟังก์ชั่นลูกศรปกติใด ๆfunctionก็จะพุ่งออกมาทันทีเพื่อกำหนดขอบเขต นักพัฒนาสามารถค้นหาfunctionคำสั่งถัดไปที่สูงขึ้นเพื่อดูว่าthisObjectมันคืออะไร

เหตุใดจึงใช้ฟังก์ชั่นปกติในขอบเขตโกลบอลหรือโมดูลขอบเขตเสมอ

  1. thisObjectเพื่อบ่งชี้ถึงฟังก์ชั่นที่ไม่ควรเข้าถึงได้
  2. windowวัตถุ (ขอบเขตทั่วโลก) ที่ดีที่สุดคือการแก้ไขอย่างชัดเจน
  3. Object.prototypeคำจำกัดความจำนวนมากอาศัยอยู่ในขอบเขตของโลก (คิดString.prototype.truncateฯลฯ ) และโดยทั่วไปจะต้องเป็นประเภทfunctionอย่างไรก็ตาม การใช้functionขอบเขตทั่วโลกอย่างสม่ำเสมอช่วยหลีกเลี่ยงข้อผิดพลาด
  4. ฟังก์ชั่นหลายอย่างในขอบเขตส่วนกลางเป็นตัวสร้างวัตถุสำหรับคำนิยามคลาสแบบเก่า
  5. ฟังก์ชั่นสามารถตั้งชื่อ1 นี้มีสองประโยชน์: (1) มันเป็นน้อยอึดอัดใจที่จะเขียนfunction foo(){}กว่าconst foo = () => {}- นอกโดยเฉพาะฟังก์ชั่นอื่น ๆ สาย (2) ชื่อฟังก์ชั่นแสดงในกองร่องรอย ในขณะที่มันน่าเบื่อที่จะตั้งชื่อการเรียกกลับภายในทุกครั้งการตั้งชื่อฟังก์ชั่นสาธารณะทั้งหมดน่าจะเป็นความคิดที่ดี
  6. การประกาศฟังก์ชั่นถูกยกขึ้น (หมายถึงพวกเขาสามารถเข้าถึงได้ก่อนที่พวกเขาจะประกาศ) ซึ่งเป็นคุณลักษณะที่มีประโยชน์ในฟังก์ชั่นยูทิลิตี้คงที่


ตัวสร้างวัตถุ

ความพยายามในการสร้างอินสแตนซ์ของฟังก์ชั่นลูกศรเกิดข้อยกเว้น:

var x = () => {};
new x(); // TypeError: x is not a constructor

ข้อได้เปรียบสำคัญอย่างหนึ่งของฟังก์ชั่นเหนือฟังก์ชั่นลูกศรจึงทำหน้าที่เป็นสองเท่าของตัวสร้างวัตถุ:

function Person(name) {
    this.name = name;
}

อย่างไรก็ตามข้อกำหนดคลาสร่างแบบร่าง ES 2 Harmony ที่เหมือนกันนั้นมีขนาดกะทัดรัด:

class Person {
    constructor(name) {
        this.name = name;
    }
}

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

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


การอ่านฟังก์ชั่นลูกศร

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

ECMAScript มีการเปลี่ยนแปลงเล็กน้อยเนื่องจาก ECMAScript 5.1 มอบฟังก์ชั่นการใช้Array.forEachงานArray.mapและคุณสมบัติการเขียนโปรแกรมฟังก์ชั่นเหล่านี้ทั้งหมดที่ให้เราใช้ฟังก์ชั่นที่จะใช้ลูปแบบลูปมาก่อน JavaScript แบบอะซิงโครนัสปิดตัวลงเล็กน้อย ES6 จะส่งมอบPromiseวัตถุซึ่งหมายถึงฟังก์ชั่นที่ไม่ระบุชื่อยิ่งขึ้น ไม่มีการย้อนกลับสำหรับการตั้งโปรแกรมการทำงาน ในจาวาสคริปต์ใช้งานฟังก์ชั่นลูกศรจะดีกว่าฟังก์ชั่นปกติ

ยกตัวอย่างชิ้นนี้ (โดยเฉพาะอย่างยิ่งทำให้สับสน) ของโค้ด3 :

function CommentController(articles) {
    this.comments = [];

    articles.getList()
        .then(articles => Promise.all(articles.map(article => article.comments.getList())))
        .then(commentLists => commentLists.reduce((a, b) => a.concat(b)));
        .then(comments => {
            this.comments = comments;
        })
}

รหัสเดียวกันกับฟังก์ชั่นปกติ:

function CommentController(articles) {
    this.comments = [];

    articles.getList()
        .then(function (articles) {
            return Promise.all(articles.map(function (article) { 
                return article.comments.getList();
            }));
        })
        .then(function (commentLists) {
            return commentLists.reduce(function (a, b) {
                return a.concat(b); 
            });
        })
        .then(function (comments) {
            this.comments = comments;
        }.bind(this));
}

ในขณะที่ฟังก์ชั่นลูกศรใด ๆ สามารถแทนที่ด้วยฟังก์ชั่นมาตรฐานจะมีน้อยมากที่จะได้รับจากการทำเช่นนั้น เวอร์ชันใดที่สามารถอ่านได้มากกว่า ฉันจะบอกคนแรก

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


หมายเหตุ

  1. ตั้งชื่อลูกศรฟังก์ชั่นได้รับการรอการตัดบัญชีในสเป็ค ES6 พวกเขาอาจยังคงเพิ่มรุ่นในอนาคต
  2. ตามข้อกำหนดร่างร่าง"การประกาศคลาส / นิพจน์สร้างคู่ฟังก์ชันของตัวสร้าง / ต้นแบบตรงตามการประกาศฟังก์ชัน"ตราบใดที่คลาสไม่ใช้extendคำสำคัญ ข้อแตกต่างเล็กน้อยคือการประกาศคลาสเป็นค่าคงที่ในขณะที่การประกาศฟังก์ชันไม่ใช่
  3. หมายเหตุเกี่ยวกับบล็อกในฟังก์ชั่นลูกศรคำสั่งเดียว: ฉันชอบที่จะใช้บล็อกทุกที่ที่ฟังก์ชันลูกศรถูกเรียกใช้สำหรับผลข้างเคียงเพียงอย่างเดียว (เช่นการกำหนด) วิธีนี้ชัดเจนว่าสามารถส่งคืนค่าที่ส่งคืนได้

4
เวลาอื่นที่คุณต้องการใช้functionคือเมื่อคุณไม่ต้องการthisถูกผูกไว้ใช่ไหม สถานการณ์ที่พบบ่อยที่สุดของฉันสำหรับเหตุการณ์นี้คือเหตุการณ์ที่คุณอาจต้องการthisอ้างถึงวัตถุ (โดยปกติคือโหนด DOM) ที่ทำให้เกิดเหตุการณ์
Brett

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

3
@ สเปนเซอร์นั่นเป็นประเด็นที่ยุติธรรม จากประสบการณ์ของฉัน=>จบลงด้วยการดูดีขึ้นตามเวลา ฉันสงสัยว่าผู้ที่ไม่ใช่โปรแกรมเมอร์จะรู้สึกแตกต่างอย่างมากเกี่ยวกับสองตัวอย่าง หากคุณเขียนรหัส ES2016 โดยปกติคุณจะไม่ต้องใช้ฟังก์ชั่นลูกศรจำนวนมากเช่นนี้ ในตัวอย่างนี้การใช้ async / await และ array comprehension คุณจะได้ฟังก์ชันลูกศรเพียงอันเดียวในการreduce()โทร
lyschoening

3
ฉันเห็นด้วยกับสเปนเซอร์อย่างสมบูรณ์ว่าฟังก์ชั่นทั่วไปนั้นอ่านได้ง่ายกว่ามาก
jonschlinkert

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

80

ตามข้อเสนอลูกศรเล็ง "เพื่อแก้ไขและแก้ไขจุดปวดทั่วไปหลายประการFunction Expression" พวกเขาตั้งใจที่จะปรับปรุงเรื่องต่าง ๆ โดยการผูกคำthisศัพท์และเสนอซินแท็คซ์

อย่างไรก็ตาม

  • หนึ่งไม่สามารถผูกthisศัพท์อย่างสม่ำเสมอ
  • ไวยากรณ์ของฟังก์ชันลูกศรนั้นละเอียดอ่อนและคลุมเครือ

ดังนั้นฟังก์ชั่นลูกศรสร้างโอกาสสำหรับความสับสนและข้อผิดพลาดและควรแยกออกจากคำศัพท์ของโปรแกรมเมอร์ JavaScript แทนที่ด้วยคำศัพท์functionเฉพาะ

เกี่ยวกับคำศัพท์ this

this เป็นปัญหา:

function Book(settings) {
    this.settings = settings;
    this.pages = this.createPages();
}
Book.prototype.render = function () {
    this.pages.forEach(function (page) {
        page.draw(this.settings);
    }, this);
};

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

this.pages.forEach(page => page.draw(this.settings));

อย่างไรก็ตามพิจารณาว่ารหัสใช้ห้องสมุดเช่น jQuery ซึ่งวิธีการผูกthisเป็นพิเศษ ตอนนี้มีสองthisค่าที่จะจัดการกับ:

Book.prototype.render = function () {
    var book = this;
    this.$pages.each(function (index) {
        var $page = $(this);
        book.draw(book.currentPage + index, $page);
    });
};

เราต้องใช้functionเพื่อeachผูกมัดthisแบบไดนามิก เราไม่สามารถใช้ฟังก์ชั่นลูกศรที่นี่

การจัดการกับหลายthisค่าอาจทำให้สับสนเนื่องจากเป็นการยากที่จะรู้ว่าthisผู้เขียนคนใดพูดถึง:

function Reader() {
    this.book.on('change', function () {
        this.reformat();
    });
}

ผู้เขียนตั้งใจจะโทรจริงBook.prototype.reformatหรือ หรือเขาลืมผูกthisไว้และตั้งใจจะโทรReader.prototype.reformat? หากเราเปลี่ยนฟังก์ชั่นการจัดการเป็นฟังก์ชั่นลูกศรเราจะสงสัยเช่นเดียวกันว่าผู้เขียนต้องการไดนามิกthisแต่เลือกลูกศรเพราะมันพอดีกับหนึ่งบรรทัด:

function Reader() {
    this.book.on('change', () => this.reformat());
}

หนึ่งอาจก่อให้เกิด: "มันเป็นพิเศษที่บางครั้งลูกศรอาจเป็นฟังก์ชั่นที่ไม่ถูกต้องในการใช้งานหรือบางทีถ้าเราไม่ค่อยต้องการthisค่าแบบไดนามิก

แต่ถามตัวคุณเองว่า: "ควรหรือไม่ที่จะดีบั๊กโค้ดและพบว่าผลลัพธ์ของข้อผิดพลาดเกิดขึ้นจาก 'edge case?'" ฉันต้องการหลีกเลี่ยงปัญหาไม่ใช่แค่เวลาส่วนใหญ่ แต่ 100% ของเวลา

มีวิธีที่ดีกว่าคือใช้ทุกครั้งfunction( thisสามารถผูกได้ตลอดเวลา) และอ้างอิงthisผ่านตัวแปรเสมอ ตัวแปรเป็นศัพท์และสมมติชื่อหลายชื่อ การกำหนดthisตัวแปรจะทำให้ความตั้งใจของคุณชัดเจน:

function Reader() {
    var reader = this;
    reader.book.on('change', function () {
        var book = this;
        book.reformat();
        reader.reformat();
    });
}

นอกจากนี้การกำหนดตัวแปรให้เสมอthis (แม้ในขณะที่มีthisฟังก์ชั่นเดียวหรือไม่มีฟังก์ชั่นอื่น ๆ ) ช่วยให้มั่นใจได้ว่าความตั้งใจของคนจะยังคงชัดเจนแม้หลังจากเปลี่ยนรหัสแล้ว

นอกจากนี้ไดนามิกthisยังไม่ได้รับการยกเว้น jQuery มีการใช้งานบนเว็บไซต์มากกว่า 50 ล้านเว็บไซต์ (ข้อมูลนี้เขียนในเดือนกุมภาพันธ์ 2559) นี่คือ API อื่น ๆ ที่มีผลผูกพันthisแบบไดนามิก:

  • มอคค่า (~ 120k ดาวน์โหลดเมื่อวานนี้) exposes thisวิธีการทดสอบที่ผ่านการ
  • ฮึดฮัด (~ 63k ดาวน์โหลดเมื่อวานนี้) exposes thisวิธีการในการสร้างงานผ่านทาง
  • Backbone (ดาวน์โหลดถึงthis22 พันเมื่อวานนี้) กำหนดวิธีการเข้าถึง
  • APIs เหตุการณ์ (เช่น DOM) ที่อ้างถึงด้วยEventTargetthis
  • APIs prototypal ที่ patched thisหรือขยายหมายถึงกรณีที่มี

(สถิติผ่านhttp://trends.builtwith.com/javascript/jQueryและhttps://www.npmjs.com )

คุณน่าจะต้องการการthisเชื่อมโยงแบบไดนามิกอยู่แล้ว

thisบางครั้งคำศัพท์คาดหวัง แต่บางครั้งไม่; thisบางครั้งก็คาดหวังแบบไดนามิกแต่บางครั้งก็ไม่เป็นเช่นนั้น โชคดีที่มีวิธีที่ดีกว่าซึ่งมักจะผลิตและสื่อสารการผูกที่คาดไว้

เกี่ยวกับไวยากรณ์สั้น

ฟังก์ชั่นลูกศรประสบความสำเร็จในการให้ "รูปแบบประโยคสั้นลง" สำหรับฟังก์ชั่น แต่ฟังก์ชั่นที่สั้นกว่านี้จะทำให้คุณประสบความสำเร็จมากกว่านี้หรือไม่?

คือx => x * x"ง่ายต่อการอ่าน" มากกว่าfunction (x) { return x * x; }? อาจเป็นเพราะมีแนวโน้มที่จะสร้างรหัสบรรทัดสั้น ๆ accoring จะไดสันอิทธิพลของการอ่านความยาวสายความเร็วและประสิทธิภาพของการอ่านจากหน้าจอ ,

ความยาวบรรทัดกลาง (55 อักขระต่อบรรทัด) ปรากฏขึ้นเพื่อรองรับการอ่านที่มีประสิทธิภาพที่ความเร็วปกติและรวดเร็ว สิ่งนี้สร้างความเข้าใจในระดับสูงสุด . .

เหตุผลที่คล้ายกันจะทำสำหรับเงื่อนไข (ternary) ผู้ประกอบการและสำหรับบรรทัดเดียวifงบ

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

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

มีปัญหามากมายเกี่ยวกับไวยากรณ์ฟังก์ชันลูกศร:

const a = x =>
    doSomething(x);

const b = x =>
    doSomething(x);
    doSomethingElse(x);

ทั้งสองฟังก์ชั่นเหล่านี้มีความถูกต้องทางไวยากรณ์ แต่doSomethingElse(x);ไม่ได้อยู่ในเนื้อหาของbมันเป็นเพียงคำสั่งระดับล่างที่เยื้อง ๆ

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

const create = () => User.create();

const create = () => {
    let user;
    User.create().then(result => {
        user = result;
        return sendEmail();
    }).then(() => user);
};

const create = () => {
    let user;
    return User.create().then(result => {
        user = result;
        return sendEmail();
    }).then(() => user);
};

สิ่งที่อาจตั้งใจให้เป็นพารามิเตอร์ที่เหลือสามารถแยกวิเคราะห์เป็นตัวดำเนินการสเปรด:

processData(data, ...results => {}) // Spread
processData(data, (...results) => {}) // Rest

การมอบหมายสามารถสับสนกับอาร์กิวเมนต์เริ่มต้น:

const a = 1;
let x;
const b = x => {}; // No default
const b = x = a => {}; // "Adding a default" instead creates a double assignment
const b = (x = a) => {}; // Remember to add parens

บล็อกมีลักษณะเหมือนวัตถุ:

(id) => id // Returns `id`
(id) => {name: id} // Returns `undefined` (it's a labeled statement)
(id) => ({name: id}) // Returns an object

สิ่งนี้หมายความว่า?

() => {}

ผู้แต่งตั้งใจจะสร้าง no-op หรือฟังก์ชั่นที่ส่งคืนวัตถุเปล่าหรือไม่? (ด้วยสิ่งนี้ในใจเราควรจะทำ{ต่อไป=>หรือไม่เราควร จำกัด ตัวเองกับไวยากรณ์การแสดงออกเท่านั้นหรือไม่ซึ่งจะช่วยลดความถี่ของลูกศรต่อไป)

=>ดูเหมือน<=และ>=:

x => 1 ? 2 : 3
x <= 1 ? 2 : 3

if (x => 1) {}
if (x >= 1) {}

ในการเรียกใช้นิพจน์ฟังก์ชันลูกศรได้ทันทีจะต้องวาง()ด้านนอก แต่การวาง()ด้านในนั้นถูกต้องและอาจเป็นการจงใจ

(() => doSomething()()) // Creates function calling value of `doSomething()`
(() => doSomething())() // Calls the arrow function

แม้ว่าถ้ามีใครเขียน(() => doSomething()());ด้วยความตั้งใจที่จะเขียนการแสดงออกของฟังก์ชั่นที่เรียกใช้ทันทีก็ไม่มีอะไรจะเกิดขึ้น

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

ไวยากรณ์ของfunctionถูกวางนัยทั่วไป หากต้องการใช้functionเฉพาะหมายความว่าภาษานั้นป้องกันไม่ให้มีการเขียนรหัสที่สับสน functionขั้นตอนของการเขียนที่ควรจะเข้าใจไวยากรณ์ในทุกกรณีที่ฉันเลือก

เกี่ยวกับแนวทาง

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

แนวทางสำหรับสัญกรณ์ทำงานใน ES6:

  • สร้างขั้นตอนด้วยfunctionเสมอ
  • กำหนดให้thisกับตัวแปรเสมอ () => {}อย่าใช้

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

7
"การมีหลายวิธีในการทำสิ่งต่าง ๆ นั้นเป็นการสร้างเวกเตอร์ที่ไม่จำเป็นสำหรับข้อโต้แย้งและความขัดแย้งในที่ทำงานและชุมชนภาษามันจะดีกว่าถ้าไวยากรณ์ภาษาไม่อนุญาตให้เราเลือกได้ไม่ดี" เห็นด้วยมาก เขียนได้ดี! ฉันคิดว่าฟังก์ชั่นลูกศรเป็นจริงย้อนกลับไป ในหัวข้ออื่นฉันหวังว่าเพื่อนร่วมงานของฉันจะหยุดพยายามเปลี่ยน JavaScript เป็น C # ด้วยชุดคำจำกัดความต้นแบบ มันน่าขยะแขยง ฉันไม่ระบุชื่อควรเชื่อมโยงการโพสต์ของคุณ :)
สเปนเซอร์

11
เขียนได้ดีมาก! แม้ว่าฉันจะไม่เห็นด้วยกับคะแนนส่วนใหญ่ของคุณ แต่มันเป็นสิ่งสำคัญที่จะต้องพิจารณามุมมองตรงกันข้าม
minexew

4
ไม่ใช่ฟังก์ชั่นลูกศร แต่พฤติกรรมแปลก ๆ ของthisปัญหาของ Javascript แทนที่จะถูกผูกมัดโดยนัยthisควรส่งผ่านเป็นอาร์กิวเมนต์ที่ชัดเจน
บ๊อบ

5
msgstr " ใช้ฟังก์ชั่นเสมอ (เพื่อให้มันสามารถถูกผูกไว้แบบไดนามิกได้เสมอ) และอ้างอิงสิ่งนี้ผ่านตัวแปรเสมอ ". ฉันไม่เห็นด้วยอีกต่อไป!

48

ฟังก์ชั่นลูกศรถูกสร้างขึ้นเพื่อลดความซับซ้อนของฟังก์ชั่นscopeและการแก้ไขthisคำหลักโดยทำให้มันง่ายขึ้น พวกเขาใช้=>ไวยากรณ์ซึ่งดูเหมือนลูกศร

หมายเหตุ: มันไม่ได้แทนที่ฟังก์ชั่นที่มีอยู่ หากคุณแทนที่ทุกฟังก์ชั่นไวยากรณ์ด้วยฟังก์ชั่นลูกศรมันจะไม่ทำงานในทุกกรณี

ลองดูที่ไวยากรณ์ ES5 ที่มีอยู่ถ้าthisคำสำคัญอยู่ในวิธีการของวัตถุ (ฟังก์ชั่นที่เป็นของวัตถุ) มันจะหมายถึงอะไร?

var Actor = {
  name: 'RajiniKanth',
  getName: function() {
     console.log(this.name);
  }
};
Actor.getName();

ข้อมูลข้างต้นจะอ้างถึงและพิมพ์ชื่อobject "RajiniKanth"ลองสำรวจตัวอย่างด้านล่างและดูว่าจุดนี้จะอยู่ตรงไหน

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach(function(movie) {
     alert(this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

ทีนี้ถ้าthisคำสำคัญอยู่ข้างในmethod’s functionล่ะ

นี่นี้จะอ้างถึงwindow objectกว่าเป็นลดลงออกจากinner function scopeเพราะthisมักจะอ้างอิงถึงเจ้าของฟังก์ชั่นที่มันมีอยู่ในกรณีนี้ - เนื่องจากขณะนี้อยู่นอกขอบเขต - วัตถุหน้าต่าง / โกลบอล

เมื่อมันอยู่ในobjectเมธอด 's - functionเจ้าของคือวัตถุ ดังนั้นคำหลักนี้ถูกผูกไว้กับวัตถุ แต่เมื่อมันอยู่ภายในฟังก์ชั่นไม่ว่าจะเป็นแบบสแตนด์อะโลนหรือในวิธีอื่นมันจะอ้างถึงwindow/globalวัตถุเสมอ

var fn = function(){
  alert(this);
}

fn(); // [object Window]

มีวิธีในการแก้ปัญหานี้ในES5ตัวเราเองให้เราดูก่อนที่จะดำดิ่งลงในฟังก์ชันลูกศร ES6 เกี่ยวกับวิธีแก้ปัญหา

โดยทั่วไปแล้วคุณจะสร้างตัวแปรนอกฟังก์ชั่นด้านในของวิธีการ ตอนนี้‘forEach’วิธีการเข้าถึงthisและทำให้object’sคุณสมบัติและค่าของพวกเขา

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   var _this = this;
   this.movies.forEach(function(movie) {
     alert(_this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

โดยใช้bindแนบคำหลักที่หมายถึงวิธีการที่จะthismethod’s inner function

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach(function(movie) {
     alert(this.name + " has acted in " + movie);
   }.bind(this));
  }
};

Actor.showMovies();

ขณะนี้มีES6ฟังก์ชั่นลูกศรเราสามารถจัดการกับlexical scopingปัญหาได้ง่ายขึ้น

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach((movie) => {
     alert(this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

Arrow functionsมีมากขึ้นเช่นงบฟังก์ชั่นยกเว้นว่าพวกเขานี้ไปbind parent scopeถ้าarrow function is in top scope, thisอาร์กิวเมนต์จะอ้างถึงwindow/global scopeในขณะที่ฟังก์ชั่นภายในลูกศรฟังก์ชั่นปกติจะมีเรื่องนี้มันเป็นเช่นเดียวกับฟังก์ชั่นด้านนอก

ด้วยarrowฟังก์ชั่นที่thisถูกผูกไว้กับสิ่งที่แนบมาscopeในเวลาการสร้างและไม่สามารถเปลี่ยนแปลงได้ ผู้ประกอบการใหม่ผูกโทรและนำไปใช้ไม่มีผลกับสิ่งนี้

var asyncFunction = (param, callback) => {
  window.setTimeout(() => {
  callback(param);
  }, 1);
};

// With a traditional function if we don't control
// the context then can we lose control of `this`.
var o = {
  doSomething: function () {
  // Here we pass `o` into the async function,
  // expecting it back as `param`
  asyncFunction(o, function (param) {
  // We made a mistake of thinking `this` is
  // the instance of `o`.
  console.log('param === this?', param === this);
  });
  }
};

o.doSomething(); // param === this? false

ในตัวอย่างด้านบนเราสูญเสียการควบคุมสิ่งนี้ เราสามารถแก้ตัวอย่างข้างต้นโดยใช้การอ้างอิงตัวแปรหรือใช้this bindด้วย ES6 ก็กลายเป็นเรื่องง่ายในการจัดการกับการผูกพันของthislexical scoping

var asyncFunction = (param, callback) => {
  window.setTimeout(() => {
  callback(param);
  }, 1);
};

var o = {
  doSomething: function () {
  // Here we pass `o` into the async function,
  // expecting it back as `param`.
  //
  // Because this arrow function is created within
  // the scope of `doSomething` it is bound to this
  // lexical scope.
  asyncFunction(o, (param) => {
  console.log('param === this?', param === this);
  });
  }
};

o.doSomething(); // param === this? true

เมื่อไม่ใช้ฟังก์ชั่น Arrow

ภายในวัตถุที่แท้จริง

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  getName: () => {
     alert(this.name);
  }
};

Actor.getName();

Actor.getNameถูกกำหนดให้มีฟังก์ชั่นลูกศร แต่ในการภาวนามันแจ้งเตือนไม่ได้กำหนดเพราะthis.nameเป็นแล้วแต่บริบทจะยังคงที่undefinedwindow

มันเกิดขึ้นเพราะฟังก์ชั่นลูกศรเชื่อมโยงบริบทกับwindow objectขอบเขต ... คือขอบเขตภายนอก การดำเนินการthis.nameเทียบเท่ากับwindow.nameซึ่งไม่ได้กำหนด

ต้นแบบวัตถุ

prototype objectกฎเดียวกันกับเมื่อกำหนดวิธีการใน แทนที่จะใช้ฟังก์ชันลูกศรเพื่อกำหนดเมธอด sayCatName ซึ่งทำให้เกิดข้อผิดพลาดcontext window:

function Actor(name) {
  this.name = name;
}
Actor.prototype.getName = () => {
  console.log(this === window); // => true
  return this.name;
};
var act = new Actor('RajiniKanth');
act.getName(); // => undefined

เรียกการก่อสร้าง

thisในการขอร้องการก่อสร้างเป็นวัตถุที่สร้างขึ้นใหม่ เมื่อมีการดำเนิน Fn () ใหม่บริบทของเป็นวัตถุใหม่:constructor Fnthis instanceof Fn === true

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

var Message = (text) => {
  this.text = text;
};
// Throws "TypeError: Message is not a constructor"
var helloMessage = new Message('Hello World!');

โทรกลับด้วยบริบทแบบไดนามิก

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

var button = document.getElementById('myButton');
button.addEventListener('click', () => {
  console.log(this === window); // => true
  this.innerHTML = 'Clicked button';
});

thisเป็นหน้าต่างในฟังก์ชั่นลูกศรที่กำหนดไว้ในบริบทโลก เมื่อมีเหตุการณ์การคลิกเกิดขึ้นเบราว์เซอร์จะพยายามเรียกใช้ฟังก์ชันตัวจัดการด้วยบริบทของปุ่ม แต่ฟังก์ชันลูกศรจะไม่เปลี่ยนบริบทที่กำหนดไว้ล่วงหน้า this.innerHTMLเทียบเท่าwindow.innerHTMLและไม่มีเหตุผล

คุณต้องใช้นิพจน์ฟังก์ชั่นซึ่งจะช่วยให้การเปลี่ยนแปลงนี้ขึ้นอยู่กับองค์ประกอบเป้าหมาย:

var button = document.getElementById('myButton');
button.addEventListener('click', function() {
  console.log(this === button); // => true
  this.innerHTML = 'Clicked button';
});

เมื่อผู้ใช้คลิกปุ่มสิ่งนี้ในฟังก์ชั่นการจัดการคือปุ่ม ดังนั้นการthis.innerHTML = 'Clicked button'ปรับเปลี่ยนข้อความปุ่มอย่างถูกต้องเพื่อสะท้อนสถานะการคลิก

ข้อมูลอ้างอิง: https://dmitripavlutin.com/when-not-to-use-arrow-functions-in-javascript/


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

@DmitriPavlutin: ตรวจสอบโพสต์ที่อัปเดตของฉันมันมีหลายสิ่งหลายอย่าง ... อาจเป็นไปได้ว่าฉันควรโพสต์ข้อมูลอ้างอิง
Thalaivar

2
โค้ดของคุณหลังจากบรรทัด 'using bind เพื่อแนบคำหลักนี้ที่อ้างถึงวิธีการกับฟังก์ชั่นด้านในของวิธีการ' มีข้อบกพร่องอยู่ในนั้น คุณทดสอบตัวอย่างที่เหลือแล้วหรือยัง?
Isaac Pak

หนึ่งusing bind to attach the this keyword that refers to the method to the method’s inner function.มีข้อผิดพลาดทางไวยากรณ์
Coda Chang

ควรเป็นvar Actor = { name: 'RajiniKanth', movies: ['Kabali', 'Sivaji', 'Baba'], showMovies: function() { this.movies.forEach(function(movie){ alert(this.name + ' has acted in ' + movie); }.bind(this)) } }; Actor.showMovies();
Coda Chang

14

ฟังก์ชั่นลูกศร - คุณสมบัติ ES6 ที่ใช้กันอย่างแพร่หลายจนถึง ...

การใช้งาน: ฟังก์ชั่น ES5 ทั้งหมดควรถูกแทนที่ด้วยฟังก์ชั่นลูกศร ES6 ยกเว้นในสถานการณ์ต่อไปนี้:

ไม่ควรใช้ฟังก์ชันลูกศร:

  1. เมื่อเราต้องการฟังก์ชั่นการยก
    • ฟังก์ชั่นลูกศรไม่ระบุชื่อ
  2. เมื่อเราต้องการใช้this/ argumentsในฟังก์ชั่น
    • เนื่องจากฟังก์ชั่นลูกศรไม่มีthis/ เป็นargumentsของตัวเองพวกมันจึงขึ้นอยู่กับบริบทภายนอก
  3. เมื่อเราต้องการใช้ฟังก์ชั่นที่มีชื่อ
    • ฟังก์ชั่นลูกศรไม่ระบุชื่อ
  4. เมื่อเราต้องการใช้ฟังก์ชั่นเป็น constructor
    • thisเป็นฟังก์ชั่นลูกศรไม่ได้มีของตัวเอง
  5. เมื่อเราต้องการเพิ่มฟังก์ชั่นเป็นคุณสมบัติในวัตถุตัวอักษรและใช้วัตถุในนั้น
    • ในขณะที่เราไม่สามารถเข้าถึงthis(ซึ่งควรเป็นวัตถุเอง)

ให้เราเข้าใจฟังก์ชั่นลูกศรบางอย่างเพื่อทำความเข้าใจให้ดีขึ้น:

Variant 1 : เมื่อเราต้องการส่งอาร์กิวเมนต์มากกว่าหนึ่งอาร์กิวเมนต์ไปยังฟังก์ชันและส่งคืนค่าบางค่าจากมัน

รุ่น ES5 :

var multiply = function (a,b) {
    return a*b;
};
console.log(multiply(5,6)); //30

รุ่น ES6 :

var multiplyArrow = (a,b) => a*b;
console.log(multiplyArrow(5,6)); //30

หมายเหตุ: functionไม่จำเป็นต้องใช้คำสำคัญ =>ต้องระบุ. {}เป็นตัวเลือกเมื่อเราไม่ได้ให้{} returnจะถูกเพิ่มโดย JavaScript และเมื่อเราให้{}เราจำเป็นต้องเพิ่มreturnถ้าเราต้องการมัน

Variant 2 : เมื่อเราต้องการส่งอาร์กิวเมนต์เพียงหนึ่งตัวไปยังฟังก์ชันและส่งคืนค่าบางค่าจากมัน

รุ่น ES5 :

var double = function(a) {
    return a*2;
};
console.log(double(2)); //4

รุ่น ES6 :

var doubleArrow  = a => a*2;
console.log(doubleArrow(2)); //4

หมายเหตุ: ()เมื่อผ่านอาร์กิวเมนต์เดียวเท่านั้นที่เราสามารถละเว้นวงเล็บ

Variant 3 : เมื่อเราไม่ต้องการส่งอาร์กิวเมนต์ใด ๆ ไปยังฟังก์ชันและไม่ต้องการคืนค่าใด ๆ

รุ่น ES5 :

var sayHello = function() {
    console.log("Hello");
};
sayHello(); //Hello

รุ่น ES6 :

var sayHelloArrow = () => {console.log("sayHelloArrow");}
sayHelloArrow(); //sayHelloArrow

Variant 4 : เมื่อเราต้องการกลับอย่างชัดเจนจากฟังก์ชั่นลูกศร

รุ่น ES6 :

var increment = x => {
  return x + 1;
};
console.log(increment(1)); //2

ชุดที่ 5 : เมื่อเราต้องการคืนค่าวัตถุจากฟังก์ชั่นลูกศร

รุ่น ES6 :

var returnObject = () => ({a:5});
console.log(returnObject());

หมายเหตุ: เราต้องล้อมวัตถุไว้ในวงเล็บ()มิฉะนั้น JavaScript ไม่สามารถแยกความแตกต่างระหว่างบล็อกและวัตถุ

ตัวแปร 6 : ฟังก์ชั่นลูกศรไม่ได้arguments(อาร์เรย์เช่นวัตถุ) argumentsของตัวเองพวกเขาขึ้นอยู่กับบริบทด้านนอกสำหรับ

รุ่น ES6 :

function foo() {
  var abc = i => arguments[0];
  console.log(abc(1));
};    
foo(2); // 2

หมายเหตุ: fooเป็นฟังก์ชั่น ES5 กับargumentsอาร์เรย์เช่นวัตถุและการโต้แย้งผ่านไปมันก็เป็น2เช่นนั้นarguments[0]สำหรับfoo2

abcเป็น ES6 ลูกศรฟังก์ชั่นเพราะมันไม่ได้มีเป็นของตัวเองargumentsด้วยเหตุนี้มันพิมพ์arguments[0]ของfooบริบทด้านนอกของมันแทน

Variant 7 : ฟังก์ชั่นลูกศรไม่ได้มีเป็นthisของตัวเอง แต่ขึ้นอยู่กับบริบทภายนอกthis

รุ่น ES5 :

var obj5 = {
  greet: "Hi, Welcome ",
  greetUser : function(user) {
        setTimeout(function(){
        console.log(this.greet + ": " +  user); // "this" here is undefined.
        });
     }
};

obj5.greetUser("Katty"); //undefined: Katty

หมายเหตุ: การโทรกลับที่ส่งไปยัง setTimeout เป็นฟังก์ชัน ES5 และมีเป็นของตัวเองthisซึ่งไม่ได้กำหนดในuse-strictสภาพแวดล้อมดังนั้นเราจึงได้รับผลลัพธ์:

undefined: Katty

รุ่น ES6 :

var obj6 = {
  greet: "Hi, Welcome ",
  greetUser : function(user) {
    setTimeout(() => console.log(this.greet + ": " +  user)); 
      // this here refers to outer context
   }
};

obj6.greetUser("Katty"); //Hi, Welcome: Katty

หมายเหตุ: การโทรกลับที่ส่งไปยังsetTimeoutเป็นฟังก์ชั่นลูกศร ES6 และมันไม่ได้เป็นของตัวเองthisดังนั้นจึงนำมาจากบริบทภายนอกซึ่งเป็นgreetUserสิ่งthisที่obj6เราได้รับ:

Hi, Welcome: Katty

เบ็ดเตล็ด: เราไม่สามารถใช้newกับฟังก์ชั่นลูกศร ฟังก์ชั่นลูกศรไม่มีprototypeคุณสมบัติ เราไม่ได้มีผลผูกพันของthisเมื่อลูกศรฟังก์ชั่นถูกเรียกผ่านหรือapplycall


6

นอกเหนือจากคำตอบที่ยอดเยี่ยมจนถึงตอนนี้ฉันต้องการนำเสนอเหตุผลที่แตกต่างกันอย่างมากว่าทำไมฟังก์ชั่นลูกศรจึงมีความรู้สึกที่ดีกว่าฟังก์ชั่นจาวาสคริปต์ทั่วไป เพื่อประโยชน์ในการพูดคุยสมมติว่าเราใช้ตัวตรวจสอบชนิดเช่น TypeScript หรือ "Flow" ของ Facebook เป็นการชั่วคราว พิจารณาโมดูลของเล่นต่อไปนี้ซึ่งเป็นรหัส ECMAScript 6 ที่ถูกต้องพร้อมคำอธิบายประกอบแบบ Flow: (ฉันจะใส่รหัสที่ไม่พิมพ์ซึ่งจะเป็นผลมาจาก Babel ในตอนท้ายของคำตอบนี้เพื่อให้สามารถทำงานได้จริง)

export class C {
  n : number;
  f1: number => number; 
  f2: number => number;

  constructor(){
    this.n = 42;
    this.f1 = (x:number) => x + this.n;
    this.f2 = function (x:number) { return  x + this.n;};
  }
}

ตอนนี้ดูว่าเกิดอะไรขึ้นเมื่อเราใช้คลาส C จากโมดูลอื่นเช่นนี้

let o = { f1: new C().f1, f2: new C().f2, n: "foo" };
let n1: number = o.f1(1); // n1 = 43
console.log(n1 === 43); // true
let n2: number = o.f2(1); // n2 = "1foo"
console.log(n2 === "1foo"); // true, not a string!

อย่างที่คุณเห็นตัวตรวจสอบชนิดล้มเหลวที่นี่: f2 ควรส่งคืนตัวเลข แต่ส่งคืนสตริง!

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

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

นี่คือรุ่นที่ยังพิมพ์ไม่ได้ซึ่งเรียกใช้โดย Babel:

class C {
    constructor() {
        this.n = 42;
        this.f1 = x => x + this.n;
        this.f2 = function (x) { return x + this.n; };
    }
}

let o = { f1: new C().f1, f2: new C().f2, n: "foo" };
let n1 = o.f1(1); // n1 = 43
console.log(n1 === 43); // true
let n2 = o.f2(1); // n2 = "1foo"
console.log(n2 === "1foo"); // true, not a string!



3

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

เกี่ยวกับคำศัพท์ this

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

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

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

แม้ว่าการเขียนโค้ดด้วยthisลูกศรจะง่ายกว่าหากไม่มีกฎการใช้ลูกศรยังคงซับซ้อนมาก (ดู: เธรดปัจจุบัน) ดังนั้นแนวทางจะไม่“ ชัดเจน” หรือ“ สอดคล้องกัน” ตามที่คุณร้องขอ แม้ว่าโปรแกรมเมอร์รู้เกี่ยวกับความกำกวมของลูกศรฉันคิดว่าพวกเขายักไหล่และยอมรับพวกเขาต่อไปเพราะคุณค่าของคำศัพท์thisครอบคลุมพวกเขา

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

เกี่ยวกับไวยากรณ์สั้น

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

ในคำอื่น ๆ : dammit ฉันต้องการฟังก์ชั่นหนึ่งซับเช่นกัน!

เกี่ยวกับแนวทาง

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

แนวทางสำหรับสัญกรณ์ทำงานใน ES6:

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

ยอมรับ 100% กับส่วน "คำแนะนำสำหรับฟังก์ชั่นโน้ตใน ES6" ที่ด้านล่าง - โดยเฉพาะอย่างยิ่งกับฟังก์ชั่นการยกและการโทรกลับแบบอินไลน์ คำตอบที่ดี!
Jeff McCloud

1

ในวิธีที่ง่าย

var a =20; function a(){this.a=10; console.log(a);} 
//20, since the context here is window.

อีกตัวอย่าง:

var a = 20;
function ex(){
this.a = 10;
function inner(){
console.log(this.a); //can you guess the output of this line.
}
inner();
}
var test = new ex();

ตอบ: คอนโซลจะพิมพ์ 20

เหตุผลที่ถูกเมื่อใดก็ตามที่ฟังก์ชั่นจะถูกดำเนินการสแต็คของตัวเองจะถูกสร้างขึ้นในตัวอย่างนี้exฟังก์ชั่นจะถูกดำเนินการกับnewผู้ประกอบการเพื่อให้บริบทจะถูกสร้างขึ้นและเมื่อinnerถูกดำเนินการมัน JS จะสร้างสแต็คใหม่และดำเนินการinnerการทำงานglobal contextแม้ว่าจะมี บริบทท้องถิ่น

ดังนั้นถ้าเราต้องการให้innerfunction มีบริบทเฉพาะที่exเราจำเป็นต้องผูกบริบทกับ function ภายใน

ลูกศรแก้ปัญหานี้แทนที่จะเอาสิ่งGlobal contextที่local contextมีอยู่ไปใช้ ในการgiven example,ที่จะใช้เวลาเป็นnew ex()this

ดังนั้นในทุกกรณีที่ลูกศรที่มีการผูกไว้อย่างชัดเจนแก้ปัญหาโดยค่าเริ่มต้น


1

ฟังก์ชั่นลูกศรหรือแลมบ์ดาถูกนำมาใช้ใน ES 6 นอกเหนือจากความสง่างามในรูปแบบที่น้อยที่สุดแล้วความแตกต่าง ในการใช้งานที่โดดเด่นที่สุดคือการกำหนดขอบเขตthis ภายในฟังก์ชั่นลูกศร

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

ในฟังก์ชั่นลูกศร , thisเป็นlexicallyผูกพันซึ่งหมายความว่ามันปิดเหนือthisจากขอบเขตที่ฟังก์ชั่นที่ลูกศรที่ถูกกำหนด (แม่ขอบเขต) และไม่เปลี่ยนแปลงไม่ว่าที่ไหนและวิธีการที่จะเรียก / เรียกว่า

ข้อ จำกัด Arrow-Function เป็นวิธีการบนวัตถุ

// this = global Window
let objA = {
 id: 10,
 name: "Simar",
 print () { // same as print: function() 
  console.log(`[${this.id} -> ${this.name}]`);
 }
}
objA.print(); // logs: [10 -> Simar]
objA = {
 id: 10,
 name: "Simar",
 print: () => {
  // closes over this lexically (global Window)
  console.log(`[${this.id} -> ${this.name}]`);
 }
};
objA.print(); // logs: [undefined -> undefined]

ในกรณีที่objA.print()เมื่อprint()วิธีการกำหนดโดยใช้ปกติfunction ก็ทำงานโดยการแก้ไขthisอย่างถูกต้องเพื่อobjAสำหรับวิธีการอุทธรณ์ แต่ล้มเหลวเมื่อกำหนดเป็นลูกศร=>ฟังก์ชั่น เป็นเพราะthisในฟังก์ชั่นปกติเมื่อมีการเรียกใช้เป็นวิธีการในวัตถุ ( objA) เป็นวัตถุเอง อย่างไรก็ตามในกรณีที่มีฟังก์ชั่นลูกศรthisได้รับการผูก lexically เพื่อที่thisขอบเขตการปิดล้อมที่มันถูกกำหนดไว้ (ทั่วโลก / หน้าต่างในกรณีของเรา) objAและการเข้าพักก็ยังคงอยู่ในช่วงเดียวกันของการภาวนาเป็นวิธีการใน

ข้อดีของฟังก์ชั่นลูกศรเหนือฟังก์ชั่นปกติในวิธีการของวัตถุ แต่เมื่อthisคาดว่าจะได้รับการแก้ไขและผูกพันตามคำจำกัดความของเวลา

/* this = global | Window (enclosing scope) */

let objB = {
 id: 20,
 name: "Paul",
 print () { // same as print: function() 
  setTimeout( function() {
    // invoked async, not bound to objB
    console.log(`[${this.id} -> ${this.name}]`);
  }, 1)
 }
};
objB.print(); // logs: [undefined -> undefined]'
objB = {
 id: 20,
 name: "Paul",
 print () { // same as print: function() 
  setTimeout( () => {
    // closes over bind to this from objB.print()
    console.log(`[${this.id} -> ${this.name}]`);
  }, 1)
 }
};
objB.print(); // logs: [20 -> Paul]

ในกรณีของการobjB.print()ที่print()วิธีการที่ถูกกำหนดให้เป็นฟังก์ชั่นที่จะเรียกconsole.log([$ {this.id} -> {this.name}] )ถ่ายทอดสดเป็นโทรกลับในsetTimeout , thisได้รับการแก้ไขอย่างถูกต้องobjBเมื่อมีฟังก์ชั่นลูกศรถูกใช้เป็นโทรกลับ แต่ล้มเหลว เมื่อโทรกลับถูกกำหนดให้เป็นฟังก์ชั่นปกติ เป็นเพราะ=>ฟังก์ชั่นลูกศรส่งผ่านไปยังsetTimeout(()=>..)ปิดเหนือthislexically จากเช่นแม่ของมัน ภาวนาobjB.print()ซึ่งกำหนดไว้ ในคำอื่น ๆ=>ฟังก์ชั่นลูกศรส่งผ่านไปยังที่จะsetTimeout(()==>...ผูกพันกับobjBมันthisเพราะในการภาวนาของobjB.print() thisเป็นของobjBตัวเอง

เราสามารถใช้เพื่อให้โทรกลับกำหนดให้เป็นฟังก์ชั่นการทำงานปกติโดยมีผลผูกพันให้ถูกต้องFunction.prototype.bind()this

const objB = {
 id: 20,
 name: "Singh",
 print () { // same as print: function() 
  setTimeout( (function() {
    console.log(`[${this.id} -> ${this.name}]`);
  }).bind(this), 1)
 }
}
objB.print() // logs: [20 -> Singh]

อย่างไรก็ตามฟังก์ชั่นลูกศรมีประโยชน์และมีข้อผิดพลาดน้อยลงสำหรับกรณีของ async call-backs ที่เรารู้thisในเวลาที่นิยามฟังก์ชั่นที่มันได้รับและควรถูกผูกไว้

ข้อ จำกัด ของฟังก์ชั่นลูกศรที่จำเป็นต้องเปลี่ยนระหว่างการเรียกใช้

เมื่อใดก็ตามที่เราต้องการฟังก์ชั่นที่thisสามารถเปลี่ยนแปลงได้ในช่วงเวลาของการร้องขอเราไม่สามารถใช้ฟังก์ชั่นลูกศร

/* this = global | Window (enclosing scope) */

function print() { 
   console.log(`[${this.id} -> {this.name}]`);
}
const obj1 = {
 id: 10,
 name: "Simar",
 print // same as print: print
};
obj.print(); // logs: [10 -> Simar]
const obj2 = {
 id: 20,
 name: "Paul",
};
printObj2 = obj2.bind(obj2);
printObj2(); // logs: [20 -> Paul]
print.call(obj2); // logs: [20 -> Paul]

ไม่สามารถทำงานกับฟังก์ชั่นลูกศรconst print = () => { console.log([$ {this.id} -> {this.name}] นี้ได้);}เนื่องจากthisจะไม่สามารถเปลี่ยนแปลงได้และจะยังคงผูกพันกับthisขอบเขตการล้อมรอบซึ่งถูกกำหนดไว้ (โกลบอล / หน้าต่าง) ในตัวอย่างเหล่านี้ทั้งหมดเราเรียกใช้ฟังก์ชันเดียวกันโดยมีวัตถุที่แตกต่างกัน ( obj1และobj2) หนึ่งรายการถัดไปซึ่งทั้งสองอย่างนั้นถูกสร้างขึ้นหลังจากprint()ฟังก์ชั่นถูกประกาศ

สิ่งเหล่านี้เป็นตัวอย่างที่ได้วางแผนไว้ แต่ลองคิดดูตัวอย่างชีวิตจริงเพิ่มเติมอีก หากเราต้องเขียนreduce()วิธีการของเราคล้ายกับวิธีที่ใช้งานได้arrays เราไม่สามารถนิยามมันเป็นแลมบ์ดาอีกครั้งเพราะมันจำเป็นต้องอนุมานthisจากบริบทการเรียกใช้เช่น อาร์เรย์ที่ถูกเรียกใช้

ด้วยเหตุผลนี้constructorฟังก์ชั่นจึงไม่สามารถกำหนดเป็นฟังก์ชั่นลูกศรได้เนื่องจากฟังก์ชั่นคอนthisสตรัคเตอร์ไม่สามารถตั้งค่าได้ในเวลาที่มีการประกาศ ทุกครั้งที่มีการเรียกใช้ฟังก์ชัน Constructor ด้วยnewคีย์เวิร์ดวัตถุใหม่จะถูกสร้างขึ้นซึ่งจะถูกผูกไว้กับการเรียกใช้เฉพาะนั้น

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

'use strict'
var button = document.getElementById('button');
button.addEventListener('click', function {
  // web-api invokes with this bound to current-target in DOM
  this.classList.toggle('on');
});
var button = document.getElementById('button');
button.addEventListener('click', () => {
  // TypeError; 'use strict' -> no global this
  this.classList.toggle('on');
});

นี่คือเหตุผลที่ว่าทำไมในเฟรมเวิร์กเช่นAngular 2+และVue.jsคาดว่าเมธอดการโยงเท็มเพลตคอมโพเนนต์เป็นฟังก์ชัน / เมธอดปกติthisสำหรับการเรียกใช้ถูกจัดการโดยเฟรมเวิร์กสำหรับฟังก์ชันการโยง (เชิงมุมใช้ Zone.js เพื่อจัดการบริบท async สำหรับการเรียกฟังก์ชั่นการเชื่อมโยงมุมมองเทมเพลต)

ในทางกลับกันในการตอบสนองเมื่อเราต้องการผ่านวิธีการขององค์ประกอบเป็น event-handler เช่น<input onChange={this.handleOnchange} />เราควรกำหนดhandleOnchanage = (event)=> {this.props.onInputChange(event.target.value);}เป็นฟังก์ชั่นลูกศรสำหรับทุกการร้องขอเราต้องการให้สิ่งนี้เป็นอินสแตนซ์เดียวกันขององค์ประกอบที่ผลิต JSX สำหรับการเรนเดอร์ องค์ประกอบ DOM


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

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