ขอบเขตคำศัพท์คืออะไร?


682

การแนะนำสั้น ๆ เกี่ยวกับการกำหนดขอบเขตคำศัพท์คืออะไร?


89
ในพอดคาสต์ 58 โจเอลกระตุ้นคำถามเช่นนี้เนื่องจากเขาต้องการให้ SO เป็นสถานที่สำหรับคำตอบแม้ว่าจะได้รับคำตอบจากที่อื่นก็ตาม นี่เป็นคำถามที่ถูกต้องแม้ว่าใคร ๆ ก็สามารถใส่ความสุภาพได้อีกเล็กน้อย
Ralph M. Rickenbach

5
@ ราอูลฉันเข้าใจว่ามันเป็นคำถามเก่า แต่ฉันแน่ใจว่าแม้กระทั่งในปี 2009 ดังนั้นผู้ถามจึงคาดหวังว่าจะมีความพยายามขั้นพื้นฐานในการแก้ไขปัญหาดังกล่าว มันไม่แสดงความพยายามใด ๆเลย อาจเป็นเพราะเหตุใดหลายคนจึงถูกลดระดับลง?
PP

13
เป็นไปได้ว่าผู้ถามไม่ได้พูดภาษาอังกฤษคล่องแคล่วในการเขียนคำถามนี้
Martin

27
คำถามคือสุภาพเขาแค่พูดในสิ่งที่เขาต้องการ คุณมีอิสระที่จะตอบ ไม่จำเป็นต้องมีพยานพระยะโฮวาที่นี่
Markus Siebeneicher

25
ฉันคิดว่าคำถามเหล่านี้ยอดเยี่ยมเพราะมันสร้างเนื้อหาสำหรับ SO IMO ใครจะสนใจถ้าคำถามไม่มีความพยายาม ... คำตอบจะมีเนื้อหาที่ยอดเยี่ยมและนั่นคือสิ่งที่สำคัญในกระดานข้อความนี้
Jwan622

คำตอบ:


686

ฉันเข้าใจพวกเขาผ่านตัวอย่าง :)

ครั้งแรกขอบเขตคำศัพท์ (หรือที่เรียกว่าขอบเขตคงที่ ) ในรูปแบบ C-like:

void fun()
{
    int x = 5;

    void fun2()
    {
        printf("%d", x);
    }
}

ทุกระดับภายในสามารถเข้าถึงระดับนอกของมัน

มีอีกวิธีหนึ่งที่เรียกว่าขอบเขตแบบไดนามิกที่ใช้โดยการใช้งานLispครั้งแรกอีกครั้งในรูปแบบ C-like:

void fun()
{
    printf("%d", x);
}

void dummy1()
{
    int x = 5;

    fun();
}

void dummy2()
{
    int x = 10;

    fun();
}

ที่นี่funสามารถเข้าถึงxในdummy1หรือdummy2หรือใด ๆxในการทำงานใด ๆ ที่โทรfunที่มีxการประกาศในนั้น

dummy1();

จะพิมพ์ 5

dummy2();

จะพิมพ์ 10

อันแรกเรียกว่าสแตติกเพราะมันสามารถอนุมานได้ในเวลารวบรวมและที่สองเรียกว่าแบบไดนามิกเพราะขอบเขตด้านนอกเป็นแบบไดนามิกและขึ้นอยู่กับการเรียกโซ่ของฟังก์ชั่น

ฉันพบว่าการกำหนดขอบเขตคงที่ง่ายขึ้นสำหรับดวงตา ภาษาส่วนใหญ่ใช้วิธีนี้ในที่สุดแม้แต่เสียงกระเพื่อม (สามารถทำทั้งสองอย่างได้ไหม) การกำหนดขอบเขตแบบไดนามิกเปรียบเสมือนการส่งการอ้างอิงของตัวแปรทั้งหมดไปยังฟังก์ชันที่เรียกว่า

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

if(/* some condition */)
    dummy1();
else
    dummy2();

ห่วงโซ่การโทรขึ้นอยู่กับเงื่อนไขเวลาทำงาน หากเป็นจริงแล้วเชนการโทรจะมีลักษณะดังนี้:

