ขอบเขต
ขอบเขตคือขอบเขตของฟังก์ชันของตัวแปรและฟังก์ชั่น ตัวแปรทั้งหมดที่ประกาศในฟังก์ชั่นในจาวาสคริปต์สามารถมองเห็นได้เสมอในร่างกายฟังก์ชั่น มีขอบเขตทั่วโลกและขอบเขตท้องถิ่นในจาวาสคริปต์ แต่ไม่มีขอบเขตระดับบล็อก ลำดับความสำคัญของตัวแปรท้องถิ่นสูงกว่าตัวแปรทั่วโลก จากตัวอย่างหลายตัวอย่างเราสามารถเข้าใจ "กฎที่ไม่ได้พูด" ของขอบเขตใน JavaScript (เหล่านี้เป็นคำถามที่มักถูกถามในการสัมภาษณ์ส่วนหน้า)
1. การประกาศตัวแปรล่วงหน้า
ตัวอย่างที่ 1:
var scope = "global"; ฟังก์ชั่น scopetest () {console.log (ขอบเขต); var scope = "local"} scopetest (); // ไม่ได้กำหนดเอาต์พุตที่นี่ไม่ได้กำหนดและไม่มีข้อผิดพลาด นี่เป็นเพราะการประกาศในฟังก์ชั่นที่เรากล่าวถึงข้างต้นสามารถมองเห็นได้เสมอในร่างกายฟังก์ชั่น ฟังก์ชั่นข้างต้นเทียบเท่ากับ:
var scope = "global"; ฟังก์ชั่น scopetest () {ขอบเขต var; console.log (ขอบเขต); scope = "local"} scopetest (); //ท้องถิ่นโปรดทราบว่าหากลืม VAR ตัวแปรจะถูกประกาศว่าเป็นตัวแปรส่วนกลาง
2. ไม่มีขอบเขตระดับบล็อก
ต่างจากภาษาอื่น ๆ ที่เราใช้โดยทั่วไปไม่มีขอบเขตระดับบล็อกใน JavaScript:
ฟังก์ชั่น scopetest () {var scope = {}; if (scope instanceof object) {var j = 1; สำหรับ (var i = 0; i <10; i ++) {//console.log(i); } console.log (i); // output 10} console.log (j); // output 1}ใน JavaScript ขอบเขตของฟังก์ชั่นของตัวแปรคือระดับฟังก์ชั่นนั่นคือตัวแปรทั้งหมดในฟังก์ชั่นถูกกำหนดไว้ในฟังก์ชันทั้งหมดซึ่งนำ "กฎที่ไม่ได้พูด" บางอย่างที่เราจะพบหากเราไม่ระวัง:
var scope = "hello"; function scopetest () {console.log (ขอบเขต); // ① var scope = "no"; console.log (ขอบเขต); // ②}เอาต์พุตค่าที่①นั้นไม่ได้กำหนดจริง ๆ ซึ่งบ้าไปแล้ว เราได้กำหนดค่าของตัวแปรส่วนกลาง สถานที่แห่งนี้ไม่ควรสวัสดี? ในความเป็นจริงรหัสข้างต้นเทียบเท่ากับ:
var scope = "hello"; ฟังก์ชั่น scopetest () {ขอบเขต var; console.log (ขอบเขต); // ①ขอบเขต = "ไม่"; console.log (ขอบเขต); // ②}ประกาศตัวแปรก่อนและทั่วโลกมีลำดับความสำคัญต่ำกว่าตัวแปรท้องถิ่น ตามกฎทั้งสองนี้มันไม่ยากที่จะเข้าใจว่าทำไมเอาต์พุตถึงไม่ได้กำหนด
ห่วงโซ่ขอบเขต
ใน JavaScript แต่ละฟังก์ชั่นมีบริบทการดำเนินการของตัวเอง เมื่อรหัสถูกดำเนินการในสภาพแวดล้อมนี้ห่วงโซ่ขอบเขตของวัตถุตัวแปรจะถูกสร้างขึ้น ห่วงโซ่ขอบเขตเป็นรายการวัตถุหรือห่วงโซ่วัตถุซึ่งทำให้มั่นใจได้ว่าการเข้าถึงวัตถุตัวแปรอย่างเป็นระเบียบ
ส่วนหน้าของห่วงโซ่ขอบเขตเป็นวัตถุตัวแปรของสภาพแวดล้อมการดำเนินการรหัสปัจจุบันซึ่งมักเรียกว่า "วัตถุที่ใช้งานอยู่" การค้นหาตัวแปรเริ่มต้นจากวัตถุของห่วงโซ่แรก หากวัตถุมีแอตทริบิวต์ตัวแปรการค้นหาจะหยุดลง ถ้าไม่การค้นหาจะยังคงค้นหาห่วงโซ่ขอบเขตที่เหนือกว่าจนกว่าจะพบวัตถุทั่วโลก:
การค้นหาขอบเขตโซ่ทีละขั้นตอนจะส่งผลกระทบต่อประสิทธิภาพของโปรแกรม ยิ่งโซ่ขอบเขตของตัวแปรนานขึ้นก็จะยิ่งส่งผลกระทบต่อประสิทธิภาพมากขึ้นเท่านั้น นี่เป็นเหตุผลสำคัญที่ทำให้เราพยายามหลีกเลี่ยงการใช้ตัวแปรทั่วโลก
การปิด
แนวคิดพื้นฐาน
ขอบเขตเป็นสิ่งที่จำเป็นสำหรับการปิดการทำความเข้าใจ การปิดหมายถึงความสามารถในการเข้าถึงตัวแปรในขอบเขตภายนอกภายในขอบเขตปัจจุบัน
ฟังก์ชั่น createClosure () {var name = "jack"; return {setStr: function () {name = "Rose"; }, getStr: function () {return name + ": hello"; }}} var builder = ใหม่ createClosure (); builder.setstr (); console.log (builder.getStr ()); // Rose: สวัสดีตัวอย่างข้างต้นส่งคืนการปิดสองครั้งในฟังก์ชั่นซึ่งทั้งสองอย่างนี้ยังคงอ้างอิงถึงขอบเขตภายนอกดังนั้นตัวแปรในฟังก์ชั่นภายนอกสามารถเข้าถึงได้เสมอทุกที่ที่เรียก ฟังก์ชั่นที่กำหนดภายในฟังก์ชั่นจะเพิ่มวัตถุที่ใช้งานอยู่ของฟังก์ชั่นภายนอกลงในห่วงโซ่ขอบเขตของตัวเอง ดังนั้นในตัวอย่างข้างต้นฟังก์ชั่นภายในสามารถเข้าถึงคุณสมบัติของฟังก์ชั่นภายนอกผ่านฟังก์ชั่นภายใน นี่เป็นวิธีสำหรับ JavaScript ในการจำลองตัวแปรส่วนตัว
หมายเหตุ: เนื่องจากการปิดจะมีขอบเขตเพิ่มเติมของฟังก์ชั่น (ฟังก์ชั่นที่ไม่ระบุชื่อภายในมีขอบเขตของฟังก์ชั่นภายนอก) การปิดจะใช้พื้นที่หน่วยความจำมากกว่าฟังก์ชั่นอื่น ๆ และการใช้งานที่มากเกินไปอาจนำไปสู่การใช้หน่วยความจำที่เพิ่มขึ้น
ตัวแปรในการปิด
เมื่อใช้การปิดเนื่องจากอิทธิพลของกลไกห่วงโซ่ขอบเขตการปิดสามารถรับค่าสุดท้ายของฟังก์ชั่นภายในเท่านั้น ผลข้างเคียงหนึ่งของสิ่งนี้คือถ้าฟังก์ชั่นภายในอยู่ในลูปค่าของตัวแปรจะเป็นค่าสุดท้ายเสมอ
// อินสแตนซ์นี้ไม่สมเหตุสมผลและมีปัจจัยล่าช้าบางอย่าง นี่คือส่วนใหญ่เพื่อแสดงให้เห็นถึงปัญหาในฟังก์ชั่นการปิดลูป timeManage () {สำหรับ (var i = 0; i <5; i ++) {settimeout (ฟังก์ชั่น () {console.log (i);}, 1,000)}; -โปรแกรมข้างต้นไม่ได้ป้อนหมายเลข 1-5 ตามที่เราคาดไว้ แต่เอาต์พุต 5 ทั้งหมด 5 ครั้ง ลองมาดูตัวอย่างอื่น:
ฟังก์ชั่น createClosure () {var result = []; สำหรับ (var i = 0; i <5; i ++) {ผลลัพธ์ [i] = function () {return i; }} ส่งคืนผลลัพธ์;}การเรียก createclosure () [0] () ส่งคืน 5 และ createClosure () [4] () ค่าคืนค่ายังคงเป็น 5 จากสองตัวอย่างข้างต้นเราสามารถเห็นปัญหาที่ปิดอยู่เมื่อใช้ฟังก์ชั่นภายในที่มีลูป เมื่อฟังก์ชั่นภายนอกส่งคืนค่าของ I ในเวลานี้คือ 5 ดังนั้นค่าของฟังก์ชันภายในแต่ละรายการที่ฉันก็คือ 5
แล้วจะแก้ปัญหานี้ได้อย่างไร? เราสามารถบังคับให้กลับมาของผลลัพธ์ที่คาดหวังได้ผ่านการห่อหุ้มแบบไม่ระบุชื่อ
ฟังก์ชัน timeManage () {สำหรับ (var i = 0; i <5; i ++) {(ฟังก์ชั่น (num) {settimeout (ฟังก์ชัน () {console.log (num);}, 1000);}) (i); -หรือส่งคืนการกำหนดฟังก์ชั่นที่ไม่ระบุชื่อในฟังก์ชั่นที่ไม่ระบุชื่อปิด:
ฟังก์ชั่น timeManage () {สำหรับ (var i = 0; i <10; i ++) {settimeout ((ฟังก์ชั่น (e) {return function () {console.log (e);}}) (i), 1000)}} // timeManager (); เอาท์พุท 1,2,3,4,5function createClosure () {var result = []; สำหรับ (var i = 0; i <5; i ++) {result [i] = function (num) {return function () {console.log (num); } }(ฉัน); } ผลตอบแทนผลลัพธ์;} // createClosure () [1] () เอาต์พุต 1; createClosure () [2] () เอาต์พุต 2ไม่ว่าจะเป็น wrapper ที่ไม่ระบุชื่อหรือฟังก์ชั่นที่ไม่ระบุชื่อที่ไม่ระบุชื่อตามหลักการเนื่องจากฟังก์ชั่นถูกส่งผ่านตามค่าค่าของตัวแปรที่ฉันจะถูกคัดลอกไปยังพารามิเตอร์จริงและฟังก์ชั่นที่ไม่ระบุชื่อถูกสร้างขึ้นภายในฟังก์ชั่นที่ไม่ระบุชื่อเพื่อส่งคืน
สิ่งนี้ปิด
ให้ความสนใจเป็นพิเศษเมื่อใช้สิ่งนี้ในการปิดเนื่องจากความประมาทเล็กน้อยอาจทำให้เกิดปัญหา โดยปกติแล้วเราจะเข้าใจว่าวัตถุนี้ถูกผูกไว้ด้วยฟังก์ชั่นที่รันไทม์ ในฟังก์ชั่นส่วนกลางวัตถุนี้เป็นวัตถุหน้าต่าง เมื่อฟังก์ชั่นถูกเรียกว่าเป็นวิธีการในวัตถุสิ่งนี้จะเท่ากับวัตถุนี้ (TODO ทำกระบวนการเรียงลำดับเกี่ยวกับเรื่องนี้) เนื่องจากขอบเขตของฟังก์ชั่นที่ไม่ระบุชื่อเป็นระดับโลกการปิดนี้มักจะชี้ไปที่หน้าต่างวัตถุทั่วโลก:
var scope = "global"; var object = {scope: "local", getScope: function () {return function () {return this.scope; -การโทร Object.getScope () () ส่งคืนค่าทั่วโลกแทนที่จะเป็นท้องถิ่นที่เราคาดไว้ เราได้กล่าวก่อนหน้านี้ว่าฟังก์ชั่นที่ไม่ระบุชื่อภายในในการปิดจะมีขอบเขตของฟังก์ชั่นภายนอกดังนั้นทำไมไม่ได้รับฟังก์ชั่นภายนอก? เมื่อเรียกใช้แต่ละฟังก์ชันสิ่งนี้และอาร์กิวเมนต์จะถูกสร้างขึ้นโดยอัตโนมัติ เมื่อค้นหาฟังก์ชั่นที่ไม่ระบุชื่อภายในพวกเขาจะค้นหาตัวแปรที่เราต้องการในวัตถุที่ใช้งานอยู่ ดังนั้นหยุดค้นหาฟังก์ชั่นภายนอกและไม่สามารถเข้าถึงตัวแปรโดยตรงในฟังก์ชั่นภายนอกได้ ในระยะสั้นเมื่อฟังก์ชั่นถูกเรียกว่าเป็นวิธีการของวัตถุในการปิดมันเป็นสิ่งสำคัญที่จะต้องให้ความสนใจเป็นพิเศษว่าสิ่งนี้ในฟังก์ชันที่ไม่ระบุชื่อภายในวิธีการชี้ไปที่ตัวแปรทั่วโลก
โชคดีที่เราสามารถแก้ปัญหานี้ได้ง่ายๆเพียงแค่เก็บสิ่งนี้ไว้ในขอบเขตของฟังก์ชั่นภายนอกในตัวแปรที่สามารถเข้าถึงได้โดยการปิด:
var scope = "global"; var object = {scope: "local", getScope: function () {var that = this; return function () {return that.scope; }}} Object.getScope () () () ส่งคืนค่าโลคัลหน่วยความจำและประสิทธิภาพ
เนื่องจากการปิดมีการอ้างอิงโซ่ขอบเขตเดียวกันกับบริบทของฟังก์ชันรันไทม์มันจะมีผลกระทบเชิงลบบางอย่าง เมื่อวัตถุที่ใช้งานอยู่และบริบทรันไทม์ในฟังก์ชั่นถูกทำลายวัตถุที่ใช้งานไม่สามารถทำลายได้เนื่องจากยังคงมีการอ้างอิงถึงวัตถุที่ใช้งานอยู่ซึ่งหมายความว่าการปิดใช้พื้นที่หน่วยความจำมากกว่าฟังก์ชั่นทั่วไปและอาจทำให้หน่วยความจำรั่วไหลในเบราว์เซอร์ IE ดังต่อไปนี้:
ฟังก์ชั่น bindeVent () {var target = document.getElementById ("elem"); target.onclick = function () {console.log (target.name); -ในตัวอย่างข้างต้นฟังก์ชั่นที่ไม่ระบุชื่อจะสร้างการอ้างอิงไปยังเป้าหมายวัตถุภายนอก ตราบใดที่ฟังก์ชั่นที่ไม่ระบุชื่อมีอยู่การอ้างอิงจะไม่หายไปและวัตถุเป้าหมายของฟังก์ชั่นภายนอกจะไม่ถูกทำลายซึ่งสร้างการอ้างอิงแบบวงกลม วิธีแก้ปัญหาคือการลดการอ้างอิงแบบวงกลมไปยังตัวแปรภายนอกโดยการสร้างสำเนาของ target.name และรีเซ็ตวัตถุด้วยตนเอง:
ฟังก์ชั่น bindeVent () {var target = document.getElementById ("elem"); ชื่อ var = target.name; target.onclick = function () {console.log (ชื่อ); } target = null; -หากมีการเข้าถึงตัวแปรภายนอกในการปิดเส้นทางการค้นหาสำหรับตัวระบุจะถูกเพิ่มอย่างไม่ต้องสงสัยและในบางสถานการณ์สิ่งนี้จะทำให้เกิดการสูญเสียประสิทธิภาพ เราได้กล่าวถึงก่อนหน้านี้: ลองเก็บตัวแปรภายนอกไว้ในตัวแปรท้องถิ่นเพื่อลดความยาวการค้นหาของโซ่ขอบเขต
สรุป: การปิดไม่ซ้ำกับ JavaScript แต่พวกเขามีอาการที่ไม่ซ้ำกันใน JavaScript การใช้การปิดเราสามารถกำหนดตัวแปรส่วนตัวบางตัวในจาวาสคริปต์และแม้แต่เลียนแบบขอบเขตระดับบล็อก อย่างไรก็ตามในระหว่างการใช้งานปิดเราต้องเข้าใจปัญหาที่มีอยู่เพื่อหลีกเลี่ยงปัญหาที่ไม่จำเป็น