ฉันไม่รู้เรื่องนี้เช่นกันกลับบ้านและฟาร์ม
การคัดลอกรหัสมีดังนี้:
ลบ ThisIsObject [คีย์]
หรือ
ลบ thisiBject.key
มาพูดถึงการใช้การลบกันเถอะ
ไม่กี่สัปดาห์ที่ผ่านมาฉันมีโอกาสอ่านหนังสือ JavaScript เชิงวัตถุของ Stoyan Stefanov หนังสือเล่มนี้ได้รับการจัดอันดับสูงใน Amazon (12 บทวิจารณ์ 5 ดาว) ดังนั้นฉันจึงอยากรู้ว่ามันเป็นหนังสือที่แนะนำดังนั้นฉันจึงเริ่มอ่านบทของฟังก์ชั่น ฉันซาบซึ้งในวิธีที่หนังสือเล่มนี้อธิบายสิ่งต่าง ๆ และตัวอย่างมีการจัดระเบียบด้วยวิธีที่สวยงามและค่อยเป็นค่อยไปและดูเหมือนว่าแม้แต่ผู้เริ่มต้นก็สามารถควบคุมความรู้นี้ได้อย่างง่ายดาย อย่างไรก็ตามเกือบจะในทันทีฉันค้นพบความเข้าใจผิดที่น่าสนใจตลอดทั้งบท - การลบฟังก์ชั่นการใช้งาน นอกจากนี้ยังมีข้อผิดพลาดอื่น ๆ (เช่นความแตกต่างระหว่างการประกาศฟังก์ชั่นและการแสดงออกของฟังก์ชั่น) แต่เราจะไม่พูดคุยกันในเวลานี้
หนังสืออ้างว่า:
"ฟังก์ชั่นได้รับการปฏิบัติเหมือนตัวแปรปกติ - สามารถคัดลอกไปยังตัวแปรที่แตกต่างกันหรือแม้แต่ลบ" ตัวอย่างแนบกับคำอธิบายนี้:
การคัดลอกรหัสมีดังนี้:
var sum = function (a, b) {return a + b;}
var add = sum;
ลบผลรวม
จริง
ประเภทของผลรวม;
"ไม่ได้กำหนด"
ไม่สนใจเครื่องหมายอัฒภาคที่ขาดหายไปคุณจะเห็นว่าข้อผิดพลาดในรหัสเหล่านี้อยู่ที่ไหน? เห็นได้ชัดว่าข้อผิดพลาดคือการดำเนินการของการลบตัวแปรผลรวมจะไม่ประสบความสำเร็จ นิพจน์การลบไม่ควรส่งคืนจริงและผลรวมประเภทของไม่ควรส่งคืน "ไม่ได้กำหนด" ทั้งหมดนี้เป็นเพราะมันเป็นไปไม่ได้ที่จะลบตัวแปรใน JavaScript อย่างน้อยก็เป็นไปไม่ได้ในการประกาศ
ดังนั้นเกิดอะไรขึ้นในตัวอย่างนี้? มันเป็นข้อผิดพลาดหรือไม่? หรือการใช้งานพิเศษ? อาจไม่ใช่ รหัสนี้เป็นเอาต์พุตจริงในคอนโซล Firebug และ Stoyan ต้องใช้เป็นเครื่องมือสำหรับการทดสอบอย่างรวดเร็ว มันเกือบจะเหมือน Firebug ปฏิบัติตามกฎการลบอื่น ๆ มันคือ Firebug ที่ทำให้ Stoyan หลงทาง! แล้วเกิดอะไรขึ้นที่นี่?
ก่อนที่จะตอบคำถามนี้ก่อนอื่นเราต้องเข้าใจว่าผู้ดำเนินการลบทำงานใน JavaScript ได้อย่างไร: สามารถลบอะไรได้บ้างและสิ่งที่ไม่สามารถลบได้? วันนี้ฉันจะพยายามอธิบายรายละเอียดนี้ เราจะดูพฤติกรรม "แปลก" ของ Firebug และตระหนักว่ามันไม่แปลกเลย เราจะขุดลึกลงไปในสิ่งที่ซ่อนอยู่เบื้องหลังฉากที่ตัวแปรฟังก์ชั่นกำหนดค่าให้กับแอตทริบิวต์และลบ เราจะดูความเข้ากันได้ของเบราว์เซอร์และข้อบกพร่องที่มีชื่อเสียงที่สุด นอกจากนี้เรายังจะหารือเกี่ยวกับรูปแบบที่เข้มงวดของ ES5 และวิธีการเปลี่ยนแปลงพฤติกรรมของตัวดำเนินการลบ
ฉันจะสลับ JavaScript และ Ecmascript ซึ่งทั้งสองอย่างนี้หมายถึง ECMASCRIPT (เว้นแต่จะเห็นได้ชัดว่าการใช้งาน JavaScript ของ Mozilla)
ตามที่คาดไว้บนอินเทอร์เน็ตคำอธิบายของการลบนั้นค่อนข้างหายาก บทความ MDC น่าจะเป็นทรัพยากรที่ดีที่สุดที่จะเข้าใจ แต่น่าเสียดายที่มันขาดรายละเอียดที่น่าสนใจของหัวข้อ สิ่งที่น่าแปลกที่สิ่งหนึ่งที่ถูกลืมคือเหตุผลของการสำแดงที่แปลกประหลาดของ Firebug และการอ้างอิง MSDN นั้นเกือบจะไร้ประโยชน์ในด้านเหล่านี้
ทฤษฎี
ดังนั้นทำไมเราถึงสามารถลบคุณสมบัติของวัตถุ:
การคัดลอกรหัสมีดังนี้:
var o = {x: 1};
ลบวัว; // จริง
วัว; // ไม่ได้กำหนด
แต่วัตถุที่ประกาศเช่นนี้ไม่สามารถลบได้:
การคัดลอกรหัสมีดังนี้:
var x = 1;
ลบ x; // เท็จ
x; // 1
หรือฟังก์ชั่น:
การคัดลอกรหัสมีดังนี้:
ฟังก์ชัน x () {}
ลบ x; // เท็จ
typeof x; // "การทำงาน"
หมายเหตุ: เมื่อไม่สามารถลบคุณสมบัติผู้ดำเนินการลบจะส่งคืนเท็จเท่านั้น
เพื่อให้เข้าใจสิ่งนี้ก่อนอื่นเราต้องเชี่ยวชาญแนวคิดเหล่านี้เกี่ยวกับอินสแตนซ์ตัวแปรและคุณสมบัติแอตทริบิวต์ - แนวคิดเหล่านี้ไม่ค่อยมีการกล่าวถึงในหนังสือ JavaScript ฉันจะพยายามทบทวนแนวคิดเหล่านี้สั้น ๆ ในสองสามย่อหน้าถัดไป แนวคิดเหล่านี้ยากที่จะเข้าใจ! หากคุณไม่สนใจว่า "ทำไมสิ่งเหล่านี้ถึงใช้วิธีนี้" เพียงแค่ข้ามบทนี้
ประเภทของรหัส:
ใน ECMASCript มีรหัสปฏิบัติการ 3 ประเภท: รหัสส่วนกลางรหัสฟังก์ชันและรหัสประเมิน ประเภทเหล่านี้มีการอธิบายตนเองมากหรือน้อยในแง่ของชื่อนี่คือภาพรวมสั้น ๆ :
เมื่อชิ้นส่วนของซอร์สโค้ดถูกมองว่าเป็นโปรแกรมมันจะถูกดำเนินการในสภาพแวดล้อมทั่วโลกและถือว่าเป็นรหัสทั่วโลก ในสภาพแวดล้อมของเบราว์เซอร์เนื้อหาขององค์ประกอบสคริปต์มักถูกตีความว่าเป็นโปรแกรมและดังนั้นจึงถูกดำเนินการเป็นรหัสทั่วโลก
รหัสใด ๆ ที่ดำเนินการโดยตรงในฟังก์ชั่นจะถือว่าเป็นรหัสฟังก์ชัน ในเบราว์เซอร์เนื้อหาของคุณสมบัติเหตุการณ์ (เช่น <p onclick = "... ">) มักจะตีความว่าเป็นรหัสฟังก์ชัน
ในที่สุดข้อความรหัสที่ใช้กับฟังก์ชั่นในตัวจะตีความว่าเป็นรหัสประเมิน เร็ว ๆ นี้เราจะพบว่าทำไมประเภทนี้ถึงพิเศษ
บริบทการดำเนินการ:
เมื่อรหัส ECMASCRIPT ถูกเรียกใช้งานมักจะเกิดขึ้นในบริบทการดำเนินการเฉพาะ บริบทการดำเนินการเป็นแนวคิดเอนทิตีที่เป็นนามธรรมซึ่งสามารถช่วยให้เข้าใจว่าขอบเขตและอินสแตนซ์ของตัวแปรทำงานอย่างไร สำหรับแต่ละรหัสที่เรียกใช้งานได้ทั้งสามรหัสมีบริบทการดำเนินการที่สอดคล้องกับมัน เมื่อมีการดำเนินการฟังก์ชั่นเราจะพูดว่า "การควบคุมโปรแกรมจะเข้าสู่บริบทการดำเนินการของรหัสฟังก์ชัน"; เมื่อมีการดำเนินการโค้ดส่วนกลางส่วนหนึ่งการควบคุมโปรแกรมจะเข้าสู่บริบทการดำเนินการของรหัสทั่วโลก ฯลฯ
อย่างที่คุณเห็นบริบทการดำเนินการสามารถสร้างสแต็กอย่างมีเหตุผล ขั้นแรกอาจมีส่วนหนึ่งของรหัสทั่วโลกและบริบทการดำเนินการของตัวเองจากนั้นรหัสชิ้นนี้อาจเรียกฟังก์ชั่นที่มีบริบทการดำเนินการ (ฟังก์ชั่น) ฟังก์ชั่นชิ้นนี้สามารถเรียกฟังก์ชั่นอื่นได้ ฯลฯ แม้ว่าฟังก์ชั่นจะเรียกว่าซ้ำ แต่จะถูกป้อนลงในบริบทการดำเนินการใหม่ทุกครั้งที่เรียก
วัตถุที่ใช้งานอยู่ (วัตถุการเปิดใช้งาน) / วัตถุตัวแปร:
แต่ละบริบทการดำเนินการมีวัตถุที่เรียกว่าตัวแปรที่เกี่ยวข้อง คล้ายกับบริบทการดำเนินการวัตถุตัวแปรเป็นเอนทิตีนามธรรมซึ่งเป็นกลไกที่ใช้อธิบายอินสแตนซ์ตัวแปร ที่น่าสนใจคือตัวแปรและฟังก์ชั่นที่ประกาศในซอร์สโค้ดมักจะถูกเพิ่มเข้าไปในวัตถุตัวแปรนี้เป็นคุณสมบัติ
เมื่อการควบคุมโปรแกรมเข้าสู่บริบทการดำเนินการของรหัสทั่วโลกวัตถุส่วนกลางจะถูกใช้เป็นวัตถุตัวแปร นี่คือเหตุผลว่าทำไมตัวแปรฟังก์ชั่นที่ประกาศว่าทั่วโลกกลายเป็นคุณสมบัติวัตถุระดับโลก
การคัดลอกรหัสมีดังนี้:
/ * โปรดจำไว้ว่า `นี่จะหมายถึงวัตถุทั่วโลกเมื่ออยู่ในขอบเขตทั่วโลก *//
var global_object = this;
var foo = 1;
global_object.foo; // 1
foo === global_object.foo; // จริง
ฟังก์ชั่นแถบ () {}
typeof global_object.bar; // "การทำงาน"
global_object.bar === bar; // จริง
ตกลงดังนั้นตัวแปรทั่วโลกจะกลายเป็นคุณสมบัติของวัตถุทั่วโลก แต่จะเกิดอะไรขึ้นกับตัวแปรท้องถิ่น (ที่กำหนดไว้ในรหัสฟังก์ชั่น) ในความเป็นจริงพวกเขามีพฤติกรรมคล้ายกันมาก: พวกเขาจะกลายเป็นคุณสมบัติของวัตถุตัวแปร (วัตถุตัวแปร) ความแตกต่างเพียงอย่างเดียวคือเมื่ออยู่ในรหัสฟังก์ชันวัตถุตัวแปรไม่ใช่วัตถุส่วนกลาง แต่เป็นวัตถุการเปิดใช้งานที่เรียกว่า วัตถุที่ใช้งานถูกสร้างขึ้นทุกครั้งที่เข้าสู่บริบทการดำเนินการของรหัสฟังก์ชัน
ไม่เพียง แต่ตัวแปรและฟังก์ชั่นที่ประกาศในรหัสฟังก์ชันจะกลายเป็นคุณสมบัติของวัตถุที่ใช้งานอยู่ สิ่งนี้จะเกิดขึ้นในแต่ละพารามิเตอร์ฟังก์ชัน (ชื่อที่สอดคล้องกับพารามิเตอร์ที่เป็นทางการที่สอดคล้องกัน) และวัตถุอาร์กิวเมนต์พิเศษ (ชื่อของอาร์กิวเมนต์) โปรดทราบว่าวัตถุที่ใช้งานอยู่เป็นกลไกคำอธิบายภายในและไม่สามารถเข้าถึงได้ในรหัสโปรแกรม
การคัดลอกรหัสมีดังนี้:
(ฟังก์ชั่น (foo) {
var bar = 2;
function baz () {}
-
ในแง่นามธรรม
วัตถุพิเศษ `อาร์กิวเมนต์ 'กลายเป็นคุณสมบัติของวัตถุการเปิดใช้งานของฟังก์ชั่น:
activation_object.arguments; // อาร์กิวเมนต์วัตถุ
... เช่นเดียวกับการโต้แย้ง `foo`:
activation_object.foo; // 1
... เช่นเดียวกับตัวแปร `bar`:
activation_object.bar; // 2
... เช่นเดียวกับฟังก์ชั่นที่ประกาศในพื้นที่:
typeof activation_object.baz; // "การทำงาน"
-
}) (1);
ในที่สุดตัวแปรที่ประกาศในรหัสประเมินกลายเป็นคุณสมบัติของวัตถุตัวแปรในบริบทผู้โทร รหัสประเมินเพียงแค่ใช้วัตถุตัวแปรในบริบทการดำเนินการของรหัสที่เรียกมัน
การคัดลอกรหัสมีดังนี้:
var global_object = this;
/* `foo` ถูกสร้างขึ้นเป็นคุณสมบัติของวัตถุตัวแปรบริบทการเรียก
ซึ่งในกรณีนี้เป็นวัตถุระดับโลก */
eval ('var foo = 1;');
global_object.foo; // 1
(การทำงาน(){
/* `bar` ถูกสร้างขึ้นเป็นคุณสมบัติของวัตถุตัวแปรบริบทการโทร
ซึ่งในกรณีนี้เป็นวัตถุการเปิดใช้งานที่มีฟังก์ชั่น */
eval ('var bar = 1;');
-
ในแง่นามธรรม
activation_object.bar; // 1
-
-
คุณสมบัติคุณสมบัติ
เราเกือบจะมาที่นี่ ตอนนี้เราตระหนักดีถึงสิ่งที่เกิดขึ้นกับตัวแปร (พวกเขากลายเป็นคุณสมบัติ) แนวคิดเดียวที่เหลืออยู่ที่จะเข้าใจคือคุณสมบัติทรัพย์สิน แต่ละแอตทริบิวต์สามารถมีคุณสมบัติ 0 ขึ้นไปซึ่งถูกเลือกจากชุดต่อไปนี้: อ่านอย่างเดียว, Dontenum, DontDelete และภายใน คุณสามารถคิดว่าพวกเขาเป็นธง - คุณลักษณะที่สามารถมีอยู่หรือไม่มีอยู่ในคุณสมบัติ สำหรับการสนทนาของเราในวันนี้เราสนใจ DontDelete เท่านั้น
เมื่อตัวแปรและฟังก์ชั่นที่ประกาศกลายเป็นคุณลักษณะของวัตถุตัวแปร (หรือวัตถุที่ใช้งานของรหัสฟังก์ชันหรือวัตถุทั่วโลกของรหัสทั่วโลก) แอตทริบิวต์เหล่านี้จะถูกสร้างขึ้นด้วยแอตทริบิวต์ DontDelete อย่างไรก็ตามแอตทริบิวต์ที่ชัดเจน (หรือโดยนัย) จะไม่รวมอยู่ในแอตทริบิวต์ DontDelete นี่คือเหตุผลที่เราสามารถลบคุณลักษณะบางอย่าง แต่ไม่สามารถลบผู้อื่นได้
การคัดลอกรหัสมีดังนี้:
var global_object = this;
/* `foo` เป็นทรัพย์สินของวัตถุระดับโลก
มันถูกสร้างขึ้นผ่านการประกาศตัวแปรและมีแอตทริบิวต์ DontDelete
นี่คือเหตุผลที่ไม่สามารถลบได้ -
var foo = 1;
ลบ foo; // เท็จ
typeof foo; // "ตัวเลข"
/* `bar` เป็นทรัพย์สินของวัตถุระดับโลก
มันถูกสร้างขึ้นผ่านการประกาศฟังก์ชั่นและมีแอตทริบิวต์ DontDelete
นี่คือเหตุผลที่ไม่สามารถลบได้เช่นกัน -
ฟังก์ชั่นแถบ () {}
ลบบาร์; // เท็จ
typeof bar; // "การทำงาน"
/* `baz` ยังเป็นทรัพย์สินของวัตถุระดับโลก
อย่างไรก็ตามมันถูกสร้างขึ้นผ่านการกำหนดคุณสมบัติและไม่มีแอตทริบิวต์ DontDelete
นี่คือเหตุผลที่สามารถลบได้ -
global_object.baz = 'blah';
ลบ global_object.baz; // จริง
typeof global_object.baz; // "ไม่ได้กำหนด"
วัตถุในตัวและ dontdelete
ดังนั้นนี่คือทั้งหมดที่เกี่ยวกับมัน (DontDelete): คุณสมบัติพิเศษของคุณสมบัติที่ควบคุมว่าคุณสมบัตินี้สามารถลบได้หรือไม่ โปรดทราบว่าวัตถุในตัวบางตัวถูกระบุให้มี DontDelete ดังนั้นจึงไม่สามารถลบได้ ตัวอย่างเช่นตัวแปรอาร์กิวเมนต์พิเศษ (หรืออย่างที่เราทราบในขณะนี้คุณสมบัติของวัตถุที่ใช้งานอยู่) มี DontDelete คุณสมบัติความยาวของอินสแตนซ์ฟังก์ชั่นยังมีคุณสมบัติ DontDelete
การคัดลอกรหัสมีดังนี้:
(การทำงาน(){
/ * ไม่สามารถลบ `อาร์กิวเมนต์ 'ได้เนื่องจากมี dontdelete */
ลบอาร์กิวเมนต์; // เท็จ
อาร์กิวเมนต์ typeof; // "วัตถุ"
/* ไม่สามารถลบความยาวของฟังก์ชั่นได้; นอกจากนี้ยังมี dontdelete */
ฟังก์ชั่น f () {}
ลบ f.length; // เท็จ
typeof f.length; // "ตัวเลข"
-
แอตทริบิวต์ที่สอดคล้องกับพารามิเตอร์ฟังก์ชั่นยังมีคุณสมบัติ DontDelete ตั้งแต่การจัดตั้งดังนั้นเราจึงไม่สามารถลบได้
การคัดลอกรหัสมีดังนี้:
(ฟังก์ชั่น (foo, bar) {
ลบ foo; // เท็จ
ฟู; // 1
ลบบาร์; // เท็จ
บาร์; // 'blah'
}) (1, 'blah');
การมอบหมายที่ไม่ได้ประกาศ:
คุณอาจจำได้ว่าการมอบหมายที่ไม่ได้ประกาศสร้างทรัพย์สินบนวัตถุทั่วโลกเว้นแต่จะพบทรัพย์สินที่อื่นในห่วงโซ่ขอบเขตนี้ก่อนวัตถุทั่วโลก และตอนนี้เรารู้ถึงความแตกต่างระหว่างการกำหนดทรัพย์สินและการประกาศตัวแปร - หลังตั้งค่าคุณสมบัติ DontDelete แต่อดีตไม่ได้ เราต้องชัดเจนว่าทำไมการมอบหมายที่ไม่ได้ประกาศสร้างคุณสมบัติที่ลบได้
การคัดลอกรหัสมีดังนี้:
var global_object = this;
/* สร้างคุณสมบัติทั่วโลกผ่านการประกาศตัวแปร; คุณสมบัติมี dontdelete */
var foo = 1;
/* สร้างคุณสมบัติทั่วโลกผ่านการมอบหมายที่ไม่ได้ประกาศ; คุณสมบัติไม่มี dontdelete */
บาร์ = 2;
ลบ foo; // เท็จ
typeof foo; // "ตัวเลข"
ลบบาร์; // จริง
typeof bar; // "ไม่ได้กำหนด"
โปรดทราบ: คุณสมบัติจะถูกกำหนดเมื่อสร้างแอตทริบิวต์และการมอบหมายที่ตามมาจะไม่แก้ไขคุณสมบัติของแอตทริบิวต์ที่มีอยู่ มันสำคัญมากที่จะเข้าใจความแตกต่างนี้
การคัดลอกรหัสมีดังนี้:
/ * `foo` ถูกสร้างขึ้นเป็นคุณสมบัติที่มี DontDelete *//
function foo () {}
/* การกำหนดในภายหลังไม่ได้แก้ไขแอตทริบิวต์ Dontdelete ยังอยู่ที่นั่น! -
foo = 1;
ลบ foo; // เท็จ
typeof foo; // "ตัวเลข"
/* แต่การกำหนดคุณสมบัติที่ไม่มีอยู่
สร้างคุณสมบัตินั้นด้วยแอตทริบิวต์ที่ว่างเปล่า (และโดยไม่มี dontdelete) */
this.bar = 1;
ลบบาร์; // จริง
typeof bar; // "ไม่ได้กำหนด"
Firebug confusion:
จะเกิดอะไรขึ้นใน Firebug? ทำไมตัวแปรที่ประกาศในคอนโซลจึงสามารถลบได้? นี่ไม่ใช่สิ่งที่ตรงกันข้ามกับสิ่งที่เราได้เรียนรู้มาก่อนหรือไม่? อย่างที่ฉันได้กล่าวไว้ก่อนหน้านี้รหัสประเมินมีประสิทธิภาพพิเศษเมื่อเผชิญกับการประกาศตัวแปร ตัวแปรที่ประกาศใน Eval นั้นถูกสร้างขึ้นเป็นแอตทริบิวต์ที่ไม่มีแอตทริบิวต์ DontDelete
การคัดลอกรหัสมีดังนี้:
eval ('var foo = 1;');
ฟู; // 1
ลบ foo; // จริง
typeof foo; // "ไม่ได้กำหนด"
ในทำนองเดียวกันเมื่อเรียกในรหัสฟังก์ชัน:
การคัดลอกรหัสมีดังนี้:
(การทำงาน(){
eval ('var foo = 1;');
ฟู; // 1
ลบ foo; // จริง
typeof foo; // "ไม่ได้กำหนด"
-
นี่คือพื้นฐานสำหรับพฤติกรรมที่ผิดปกติของ Firebug ข้อความทั้งหมดในคอนโซลจะถูกแยกวิเคราะห์และดำเนินการเป็นรหัสประเมินมากกว่ารหัสทั่วโลกหรือรหัสฟังก์ชัน เห็นได้ชัดว่าตัวแปรทั้งหมดที่ประกาศในที่สุดจะกลายเป็นคุณสมบัติโดยไม่ต้องใช้แอตทริบิวต์ DontDelete ดังนั้นพวกเขาจึงสามารถลบได้อย่างง่ายดาย เราจำเป็นต้องเข้าใจความแตกต่างระหว่างรหัสทั่วโลกและคอนโซล Firebug
ลบตัวแปรผ่าน eval:
พฤติกรรมการประเมินที่น่าสนใจนี้ประกอบกับ ECMASCRIPT อีกด้านหนึ่งสามารถช่วยให้เราสามารถลบคุณสมบัติของ "ไม่สามารถลบได้" สิ่งหนึ่งที่เกี่ยวกับการประกาศฟังก์ชั่นคือพวกเขาสามารถแทนที่ตัวแปรที่มีชื่อเดียวกันในบริบทการดำเนินการเดียวกัน
การคัดลอกรหัสมีดังนี้:
ฟังก์ชัน x () {}
var x;
typeof x; // "การทำงาน"
หมายเหตุว่าการประกาศฟังก์ชั่นได้รับความสำคัญและตัวแปรเขียนทับด้วยชื่อเดียวกัน (หรือกล่าวอีกนัยหนึ่งคุณสมบัติเดียวกันในวัตถุตัวแปร) นี่เป็นเพราะการประกาศของฟังก์ชั่นมีการสร้างอินสแตนซ์หลังจากการประกาศตัวแปรและได้รับอนุญาตให้เขียนทับพวกเขา (การประกาศตัวแปร) การประกาศฟังก์ชั่นไม่เพียง แต่แทนที่ค่าของคุณสมบัติ แต่ยังแทนที่คุณสมบัติของคุณสมบัตินั้น หากเราประกาศฟังก์ชั่นผ่านการประเมินฟังก์ชันนั้นควรแทนที่คุณสมบัติของคุณสมบัติดั้งเดิม (แทนที่) ด้วยคุณสมบัติของตัวเอง และเนื่องจากตัวแปรที่ประกาศผ่านการประเมินคุณสมบัติสร้างคุณสมบัติโดยไม่มีแอตทริบิวต์ DontDelete การสร้างอินสแตนซ์ฟังก์ชั่นใหม่นี้จะลบแอตทริบิวต์ DONTDELETE ที่มีอยู่ออกจากคุณสมบัติเพื่อให้สามารถลบคุณสมบัติ (และเห็นได้ชัดว่าคะแนนของมันเป็นฟังก์ชันที่สร้างขึ้นใหม่)
การคัดลอกรหัสมีดังนี้:
var x = 1;
/ * ไม่สามารถลบได้ `x` มี dontdelete */
ลบ x; // เท็จ
typeof x; // "ตัวเลข"
evals ('function x () {}');
/ * `x` คุณสมบัติการอ้างอิงฟังก์ชั่นและไม่ควรมี dontdelete */
typeof x; // "การทำงาน"
ลบ x; // ควรเป็น `จริง '
typeof x; // ควรเป็น "ไม่ได้กำหนด"
น่าเสียดายที่ "การหลอกลวง" นี้ไม่ได้ผลในการดำเนินการใด ๆ ในปัจจุบัน บางทีฉันอาจจะขาดอะไรบางอย่างที่นี่หรือบางทีพฤติกรรมอาจจะคลุมเครือเกินไปที่ผู้ดำเนินการไม่สังเกตเห็น
ความเข้ากันได้ของเบราว์เซอร์:
มันมีประโยชน์ในทางทฤษฎีที่จะเข้าใจว่าสิ่งต่าง ๆ ทำงานอย่างไร แต่การฝึกฝนเป็นสิ่งที่สำคัญที่สุด เบราว์เซอร์ปฏิบัติตามมาตรฐานเมื่อพูดถึงการสร้าง/ลบตัวแปร/คุณสมบัติหรือไม่? คำตอบคือ: ในกรณีส่วนใหญ่ใช่
ฉันเขียนชุดทดสอบอย่างง่ายเพื่อทดสอบความเข้ากันได้ของเบราว์เซอร์กับตัวดำเนินการลบรวมถึงการทดสอบภายใต้รหัสทั่วโลกรหัสฟังก์ชั่นและรหัสประเมิน ชุดทดสอบตรวจสอบว่าค่าส่งคืนและค่าแอตทริบิวต์ของตัวดำเนินการลบ (ตามที่ควรทำ) จะถูกลบจริงหรือไม่ ค่าการส่งคืนของการลบนั้นไม่สำคัญเท่ากับผลลัพธ์ที่แท้จริง หากการลบส่งคืนจริงแทนที่จะเป็นเท็จสิ่งนี้ไม่สำคัญและสิ่งที่สำคัญคือคุณลักษณะที่มีแอตทริบิวต์ DontDelete จะไม่ถูกลบและในทางกลับกัน
เบราว์เซอร์ที่ทันสมัยโดยทั่วไปจะเข้ากันได้ค่อนข้างมาก นอกเหนือจากคุณสมบัติการประเมินที่ฉันกล่าวถึงก่อนหน้านี้เบราว์เซอร์ต่อไปนี้ได้ผ่านชุดทดสอบทั้งหมด: Opera 7.54+, Firefox 1.0+, Safari 3.1.2+, Chrome 4+
Safari 2.x และ 3.0.4 มีปัญหาเมื่อลบพารามิเตอร์ฟังก์ชัน คุณสมบัติเหล่านี้ดูเหมือนจะถูกสร้างขึ้นโดยไม่มีการลบออกดังนั้นจึงสามารถลบได้ Safari 2.x มีปัญหามากขึ้น - การลบตัวแปรที่ไม่ได้อ้างอิง (เช่น DELETE 1) จะทำให้เกิดข้อยกเว้น การประกาศฟังก์ชั่นสร้างคุณสมบัติที่ลบได้ (แต่แปลกประหลาดการประกาศตัวแปรไม่ได้); การประกาศตัวแปรใน Eval จะกลายเป็นแบบไม่ลบล้าง (แต่การประกาศฟังก์ชั่นจะลบได้)
คล้ายกับ Safari, Konqueror (3.5, ไม่ใช่ 4.3) โยนข้อยกเว้นเมื่อลบประเภทที่ไม่ได้อ้างอิง (เช่น: ลบ 1) และทำให้ตัวแปรฟังก์ชั่นลบไม่ถูกต้อง
หมายเหตุของนักแปล:
ฉันทดสอบเวอร์ชันล่าสุดของ Chrome, Firefox และ IE และโดยทั่วไปยังคงรักษาสถานการณ์ที่ผ่านอื่น ๆ ทั้งหมดยกเว้น 23 และ 24 จะล้มเหลว ในเวลาเดียวกันฉันทดสอบ UC และเบราว์เซอร์มือถือ ยกเว้นเบราว์เซอร์ในตัวของ Nokia E72 ซึ่งยังล้มเหลว 15 และ 16 เบราว์เซอร์ในตัวอื่น ๆ ส่วนใหญ่จะเหมือนกับเบราว์เซอร์เดสก์ท็อป แต่มันก็คุ้มค่าที่จะกล่าวถึงว่าเบราว์เซอร์ในตัวของ Blackberry Curve 8310/8900 สามารถผ่าน 23 ซึ่งทำให้ฉันประหลาดใจ
Gecko Dontdelete Bug:
Gecko 1.8.x เบราว์เซอร์ - Firefox 2.x, Camino 1.x, Seamonkey 1.x, ฯลฯ - แสดงข้อผิดพลาดที่น่าสนใจมากการกำหนดคุณสมบัติที่ชัดเจนจะลบคุณสมบัติ DontDelete แม้ว่าคุณสมบัตินี้จะถูกสร้างขึ้นผ่านการประกาศตัวแปรหรือการประกาศฟังก์ชั่น
การคัดลอกรหัสมีดังนี้:
function foo () {}
ลบ foo; // false (ตามที่คาดไว้)
typeof foo; // "ฟังก์ชั่น" (ตามที่คาดไว้)
/ * ตอนนี้กำหนดให้กับคุณสมบัติอย่างชัดเจน */
this.foo = 1; // การล้างแอตทริบิวต์ DontDelete อย่างผิดพลาด
ลบ foo; // จริง
typeof foo; // "ไม่ได้กำหนด"
/ * โปรดทราบว่าสิ่งนี้จะไม่เกิดขึ้นเมื่อกำหนดคุณสมบัติโดยปริยาย */
ฟังก์ชั่นแถบ () {}
บาร์ = 1;
ลบบาร์; // เท็จ
typeof bar; // "number" (แม้ว่าจะมีการแทนที่คุณสมบัติ)
น่าแปลกที่ Internet Explorer 5.5 - 8 ผ่านชุดทดสอบเต็มรูปแบบยกเว้นว่าการลบประเภทที่ไม่ได้อ้างอิง (เช่นลบ 1) จะโยนข้อยกเว้น (เช่นเดียวกับ Safari เก่า) อย่างไรก็ตามมีข้อบกพร่องที่ร้ายแรงกว่าภายใต้ IE ซึ่งไม่ชัดเจน ข้อบกพร่องเหล่านี้เกี่ยวข้องกับวัตถุทั่วโลก
คือข้อบกพร่อง:
บททั้งหมดนี้พูดถึงข้อบกพร่องของ Internet Explorer? ว้าว! มันวิเศษมาก!
ใน IE (อย่างน้อยก็คือ 6-8) นิพจน์ต่อไปนี้จะมีข้อยกเว้น (เมื่อดำเนินการในรหัสทั่วโลก):
this.x = 1;
ลบ x; // typeError: วัตถุไม่สนับสนุนการกระทำนี้
อันนี้จะทำเช่นกัน แต่จะโยนข้อยกเว้นที่แตกต่างกันซึ่งทำให้สิ่งต่าง ๆ น่าสนใจยิ่งขึ้น:
var x = 1;
ลบ this.x; // typeError: ไม่สามารถลบ 'this.x'
ดูเหมือนว่าใน IE การประกาศตัวแปรในรหัสส่วนกลางไม่ได้สร้างแอตทริบิวต์บนวัตถุทั่วโลก การสร้างแอตทริบิวต์โดยการกำหนด (this.x = 1) จากนั้นลบออกโดยลบ x หลังจากนั้นจะเกิดข้อผิดพลาด การสร้างแอตทริบิวต์โดยการประกาศ (var x = 1) และการลบหลังจากนั้นจะเกิดข้อผิดพลาดอีกครั้ง
แต่นั่นไม่ใช่ทั้งหมด การสร้างคุณสมบัติโดยการมอบหมายที่ชัดเจนจะทำให้เกิดข้อยกเว้นเมื่อถูกลบ ไม่เพียง แต่มีข้อผิดพลาดที่นี่ แต่คุณสมบัติที่สร้างขึ้นดูเหมือนจะมีแอตทริบิวต์ DontDelete ซึ่งแน่นอนว่าไม่ควรเป็น
this.x = 1;
ลบ this.x; // typeError: วัตถุไม่สนับสนุนการกระทำนี้
typeof x; // "หมายเลข" (ยังคงมีอยู่ไม่ได้ถูกลบตามที่ควรจะเป็น!)
ลบ x; // typeError: วัตถุไม่สนับสนุนการกระทำนี้
typeof x; // "หมายเลข" (ไม่ถูกลบอีกครั้ง)
ตอนนี้เราจะคิดว่าภายใต้ IE การมอบหมายที่ไม่ได้ประกาศ (ควรสร้างคุณสมบัติบนวัตถุทั่วโลก) สร้างคุณสมบัติที่ลบได้
x = 1;
ลบ x; // จริง
typeof x; // "ไม่ได้กำหนด"
อย่างไรก็ตามหากคุณลบคุณสมบัตินี้ผ่านการอ้างอิงนี้ในรหัสทั่วโลก (ลบ this.x) ข้อผิดพลาดที่คล้ายกันจะปรากฏขึ้น
x = 1;
ลบ this.x; // typeError: ไม่สามารถลบ 'this.x'
หากเราต้องการสรุปพฤติกรรมนี้ดูเหมือนว่าการใช้ DELETE This.x เพื่อลบตัวแปรจากรหัสทั่วโลกจะไม่ประสบความสำเร็จ เมื่อคุณสมบัติในคำถามถูกสร้างขึ้นโดยการมอบหมายที่ชัดเจน (this.x = 1) ให้ลบเกิดข้อผิดพลาด; เมื่อคุณสมบัติถูกสร้างขึ้นโดยการมอบหมายที่ไม่ได้ประกาศ (x = 1) หรือโดยการประกาศ (var x = 1) ลบจะโยนข้อผิดพลาดอื่น
ในทางกลับกันข้อผิดพลาดควรถูกโยนข้อผิดพลาดเฉพาะเมื่อคุณสมบัติถูกสร้างขึ้นโดยการกำหนดที่ชัดเจน - this.x = 1. หากคุณสมบัติถูกสร้างขึ้นโดยการประกาศ (var x = 1) การดำเนินการลบจะไม่เกิดขึ้นและการดำเนินการลบจะส่งกลับเท็จอย่างถูกต้อง หากคุณสมบัติถูกสร้างขึ้นโดยการมอบหมายที่ไม่ได้ประกาศ (x = 1) การดำเนินการลบจะทำงานตามที่คาดไว้
ฉันคิดถึงปัญหานี้อีกครั้งในเดือนกันยายน การ์เร็ตต์สมิ ธ แนะนำว่าภายใต้ IE
"วัตถุตัวแปรส่วนกลางถูกนำไปใช้เป็นวัตถุ JScript และวัตถุส่วนกลางถูกนำไปใช้โดยโฮสต์"
Garrett ใช้รายการบล็อกของ Eric Lippert เป็นข้อมูลอ้างอิง
เราสามารถยืนยันทฤษฎีนี้ได้มากหรือน้อยโดยใช้การทดสอบบางอย่าง โปรดทราบว่าสิ่งนี้และหน้าต่างดูเหมือนจะชี้ไปที่วัตถุเดียวกัน (ถ้าเราสามารถเชื่อถือตัวดำเนินการ === ตัวดำเนินการ) แต่วัตถุตัวแปร (วัตถุที่มีการประกาศฟังก์ชัน) แตกต่างจากสิ่งนี้
การคัดลอกรหัสมีดังนี้:
/ * ในรหัสทั่วโลก */
ฟังก์ชั่น getBase () {ส่งคืนสิ่งนี้; -
getBase () === this.getBase (); // เท็จ
this.getBase () === this.getBase (); // จริง
window.getBase () === this.getBase (); // จริง
window.getBase () === getBase (); // เท็จ
ความเข้าใจผิด:
ความงามของการทำความเข้าใจว่าทำไมสิ่งต่าง ๆ จึงไม่ได้รับการประเมินต่ำเกินไป ฉันได้เห็นความเข้าใจผิดเกี่ยวกับตัวดำเนินการลบบนอินเทอร์เน็ต ตัวอย่างเช่นคำตอบของ Stackoverflow (ด้วยคะแนนสูงอย่างน่าประหลาดใจ) อธิบายอย่างมั่นใจ
"เมื่อตัวถูกดำเนินการเป้าหมายไม่ใช่คุณสมบัติวัตถุการลบควรดำเนินการ"
ตอนนี้เราได้เข้าใจแกนกลางของพฤติกรรมการดำเนินการลบความผิดพลาดในคำตอบนี้จะชัดเจน การลบไม่ได้แยกความแตกต่างระหว่างตัวแปรและแอตทริบิวต์ (อันที่จริงสำหรับการลบพวกเขาเป็นทั้งประเภทอ้างอิง) และในความเป็นจริงมีเพียงการใส่ใจเกี่ยวกับแอตทริบิวต์ DontDelete (และคุณลักษณะนั้นมีอยู่จริงหรือไม่)
นอกจากนี้ยังเป็นเรื่องที่น่าสนใจมากที่จะเห็นความเข้าใจผิดต่าง ๆ ที่ปฏิเสธซึ่งกันและกัน ในหัวข้อเดียวกันคนหนึ่งแนะนำให้ลบตัวแปรเท่านั้น (ซึ่งจะไม่ทำงานเว้นแต่จะมีการประกาศใน eval) ในขณะที่บุคคลอื่นให้การแก้ไขข้อผิดพลาดในการลบวิธีการลบตัวแปรในรหัสทั่วโลก แต่ไม่ใช่ในรหัสฟังก์ชัน
ระมัดระวังเป็นพิเศษเกี่ยวกับคำอธิบายของ JavaScript บนอินเทอร์เน็ต วิธีการในอุดมคติคือการเข้าใจสาระสำคัญของปัญหาเสมอ -
ลบและโฮสต์วัตถุ (วัตถุโฮสต์):
อัลกอริทึมการลบเป็นแบบนี้:
ส่งคืนจริงถ้าตัวถูกดำเนินการไม่ใช่ประเภทอ้างอิง
หากวัตถุไม่มีแอตทริบิวต์โดยตรงของชื่อนี้ให้ส่งคืนจริง (ดังที่เราทราบวัตถุสามารถเป็นวัตถุที่ใช้งานอยู่หรือวัตถุส่วนกลาง)
หากคุณสมบัติมีอยู่ แต่มีแอตทริบิวต์ DontDelete ให้ส่งคืน FALSE
ในกรณีอื่น ๆ ให้ลบแอตทริบิวต์และส่งคืนจริง
อย่างไรก็ตามพฤติกรรมของตัวดำเนินการลบบนวัตถุโฮสต์นั้นไม่สามารถคาดเดาได้ และพฤติกรรมนี้ไม่ผิดจริง: (ตามมาตรฐาน) วัตถุโฮสต์ได้รับอนุญาตให้ใช้พฤติกรรมใด ๆ สำหรับผู้ประกอบการหลายรายเช่นอ่าน (ภายใน [[GET]]), เขียน (วิธีการภายใน [[ใส่]]) และลบ (ภายใน [[ลบ]]) ความสง่างามนี้สำหรับ [[ลบ]] พฤติกรรมเป็นสิ่งที่ทำให้วัตถุโฮสต์สับสน
เราได้เห็นบางอย่างที่เป็นนิสัยใจคอที่การลบวัตถุเฉพาะ (ซึ่งถูกนำไปใช้อย่างชัดเจนเป็นวัตถุโฮสต์) จะทำให้เกิดข้อผิดพลาด Firefox บางรุ่นจะโยนเมื่อลบ Window.Location เมื่อตัวถูกดำเนินการเป็นวัตถุโฮสต์คุณไม่สามารถเชื่อถือค่าคืนสินค้าของการลบได้ มาดูกันว่าเกิดอะไรขึ้นใน Firefox:
การคัดลอกรหัสมีดังนี้:
/ * "การแจ้งเตือน" เป็นทรัพย์สินโดยตรงของ `window '(ถ้าเราเชื่อว่า` hasownproperty`) *//
window.hasownproperty ('Alert'); // จริง
ลบ window.alert; // จริง
typeof window.alert; // "การทำงาน"
DELETE WINDOW.ALERT ส่งคืนจริงแม้ว่าจะไม่มีเหตุผลที่คุณสมบัตินี้จะทำให้เกิดผลลัพธ์ดังกล่าวเลย มันจะแก้ไขเป็นข้อมูลอ้างอิง (ดังนั้นจะไม่กลับมาจริงในขั้นตอนแรก) นี่เป็นคุณสมบัติโดยตรงของวัตถุหน้าต่าง (ดังนั้นจะไม่กลับมาจริงในขั้นตอนที่สอง) ดังนั้นกรณีเดียวที่การลบสามารถส่งคืนจริงคือไปถึงขั้นตอนที่สี่และลบคุณสมบัตินั้นจริง อย่างไรก็ตามคุณสมบัตินี้จะไม่ถูกลบ
คุณธรรมของเรื่องนี้คือ: อย่าไว้วางใจวัตถุโฮสต์
ES5 โหมดเข้มงวด:
ดังนั้น Ecmascript5 โหมดที่เข้มงวดจะนำมาให้เราอย่างไร มันแนะนำข้อ จำกัด เล็กน้อย เมื่อนิพจน์ของตัวดำเนินการลบเป็นการอ้างอิงโดยตรงไปยังตัวแปรพารามิเตอร์ฟังก์ชันหรือตัวระบุฟังก์ชั่นข้อผิดพลาดทางไวยากรณ์จะถูกโยนลง นอกจากนี้หากคุณสมบัติมีคุณสมบัติภายใน [[กำหนดค่า]] == FALSE ข้อผิดพลาดประเภทจะถูกโยนลงไป
การคัดลอกรหัสมีดังนี้:
(ฟังก์ชั่น (foo) {
"ใช้เข้มงวด"; // เปิดใช้งานโหมดที่เข้มงวดภายในฟังก์ชั่นนี้
var bar;
function baz () {}
ลบ foo; // syntaxerror (เมื่อลบอาร์กิวเมนต์)
ลบบาร์; // syntaxerror (เมื่อลบตัวแปร)
ลบ baz; // syntaxError (เมื่อลบตัวแปรที่สร้างขึ้นด้วยการประกาศฟังก์ชั่น)
/ * `ความยาวของอินสแตนซ์ฟังก์ชันมี {[[กำหนดค่าได้]]: false} *//
ลบ (ฟังก์ชั่น () {}). ความยาว; // typeerror
-
นอกจากนี้การลบตัวแปรที่ไม่ได้ประกาศ (หรือการอ้างอิงที่ไม่ได้รับการแก้ไข) จะทำให้เกิดข้อผิดพลาดทางไวยากรณ์:
"ใช้เข้มงวด";
ลบ I_DONT_EXIST; // syntaxerror
การมอบหมายที่ไม่ได้ประกาศนั้นมีพฤติกรรมคล้ายกับตัวแปรที่ไม่ได้ประกาศในโหมดที่เข้มงวด (ยกเว้นเวลานี้มันทำให้เกิดข้อผิดพลาดในการเสนอราคาแทนข้อผิดพลาดทางไวยากรณ์):
"ใช้เข้มงวด";
I_DONT_EXIST = 1; // ReferenceRorror
ตามที่คุณเข้าใจตอนนี้ข้อ จำกัด ทั้งหมดทำให้รู้สึกมากขึ้นหรือน้อยลงเนื่องจากการลบตัวแปรการประกาศฟังก์ชั่นและพารามิเตอร์อาจทำให้เกิดความสับสนมาก แทนที่จะเพิกเฉยต่อการดำเนินการลบอย่างเงียบ ๆ รูปแบบที่เข้มงวดใช้การวัดที่รุนแรงและอธิบายได้มากขึ้น
สรุป:
โพสต์บล็อกนี้จบลงด้วยความยาวดังนั้นฉันจะไม่พูดถึงบางสิ่งเช่นการใช้ลบเพื่อลบวัตถุอาร์เรย์หรือความหมาย คุณสามารถอ้างถึงคำอธิบายพิเศษของบทความ MDC (หรืออ่านมาตรฐานและทำการทดลองของคุณเอง)
นี่คือบทสรุปโดยย่อว่าการดำเนินการลบทำงานใน JavaScript อย่างไร:
ตัวแปรและการประกาศฟังก์ชั่นเป็นคุณสมบัติของวัตถุที่ใช้งานอยู่หรือวัตถุทั่วโลก
แอตทริบิวต์มีคุณสมบัติบางอย่างและ DontDelete เป็นแอตทริบิวต์ที่กำหนดว่าสามารถลบแอตทริบิวต์นี้ได้หรือไม่
ตัวแปรและการประกาศฟังก์ชั่นใน Global หรือ Code Function Code มักจะสร้างแอตทริบิวต์ด้วยแอตทริบิวต์ DontDelete
พารามิเตอร์ฟังก์ชั่นเป็นแอตทริบิวต์ของวัตถุที่ใช้งานอยู่เสมอและมาพร้อมกับ DontDelete
ตัวแปรและฟังก์ชั่นที่ประกาศในรหัส Eval มักจะสร้างคุณสมบัติโดยไม่ต้อง DontDelete
คุณสมบัติใหม่ไม่มีคุณลักษณะเมื่อสร้างขึ้น (แน่นอนว่าไม่มี Dontdelete เช่นกัน)
วัตถุโฮสต์ได้รับอนุญาตให้ตัดสินใจเกี่ยวกับวิธีการตอบสนองต่อการดำเนินการลบ
หากคุณต้องการคุ้นเคยกับสิ่งที่อธิบายไว้ที่นี่มากขึ้นให้ดูข้อมูลจำเพาะ ECMA-262 ฉบับที่ 3
ฉันหวังว่าคุณจะได้เพลิดเพลินกับบทความนี้และเรียนรู้สิ่งใหม่ ๆ ยินดีต้อนรับคำถามคำแนะนำหรือการแก้ไขใด ๆ