ตัวดำเนินการลูกน้ำมีประโยชน์เมื่อใด


88

ฉันอ่านคำถามนี้เกี่ยวกับ "ตัวดำเนินการลูกน้ำ" ในนิพจน์ ( ,) และเอกสาร MDNเกี่ยวกับเรื่องนี้ แต่ฉันไม่สามารถนึกถึงสถานการณ์ที่เป็นประโยชน์ได้

ดังนั้นตัวดำเนินการลูกน้ำจึงมีประโยชน์เมื่อใด


2
var i, j, k;VS var i; var j, var k?
Salman A

15
@SalmanA. ฉันไม่แน่ใจว่ามันเกี่ยวข้องอะไรกับตัว,ดำเนินการ บรรทัดนั้นก็ใช้ได้C#เช่นกัน แต่,ไม่มีตัวดำเนินการอยู่ที่นั่น
gdoron สนับสนุน Monica

2
@SalmanA. ฉันทำ. ไม่พบมัน
ตรัสรู้

5
@SalmanA a ,ไม่ใช่ตัว,ดำเนินการเสมอไป(และไม่เคยเป็นตัว,ดำเนินการใน C #) ดังนั้น C # จึงขาดตัว,ดำเนินการในขณะที่ยังใช้,เป็นส่วนหนึ่งของไวยากรณ์ได้อย่างอิสระ
Seth Carnegie

8
ผมคิดว่าคำตอบที่นี่ได้สรุปความจริงที่ว่า,ไม่ได้ใช้กันอย่างแพร่หลาย(และไม่ได้เกิดขึ้นของทุกคน,เป็นผู้ดำเนินจุลภาค) แต่คุณสามารถยืมมันและ Array เพื่อทำการสลับตัวแปรแบบอินไลน์ได้โดยไม่ต้องสร้างตัวแปรชั่วคราว ระบุว่าคุณต้องการที่จะสลับค่าของaและคุณสามารถทำb a = [b][b = a,0]ซึ่งจะวางกระแสbใน Array ประการที่สอง[]คือสัญกรณ์การเข้าถึงคุณสมบัติ ดัชนีที่เข้าถึงนั้น0ไม่ใช่ก่อนที่จะกำหนดaให้bซึ่งตอนนี้ปลอดภัยเนื่องจากbจะถูกเก็บไว้ใน Array ช่วยให้เราทำสำนวนที่หลายแห่งใน, []

คำตอบ:


128

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

if(x){foo();return bar()}else{return 1}

จะกลายเป็น:

return x?(foo(),bar()):1

? :ผู้ประกอบการสามารถนำมาใช้ในขณะนี้เนื่องจากผู้ประกอบการจุลภาค (ในระดับหนึ่ง) ช่วยให้สองงบที่จะเขียนเป็นคำสั่งอย่างใดอย่างหนึ่ง

สิ่งนี้มีประโยชน์ในการช่วยให้สามารถบีบอัดข้อมูลได้อย่างเรียบร้อย (39 -> 24 ไบต์ที่นี่)


ฉันต้องการที่จะเน้นความจริงที่ว่าจุลภาคในvar a, bคือไม่ได้ประกอบจุลภาคเพราะมันไม่ได้อยู่ภายในการแสดงออก เครื่องหมายจุลภาคมีความหมายพิเศษในงบvar a, bในการแสดงออกจะหมายถึงสองตัวแปรและประเมินผลการซึ่งไม่ได้เป็นกรณีที่bvar a, b


3
คุณคิดเกี่ยวกับเรื่องนั้นได้อย่างไร? คุณอ่านมันมาจากไหน? มีการใช้งานจริงหรือไม่?
gdoron ให้การสนับสนุน Monica

16
วันก่อนฉันแค่ยุ่งกับClosure Compilerเพื่อดูว่ามันทำอะไรได้บ้างและฉันสังเกตเห็นการแทนที่นี้
pimvdb

2
การใช้งานที่คล้ายกันซึ่งฉันคิดว่ามีประโยชน์ในโค้ดของคุณคือการกำหนดตัวแปรหลายตัวในคำสั่ง inline if ตัวอย่างเช่นโดยif (condition) var1 = val1, var2 = val2;ส่วนตัวฉันคิดว่าการหลีกเลี่ยงวงเล็บที่เป็นไปได้ทำให้โค้ดอ่านง่ายขึ้น
Aidiakapi

2
ในช่วงเวลาเดียวที่ฉันใช้ตัวดำเนินการลูกน้ำคือเมื่อฉันเพิ่มคำสั่งบันทึกลงในนิพจน์ (foo => (console.log ('foo', foo), foo)) หรือถ้าฉันฉลาดเกินไปด้วยการลดการวนซ้ำ . (pairs.reduce ((acc, [k, v]) => (acc [k] = v, acc), {}))
Joseph Sikorski

38

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

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

// j is initialized to some other value
// as the for loop executes both i and j are incremented
// because the comma operator allows two statements to be put in place of one
for (var i = 0; i < items.len; i++, j++) {
    // loop code here that operates on items[i] 
    // and sometimes uses j to access a different array
}

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


38

Comma Operator มักมีประโยชน์เมื่อเขียนโค้ดการทำงานใน Javascript

พิจารณารหัสนี้ที่ฉันเขียนสำหรับสปาในขณะที่ย้อนกลับไปซึ่งมีสิ่งต่อไปนี้

const actions = _.chain(options)
                 .pairs() // 1
                 .filter(selectActions) // 2
                 .map(createActionPromise) // 3
                 .reduce((state, pair) => (state[pair[0]] = pair[1], state), {}) // 4
                 .value();

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


สิ่งนี้ใช้ขีดเส้นใต้ของ

  1. แยกตัวเลือกทั้งหมดที่ส่งผ่านไปยังฟังก์ชันนี้โดยใช้pairs ซึ่งจะเปลี่ยน{ a: 1, b: 2}เป็น[['a', 1], ['b', 2]]

  2. อาร์เรย์ของคู่คุณสมบัตินี้ถูกกรองโดยที่ถือว่าเป็น 'การกระทำ' ในระบบ

  3. จากนั้นดัชนีที่สองในอาร์เรย์จะถูกแทนที่ด้วยฟังก์ชันที่ส่งกลับคำสัญญาที่แสดงถึงการกระทำนั้น (โดยใช้map)

  4. ในที่สุดการเรียกร้องreduceจะรวม "อาร์เรย์คุณสมบัติ" ( ['a', 1]) แต่ละรายการกลับเป็นวัตถุสุดท้าย

ผลลัพธ์สุดท้ายคือoptionsอาร์กิวเมนต์เวอร์ชันที่แปลงแล้วซึ่งมีเฉพาะคีย์ที่เหมาะสมและมีค่าที่ฟังก์ชันการเรียกใช้งานได้


มองแค่

.reduce((state, pair) => (state[pair[0]] = pair[1], state), {})

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

โดยส่วนตัวแล้วฉันเจอหลายกรณีในขณะที่เขียน Javascript ในรูปแบบที่ใช้งานได้มากขึ้นด้วย ECMAScript 2015 + Arrow Functions ต้องบอกว่าก่อนที่จะพบกับฟังก์ชันลูกศร (เช่นในขณะที่เขียนคำถาม) ฉันไม่เคยใช้ตัวดำเนินการลูกศรในทางที่ตั้งใจ


2
นี่เป็นคำตอบเดียวที่มีประโยชน์จริงๆเกี่ยวกับวิธี / เวลาที่โปรแกรมเมอร์สามารถใช้ตัวดำเนินการลูกน้ำได้ มีประโยชน์มากโดยเฉพาะในreduce
hgoebl

คำตอบที่ดี แต่ถ้าผมอาจจะแนะนำสิ่งเล็กน้อย.reduce((state, [key, value]) => (state[key] = value, state), {})ที่สามารถอ่านเพิ่มเติมได้ที่: และฉันตระหนักดีว่าสิ่งนี้เอาชนะจุดประสงค์ของคำตอบ แต่.reduce((state, [key, value]) => Object.assign(state, { [key]: value }), {})จะขจัดความจำเป็นในการใช้ตัวดำเนินการลูกน้ำทั้งหมด
Patrick Roberts

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

18

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

ตัวอย่างเช่นหากคุณประเมินmyVariable = aWholeLotOfTextในการจำลองหรือคอนโซลระบบจะพิมพ์ข้อมูลทั้งหมดที่คุณเพิ่งกำหนดให้ ซึ่งอาจเป็นเพจและเพจต่างๆและหากคุณไม่ต้องการเห็นคุณสามารถประเมินแทนได้myVariable = aWholeLotOfText, 'done'และ repl / console จะพิมพ์แค่ 'เสร็จสิ้น'

Oriel อย่างถูกต้องชี้ให้เห็นที่กำหนดเองtoString()หรือget()ฟังก์ชั่นก็อาจจะทำให้มีประโยชน์นี้


1
ฮาไอเดียดีมาก! (ในที่สุดก็เป็นคำตอบที่ตอบคำถามได้จริงซึ่งแตกต่างจากคำตอบเกือบทั้งหมด {และ 3 คำตอบที่ถูกลบซึ่งคุณต้องมีชื่อเสียงถึง 20K เพื่อดู ... })
gdoron รองรับ Monica

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

@ โอริออล - ดี! คุณถูกต้องทั้งหมด อย่างไรก็ตามสิ่งนี้อาจเป็นประโยชน์ทำให้รู้สึกผิดหวังเล็กน้อย :)
Julian de Bhal

