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


158

ฉันไม่สามารถเข้าใจได้ว่าทำไมตัวแปรต่าง ๆ จึงแปลกประหลาดเมื่อถูกประกาศภายในฟังก์ชั่น

  1. ในfirstฟังก์ชั่นฉันประกาศด้วยletตัวแปรbและcด้วยค่า10 :

    b = c = 10;

    ในsecondฟังก์ชั่นที่ฉันแสดง:

    b + ", " + c

    และสิ่งนี้แสดงให้เห็นว่า:

    10, 10
  2. ในfirstฟังก์ชั่นฉันประกาศaด้วยค่า10 :

    let a = b = c = 10;

    แต่ในsecondฟังก์ชั่นมันแสดงข้อผิดพลาด:

    ไม่พบตัวแปร: a

  3. ตอนนี้ในfirstฟังก์ชันฉันประกาศdด้วยค่า20 :

    var d = 20;

    แต่ในsecondฟังก์ชั่นจะแสดงข้อผิดพลาดเหมือนเดิม แต่มีตัวแปรd:

    ไม่พบตัวแปร: d

ตัวอย่าง:

function first() {
  let a = b = c = 10;
  var d = 20;
  second();
}

function second() {
  console.log(b + ", " + c); //shows "10, 10"

  try{ console.log(a); }  // Rreference error
  catch(e){ console.error(e.message) }

  try{ console.log(d); } // Reference error
  catch(e){ console.error(e.message) }
}
first()


31
คุณกำลังประกาศ globals เนื่องจากbและcไม่ได้ขึ้นต้นด้วยvarคำหลัก aและมีในท้องถิ่นเพื่อd first
VLAZ

1
ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนาเกี่ยวกับว่าวงนี้จะเป็นคำถามสัมภาษณ์ที่ดีได้รับการเก็บไว้ในการแชท
Cody Grey

1
สิ่งนี้ทำให้ฉันนึกถึงสถานการณ์ที่คล้ายกันใน Visual Basic Dim Apple, Banana, Pear As Fruitวิธีการและไม่Dim Apple / Dim Banana / Dim Pear As Fruit Dim Apple As Fruit / ...
Eric Lippert

คำตอบ:


179

เป็นเพราะคุณกำลังพูดจริง:

c = 10;
b = c;
let a = b;

และไม่ใช่สิ่งที่คุณคิดว่าคุณกำลังพูดซึ่งก็คือ:

let a = 10;
let b = 10;
let c = 10;

คุณจะสังเกตเห็นว่าไม่ว่าคุณจะเพิ่มตัวแปรเข้าไปในห่วงโซ่ของคุณกี่ตัวแปรมันจะเป็นเพียงตัวแรก (a) ที่ทำให้เกิดข้อผิดพลาด

นี่เป็นเพราะ "ให้" กำหนดขอบเขตตัวแปรของคุณไปยังบล็อก (หรือ, "ในเครื่อง", มีความหมายมากหรือน้อย "ในวงเล็บ") ที่คุณประกาศ

หากคุณประกาศตัวแปรโดยไม่มี "อนุญาต" ตัวแปรจะกำหนดขอบเขตไปทั่วโลก

ดังนั้นในฟังก์ชั่นที่คุณตั้งค่าตัวแปรของคุณทุกอย่างจะได้รับค่า 10 (คุณสามารถเห็นสิ่งนี้ได้ในตัวดีบั๊กถ้าคุณใส่จุดพัก) หากคุณใส่บันทึกคอนโซลสำหรับ a, b, c ในฟังก์ชั่นแรกนั้นทั้งหมดเป็นสิ่งที่ดี

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

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

ลองทำ: ลบคำสำคัญ "ปล่อย" vars ทั้งหมดของคุณจะสามารถใช้ได้

"var" มีเอฟเฟ็กต์ของขอบเขตคล้ายกัน แต่แตกต่างกันว่าตัวแปรคือ "hoisted" ซึ่งเป็นสิ่งที่คุณควรเข้าใจอย่างแน่นอน แต่ไม่เกี่ยวข้องกับคำถามของคุณโดยตรง

(BTW คำถามนี้จะทำให้ตอ pro JS devs เพียงพอที่จะทำให้ดี)

ขอแนะนำให้คุณใช้เวลากับความแตกต่างในวิธีการประกาศตัวแปรใน JS: โดยไม่มีคำหลักด้วย "ให้" และกับ "var"


