Tại sao một số biến được khai báo sử dụng let bên trong một hàm trở nên khả dụng trong một hàm khác, trong khi các biến khác dẫn đến lỗi tham chiếu?


158

Tôi không thể hiểu tại sao các biến hành động rất lạ khi được khai báo bên trong một hàm.

  1. Trong firsthàm tôi khai báo với letcác biến bcvới giá trị 10 :

    b = c = 10;

    Trong secondchức năng tôi hiển thị:

    b + ", " + c

    Và điều này cho thấy:

    10, 10
  2. Cũng trong firsthàm tôi khai báo avới giá trị 10 :

    let a = b = c = 10;

    Nhưng trong secondchức năng nó hiển thị một lỗi:

    Không thể tìm thấy biến: a

  3. Bây giờ trong firsthàm tôi khai báo dvới giá trị 20 :

    var d = 20;

    Nhưng trong secondhàm nó hiển thị lỗi tương tự như trước, nhưng với biến d:

    Không thể tìm thấy biến: d

Thí 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
Bạn đang tuyên bố toàn cầu, vì bckhông có tiền tố với vartừ khóa. adlà địa phương để first.
VLAZ

1
Bình luận không dành cho thảo luận mở rộng; một cuộc trò chuyện tiếp tuyến về việc liệu đây có phải là một câu hỏi phỏng vấn hay đã được lưu trữ trong trò chuyện hay không .
Cody Grey

1
Điều này nhắc nhở tôi về một tình huống tương tự trong Visual Basic; Dim Apple, Banana, Pear As Fruitcó nghĩa là Dim Apple / Dim Banana / Dim Pear As Fruit, và không Dim Apple As Fruit / ....
Eric Lippert

Câu trả lời:


179

Đó là bởi vì bạn đang thực sự nói:

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

Và không phải những gì bạn nghĩ rằng bạn đang nói, đó là:

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

Bạn sẽ nhận thấy rằng cho dù bạn thêm bao nhiêu biến vào chuỗi của mình, thì đó sẽ chỉ là (a) đầu tiên gây ra lỗi.

Điều này là do "let" phạm vi biến của bạn với khối (hoặc "cục bộ", ít nhiều có nghĩa là "trong ngoặc") trong đó bạn khai báo nó.

Nếu bạn khai báo một biến mà không có "let", nó sẽ phạm vi biến trên toàn cầu.

Vì vậy, trong hàm nơi bạn đặt các biến của mình, mọi thứ đều có giá trị 10 (bạn có thể thấy điều này trong trình gỡ lỗi nếu bạn đặt điểm dừng). Nếu bạn đặt một bản ghi giao diện điều khiển cho a, b, c trong chức năng đầu tiên đó, tất cả đều ổn.

Nhưng ngay khi bạn rời khỏi chức năng đó, cái đầu tiên (a) - và một lần nữa, hãy ghi nhớ, về mặt kỹ thuật theo thứ tự gán, nó là cái cuối cùng-- "biến mất" (một lần nữa, bạn có thể thấy điều này trong trình gỡ lỗi nếu bạn đặt điểm dừng trong hàm thứ hai), nhưng hai điểm còn lại (hoặc tuy nhiên nhiều bạn thêm) vẫn khả dụng.

Điều này là do, "cho phép" CHỈ ÁP DỤNG (chỉ phạm vi cục bộ) BIẾN ĐỔI ĐẦU TIÊN - một lần nữa, về mặt kỹ thuật là lần cuối cùng được khai báo và gán giá trị - trong chuỗi. Phần còn lại về mặt kỹ thuật không có "cho phép" trước mặt họ. Vì vậy, những thứ đó được khai báo trên toàn cầu (nghĩa là trên đối tượng toàn cầu), đó là lý do tại sao chúng xuất hiện trong chức năng thứ hai của bạn.

Hãy thử: xóa từ khóa "cho phép". Tất cả các lọ của bạn bây giờ sẽ có sẵn.

"var" có hiệu ứng phạm vi cục bộ tương tự, nhưng khác ở cách biến "được nâng", đây là điều bạn chắc chắn nên hiểu, nhưng không liên quan trực tiếp đến câu hỏi của bạn.

(BTW, câu hỏi này sẽ đặt ra đủ các nhà phát triển JS chuyên nghiệp để làm cho nó trở thành một câu hỏi hay).

Đặc biệt khuyên bạn nên dành thời gian với sự khác biệt về cách các biến có thể được khai báo trong JS: không có từ khóa, với "let" và với "var".


4
Nó đồng thời là điều tốt nhất và tồi tệ nhất về lập trình: máy tính sẽ làm chính xác những gì bạn bảo nó làm. Không nhất thiết là những gì bạn định nói với nó để làm. Các chương trình là hoàn hảo. Chúng tôi tạo ra các vấn đề.
Niet the Dark Tuyệt đối

8
@Thevs Tại sao bạn đề nghị vartrên lettrong bối cảnh của câu trả lời này? Tôi không hiểu
Klaycon

4
@Thevs tôi rất không đồng ý với bạn. varcó thể dễ bị lỗi nếu sử dụng bất cẩn. Kiểm tra câu đố này
Cid