13

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

i = a += 2, a + b;

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


1
คิดว่านี่เป็นอีกทางเลือกหนึ่งแม้ว่าคำจำกัดความของความดีอาจแตกต่างกันไปในแต่ละคน อย่างไรก็ตามฉันไม่พบตัวอย่างที่คุณต้องใช้ลูกน้ำ สิ่งที่คล้ายกันอีกอย่างคือ ternary?: operator ซึ่งสามารถแทนที่ด้วย if-else ได้เสมอ แต่บางครั้ง?: ทำให้โค้ดอ่านง่ายกว่า if-else แนวคิดเดียวกันใช้กับลูกน้ำเช่นกัน
taskinoor

BTW ฉันไม่ได้พิจารณาการใช้ลูกน้ำในการประกาศตัวแปรหรือเริ่มต้นตัวแปรหลายตัวในลูป ในกรณีเหล่านี้ส่วนใหญ่แล้วลูกน้ำจะดีกว่า
taskinoor

2
มันดูสับสน af ... wtf
Timmerz

6

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

นี่คือบทความโดยละเอียดเกี่ยวกับการใช้งานจุลภาค:

ตัวอย่างมากมายจากที่นั่นเพื่อพิสูจน์การสาธิต:

function renderCurve() {
  for(var a = 1, b = 10; a*b; a++, b--) {
    console.log(new Array(a*b).join('*'));
  }
}