4
มันเป็นพร้อมกันที่ดีที่สุดและเลวร้ายที่สุดสิ่งที่เกี่ยวกับการเขียนโปรแกรมคอมพิวเตอร์จะทำว่าสิ่งที่คุณบอกว่าจะทำอย่างไร ไม่จำเป็นว่าคุณตั้งใจจะบอกให้ทำ โปรแกรมที่สมบูรณ์แบบ เราสร้างปัญหา
Niet the Dark Absolute

8
@Thevs ทำไมคุณแนะนำvarมากกว่าletในบริบทของคำตอบนี้หรือไม่? ฉันไม่เข้าใจ
Klaycon

4
@ พวกเขาไม่เห็นด้วยอย่างยิ่งกับคุณ varอาจมีแนวโน้มที่จะเป็นโรคจิตหากใช้อย่างไม่ระมัดระวัง ตรวจสอบซอ
Cid

2
@Thevs ในสิ่งที่กรณีไม่varต้องใด ๆเปรียบกว่าlet ? ให้ฉันอธิบาย: บริบทที่ทันสมัยเมื่อทั้งสองเป็นตัวเลือกและฉันขอรหัสที่ควรเขียน เมื่อฉันถามก่อนหน้านี้ผมได้รับคำตอบเกี่ยวกับ "คุณสามารถ re-ประกาศตัวแปรด้วยvar" ซึ่งในกรณีนี้ผมต้องเตือนคนที่คุณไม่ควรจะตัวแปรใหม่ประกาศ นั่นเป็นทั้งข้อผิดพลาดหรือข้อผิดพลาดในตรรกะของรหัส - ประโยชน์ของการประกาศอีกครั้งคือ ... ที่ช่วยให้คุณสามารถเขียนรหัสที่ผิดพลาดได้ ฉันยังไม่เห็นเหตุผลใด ๆ ที่เหมาะสมในการสนับสนุนสำหรับvarเมื่อletยังเป็นตัวเลือก
VLAZ