2
@Thevs trong trường hợp những gì varbất kỳ lợi thế hơn let? Hãy để tôi làm rõ: một bối cảnh hiện đại khi cả hai đều là các tùy chọn và tôi đang yêu cầu mã mà người ta nên viết. Khi tôi hỏi điều này trước đây, tôi đã nhận được câu trả lời về "bạn có thể khai báo lại một biến với var" trong trường hợp đó tôi phải nhắc mọi người rằng bạn không nên khai báo lại các biến . Đó là lỗi hoặc lỗi trong logic của mã - vì vậy lợi ích của việc khai báo lại là ... nó cho phép bạn viết mã bị lỗi. Tôi chưa thấy bất kỳ lý do hợp lý có lợi cho varkhi nào letcũng là một lựa chọn.
VLAZ

2
Viết lên một dấu hiệu khác chống lại javascript. Tất cả các biến là toàn cầu trừ khi khai báo cục bộ. :(
JRE

68

Trong hàm first(), các biến bcđược tạo nhanh chóng, không sử dụng varhoặc let.

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

Khác với

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

Họ trở thành ngầm toàn cầu. Đó là lý do tại sao chúng có sẵn trongsecond()

Từ tài liệu

Việc gán một giá trị cho một biến không được khai báo sẽ tạo ra nó như một biến toàn cục (nó trở thành một thuộc tính của đối tượng toàn cục) khi phép gán được thực thi.

Để tránh điều này, bạn có thể sử dụng "use strict"sẽ cung cấp lỗi khi một người sử dụng biến không được khai báo

"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
Ngoài ra: let a = 10, b = 10, c = 10;hoặc let a, b, c; a = b = c = 10;nếu không sẽ là cách khai báo các biến chính xác.
Rickard Elimää

Vậy với chế độ nghiêm ngặt, còn biến b thì sao?

2
@ Tick20 biến bsẽ không được đánh giá / đạt, lỗi sẽ xảy ra trên dòng let a = b = c = 10;, đọc từ phải sang trái . clà biến đầu tiên gây ra ReferenceError, phần còn lại của dòng sẽ không được thực thi (tập lệnh đã dừng)
Cid

2
một cái gì đó giống như let a = 10, b = a, c = b;là hợp lệ quá
Kaddath

8
nâng cao chủ yếu cho "sử dụng nghiêm ngặt". Trong bối cảnh của một câu hỏi phỏng vấn, đó cũng sẽ là khởi đầu cho nhận xét của tôi về mã này.
Pac0

23

Trước khi gọi những điều lạ, trước tiên hãy biết một số điều cơ bản:

varlet đều được sử dụng để khai báo biến trong JavaScript. Ví dụ,

var one = 1;
let two = 2;

Các biến cũng có thể được khai báo mà không sử dụng varhoặc let. Ví dụ,

three = 3;

Bây giờ sự khác biệt giữa các cách tiếp cận trên là:

var là phạm vi chức năng

let là khối phạm vi.

trong khi phạm vi của các biến được khai báo không có var/ lettừ khóa trở thành toàn cầu bất kể nó được khai báo ở đâu.

Biến toàn cục có thể được truy cập từ bất cứ đâu trong trang web (không được khuyến nghị vì có thể vô tình sửa đổi toàn cầu).

Bây giờ theo các khái niệm này, chúng ta hãy xem mã trong câu hỏi:

 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
Bạn đạo văn một phần câu trả lời của JonoJames . Tại sao?
Peter Mortensen

2
Tôi xin lỗi nhưng tôi không có ý định như vậy, một cái gì đó tương tự có thể ở đó bởi vì chúng tôi có thể đã thu thập một phần thông tin từ cùng một nguồn.
fatimasajjad

2
Có thể là cả hai câu trả lời đều chứa nội dung được sao chép từ cùng một nguồn ban đầu mà không cần trích dẫn rõ ràng - có thể là guidesteacher.com/javascript/javascript-variable . Sự hiện diện của đạo văn là rõ ràng, vì một lỗi ngữ pháp được sao chép từ nguyên bản - "phạm vi của các biến được khai báo mà không có vartừ khóa trở thành toàn cầu bất kể nơi nào được khai báo" nên là "phạm vi ... trở thành" hoặc " phạm vi ... trở thành " . Sử dụng các từ chính xác của người khác yêu cầu trích dẫn, cho dù từ đây hay ở nơi khác. meta.stackexchange.com/q/160071/211183
Michael - sqlbot

Cảm ơn các bạn, tôi đã thêm một liên kết tham khảo cho nguồn.
fatimasajjad

6

Các biến sử dụng let từ khóa chỉ nên có sẵn trong phạm vi của khối và không có sẵn trong một chức năng bên ngoài ...

Mỗi biến mà bạn khai báo theo cách đó không sử dụng lethoặcvar . Bạn đang thiếu một dấu phẩy trong khai báo biến.

Không nên khai báo một biến mà không có vartừ khóa. Nó có thể vô tình ghi đè lên một biến toàn cục hiện có. Phạm vi của các biến được khai báo mà không có vartừ khóa trở thành toàn cầu bất kể nó được khai báo ở đâu. Biến toàn cầu có thể được truy cập từ bất cứ nơi nào trong trang web.

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

Đó là bởi vì khi bạn không sử dụng lethoặc varsau đó biến sẽ được khai báo một cách nhanh chóng, tốt hơn là bạn khai báo như sau.

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

2

Vấn đề lạ là do quy tắc phạm vi trong 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
}

