(1, eval) ('this') vs eval ('this') ใน JavaScript?


85

ฉันเริ่มอ่านรูปแบบ JavaScriptบางรหัสทำให้ฉันสับสน

var global = (function () {
    return this || (1, eval)('this');
}());

นี่คือคำถามของฉัน:

Q1:

(1, eval) === evalเหรอ?

ทำไมและทำงานอย่างไร?

Q2: ทำไมไม่เพียง

var global = (function () {
    return this || eval('this');
}());

หรือ

 var global = (function () {
    return this;
}());

ฉันได้อัปเดตชื่อเรื่องนี้เนื่องจากเป็นกรณีเฉพาะ นอกจากนี้วงเล็บสำหรับชนิดที่เฉพาะเจาะจงของวงเล็บ [] และ {} จะแตกต่างกันอย่างสิ้นเชิง :)

คำตอบ:


105

ความแตกต่างระหว่าง(1,eval)และเก่าธรรมดาevalคืออดีตเป็นค่านิยมและหลังเป็นค่าลอว์ จะชัดเจนยิ่งขึ้นหากเป็นตัวระบุอื่น ๆ :

var x;
x = 1;
(1, x) = 1; //  syntax error, of course!

นั่นคือ(1,eval)คือการแสดงออกที่อัตราผลตอบแทนeval(เช่นเดียวกับที่พูด(true && eval)หรือ(0 ? 0 : eval)จะ) evalแต่ก็ไม่ได้มีการอ้างอิงถึง

จะแคร์ทำไม

ดี Ecma ข้อมูลจำเพาะพิจารณาอ้างอิงเพื่อevalให้เป็น "โทร EVAL โดยตรง" แต่การแสดงออกว่าเป็นเพียงผลตอบแทนถัวเฉลี่ยevalที่จะเป็นหนึ่งในทางอ้อม - และบริการโทร EVAL อ้อมมีการรับประกันในการดำเนินการในขอบเขตทั่วโลก

สิ่งที่ฉันยังไม่รู้:

  1. การเรียกการประเมินโดยตรงไม่ดำเนินการในขอบเขตทั่วโลกภายใต้สถานการณ์ใด
  2. ภายใต้สถานการณ์ใดที่thisฟังก์ชันของฟังก์ชันในขอบเขตส่วนกลางจะไม่ส่งผลต่อวัตถุส่วนกลาง

ข้อมูลบางอย่างมากขึ้นสามารถรวบรวมได้ที่นี่

แก้ไข

เห็นได้ชัดว่าคำตอบสำหรับคำถามแรกของฉันคือ "เกือบตลอดเวลา" evalดำเนินการโดยตรงจากขอบเขตปัจจุบัน พิจารณารหัสต่อไปนี้:

var x = 'outer';
(function() {
  var x = 'inner';
  eval('console.log("direct call: " + x)'); 
  (1,eval)('console.log("indirect call: " + x)'); 
})();

ไม่น่าแปลกใจ (heh-heh) สิ่งนี้พิมพ์ออกมา:

direct call: inner
indirect call: outer

แก้ไข

หลังจากการทดลองมากขึ้นผมกำลังจะไปชั่วคราวบอกว่าthisไม่สามารถกำหนดให้หรือnull undefinedมันสามารถตั้งค่าให้ค่า falsy อื่น ๆ (0, '', น่านเท็จ) แต่มากจงใจ

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


3
ว้าวไม่ทราบทั้งเรื่องvalueเทียบกับlvalueสิ่งต่างๆ (ในทางปฏิบัติอาจจะ แต่ไม่ใช่ในคำพูด) ไม่ใช่กฎการประเมิน ES5 (ไม่ใช่ว่าฉันจำเป็นต้องใช้อย่างสมเหตุสมผลevalเลยทีเดียว) ขอบคุณ!
Stoive

ใช่evalมีขอบคมที่น่ารังเกียจมากมายและควรใช้เป็นทางเลือกสุดท้ายเท่านั้นจากนั้นอย่างระมัดระวัง
Malvolio

ฉันพบการใช้งานที่ถูกต้องเพียงครั้งเดียว - ประเมินแท็กสคริปต์ที่ถูกเพิ่มไปยัง DOM ผ่านinnerHtml
Stoive

1
lvalue มีค่าน้อยมากในการกำหนดค่า direct eval เนื่องจากโดยปกติจะหมายถึงนิพจน์ที่สามารถปรากฏทางด้านซ้ายมือของงานดังนั้นชื่อ lvalue จึงตรงข้ามกับ rvalue การเรียกร้องให้ eval เป็นโดยตรงภายใต้เงื่อนไขที่ระบุไว้ใน 15.1.2.1.1 ของข้อมูลจำเพาะซึ่งระบุว่าตัวระบุต้องเป็นevalและเป็นส่วน MemberExpression ของ CallExpression และอ้างถึงevalฟังก์ชันมาตรฐาน
chuckj