2
ชอล์กทำเครื่องหมายกับจาวาสคริปต์อีกอัน ตัวแปรทั้งหมดเป็นโกลบอลเว้นแต่จะประกาศในท้องที่ :(
JRE

68

ในฟังก์ชั่นfirst()ตัวแปรbและcจะถูกสร้างขึ้นในทันทีโดยไม่ต้องใช้หรือvarlet

let a = b = c = 10; // b and c are created on the fly

มีความแตกต่างกว่า

let a = 10, b = 10, c = 10; // b and c are created using let (note the ,)

พวกเขากลายเป็นสากลโดยปริยาย นั่นเป็นเหตุผลที่พวกเขามีอยู่ในsecond()

จากเอกสาร

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

เพื่อหลีกเลี่ยงปัญหานี้คุณสามารถใช้"use strict"ที่จะให้ข้อผิดพลาดเมื่อหนึ่งใช้ตัวแปรไม่ได้ประกาศ

"use strict"; // <-------------- check this

function first() {
   /*
    * With "use strict" c is not defined.
    * (Neither is b, but since the line will be executed from right to left,
    * the variable c will cause the error and the script will stop)
    * Without, b and c become globals, and then are accessible in other functions
    */
   let a = b = c = 10;
   var d = 20;
   second();
}

function second() {
   console.log(b + ", " + c); //reference error
   console.log(a); //reference error
   console.log(d); //reference error
}

first();


15
นอกจากนี้: let a = 10, b = 10, c = 10;หรือlet a, b, c; a = b = c = 10;อาจเป็นวิธีที่ถูกต้องในการประกาศตัวแปร
Rickard Elimää

ดังนั้นด้วยโหมดที่เข้มงวดแล้วตัวแปร b ล่ะ?
Tick20

2
@ Tick20 ตัวแปรbจะไม่ได้รับการประเมิน / ถึงข้อผิดพลาดที่จะเกิดขึ้นในบรรทัดที่let a = b = c = 10;อ่านจากขวาไปซ้าย cเป็นตัวแปรแรกที่ก่อให้เกิดReferenceError, ส่วนที่เหลือของบรรทัดจะไม่ถูกดำเนินการ (สคริปต์หยุดทำงาน)
Cid

2
สิ่งที่ชอบlet a = 10, b = a, c = b;ก็ใช้ได้เช่นกัน
Kaddath

8
upvoted ส่วนใหญ่สำหรับ "ใช้เข้มงวด" ในบริบทของคำถามสัมภาษณ์นั่นก็จะเป็นจุดเริ่มต้นของความคิดเห็นของฉันในรหัสนี้ด้วย
Pac0

23

ก่อนที่จะเรียกสิ่งแปลก ๆ มาทำความรู้จักกับพื้นฐานก่อน:

varและletใช้สำหรับการประกาศตัวแปรใน JavaScript ตัวอย่างเช่น,

var one = 1;
let two = 2;

ตัวแปรสามารถประกาศโดยไม่ต้องใช้หรือvar letตัวอย่างเช่น,

three = 3;

ตอนนี้ความแตกต่างระหว่างแนวทางข้างต้นคือ:

var ฟังก์ชั่นขอบเขต

และ

let ถูกบล็อกขอบเขต

ในขณะที่ขอบเขตของตัวแปรที่ประกาศโดยไม่มีvar/ letkeyword กลายเป็นโกลบอลโดยไม่คำนึงถึงตำแหน่งที่จะประกาศ

ตัวแปรทั่วโลกสามารถเข้าถึงได้จากที่ใดก็ได้ในหน้าเว็บ (ไม่แนะนำให้ใช้เพราะ globals สามารถแก้ไขได้โดยไม่ตั้งใจ)

ตอนนี้ตามแนวคิดเหล่านี้ลองมาดูโค้ดที่เป็นปัญหา:

 function first() {
   let a = b = c = 10;
   /* The above line means:
    let a=10; // Block scope
    b=10; // Global scope
    c=10; // Global scope
    */

   var d = 20; // Function scope
   second();
}

function second() {
   alert(b + ", " + c); // Shows "10, 10" //accessible because of global scope
   alert(a); // Error not accessible because block scope has ended
   alert(d); // Error not accessible because function scope has ended
}

1
คุณลอกเลียนส่วนหนึ่งของคำตอบของ JonoJames ทำไม?
Peter Mortensen

2
ฉันขอโทษ แต่ฉันไม่มีเจตนาเช่นนั้นอาจมีบางอย่างที่คล้ายกันเพราะเราอาจรวบรวมข้อมูลจากแหล่งเดียวกัน
fatimasajjad

2
มันอาจจะเป็นว่าคำตอบทั้งสองมีเนื้อหาที่คัดลอกมาจากต้นฉบับเดียวกันโดยไม่ต้องอ้างอิงที่ชัดเจน - อาจtutorialsteacher.com/javascript/javascript-variable การปรากฏตัวของการลอกเลียนแบบเห็นได้ชัดเนื่องจากข้อผิดพลาดทางไวยากรณ์จะทำซ้ำจากเดิม - "ขอบเขตของตัวแปรที่ประกาศโดยไม่มีvarคำหลักกลายเป็นทั่วโลกโดยไม่คำนึงถึงสถานที่ที่ประกาศ"ควรเป็น"ขอบเขต ... กลายเป็น"หรือ" ขอบเขต ... กลายเป็น" การใช้คำพูดที่ถูกต้องของคนอื่นต้องมีการอ้างอิงไม่ว่าจะจากที่นี่หรือที่อื่น ๆ meta.stackexchange.com/q/160071/211183
Michael - sqlbot

ขอบคุณพวกฉันได้เพิ่มลิงค์อ้างอิงสำหรับแหล่งที่มา
fatimasajjad

6

ตัวแปรที่ใช้ letคำหลักควรมีอยู่ในขอบเขตของบล็อกเท่านั้นและไม่สามารถใช้ได้ในฟังก์ชั่นภายนอก ...

ตัวแปรแต่ละตัวที่คุณประกาศในลักษณะนั้นไม่ได้ใช้letหรือvarหรือคุณไม่มีเครื่องหมายจุลภาคในการประกาศตัวแปร

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

function first() {
   let a = 10;
   let b = 10;
   let c = 10;
   var d = 20;
   second();
}

function second() {
   console.log(b + ", " + c); //shows "10, 10"
   console.log(a); //reference error
   console.log(d); //reference error
}

first();


3

มันเป็นเพราะเมื่อคุณไม่ได้ใช้งานletหรือvarตัวแปรกำลังประกาศทันทีคุณดีกว่าที่จะประกาศเช่นนี้

let a = 10;
let b = 10;
let c = 10;

2

ปัญหาที่แปลกเกิดจากกฎการกำหนดขอบเขตใน JavaScript

function first() {
   let a = b = c = 10; // a is in local scope, b and c are in global scope
   var d = 20; // d is in local scope
   second(); // will have access to b and c from the global scope
}

สมมติว่าคุณต้องการประกาศตัวแปรโลคัล 3 ตัวที่เริ่มต้นด้วยค่าเดียวกัน (100) รายการแรกของคุณจะมีลักษณะดังนี้ ในกรณีนี้ที่สอง () จะไม่สามารถเข้าถึงตัวแปรใด ๆ เพราะพวกเขาเป็นท้องถิ่นถึงครั้งแรก ()

function first() {
   let a = 100; // a is in local scope init to 100
   let b = a; // b is in local scope init to a
   let c = b // c is in local scope init to b

   var d = 20; // d is in local scope
   second(); // will not have access a, b, c, or d
}

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

function first() {
   a = 100; // a is in global scope
   b = a; // b is in global scope
   c = b // c is in global scope

   d = 20; // d is in global scope
   second(); // will have access to a, b, c, and d from the global scope
}

ตัวแปรท้องถิ่น (หรือที่รู้จักว่าสามารถเข้าถึงได้ในบล็อคโค้ดที่มีการประกาศ)
บล็อกรหัสคือ {} ใด ๆ ที่มีบรรทัดของโค้ดระหว่าง

  • function () {var, let, const ในที่นี่สามารถเข้าถึงได้โดยฟังก์ชันทั้งหมด},
  • สำหรับ () {var in here สามารถเข้าถึงได้จากขอบเขตด้านนอกให้เข้าถึงได้เฉพาะที่นี่},
  • เป็นต้น

ตัวแปรทั่วโลก (aka เข้าถึงได้ในขอบเขตส่วนกลาง)
ตัวแปรเหล่านี้จะถูกแนบกับวัตถุทั่วโลก วัตถุทั่วโลกขึ้นอยู่กับสภาพแวดล้อม มันเป็นวัตถุหน้าต่างในเบราว์เซอร์

หมายเหตุพิเศษ:คุณสามารถประกาศตัวแปรใน JavaScript ได้โดยไม่ต้องใช้คำหลัก var, let, const ตัวแปรที่ประกาศด้วยวิธีนี้จะแนบกับวัตถุส่วนกลางดังนั้นจึงสามารถเข้าถึงได้ในขอบเขตส่วนกลาง
a = 100 // is valid and is in global scope

บางบทความสำหรับการอ่านเพิ่มเติม: https://www.sitepoint.com/demystifying-javascript-variable-scope-hoisting/ https://scotch.io/tutorials/understanding-scope-in-javascript https: //www.digitalocean .com / ชุมชน / บทเรียน / การทำความเข้าใจตัวแปรขอบเขต-hoisting ในจาวาสคริปต์


0

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

c = 10;
b = c;
let a = b;

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


0

นี่คือแง่มุมที่น่าสนใจ 3 ประการของการประกาศตัวแปรใน JavaScript:

  1. var จำกัด ขอบเขตของตัวแปรไว้ที่บล็อกที่กำหนดไว้ ( 'var' ใช้สำหรับขอบเขตในพื้นที่ )

  2. ให้อนุญาตการแทนที่ค่าของตัวแปรภายนอกภายในบล็อกชั่วคราว

  3. เพียงแค่ประกาศตัวแปรที่ไม่มีvarหรือletจะทำให้ตัวแปรเป็นโกลบอลไม่ว่าจะมีการประกาศที่ไหน

นี่คือตัวอย่างการอนุญาตซึ่งเป็นส่วนเสริมล่าสุดของภาษา:

// File name:  let_demo.js

function first() {
   a = b = 10
   console.log("First function:    a = " + a)
   console.log("First function:    a + b = " + (a + b))
}

function second() {
    let a = 5
    console.log("Second function:    a = " + a)
    console.log("Second function:    a + b = " + (a + b))
}

first()   

second()

console.log("Global:    a = " + a)
console.log("Global:    a + b = " + (a + b))

เอาท์พุท:

$ node let_demo.js 

First function:    a = 10
First function:    a + b = 20

Second function:    a = 5
Second function:    a + b = 15

Global:    a = 10
Global:    a + b = 20

คำอธิบาย:

ตัวแปรaและbถูก delcared ใน ' แรก () ' โดยไม่ต้อง var หรือให้คำหลัก

ดังนั้นaและbเป็นโกลบอลและดังนั้นจึงสามารถเข้าถึงได้ตลอดโปรแกรม

ในฟังก์ชั่นชื่อ'ที่สอง'คำสั่ง'ให้ a = 5'ตั้งค่าชั่วคราวของ ' a ' เป็น ' 5 ' ภายในขอบเขตของฟังก์ชันเท่านั้น

นอกขอบเขตของ ' second () ', IE ในขอบเขตส่วนกลางค่าของ ' a ' จะถูกกำหนดไว้ก่อนหน้านี้

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