เครื่องกำเนิด fibonacci:

for (
    var i=2, r=[0,1];
    i<15;
    r.push(r[i-1] + r[i-2]), i++
); 
// 0,1,1,2,3,5,8,13,21,34,55,89,144,233,377

ค้นหาองค์ประกอบหลักแรกอะนาล็อกของ.parent()ฟังก์ชันjQuery :

function firstAncestor(el, tagName) {
    while(el = el.parentNode, el && (el.tagName != tagName.toUpperCase()));
    return el;
}

//element in http://ecma262-5.com/ELS5_HTML.htm
var a = $('Section_15.1.1.2'); 

firstAncestor(a, 'div'); //<div class="page">

7
ฉันไม่แน่ใจว่าฉันจะบอกว่าสิ่งที่อ่านได้มากกว่านี้หรือไม่แต่มันก็ค่อนข้างน่ากลัวมากดังนั้น +1
Chris Marisic

1
คุณไม่จำเป็นต้องใช้ลูกน้ำในลูป while ในตัวอย่างสุดท้ายwhile ((el = el.parentNode) && (el.tagName != tagName.toUpperCase()))ก็ใช้ได้ดีในบริบทนั้น
PitaJ

5

ฉันไม่พบการใช้งานจริงนอกเหนือจากนั้น แต่นี่เป็นสถานการณ์หนึ่งที่James Padolseyใช้เทคนิคนี้อย่างดีในการตรวจจับ IEแบบวนซ้ำ:

var ie = (function(){

    var undef,
        v = 3,
        div = document.createElement('div'),
        all = div.getElementsByTagName('i');

    while ( // <-- notice no while body here
        div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
        all[0]
    );

    return v > 4 ? v : undef;

}());

สองบรรทัดนี้ต้องดำเนินการ:

div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
all[0]

และภายในตัวดำเนินการลูกน้ำทั้งสองจะได้รับการประเมินแม้ว่าจะมีใครทำให้แยกกัน


3
สิ่งนี้อาจเป็นdo- whileวนซ้ำ
Casey Chu

5

มีบางอย่าง "แปลก" ที่สามารถทำได้ใน JavaScript ที่เรียกใช้ฟังก์ชันทางอ้อมโดยใช้ตัวดำเนินการลูกน้ำ

มีคำอธิบายแบบยาวที่นี่: การเรียกฟังก์ชันทางอ้อมใน JavaScript

โดยใช้ไวยากรณ์นี้:

(function() {
    "use strict";
  
    var global = (function () { return this || (1,eval)("this"); })();
    console.log('Global === window should be true: ', global === window);
  
    var not_global = (function () { return this })();
    console.log('not_global === window should be false: ', not_global === window);
  
  }());

คุณสามารถเข้าถึงตัวแปรส่วนกลางได้เนื่องจากevalทำงานแตกต่างกันเมื่อเรียกโดยตรงกับเรียกโดยอ้อม


4

ฉันพบว่าตัวดำเนินการลูกน้ำมีประโยชน์มากที่สุดเมื่อเขียนตัวช่วยเช่นนี้

const stopPropagation = event => (event.stopPropagation(), event);
const preventDefault = event => (event.preventDefault(), event);
const both = compose(stopPropagation, preventDefault);

คุณสามารถแทนที่จุลภาคด้วย || หรือ && แต่คุณต้องรู้ว่าฟังก์ชันส่งคืนอะไร

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

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


คล้ายกับสิ่งที่ Ramda \ Lodash ทำกับtap( ramdajs.com/docs/#tap ) โดยพื้นฐานแล้วคุณกำลังเรียกใช้ผลข้างเคียงจากนั้นคืนค่าเริ่มต้น มีประโยชน์มากในการเขียนโปรแกรมเชิงฟังก์ชัน :)
avalanche1

1

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

/**
 * @param {string} [str]
 * @param {object} [obj]
 * @param {Date} [date]
 */
function f(str, obj, date) {
  // handle optional arguments
  if (typeof str !== "string") date = obj, obj = str, str = "default";
  if (obj instanceof Date) date = obj, obj = {};
  if (!(date instanceof Date)) date = new Date();

  // ...
}

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

1

สมมติว่าคุณมีอาร์เรย์:

arr = [];

เมื่อคุณpushเข้าสู่อาร์เรย์นั้นคุณจะไม่ค่อยสนใจในpushค่าตอบแทนของอาร์เรย์นั่นคือความยาวใหม่ของอาร์เรย์ แต่เป็นอาร์เรย์เอง:

arr.push('foo')  // ['foo'] seems more interesting than 1

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

(arr.push('bar'), arr.push('baz'), arr).sort(); // [ 'bar', 'baz', 'foo' ]

-2

พื้นที่ที่ผู้ประกอบการจุลภาคสามารถนำมาใช้ก็คือรหัส Obfuscation

สมมติว่าผู้พัฒนาเขียนโค้ดดังนี้:

var foo = 'bar';

ตอนนี้เธอตัดสินใจที่จะทำให้รหัสสับสน เครื่องมือที่ใช้อาจเปลี่ยนรหัสดังนี้:

var Z0b=(45,87)>(195,3)?'bar':(54,65)>(1,0)?'':'baz';// Z0b == 'bar'

การสาธิต: http://jsfiddle.net/uvDuE/


1
@gdoron โปรดดูคำตอบนี้stackoverflow.com/a/17903036/363573เกี่ยวกับ Comma Operator ใน C ++ คุณจะสังเกตเห็นความคิดเห็นของ James Kanze เกี่ยวกับความสับสน
Stephan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.