มีการประกาศตัวแปรที่มีการอนุญาตหรือไม่เผยแพร่ใน ES6 หรือไม่


266

ฉันเล่นกับ ES6 มาระยะหนึ่งแล้วและสังเกตว่าในขณะที่ตัวแปรที่ประกาศด้วยvarถูกยกขึ้นตามที่คาดไว้ ...

console.log(typeof name); // undefined
var name = "John";

... ตัวแปรที่ประกาศด้วยletหรือconstดูเหมือนจะมีปัญหากับการชักรอก:

console.log(typeof name); // ReferenceError
let name = "John";

และ

console.log(typeof name); // ReferenceError
const name = "John";

นี่หมายความว่าตัวแปรที่ประกาศด้วยletหรือconstไม่ถูกยกขึ้นมา? เกิดอะไรขึ้นที่นี่จริงเหรอ? มีความแตกต่างระหว่างletและconstในเรื่องนี้หรือไม่?

คำตอบ:


346

@thefourtheye ถูกต้องในการบอกว่าตัวแปรเหล่านี้ไม่สามารถเข้าถึงได้ก่อนที่จะมีการประกาศ อย่างไรก็ตามมันซับซ้อนกว่านั้นเล็กน้อย

มีการประกาศตัวแปรด้วยletหรือconstไม่ยก เกิดอะไรขึ้นที่นี่จริงเหรอ?

ประกาศทั้งหมด ( var, let, const, function, function*, class) เป็น "ยก"ใน JavaScript ซึ่งหมายความว่าหากมีการประกาศชื่อในขอบเขตในขอบเขตนั้นตัวระบุจะอ้างอิงตัวแปรนั้นเสมอ:

x = "global";
// function scope:
(function() {
    x; // not "global"

    var/let/… x;
}());
// block scope (not for `var`s):
{
    x; // not "global"

    let/const/… x;
}

นี่คือความจริงทั้งสำหรับการทำงานและป้องกันขอบเขต1

ความแตกต่างระหว่างvar/ function/ function*ประกาศและlet/ const/ classประกาศเป็นinitialisation
อดีตจะเริ่มต้นด้วยundefinedหรือฟังก์ชั่น (เครื่องกำเนิดไฟฟ้า) ขวาเมื่อมีการสร้างการผูกที่ด้านบนของขอบเขต ตัวแปรที่ประกาศโดย lexically จะยังคงไม่ถูกกำหนดค่าเริ่มต้นไว้ ซึ่งหมายความว่ามีReferenceErrorข้อผิดพลาดเกิดขึ้นเมื่อคุณพยายามเข้าถึง มันจะได้รับการ initialised เมื่อlet/ const/ classคำสั่งประเมินทุกอย่างก่อน (เหนือ) ที่เรียกว่าเขตตายชั่วคราว

x = y = "global";
(function() {
    x; // undefined
    y; // Reference error: y is not defined

    var x = "local";
    let y = "local";
}());

ขอให้สังเกตว่าlet y;คำสั่งเริ่มต้นตัวแปรด้วยundefinedเช่นlet y = undefined;จะมี

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

มีความแตกต่างระหว่างletและconstในเรื่องนี้หรือไม่?

ไม่พวกเขาทำงานเหมือนที่ได้รับการยกย่อง ข้อแตกต่างระหว่างพวกเขาก็คือconstมดจะต้องเป็นและสามารถได้รับมอบหมายเฉพาะในส่วนเริ่มต้นของการประกาศ ( const one = 1;ทั้งconst one;มอบหมายและต่อมาเหมือนอย่างone = 2ไม่ถูกต้อง)

1: varการประกาศยังคงใช้งานได้เฉพาะในระดับฟังก์ชันเท่านั้น


16
ฉันพบว่าสิ่งที่ชอบlet foo = () => bar; let bar = 'bar'; foo();แสดงให้เห็นถึงการประกาศทั้งหมดมีผลบังคับใช้ดียิ่งขึ้นเพราะมันไม่ชัดเจนเนื่องจากโซนตายชั่วคราว
Estus Flask

1
ฉันกำลังจะถามเกี่ยวกับการอ้างอิงคำนิยามให้ในฟังก์ชั่นประกาศก่อนที่จะให้ (เช่นปิด) ฉันคิดว่านี่ตอบคำถามมันถูกกฎหมาย แต่จะเป็นข้อผิดพลาด ref ถ้าฟังก์ชั่นที่ถูกเรียกใช้ก่อนที่จะดำเนินการคำสั่งให้และจะดีถ้าฟังก์ชั่นจะเรียกใช้ในภายหลัง บางทีนี่อาจเพิ่มคำตอบถ้าเป็นจริง?
Mike Lippert

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

1
การตัดสินใจที่จะทำconstเช่นletนั้นเป็นข้อบกพร่องในการออกแบบ ภายในขอบเขตconstควรมีการยกให้และเพิ่งเริ่มต้นเมื่อมีการเข้าถึง จริงๆพวกเขาควรจะมีความconstเป็นletและคำหลักที่สร้างตัวแปรที่ทำงานเหมือน let"อ่านได้อย่างเดียว"
Pacerier