1
@Malvolio คุณดูเหมือนจะบอกเป็นนัยว่า lvalues ​​มีส่วนเกี่ยวข้องกับการประเมินทางตรงและทางอ้อมซึ่งพวกเขาไม่ทำ การใช้ตัวระบุที่เรียกว่าevalเป็นเป้าหมายของนิพจน์การโทรเป็นแบบพิเศษ คุณระบุว่า ECMA ถือว่าการอ้างอิงถึงevalแบบพิเศษซึ่งไม่เป็นเช่นนั้น เป็นตำแหน่งในนิพจน์การเรียกที่มีความพิเศษและนิพจน์ประเมินเป็นevalฟังก์ชันมาตรฐาน ตัวอย่างเช่นvar eval = window.eval; eval('1');ยังคงเป็น eval โดยตรงและwindow.eval('1')แม้ว่า eval จะเป็นค่า lvalue ในกรณีนี้เช่นกัน
chuckj

33

ชิ้นส่วน

var global = (function () {  
    return this || (1, eval)('this');  
}());  

จะประเมินกับวัตถุส่วนกลางอย่างถูกต้องแม้ในโหมดเข้มงวด ในโหมดที่ไม่ใช่เข้มงวดค่าของการthisเป็นวัตถุที่ทั่วโลก undefinedแต่ในโหมดเข้มงวดมันเป็น นิพจน์(1, eval)('this')จะเป็นวัตถุส่วนกลางเสมอ evalเหตุผลของเรื่องนี้เกี่ยวข้องกับกฎรอบโองการอ้อมโดยตรง การโทรโดยตรงไปevalยังมีขอบเขตของผู้โทรและสตริงthisจะประเมินตามมูลค่าthisในการปิด evalประเมินผลทางอ้อมในขอบเขตส่วนกลางราวกับว่าพวกมันถูกดำเนินการภายในฟังก์ชันในขอบเขตส่วนกลาง เนื่องจากฟังก์ชั่นนั้นไม่ได้เป็นฟังก์ชันโหมดเข้มงวดวัตถุส่วนกลางจึงถูกส่งผ่านในฐานะthisและจากนั้นนิพจน์จึง'this'ประเมินไปยังวัตถุส่วนกลาง นิพจน์(1, eval)เป็นเพียงวิธีแฟนซีในการบังคับให้ไฟล์eval เป็นทางอ้อมและส่งคืนวัตถุส่วนกลาง

A1: (1, eval)('this')ไม่ได้เช่นเดียวกับเพราะกฎพิเศษรอบกลอนอ้อมสายตรงไปยังeval('this')eval

A2: ต้นฉบับทำงานในโหมดเข้มงวดเวอร์ชันที่แก้ไขจะไม่ทำ


12

ถึง Q1:

ฉันคิดว่านี่เป็นตัวอย่างที่ดีของตัวดำเนินการลูกน้ำใน JS ฉันชอบคำอธิบายสำหรับตัวดำเนินการลูกน้ำในบทความนี้: http://javascriptweblog.wordpress.com/2011/04/04/the-javascript-comma-operator/

ตัวดำเนินการลูกน้ำจะประเมินตัวถูกดำเนินการทั้งสอง (จากซ้ายไปขวา) และส่งคืนค่าของตัวถูกดำเนินการที่สอง

ถึง Q2:

(1, eval)('this')ถือเป็นการเรียก eval ทางอ้อมซึ่งใน ES5 จะรันโค้ดทั่วโลก ดังนั้นผลลัพธ์จะเป็นบริบทของโลก

ดูhttp://perfectionkills.com/global-eval-what-are-the-options/#evaling_in_global_scope


7

Q1: คำสั่ง javascript ที่ต่อเนื่องกันหลายคำโดยคั่นด้วยเครื่องหมายจุลภาคจะใช้ค่าของคำสั่งสุดท้าย ดังนั้น:

(1, eval)รับค่าของค่าสุดท้ายซึ่งเป็นการอ้างอิงฟังก์ชันไปยังeval()ฟังก์ชัน เห็นได้ชัดว่าเป็นวิธีนี้ในการeval()โทรเข้าสู่การเรียกการประเมินทางอ้อมซึ่งจะได้รับการประเมินในขอบเขตทั่วโลกใน ES5 รายละเอียดอธิบายที่นี่

Q2: จะต้องมีสภาพแวดล้อมบางอย่างที่ไม่ได้กำหนดระดับโลกแต่ไม่กำหนดthis eval('this')นั่นเป็นเหตุผลเดียวที่ฉันคิดได้


บางทีอาจมีใครบางคนพยายามหลบตะขอเช็คอินที่ขัดขวาง/eval\(/g?
Stoive

@Stoive - ใช่ฉันก็สงสัยเรื่องแบบนั้นเหมือนกัน หากไม่ใช่ตะขอเช็คอินบางตัวกรองบางส่วนในกระบวนการ (อาจย่อขนาด)
jfriend00

7
มีบางอย่างที่เกี่ยวข้องกับโหมดเข้มงวด ES5 AFAIK ในโหมดเข้มงวด ES5 evalโค้ดใด ๆจะถูกเรียกใช้ในบริบทของตัวเองมากกว่าในบริบทส่วนกลางหรือบริบทที่ปิดล้อม วิธีหนึ่งในการแก้ปัญหานี้คือการอ้างอิงโดยอ้อมเหมือนกับรหัสที่เป็นปัญหา
Cristian Sanchez


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