dummy1 --> fun()

หากเงื่อนไขเป็นเท็จ:

dummy2 --> fun()

ขอบเขตด้านนอกของfunทั้งสองกรณีคือผู้โทรบวกผู้โทรของผู้โทรและอื่น

เพียงพูดถึงว่าภาษา C ไม่อนุญาตให้ใช้ฟังก์ชันที่ซ้อนกันหรือการกำหนดขอบเขตแบบไดนามิก


19
ฉันอยากจะชี้ให้เห็นการสอนที่ง่ายมากที่ฉันเพิ่งพบ ตัวอย่างของ Arak นั้นดี แต่อาจสั้นเกินไปสำหรับคนที่ต้องการตัวอย่างเพิ่มเติม (จริง ๆ แล้วเปรียบเทียบกับภาษาอื่น .. ) ลองดูสิ. สิ่งสำคัญคือต้องเข้าใจสิ่งนี้เนื่องจากคำหลักนั้นจะทำให้เราเข้าใจขอบเขตคำศัพท์ howtonode.org/what-is-this
CppLearner

9
นี่เป็นคำตอบที่ดี JavaScriptแต่คำถามคือติดแท็กด้วย ดังนั้นฉันคิดว่านี่ไม่ควรทำเครื่องหมายว่าเป็นคำตอบที่ยอมรับได้ ขอบเขตคำศัพท์เฉพาะใน JS แตกต่างกัน
Boyang

6
คำตอบที่ดีมาก ขอบคุณ. @ หยางฉันไม่เห็นด้วย ฉันไม่ใช่ Lisp coder แต่พบว่า Lisp ตัวอย่างมีประโยชน์เนื่องจากเป็นตัวอย่างของการกำหนดขอบเขตแบบไดนามิกซึ่งคุณไม่ได้รับใน JS
dudewad

4
ตอนแรกฉันคิดว่าตัวอย่างนั้นเป็นรหัส C ที่ถูกต้องและสับสนว่ามีการกำหนดขอบเขตแบบไดนามิกใน C หรือไม่บางทีข้อจำกัดความรับผิดชอบในตอนท้ายสามารถเลื่อนไปที่ตัวอย่างก่อนหน้ารหัสได้หรือไม่
Yangshun Tay

2
นี่ยังเป็นคำตอบที่เป็นประโยชน์ แต่ฉันคิดว่า @Boyang ถูกต้อง คำตอบนี้หมายถึง 'ระดับ' ซึ่งมีมากขึ้นตามแนวของขอบเขตบล็อกที่ C มี JavaScript โดยค่าเริ่มต้นไม่มีขอบเขตระดับบล็อกดังนั้นภายในforลูปเป็นปัญหาทั่วไป ขอบเขตของคำศัพท์สำหรับ JavaScript เป็นเฉพาะในระดับฟังก์ชั่นเว้นแต่ ES6 letหรือconstถูกนำมาใช้
icc97

275

ให้ลองนิยามที่สั้นที่สุด:

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

นั่นคือทั้งหมดที่มีให้มัน!


21
ส่วนสุดท้าย: "แม้ว่าฟังก์ชั่นหลักได้กลับมา" เรียกว่าการปิด
Juanma Menendez

1
ทำความเข้าใจกับ Lexical Scoping & ปิดในประโยคเดียว ขอบคุณ !!
Dungeon

63
var scope = "I am global";
function whatismyscope(){
   var scope = "I am just a local";
   function func() {return scope;}
   return func;
}

whatismyscope()()

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

มันจะไม่รบกวนสิ่งที่มันถูกเรียก (ขอบเขตทั่วโลก / จากฟังก์ชั่นอื่นแม้) นั่นคือเหตุผลที่ค่าขอบเขตทั่วโลกที่ฉันเป็นสากลจะไม่ถูกพิมพ์

สิ่งนี้เรียกว่าการกำหนดขอบเขตศัพท์โดยที่ " ฟังก์ชันจะถูกดำเนินการโดยใช้ขอบเขตของห่วงโซ่ที่มีผลเมื่อถูกกำหนด " - ตามคู่มือนิยาม JavaScript