Giả sử rằng bạn muốn khai báo 3 biến cục bộ được khởi tạo cho cùng một giá trị (100). Đầu tiên của bạn () sẽ trông như dưới đây. Trong trường hợp này, second () sẽ không có quyền truy cập vào bất kỳ biến nào vì chúng là cục bộ của First ()

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
}

Tuy nhiên, nếu bạn muốn các biến toàn cục thì (() đầu tiên của bạn sẽ trông như dưới đây. Trong trường hợp này, thứ hai sẽ có quyền truy cập vào tất cả các biến vì chúng nằm trong phạm vi toàn cầu

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
}

Các biến cục bộ (còn gọi là có thể truy cập trong khối mã nơi chúng được khai báo).
Khối mã là bất kỳ {} nào có (các) dòng mã nằm giữa.

  • function () {var, let, const in here có thể truy cập được toàn bộ hàm},
  • for () {var ở đây có thể truy cập được đến phạm vi bên ngoài, hãy, chỉ có thể truy cập ở đây},
  • Vân vân.

Biến toàn cầu (còn gọi là có thể truy cập trong phạm vi toàn cầu).
Các biến này được gắn vào đối tượng toàn cầu. Đối tượng toàn cầu phụ thuộc vào môi trường. Nó là đối tượng cửa sổ trong trình duyệt.

Lưu ý đặc biệt: Bạn có thể khai báo các biến trong JavaScript mà không cần sử dụng các từ khóa var, let, const. Một biến được khai báo theo cách này được gắn vào đối tượng toàn cầu, do đó có thể truy cập trong phạm vi toàn cầu.
a = 100 // is valid and is in global scope

Một số bài viết để đọc thêm: https://www.sitepoint.com/demystifying-javascript-variable-scope-ho hiện / https://scotch.io/tutorials/under Hiểu-scope-in-javascript https: //www.digitalocean .com / cộng đồng / hướng dẫn / sự hiểu biết về các biến-phạm vi-hoành hành trong javascript


0

Sự khác biệt chính là quy tắc phạm vi. Các biến được khai báo bởi từ khóa var được đặt trong phạm vi thân hàm tức thời (do đó phạm vi hàm) trong khi các biến được đặt trong phạm vi khối liền kề được ký hiệu là {} (do đó là phạm vi khối). Và khi bạn nói

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

c và b có tuổi thọ như niềm vui nhưng chỉ có một khoảng thời gian và nếu bạn cố gắng truy cập a bằng cách tham chiếu thì nó luôn hiển thị lỗi nhưng c và b là trên toàn cầu nên họ sẽ không nhận ra. các biến bạn thêm vào chuỗi của mình, nó sẽ chỉ là (a) đầu tiên gây ra lỗi. Điều này là do "let" phạm vi biến của bạn với khối (hoặc "cục bộ", ít nhiều có nghĩa là "trong ngoặc") Trong đó bạn khai báo nó. Nếu bạn khai báo một biến không có "let", nó sẽ phạm vi biến trên toàn cầu. Vì vậy, trong hàm bạn đặt biến của mình, mọi thứ đều có giá trị 10 (bạn có thể thấy điều này trong trình gỡ lỗi nếu bạn đặt điểm dừng). Nếu bạn đặt nhật ký giao diện điều khiển cho a, b, c trong chức năng đầu tiên đó, tất cả đều ổn. Nhưng ngay khi bạn rời khỏi chức năng đó, thì đầu tiên (a) - và một lần nữa, hãy ghi nhớ,


0

Dưới đây là 3 khía cạnh thú vị của khai báo biến trong JavaScript:

  1. var giới hạn phạm vi của biến đối với khối được xác định. ( 'var' dành cho phạm vi địa phương .)

  2. cho phép ghi đè tạm thời giá trị của biến ngoài trong một khối.

  3. Chỉ cần khai báo một biến không có var hoặc let sẽ làm cho biến toàn cục, bất kể nó được khai báo ở đâu.

Dưới đây là bản demo cho phép , đây là phần bổ sung mới nhất cho ngôn ngữ:

// 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))

Đầu ra:

$ 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

Giải trình:

Các biến ab đã được phân định trong ' first () ', không có var hoặc cho phép từ khóa.

Do đó, ab là toàn cầu, và do đó, có thể truy cập trong suốt chương trình.

Trong hàm có tên 'giây' , câu lệnh 'let a = 5' tạm thời đặt giá trị của ' a ' thành ' 5 ', chỉ trong phạm vi của hàm.

Ngoài phạm vi ' giây () ', IE, trong phạm vi toàn cầu, giá trị của ' a ' sẽ được xác định trước đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.