1
" อดีตถูกกำหนดค่าเริ่มต้นด้วยไม่ได้กำหนด ... " อาจโอเคสำหรับการประกาศvarแต่ดูเหมือนจะไม่เหมาะสมสำหรับการประกาศฟังก์ชั่นซึ่งได้รับการกำหนดค่าก่อนที่จะดำเนินการเริ่มต้น
RobG

87

เธซเธฑ ECMAScript 6 (ECMAScript 2015) ของข้อกำหนดletและconstประกาศส่วน

ตัวแปรที่ถูกสร้างขึ้นเมื่อมีคำศัพท์สิ่งแวดล้อมของพวกเขาจะ instantiated แต่อาจไม่สามารถเข้าถึงได้ในทางใดทางหนึ่งจนตัวแปร LexicalBinding ถูกประเมิน

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


22

ES6แนะนำตัวแปรที่มากับLet block level scopingจนกว่าES5เราจะไม่ได้มีblock level scopingดังนั้นตัวแปรที่ประกาศไว้ในบล็อกอยู่เสมอhoistedจะกำหนดขอบเขตการทำงาน

โดยทั่วไปScopeหมายถึงตำแหน่งที่โปรแกรมของคุณสามารถมองเห็นตัวแปรของคุณได้ซึ่งจะกำหนดตำแหน่งที่คุณได้รับอนุญาตให้ใช้ตัวแปรที่คุณประกาศ ในES5ที่เรามีglobal scope,function scope and try/catch scopeกับES6เรายังได้รับการกำหนดขอบเขตระดับบล็อกโดยใช้เถอะ

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

     function doSomething(arr){
         //i is known here but undefined
         //j is not known here
    
         console.log(i);
         console.log(j);
    
         for(var i=0; i<arr.length; i++){
             //i is known here
         }
    
         //i is known here
         //j is not known here
    
         console.log(i);
         console.log(j);
    
         for(let j=0; j<arr.length; j++){
             //j is known here
         }
    
         //i is known here
         //j is not known here
    
         console.log(i);
         console.log(j);
     }
    
     doSomething(["Thalaivar", "Vinoth", "Kabali", "Dinesh"]);

หากคุณเรียกใช้รหัสคุณจะเห็นตัวแปรjเป็นที่รู้จักกันเฉพาะในloopและไม่ก่อนและหลัง แต่ตัวแปรของเราiเป็นที่รู้จักในentire functionจากที่มันถูกกำหนดไว้เป็นต้นไป

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

for(var i=1; i<6; i++){
   setTimeout(function(){
      console.log(i);
   },1000)
}

for(let i=1; i<6; i++){
   setTimeout(function(){
      console.log(i);
   },1000)
}

การforวนซ้ำครั้งแรกจะพิมพ์ค่าสุดท้ายเสมอด้วยletมันจะสร้างขอบเขตใหม่และผูกค่าใหม่ที่พิมพ์เรา1, 2, 3, 4, 5มันจะสร้างขอบเขตใหม่และค่าสดผูกพิมพ์เรา

เมื่อมาถึงconstantsมันก็ใช้งานได้เหมือนletความแตกต่างเพียงอย่างเดียวคือค่าของพวกเขาไม่สามารถเปลี่ยนแปลงได้ ในการกลายพันธุ์ยังคงได้รับอนุญาต แต่ไม่ได้รับมอบหมายใหม่

const foo = {};
foo.bar = 42;
console.log(foo.bar); //works

const name = []
name.push("Vinoth");
console.log(name); //works

const age = 100;
age = 20; //Throws Uncaught TypeError: Assignment to constant variable.

console.log(age);

หากค่าคงที่อ้างอิงถึงobjectมันจะอ้างอิงถึงobjectแต่objectตัวเองสามารถเปลี่ยนแปลงได้ (ถ้ามันไม่แน่นอน) ถ้าคุณชอบที่จะไม่เปลี่ยนรูปobjectคุณสามารถใช้Object.freeze([])


5

จากเอกสารเว็บ MDN:

ใน ECMAScript 2015 letและได้constรับการยก แต่ไม่ได้เริ่มต้น การอ้างอิงตัวแปรในบล็อกก่อนที่การประกาศตัวแปรจะส่งผลให้เกิดReferenceErrorเนื่องจากตัวแปรอยู่ใน "เขตเวลาชั่วคราว" จากจุดเริ่มต้นของบล็อกจนกว่าจะมีการประมวลผลการประกาศ

console.log(x); // ReferenceError
let x = 3;

0

ใน es6 เมื่อเราใช้ let หรือ const เราต้องประกาศตัวแปรก่อนใช้ เช่น. 1 -

// this will work
u = 10;
var u;

// this will give an error 
k = 10;
let k;  // ReferenceError: Cannot access 'k' before initialization.

เช่น. 2-

// this code works as variable j is declared before it is used.
function doSmth() {
j = 9;
}
let j;
doSmth();
console.log(j); // 9
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.