ขอบเขตคำศัพท์เป็นแนวคิดที่ทรงพลังมาก

หวังว่านี่จะช่วย .. :)


3
มันเป็นคำอธิบายที่ดีมากฉันต้องการเพิ่มอีกหนึ่งอย่างถ้าคุณเขียนฟังก์ชัน func () {return this.scope;} จากนั้นมันจะส่งคืน "I am global" เพียงใช้คำหลักนี้และขอบเขตของคุณจะได้รับการเปลี่ยนแปลง
Rajesh Kumar Bhawsar

41

การกำหนดขอบเขตของคำศัพท์ Lexical (คงที่ AKA) หมายถึงการกำหนดขอบเขตของตัวแปรตามตำแหน่งในคลังข้อความของรหัสเท่านั้น ตัวแปรอ้างถึงสภาพแวดล้อมระดับบนสุดเสมอ เป็นการดีที่จะเข้าใจในส่วนที่เกี่ยวกับขอบเขตไดนามิก


41

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

ส่วนคำศัพท์หมายถึงคุณสามารถสืบทอดขอบเขตจากการอ่านซอร์สโค้ด

ขอบเขตคำศัพท์หรือที่เรียกว่าขอบเขตคงที่

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

ในการกำหนดขอบเขตแบบไดนามิกในทางตรงกันข้ามคุณค้นหาในฟังก์ชั่นท้องถิ่นก่อนจากนั้นคุณค้นหาในฟังก์ชั่นที่เรียกว่าฟังก์ชั่นท้องถิ่นจากนั้นคุณค้นหาในฟังก์ชั่นที่เรียกว่าฟังก์ชั่นนั้น ๆ "Dynamic" หมายถึงการเปลี่ยนแปลงโดยที่ call stack สามารถแตกต่างกันได้ทุกครั้งที่มีการเรียกใช้ฟังก์ชั่นที่กำหนดดังนั้นฟังก์ชันอาจชนกับตัวแปรที่แตกต่างกันขึ้นอยู่กับตำแหน่งที่เรียกใช้ (ดูที่นี่ )

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

สำหรับรายละเอียดเพิ่มเติมดูที่นี่และที่นี่

ตัวอย่างบางส่วนใน Delphi / Object Pascal

Delphi มีขอบเขตคำศัพท์

unit Main;
uses aUnit;  // makes available all variables in interface section of aUnit

interface

  var aGlobal: string; // global in the scope of all units that use Main;
  type 
    TmyClass = class
      strict private aPrivateVar: Integer; // only known by objects of this class type
                                    // lexical: within class definition, 
                                    // reserved word private   
      public aPublicVar: double;    // known to everyboday that has access to a 
                                    // object of this class type
    end;

implementation

  var aLocalGlobal: string; // known to all functions following 
                            // the definition in this unit    

end.

Delphi ที่ใกล้เคียงที่สุดจะได้รับขอบเขตแบบไดนามิกคือคู่ฟังก์ชัน RegisterClass () / GetClass () สำหรับการใช้งานดูที่นี่

สมมติว่าเวลา RegisterClass ([TmyClass]) ถูกเรียกให้ลงทะเบียนคลาสที่แน่นอนไม่สามารถทำนายได้โดยการอ่านรหัส (มันถูกเรียกในวิธีการคลิกปุ่มที่เรียกโดยผู้ใช้) รหัสที่เรียก GetClass ('TmyClass') จะได้รับ ผลลัพธ์หรือไม่ การเรียกใช้ RegisterClass () ไม่จำเป็นต้องอยู่ในขอบเขตศัพท์ของหน่วยโดยใช้ GetClass ();

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


2
จริง ๆ แล้วเป็นส่วนตัวสามารถเข้าถึงได้ในหน่วยทั้งหมดที่กำหนดคลาส นี่คือเหตุผลที่เปิดตัว "Strict ส่วนตัว" ใน D2006
Marco van de Voort

2
+1 สำหรับภาษาธรรมดา (ตรงข้ามกับทั้งภาษาที่ซับซ้อนและตัวอย่างที่ไม่มีคำอธิบายมาก)
Pops

36

ฉันรักคำตอบที่โดดเด่นและไม่ใช้ภาษาจากคนอย่าง @Arak เนื่องจากคำถามนี้ถูกแท็กJavaScriptแต่ฉันต้องการที่จะชิปในบันทึกย่อบางอย่างที่เฉพาะเจาะจงกับภาษานี้

ใน JavaScript ตัวเลือกของเราสำหรับการกำหนดขอบเขตคือ:

  • ตามที่เป็น (ไม่มีการปรับขอบเขต)
  • คำศัพท์ var _this = this; function callback(){ console.log(_this); }
  • ขอบเขต callback.bind(this)

มันเป็นมูลค่า noting ผมคิดว่า JavaScript ไม่ได้จริงๆมีการกำหนดขอบเขตแบบไดนามิก .bindปรับthisคำหลักและใกล้เคียงกัน แต่ไม่เหมือนกันในทางเทคนิค

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

คำศัพท์

นี่คือสิ่งที่คุณอาจทราบLexical Scopingถึงการเรียกกลับใน JavaScript:

var downloadManager = {
  initialize: function() {
    var _this = this; // Set up `_this` for lexical access
    $('.downloadLink').on('click', function () {
      _this.startDownload();
    });
  },
  startDownload: function(){
    this.thinking = true;
    // Request the file from the server and bind more callbacks for when it returns success or failure
  }
  //...
};

ที่ถูกผูกไว้

อีกวิธีในการกำหนดขอบเขตคือการใช้Function.prototype.bind:

var downloadManager = {
  initialize: function() {
    $('.downloadLink').on('click', function () {
      this.startDownload();
    }.bind(this)); // Create a function object bound to `this`
  }
//...

วิธีการเหล่านี้เท่าที่ฉันรู้เทียบเท่ากับพฤติกรรม


การใช้bindไม่ส่งผลกระทบต่อขอบเขต
Ben Aston

12

การกำหนดขอบเขตคำศัพท์: ตัวแปรที่ประกาศภายนอกฟังก์ชั่นเป็นตัวแปรทั่วโลกและสามารถมองเห็นได้ทุกที่ในโปรแกรม JavaScript ตัวแปรที่ประกาศภายในฟังก์ชันมีขอบเขตฟังก์ชันและสามารถมองเห็นได้เฉพาะโค้ดที่ปรากฏในฟังก์ชันนั้น


12

IBMกำหนดเป็น:

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

ตัวอย่างที่ 1:

function x() {
    /*
    Variable 'a' is only available to function 'x' and function 'y'.
    In other words the area defined by 'x' is the lexical scope of
    variable 'a'
    */
    var a = "I am a";

    function y() {
        console.log( a )
    }
    y();

}
// outputs 'I am a'
x();

ตัวอย่างที่ 2:

function x() {

    var a = "I am a";

    function y() {
         /*
         If a nested routine declares an item with the same name,
         the outer item is not available in the nested routine.
         */
        var a = 'I am inner a';
        console.log( a )
    }
    y();

}
// outputs 'I am inner a'
x();

8

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

function grandfather() {
    var name = 'Hammad';
    // 'likes' is not accessible here
    function parent() {
        // 'name' is accessible here
        // 'likes' is not accessible here
        function child() {
            // Innermost level of the scope chain
            // 'name' is also accessible here
            var likes = 'Coding';
        }
    }
}

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

นอกจากนี้ยังบอกเราว่าตัวแปรที่มีชื่อเดียวกันในบริบทการดำเนินการที่แตกต่างกันจะมีความสำคัญกว่าจากบนลงล่างของสแตกการเรียกใช้ ตัวแปรที่มีชื่อคล้ายกับตัวแปรอื่นในฟังก์ชั่นด้านในสุด (บริบทสูงสุดของสแต็คการเรียกใช้งาน) จะมีลำดับความสำคัญสูงกว่า

หมายเหตุที่ว่านี้จะนำมาจากที่นี่


8

ในภาษาง่ายๆขอบเขตศัพท์เป็นตัวแปรที่กำหนดไว้นอกขอบเขตหรือขอบเขตด้านบนของคุณโดยอัตโนมัติมีอยู่ในขอบเขตของคุณซึ่งหมายความว่าคุณไม่จำเป็นต้องผ่านมันไปที่นั่น

ตัวอย่าง:

let str="JavaScript";

const myFun = () => {
    console.log(str);
}

myFun();

// เอาท์พุท: JavaScript


2
คำตอบที่สั้นที่สุดและดีที่สุดสำหรับฉันพร้อมตัวอย่าง อาจมีการเพิ่มว่าฟังก์ชั่นลูกศรของ ES6 แก้ปัญหาbindได้ กับพวกเขาbindไม่จำเป็นต้องใช้อีกต่อไป สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการเปลี่ยนแปลงตรวจสอบstackoverflow.com/a/34361380/11127383
Daniel Danielecki

4

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

การกำหนดขอบเขตแบบไดนามิกมีความสัมพันธ์กับการกำหนดขอบเขตแบบ "โกลบอล" แบบเดียวกับที่เราคิดเกี่ยวกับมันแบบดั้งเดิม (เหตุผลที่ฉันนำมาเปรียบเทียบระหว่างทั้งสองคือมันได้ถูกกล่าวถึงแล้ว- และฉันไม่ชอบคำอธิบายของบทความที่เชื่อมโยง ); มันอาจจะดีที่สุดที่เราจะไม่ทำการเปรียบเทียบระหว่างโลกกับพลวัต - แม้ว่าตามที่กล่าวไว้ในบทความที่เชื่อมโยงว่า "... [มัน] มีประโยชน์ในฐานะที่เป็นตัวแทนของตัวแปรที่มีการกำหนดขอบเขตทั่วโลก"

ดังนั้นในภาษาอังกฤษธรรมดาความแตกต่างที่สำคัญระหว่างกลไกการกำหนดขอบเขตทั้งสองคืออะไร

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

อย่างไรก็ตาม - เนื่องจากมันไม่ได้เป็นจุดสนใจของ OP - การกำหนดขอบเขตแบบไดนามิกไม่ได้รับความสนใจอย่างมากและความสนใจที่ได้รับหมายความว่ามันอาจต้องใช้อีกเล็กน้อย (นั่นไม่ใช่คำวิจารณ์ของคำตอบอื่น ๆ แต่เป็น "โอ้ คำตอบนั้นทำให้เราหวังว่าจะมีอีกเล็กน้อย ") ดังนั้นนี่เป็นอีกเล็กน้อย:

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

... [I] n การกำหนดขอบเขตแบบไดนามิก (หรือขอบเขตแบบไดนามิก) หากขอบเขตของชื่อตัวแปรเป็นฟังก์ชันที่แน่นอนขอบเขตนั้นจะเป็นช่วงเวลาระหว่างที่ฟังก์ชันกำลังดำเนินการ: ในขณะที่ฟังก์ชันกำลังทำงานอยู่ชื่อตัวแปรจะมีอยู่ และถูกผูกไว้กับตัวแปร แต่หลังจากฟังก์ชันส่งคืนชื่อตัวแปรจะไม่มีอยู่


3

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

ดูว่าขอบเขตคำศัพท์ทำงานอย่างไร Lispหากคุณต้องการรายละเอียดเพิ่มเติม คำตอบที่เลือกโดย Kyle Cronin ในตัวแปร Dynamic และ Lexical ใน Common LISPนั้นชัดเจนกว่าคำตอบที่นี่

ฉันได้เรียนรู้เรื่องนี้โดยบังเอิญในชั้นเรียน Lisp และมันก็เกิดขึ้นกับการใช้งานใน JavaScript เช่นกัน

ฉันรันรหัสนี้ในคอนโซลของ Chrome

// JavaScript               Equivalent Lisp
var x = 5;                //(setf x 5)
console.debug(x);         //(print x)
function print_x(){       //(defun print-x ()
    console.debug(x);     //    (print x)
}                         //)
(function(){              //(let
    var x = 10;           //    ((x 10))
    console.debug(x);     //    (print x)
    print_x();            //    (print-x)
})();                     //)

เอาท์พุท:

5
10
5

3

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

แนวคิดนี้ถูกใช้อย่างมากในการปิดใน JavaScript

สมมติว่าเรามีรหัสด้านล่าง

var x = 2;
var add = function() {
    var y = 1;
    return x + y;
};

ตอนนี้เมื่อคุณโทรเพิ่ม () -> สิ่งนี้จะพิมพ์ 3

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


พิจารณาว่าข้อมูลโค้ดมีไว้สำหรับภาษาที่กำหนดขอบเขตแบบไดนามิก หากadd()ฟังก์ชั่นที่เรียกใช้ทันทีต่อจากโค้ดขนาดที่กำหนดมันจะพิมพ์ 3 การกำหนดขอบเขตคำศัพท์ไม่ได้หมายความว่าฟังก์ชันสามารถเข้าถึงตัวแปรโกลบอลนอกบริบทโลคัล ดังนั้นโค้ดตัวอย่างจึงไม่ช่วยแสดงความหมายของการกำหนดขอบเขตคำศัพท์ การแสดงการกำหนดขอบเขตคำศัพท์ในโค้ดต้องการตัวอย่างตัวนับหรืออย่างน้อยคำอธิบายของการตีความที่เป็นไปได้อื่น ๆ ของรหัส
C Perkins

2

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

- global execution context
    - foo
    - bar
    - function1 execution context
        - foo2
        - bar2
        - function2 execution context
            - foo3
            - bar3

foo และ barอยู่ในส่วนของพจนานุกรมของตัวระบุที่มีอยู่เสมอเพราะอยู่ในระดับโลก

เมื่อfunction1ถูกดำเนินการก็มีการเข้าถึงพจนานุกรมของfoo2, bar2, และfoobar

เมื่อfunction2ถูกดำเนินการก็มีการเข้าถึงพจนานุกรมของfoo3, bar3, foo2, bar2, fooและbarและ

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

ในที่สุดนี่คือเหตุผลที่บริบทการดำเนินการที่ซ้อนกันสามารถเข้าถึงบริบทการดำเนินการของบรรพบุรุษได้ตลอดเวลาและเหตุใดจึงสามารถเข้าถึงพจนานุกรมของตัวระบุที่มากขึ้น

ดู:

ขอขอบคุณเป็นพิเศษที่@ robr3rdสำหรับความช่วยเหลือในการทำให้คำจำกัดความข้างต้นง่ายขึ้น


1

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

การตีความเกี่ยวข้องกับการติดตามสามสิ่ง:

  1. สถานะ - คือตัวแปรและตำแหน่งหน่วยความจำที่อ้างอิงบนฮีปและสแต็ก

  2. การทำงานกับสถานะนั้น - คือรหัสทุกบรรทัดในโปรแกรมของคุณ

  3. สภาพแวดล้อมในการที่ได้รับการดำเนินการทำงาน - คือการฉายของรัฐในการดำเนินการ

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

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

นี่คือกรอบงานที่กำหนดปัญหาการกำหนดขอบเขตไว้ ตอนนี้ไปยังส่วนถัดไปของตัวเลือกของเรา

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

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

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

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


0

คำถามโบราณ แต่นี่คือสิ่งที่ฉันทำ

ขอบเขตคำศัพท์ (คงที่) หมายถึงขอบเขตของตัวแปรในซอร์สโค้ดรหัสที่มา

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

เพื่อแสดงจุด:

var a='apple';

function doit() {
    var a='aardvark';
    return function() {
        alert(a);
    }
}

var test=doit();
test();

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

หากคุณรันสิ่งนี้คุณจะพบว่าค่าที่ใช้คือaardvarkไม่ใช่appleซึ่งจะอยู่ในขอบเขตของtest()ฟังก์ชันซึ่งไม่ได้อยู่ในขอบเขตของฟังก์ชันดั้งเดิม นั่นคือขอบเขตที่ใช้เป็นขอบเขตตามที่ปรากฏในซอร์สโค้ดไม่ใช่ขอบเขตที่ใช้ฟังก์ชันจริง

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

var a='apple',b='banana';

function init() {
  var a='aardvark',b='bandicoot';
  document.querySelector('button#a').onclick=function(event) {
    alert(a);
  }
  document.querySelector('button#b').onclick=doB;
}

function doB(event) {
  alert(b);
}

init();
<button id="a">A</button>
<button id="b">B</button>

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

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


-1

ปกติฉันเรียนรู้จากตัวอย่างและนี่คือสิ่งเล็กน้อย:

const lives = 0;

function catCircus () {
    this.lives = 1;
    const lives = 2;

    const cat1 = {
        lives: 5,
        jumps: () => {
            console.log(this.lives);
        }
    };
    cat1.jumps(); // 1
    console.log(cat1); // { lives: 5, jumps: [Function: jumps] }

    const cat2 = {
        lives: 5,
        jumps: () => {
            console.log(lives);
        }
    };
    cat2.jumps(); // 2
    console.log(cat2); // { lives: 5, jumps: [Function: jumps] }

    const cat3 = {
        lives: 5,
        jumps: () => {
            const lives = 3;
            console.log(lives);
        }
    };
    cat3.jumps(); // 3
    console.log(cat3); // { lives: 5, jumps: [Function: jumps] }

    const cat4 = {
        lives: 5,
        jumps: function () {
            console.log(lives);
        }
    };
    cat4.jumps(); // 2
    console.log(cat4); // { lives: 5, jumps: [Function: jumps] }

    const cat5 = {
        lives: 5,
        jumps: function () {
            var lives = 4;
            console.log(lives);
        }
    };
    cat5.jumps(); // 4
    console.log(cat5); // { lives: 5, jumps: [Function: jumps] }

    const cat6 = {
        lives: 5,
        jumps: function () {
            console.log(this.lives);
        }
    };
    cat6.jumps(); // 5
    console.log(cat6); // { lives: 5, jumps: [Function: jumps] }

    const cat7 = {
        lives: 5,
        jumps: function thrownOutOfWindow () {
            console.log(this.lives);
        }
    };
    cat7.jumps(); // 5
    console.log(cat7); // { lives: 5, jumps: [Function: thrownOutOfWindow] }
}

catCircus();

-1

หัวข้อนี้จะเกี่ยวข้องอย่างมากกับในตัวbindฟังก์ชั่นและนำมาใช้ใน ECMAScript 6 ฟังก์ชั่นลูกศร มันน่ารำคาญจริง ๆ เพราะสำหรับวิธี "คลาส" (ฟังก์ชั่นจริง ๆ ) ใหม่ทุกครั้งที่เราต้องการใช้เราต้องทำbindสิ่งนี้เพื่อให้สามารถเข้าถึงขอบเขตได้

JavaScript โดยค่าเริ่มต้นไม่ได้ตั้งขอบเขตthisไว้ที่ฟังก์ชั่น (มันไม่ได้ตั้งบริบทบนthis) โดยค่าเริ่มต้นคุณจะต้องพูดอย่างชัดเจนว่าบริบทใดต้องการใด

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

ดูวิธีการใช้งานจริงในตัวอย่างที่ง่ายที่สุดด้านล่าง

ก่อนฟังก์ชั่นลูกศร (ไม่มีขอบเขตคำศัพท์ตามค่าเริ่มต้น):

const programming = {
  language: "JavaScript",
  getLanguage: function() {
    return this.language;
  }
}

const globalScope = programming.getLanguage;
console.log(globalScope()); // Output: undefined

const localScope = programming.getLanguage.bind(programming);
console.log(localScope()); // Output: "JavaScript"

ด้วยฟังก์ชั่นลูกศร (ขอบเขตคำศัพท์ตามค่าเริ่มต้น):

const programming = {
  language: "JavaScript",
  getLanguage: function() {
    return this.language;
  }
}

const arrowFunction = () => {
    console.log(programming.getLanguage());
}

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