Phạm vi của các biến trong JavaScript là gì?


2013

Phạm vi của các biến trong javascript là gì? Chúng có cùng phạm vi bên trong trái ngược với bên ngoài một chức năng không? Hay nó thậm chí còn quan trọng? Ngoài ra, các biến được lưu trữ ở đâu nếu chúng được xác định trên toàn cầu?


4
Đây là một liên kết thú vị khác để ghi nhớ vấn đề này: " Giải thích phạm vi và đóng cửa JavaScript ".
Kỹ sư phần mềm Full-Stack

9
Đây là một bài viết giải thích nó rất độc đáo. Mọi thứ bạn cần biết về phạm vi biến Javascript
Saurab Parakh

2
Sách điện tử được đề cập trước đây của Kyle Simpson có sẵn để đọc trên Github và nó cho bạn biết tất cả những gì bạn cần biết về Phạm vi & Đóng cửa của JavaScript. Bạn có thể tìm thấy nó ở đây: github.com/getify/You-Dont-Know-JS/blob/master/... Đó là một phần của "Bạn không biết JS" loạt cuốn sách , đó là rất tốt cho tất cả mọi người muốn biết thêm về JavaScript.
3rik82

Câu trả lời:


2536

TLD

JavaScript có phạm vi từ vựng (còn gọi là tĩnh) và đóng. Điều này có nghĩa là bạn có thể cho biết phạm vi của một định danh bằng cách xem mã nguồn.

Bốn phạm vi là:

  1. Toàn cầu - hiển thị bởi mọi thứ
  2. Hàm - hiển thị trong một hàm (và các hàm và khối phụ của nó)
  3. Khối - hiển thị trong một khối (và các khối phụ của nó)
  4. Mô-đun - hiển thị trong một mô-đun

Ngoài các trường hợp đặc biệt của phạm vi toàn cầu và mô-đun, các biến được khai báo bằng cách sử dụng var(phạm vi hàm), let(phạm vi khối) và const(phạm vi khối). Hầu hết các hình thức khai báo định danh khác có phạm vi khối trong chế độ nghiêm ngặt.

Tổng quat

Phạm vi là khu vực của cơ sở mã mà định danh hợp lệ.

Một môi trường từ vựng là ánh xạ giữa các tên định danh và các giá trị được liên kết với chúng.

Phạm vi được hình thành từ một lồng liên kết của các môi trường từ vựng, với mỗi cấp độ trong lồng tương ứng với một môi trường từ vựng của bối cảnh thực hiện tổ tiên.

Các môi trường từ vựng được liên kết này tạo thành một "chuỗi" phạm vi. Độ phân giải định danh là quá trình tìm kiếm dọc theo chuỗi này để tìm mã định danh phù hợp.

Độ phân giải định danh chỉ xảy ra theo một hướng: hướng ra ngoài. Theo cách này, môi trường từ vựng bên ngoài không thể "nhìn" vào môi trường từ vựng bên trong.

Có ba yếu tố thích hợp trong việc quyết định phạm vi của một mã định danh trong JavaScript:

  1. Làm thế nào một định danh được khai báo
  2. Trường hợp một định danh được khai báo
  3. Cho dù bạn đang ở chế độ nghiêm ngặt hay chế độ không nghiêm ngặt

Một số cách định danh có thể được khai báo:

  1. var, letconst
  2. Các tham số chức năng
  3. Bắt tham số khối
  4. Khai báo hàm
  5. Biểu thức hàm được đặt tên
  6. Các thuộc tính được xác định ngầm định trên đối tượng toàn cầu (nghĩa là bỏ lỡ vartrong chế độ không nghiêm ngặt)
  7. import các câu lệnh
  8. eval

Một số định danh vị trí có thể được khai báo:

  1. Bối cảnh toàn cầu
  2. Cơ quan chức năng
  3. Khối thông thường
  4. Đỉnh của cấu trúc điều khiển (ví dụ: vòng lặp, nếu, trong khi, v.v.)
  5. Cơ cấu điều khiển
  6. Mô-đun

Phong cách khai báo

var

Mã định danh được khai báo sử dụng var có phạm vi chức năng , ngoài khi chúng được khai báo trực tiếp trong bối cảnh toàn cầu, trong trường hợp đó, chúng được thêm làm thuộc tính trên đối tượng toàn cầu và có phạm vi toàn cầu. Có các quy tắc riêng cho việc sử dụng chúng trong các evalchức năng.

để và const

Mã định danh được khai báo sử dụng letconst có phạm vi khối , ngoài khi chúng được khai báo trực tiếp trong bối cảnh toàn cầu, trong trường hợp đó chúng có phạm vi toàn cầu.

Lưu ý: let, constvar tất cả đều được kéo lên . Điều này có nghĩa là vị trí định nghĩa logic của chúng là đỉnh của phạm vi kèm theo (khối hoặc hàm). Tuy nhiên, các biến được khai báo sử dụng letconstkhông thể được đọc hoặc gán cho đến khi điều khiển đã vượt qua điểm khai báo trong mã nguồn. Thời gian tạm thời được gọi là vùng chết tạm thời.

function f() {
    function g() {
        console.log(x)
    }
    let x = 1
    g()
}
f() // 1 because x is hoisted even though declared with `let`!

Tên tham số chức năng

Tên tham số chức năng được đặt trong phạm vi cơ thể chức năng. Lưu ý rằng có một chút phức tạp cho việc này. Các hàm được khai báo là các đối số mặc định gần với danh sách tham số và không phải là phần thân của hàm.

Khai báo hàm

Khai báo hàm có phạm vi khối trong chế độ nghiêm ngặt và phạm vi hàm trong chế độ không nghiêm ngặt. Lưu ý: chế độ không nghiêm ngặt là một tập hợp các quy tắc khẩn cấp phức tạp dựa trên các triển khai lịch sử kỳ quặc của các trình duyệt khác nhau.

Biểu thức hàm được đặt tên

Các biểu thức hàm được đặt tên nằm trong phạm vi của chúng (ví dụ: cho mục đích đệ quy).

Các thuộc tính được xác định ngầm định trên đối tượng toàn cầu

Trong chế độ không nghiêm ngặt, các thuộc tính được xác định ngầm định trên đối tượng toàn cầu có phạm vi toàn cục, bởi vì đối tượng toàn cầu nằm ở đầu chuỗi phạm vi. Trong chế độ nghiêm ngặt này không được phép.

đánh giá

Trong các evalchuỗi, các biến được khai báo sử dụng varsẽ được đặt trong phạm vi hiện tại hoặc, nếu evalđược sử dụng gián tiếp, làm các thuộc tính trên đối tượng toàn cục.

Ví dụ

Sau đây sẽ ném một ReferenceError vì những cái tên x, yzkhông có ý nghĩa bên ngoài của hàm f.

function f() {
    var x = 1
    let y = 1
    const z = 1
}
console.log(typeof x) // undefined (because var has function scope!)
console.log(typeof y) // undefined (because the body of the function is a block)
console.log(typeof z) // undefined (because the body of the function is a block)

Sau đây sẽ ném ReferenceError cho yz, nhưng không phải cho x, vì khả năng hiển thị của xkhông bị hạn chế bởi khối. Các khối xác định các cơ quan của các cấu trúc điều khiển như if, forwhile, hoạt động tương tự.

{
    var x = 1
    let y = 1
    const z = 1
}
console.log(x) // 1
console.log(typeof y) // undefined because `y` has block scope
console.log(typeof z) // undefined because `z` has block scope

Trong phần sau đây, xcó thể nhìn thấy bên ngoài vòng lặp vì varcó phạm vi chức năng:

for(var x = 0; x < 5; ++x) {}
console.log(x) // 5 (note this is outside the loop!)

... vì hành vi này, bạn cần cẩn thận về việc đóng các biến được khai báo bằng varcác vòng lặp. Chỉ có một trường hợp của biến được xkhai báo ở đây và nó nằm bên ngoài vòng lặp.

Các bản in sau 5, năm lần, và sau đó in 5lần thứ sáu cho console.logvòng lặp bên ngoài:

for(var x = 0; x < 5; ++x) {
    setTimeout(() => console.log(x)) // closes over the `x` which is logically positioned at the top of the enclosing scope, above the loop
}
console.log(x) // note: visible outside the loop

Các bản in sau undefinedxcó phạm vi khối. Các cuộc gọi lại được chạy từng cái một cách không đồng bộ. Hành vi mới cho letcác biến có nghĩa là mỗi hàm ẩn danh đóng trên một biến khác nhau có tên x(không giống như nó đã làm với var) và do đó, các số nguyên 0thông qua 4được in.:

for(let x = 0; x < 5; ++x) {
    setTimeout(() => console.log(x)) // `let` declarations are re-declared on a per-iteration basis, so the closures capture different variables
}
console.log(typeof x) // undefined

Điều sau đây sẽ KHÔNG ném ReferenceErrorvì tầm nhìn của xkhông bị hạn chế bởi khối; tuy nhiên, nó sẽ in undefinedvì biến chưa được khởi tạo (vì ifcâu lệnh).

if(false) {
    var x = 1
}
console.log(x) // here, `x` has been declared, but not initialised

Một biến được khai báo ở đầu forvòng lặp sử dụng letđược đặt trong phạm vi thân của vòng lặp:

for(let x = 0; x < 10; ++x) {} 
console.log(typeof x) // undefined, because `x` is block-scoped

Sau đây sẽ ném một ReferenceErrorvì khả năng hiển thị xbị hạn chế bởi khối:

if(false) {
    let x = 1
}
console.log(typeof x) // undefined, because `x` is block-scoped

Các biến khai báo sử dụng var, lethoặc constđều scoped các mô-đun:

// module1.js

var x = 0
export function f() {}

//module2.js

import f from 'module1.js'

console.log(x) // throws ReferenceError

Sau đây sẽ khai báo một thuộc tính trên đối tượng toàn cục, bởi vì các biến được khai báo sử dụng vartrong bối cảnh toàn cầu, được thêm làm thuộc tính cho đối tượng toàn cục:

var x = 1
console.log(window.hasOwnProperty('x')) // true

letconsttrong bối cảnh toàn cầu không thêm thuộc tính vào đối tượng toàn cầu, nhưng vẫn có phạm vi toàn cầu:

let x = 1
console.log(window.hasOwnProperty('x')) // false

Các tham số chức năng có thể được coi là được khai báo trong thân hàm:

function f(x) {}
console.log(typeof x) // undefined, because `x` is scoped to the function

Các tham số khối bắt được đặt trong phạm vi thân khối bắt:

try {} catch(e) {}
console.log(typeof e) // undefined, because `e` is scoped to the catch block

Các biểu thức hàm được đặt tên chỉ nằm trong phạm vi của chính biểu thức:

(function foo() { console.log(foo) })()
console.log(typeof foo) // undefined, because `foo` is scoped to its own expression

Trong chế độ không nghiêm ngặt, các thuộc tính được xác định ngầm định trên đối tượng toàn cầu có phạm vi toàn cầu. Trong chế độ nghiêm ngặt, bạn nhận được một lỗi.

x = 1 // implicitly defined property on the global object (no "var"!)

console.log(x) // 1
console.log(window.hasOwnProperty('x')) // true

Trong chế độ không nghiêm ngặt, khai báo hàm có phạm vi hàm. Trong chế độ nghiêm ngặt họ có phạm vi khối.

'use strict'
{
    function foo() {}
}
console.log(typeof foo) // undefined, because `foo` is block-scoped

Làm thế nào nó hoạt động dưới mui xe

Phạm vi được định nghĩa là vùng từ vựng của mã mà định danh hợp lệ.

Trong JavaScript, mọi đối tượng hàm có một [[Environment]]tham chiếu ẩn là tham chiếu đến môi trường từ vựng của bối cảnh thực thi (khung stack) trong đó nó được tạo.

Khi bạn gọi một hàm, [[Call]]phương thức ẩn được gọi. Phương thức này tạo ra một bối cảnh thực thi mới và thiết lập một liên kết giữa bối cảnh thực hiện mới và môi trường từ vựng của đối tượng hàm. Nó thực hiện điều này bằng cách sao chép [[Environment]]giá trị trên đối tượng hàm, vào trường tham chiếu ngoài trên môi trường từ vựng của bối cảnh thực thi mới.

Lưu ý rằng liên kết này giữa bối cảnh thực hiện mới và môi trường từ vựng của đối tượng hàm được gọi là bao đóng .

Do đó, trong JavaScript, phạm vi được triển khai thông qua các môi trường từ vựng được liên kết với nhau trong một "chuỗi" bởi các tham chiếu bên ngoài. Chuỗi môi trường từ vựng này được gọi là chuỗi phạm vi và độ phân giải định danh xảy ra bằng cách tìm kiếm chuỗi tìm kiếm định danh phù hợp.

Tìm hiểu thêm .


280
Thậm chí không gần với tính toàn diện, nhưng đây có thể là bộ thủ thuật phạm vi Javascript cần biết mà người ta cần để đọc javascript hiện đại một cách hiệu quả.
Triptych

148
Một câu trả lời được đánh giá cao, không chắc chắn tại sao. Nó chỉ là một loạt các ví dụ mà không có lời giải thích phù hợp, sau đó dường như nhầm lẫn giữa thừa kế nguyên mẫu (nghĩa là độ phân giải thuộc tính) với chuỗi phạm vi (nghĩa là độ phân giải biến). Một lời giải thích toàn diện (và chính xác) về phạm vi và độ phân giải thuộc tính có trong ghi chú FAQ của comp.lang.javascript .
RobG

109
@RobG Nó được đánh giá cao bởi vì nó hữu ích và dễ hiểu đối với một loạt các lập trình viên, dù là một catachresis nhỏ. Liên kết bạn đã đăng, trong khi hữu ích cho một số chuyên gia, không thể hiểu được với hầu hết mọi người viết Javascript ngày nay. Hãy khắc phục mọi vấn đề về danh pháp bằng cách chỉnh sửa câu trả lời.
Triptych

7
@ triptych, tôi chỉ chỉnh sửa câu trả lời để sửa những thứ nhỏ nhặt, không phải chính. Thay đổi "phạm vi" thành "tài sản" sẽ sửa lỗi, nhưng không phải là vấn đề trộn lẫn giữa thừa kế và phạm vi mà không có sự phân biệt rất rõ ràng.
RobG

24
Nếu bạn xác định một biến trong phạm vi bên ngoài, và sau đó có một câu lệnh if xác định một biến bên trong hàm có cùng tên, ngay cả khi đó nếu nhánh đó không đạt được thì nó được xác định lại. Một ví dụ - jsfiddle.net/3CxVm
Chris S

233

Javascript sử dụng chuỗi phạm vi để thiết lập phạm vi cho một chức năng nhất định. Thông thường có một phạm vi toàn cầu và mỗi hàm được xác định có phạm vi lồng nhau riêng. Bất kỳ chức năng nào được xác định trong một chức năng khác đều có phạm vi cục bộ được liên kết với chức năng bên ngoài. Nó luôn luôn là vị trí trong nguồn xác định phạm vi.

Một phần tử trong chuỗi phạm vi về cơ bản là một Bản đồ với một con trỏ tới phạm vi chính của nó.

Khi giải quyết một biến, javascript bắt đầu ở phạm vi trong cùng và tìm kiếm ra bên ngoài.


1
Chuỗi phạm vi là một thuật ngữ khác cho [bộ nhớ] Đóng ... cho những người đọc ở đây để tìm hiểu / truy cập vào javascript.
New Alexandria

108

Các biến được tuyên bố trên toàn cầu có phạm vi toàn cầu. Các biến được khai báo trong một hàm nằm trong phạm vi của hàm đó và tạo bóng cho các biến toàn cục có cùng tên.

(Tôi chắc chắn có nhiều sự tinh tế mà các lập trình viên JavaScript thực sự sẽ có thể chỉ ra trong các câu trả lời khác. Đặc biệt tôi đã xem trang này về ý thisnghĩa chính xác bất cứ lúc nào. Hy vọng rằng liên kết giới thiệu này là đủ để bạn bắt đầu mặc dù .)


7
Tôi sợ thậm chí bắt đầu trả lời câu hỏi này. Là một lập trình viên thực sự của Javascript, tôi biết câu trả lời có thể nhanh chóng vượt ra khỏi tầm tay như thế nào. Bài viết hay.
Triptych

10
@Triptych: Tôi biết ý của bạn về những thứ ngoài tầm tay, nhưng vui lòng thêm câu trả lời nào. Tôi đã nhận được những điều trên chỉ bằng cách thực hiện một vài tìm kiếm ... một câu trả lời được viết bởi một người có kinh nghiệm thực tế chắc chắn sẽ tốt hơn. Xin vui lòng sửa bất kỳ câu trả lời của tôi mà chắc chắn là sai!
Jon Skeet

4
Bằng cách nào đó Jon Skeet chịu trách nhiệm cho câu trả lời phổ biến nhất của tôi trên Stack Overflow.
Triptych

75

JavaScript trường cũ

Theo truyền thống, JavaScript thực sự chỉ có hai loại phạm vi:

  1. Phạm vi toàn cầu : Các biến được biết đến trong toàn bộ ứng dụng, từ khi bắt đầu ứng dụng (*)
  2. Phạm vi chức năng : Các biến được biết trong hàm mà chúng được khai báo, từ khi bắt đầu hàm (*)

Tôi sẽ không giải thích về điều này, vì đã có nhiều câu trả lời khác giải thích sự khác biệt.


JavaScript hiện đại

Các thông số kỹ thuật JavaScript gần đây nhất hiện nay cũng cho phép phạm vi thứ ba:

  1. Phạm vi khối : Mã định danh được "biết" từ đầu phạm vi mà chúng được khai báo bên trong , nhưng chúng không thể được gán cho hoặc hủy đăng ký (đọc) cho đến sau dòng khai báo. Thời gian tạm thời này được gọi là "vùng chết tạm thời."

Làm cách nào để tạo biến phạm vi khối?

Theo truyền thống, bạn tạo các biến của mình như thế này:

var myVariable = "Some text";

Các biến phạm vi khối được tạo như thế này:

let myVariable = "Some text";

Vậy sự khác biệt giữa phạm vi chức năng và phạm vi khối là gì?

Để hiểu sự khác biệt giữa phạm vi chức năng và phạm vi khối, hãy xem xét đoạn mã sau:

// i IS NOT known here
// j IS NOT known here
// k IS known here, but undefined
// l IS NOT known here

function loop(arr) {
    // i IS known here, but undefined
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( var i = 0; i < arr.length; i++ ) {
        // i IS known here, and has a value
        // j IS NOT known here
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( let j = 0; j < arr.length; j++ ) {
        // i IS known here, and has a value
        // j IS known here, and has a value
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here
}

loop([1,2,3,4]);

for( var k = 0; k < arr.length; k++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS NOT known here
};

for( let l = 0; l < arr.length; l++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS known here, and has a value
};

loop([1,2,3,4]);

// i IS NOT known here
// j IS NOT known here
// k IS known here, and has a value
// l IS NOT known here

Ở đây, chúng ta có thể thấy rằng biến của chúng ta jchỉ được biết đến trong vòng lặp đầu tiên, nhưng không phải trước và sau. Tuy nhiên, biến của chúng tôi iđược biết đến trong toàn bộ chức năng.

Ngoài ra, hãy xem xét rằng các biến trong phạm vi khối không được biết trước khi chúng được khai báo vì chúng không được nâng lên. Bạn cũng không được phép xác định lại cùng một biến trong phạm vi khối trong cùng một khối. Điều này làm cho các biến trong phạm vi khối dễ bị lỗi hơn các biến có phạm vi toàn cầu hoặc theo chức năng, được nâng lên và không tạo ra bất kỳ lỗi nào trong trường hợp khai báo nhiều lần.


Có an toàn để sử dụng các biến phạm vi khối ngày hôm nay?

Ngày nay có an toàn hay không, tùy thuộc vào môi trường của bạn:

  • Nếu bạn đang viết mã JavaScript phía máy chủ ( Node.js ), bạn có thể sử dụng letcâu lệnh một cách an toàn .

  • Nếu bạn đang viết mã JavaScript phía máy khách và sử dụng trình chuyển đổi dựa trên trình duyệt (như Traceur hoặc babel- stand Độc ), bạn có thể sử dụng letcâu lệnh một cách an toàn , tuy nhiên mã của bạn có thể là bất cứ điều gì ngoài tối ưu về hiệu suất.

  • Nếu bạn đang viết mã JavaScript phía máy khách và sử dụng trình chuyển đổi dựa trên Node (như tập lệnh shell của dấu vết hoặc Babel ), bạn có thể sử dụng letcâu lệnh một cách an toàn . Và bởi vì trình duyệt của bạn sẽ chỉ biết về mã được dịch mã, nên hạn chế về hiệu suất.

  • Nếu bạn đang viết mã JavaScript phía máy khách và không sử dụng bộ chuyển mã, bạn cần xem xét hỗ trợ trình duyệt.

    Đây là một số trình duyệt hoàn toàn không hỗ trợ let:

    • Internet explorer 10 trở xuống
    • Firefox 43 trở xuống
    • Safari 9 trở xuống
    • Trình duyệt Android 4 trở xuống
    • Opera 27 trở xuống
    • Chome 40 trở xuống
    • MỌI phiên bản của Opera Mini & Blackberry Browser

nhập mô tả hình ảnh ở đây


Cách theo dõi hỗ trợ trình duyệt

Đối với một cái nhìn tổng quan up-to-date trong đó các trình duyệt hỗ trợ lettuyên bố tại thời điểm đọc của bạn câu trả lời này, xem này Can I Usetrang .


(*) Các biến phạm vi toàn cầu và chức năng có thể được khởi tạo và sử dụng trước khi chúng được khai báo vì các biến JavaScript được nâng lên . Điều này có nghĩa là các khai báo luôn luôn đứng đầu phạm vi.


2
"KHÔNG được biết" là sai lệch, bởi vì biến được khai báo ở đó do cẩu.
Oriol

Ví dụ trên là sai lệch, các biến 'i' và 'j' không được biết bên ngoài khối. Các biến 'Let' chỉ có phạm vi trong khối cụ thể đó không nằm ngoài khối. Cũng có những lợi thế khác, bạn không thể xác định lại biến và nó giữ phạm vi từ vựng.
zakir

1
Điều này rất hữu ích, cảm ơn! Tôi nghĩ sẽ hữu ích hơn nữa khi nói cụ thể về ý của bạn về "JavaScript hiện đại" và "JavaScript trường học cũ"; Tôi nghĩ rằng những điều này tương ứng với ECMAScript 6 / ES6 / ECMAScript 2015, và với các phiên bản trước đó, tương ứng?
Jon Schneider

1
@JonSchneider: Đúng! Khi tôi nói "JavaScript trường học cũ", tôi đang nói về ECMAScript 5 và nơi tôi đang đề cập đến "JavaScript hiện đại", tôi đang nói về ECMAScript 6 (còn gọi là ECMAScript 2015). Mặc dù vậy, tôi không nghĩ rằng việc đi sâu vào chi tiết thực sự rất quan trọng vì hầu hết mọi người chỉ muốn biết (1) sự khác biệt giữa phạm vi khối và phạm vi chức năng, (2) trình duyệt nào hỗ trợ phạm vi khối và (3) liệu có an toàn khi sử dụng phạm vi khối ngày hôm nay cho bất kỳ dự án nào họ đang làm việc không. Vì vậy, tôi tập trung câu trả lời của tôi vào việc giải quyết những vấn đề đó.
John Slegers

1
@JonSchneider: (còn tiếp) Tuy nhiên, tôi vừa thêm một liên kết đến bài viết trên Tạp chí Smashing trên ES6 / ES2015 cho những ai muốn tìm hiểu thêm về những tính năng nào đã được thêm vào JavaScript trong vài năm qua ... của bất kỳ ai khác có thể tự hỏi ý tôi là gì với "JavaScript hiện đại".
John Slegers

39

Đây là một ví dụ:

<script>

var globalVariable = 7; //==window.globalVariable

function aGlobal( param ) { //==window.aGlobal(); 
                            //param is only accessible in this function
  var scopedToFunction = {
    //can't be accessed outside of this function

    nested : 3 //accessible by: scopedToFunction.nested
  };

  anotherGlobal = {
    //global because there's no `var`
  }; 

}

</script>

Bạn sẽ muốn điều tra các lần đóng cửa và cách sử dụng chúng để tạo thành viên riêng .



26

Trong "Javascript 1.7" (phần mở rộng của Mozilla thành Javascript), người ta cũng có thể khai báo các biến phạm vi khối bằng letcâu lệnh :

 var a = 4;
 let (a = 3) {
   alert(a); // 3
 }
 alert(a);   // 4

2
Vâng, nhưng nó có an toàn để sử dụng? Ý tôi là tôi thực sự sẽ chọn triển khai này nếu mã của tôi sẽ chạy trong WebKit?
IgorGanapolsky

10
@Python: Không, WebKit không hỗ trợ let.
kennytm

Tôi đoán việc sử dụng hợp lệ duy nhất cho việc này sẽ là nếu bạn biết tất cả các khách hàng sẽ sử dụng trình duyệt Mozilla như đối với hệ thống nội bộ của các công ty.
GazB

Hoặc nếu bạn đang lập trình bằng khung XUL, khung giao diện của Mozilla nơi bạn xây dựng bằng css, xml và javascript.
Gerard ONeill

1
@GazB thậm chí đó là một ý tưởng kinh khủng! Vì vậy, hôm nay bạn biết rằng khách hàng của bạn đang sử dụng Mozilla, sau đó xuất hiện một bản ghi nhớ mới nói rằng bây giờ họ đang sử dụng một cái gì đó khác. IE lý do hệ thống thanh toán của chúng tôi tệ ... Bạn phải sử dụng IE8 và không bao giờ IE9 hoặc IE10 hoặc Firefox hoặc Chrome vì nó không hoạt động ...
buzzsawddog

25

Ý tưởng về phạm vi trong JavaScript khi được Brendan Eich thiết kế ban đầu xuất phát từ ngôn ngữ kịch bản HyperCard HyperTalk .

Trong ngôn ngữ này, các màn hình được thực hiện tương tự như một chồng thẻ chỉ mục. Có một thẻ chủ được gọi là nền. Nó trong suốt và có thể được xem như là thẻ dưới cùng. Bất kỳ nội dung nào trên thẻ cơ sở này đã được chia sẻ với các thẻ được đặt trên đầu trang. Mỗi thẻ được đặt lên hàng đầu có nội dung riêng được ưu tiên so với thẻ trước, nhưng vẫn có quyền truy cập vào các thẻ trước nếu muốn.

Đây chính xác là cách hệ thống phạm vi JavaScript được thiết kế. Nó chỉ có tên khác nhau. Các thẻ trong JavaScript được gọi là Bối cảnh thực thi ECMA . Mỗi một trong những bối cảnh này chứa ba phần chính. Một môi trường thay đổi, môi trường từ vựng và ràng buộc này. Quay trở lại tham chiếu thẻ, môi trường từ vựng chứa tất cả nội dung từ các thẻ trước thấp hơn trong ngăn xếp. Bối cảnh hiện tại nằm ở đầu ngăn xếp và bất kỳ nội dung nào được khai báo sẽ được lưu trữ trong môi trường biến. Môi trường thay đổi sẽ được ưu tiên trong trường hợp đặt tên va chạm.

Các ràng buộc này sẽ trỏ đến đối tượng có chứa. Đôi khi phạm vi hoặc bối cảnh thực thi thay đổi mà không thay đổi đối tượng chứa, chẳng hạn như trong một hàm khai báo trong đó đối tượng chứa có thể là windowhoặc hàm xây dựng.

Các bối cảnh thực hiện được tạo ra bất cứ khi nào điều khiển được chuyển. Kiểm soát được chuyển khi mã bắt đầu thực thi và điều này chủ yếu được thực hiện từ thực thi chức năng.

Vì vậy, đó là lời giải thích kỹ thuật. Trong thực tế, điều quan trọng cần nhớ là trong JavaScript

  • Phạm vi về mặt kỹ thuật là "Bối cảnh thực thi"
  • Các bối cảnh tạo thành một chồng các môi trường nơi các biến được lưu trữ
  • Đỉnh của ngăn xếp được ưu tiên (phía dưới là bối cảnh toàn cầu)
  • Mỗi hàm tạo ra một bối cảnh thực thi (nhưng không phải lúc nào cũng là ràng buộc mới này)

Áp dụng điều này cho một trong các ví dụ trước (5. "Đóng cửa") trên trang này, có thể theo dõi các bối cảnh thực hiện. Trong ví dụ này có ba bối cảnh trong ngăn xếp. Chúng được xác định bởi bối cảnh bên ngoài, bối cảnh trong hàm được gọi ngay lập tức được gọi bởi var six và bối cảnh trong hàm được trả về bên trong hàm được gọi ngay lập tức của var six.

i ) Bối cảnh bên ngoài. Nó có môi trường biến là a = 1
ii ) Bối cảnh IIFE, nó có môi trường từ vựng là a = 1, nhưng môi trường biến là a = 6 được ưu tiên trong ngăn xếp
iii ) Bối cảnh hàm được trả về, nó có từ vựng môi trường của a = 6 và đó là giá trị được tham chiếu trong cảnh báo khi được gọi.

nhập mô tả hình ảnh ở đây


17

1) Có phạm vi toàn cầu, phạm vi chức năng và phạm vi với và bắt. Nói chung, không có phạm vi cấp 'khối' cho biến '- câu lệnh with và câu lệnh bắt thêm tên vào khối của chúng.

2) Phạm vi được lồng bởi các chức năng cho đến phạm vi toàn cầu.

3) Thuộc tính được giải quyết bằng cách đi qua chuỗi nguyên mẫu. Câu lệnh with đưa tên thuộc tính đối tượng vào phạm vi từ vựng được xác định bởi khối with.

EDIT: ECMAAScript 6 (Harmony) được chỉ định để hỗ trợ cho phép và tôi biết chrome cho phép cờ 'hòa hợp', vì vậy có lẽ nó hỗ trợ nó ..

Hãy để là một hỗ trợ cho phạm vi cấp khối, nhưng bạn phải sử dụng từ khóa để làm cho nó xảy ra.

EDIT: Dựa trên việc Benjamin chỉ ra các phát biểu với và bắt trong các bình luận, tôi đã chỉnh sửa bài đăng và thêm nhiều hơn nữa. Cả hai câu lệnh with và Catch đều đưa các biến vào các khối tương ứng của chúng và đó một phạm vi khối. Các biến này được đặt bí danh cho các thuộc tính của các đối tượng được truyền vào chúng.

 //chrome (v8)

 var a = { 'test1':'test1val' }
 test1   // error not defined
 with (a) { var test1 = 'replaced' }
 test1   // undefined
 a       // a.test1 = 'replaced'

EDIT: Làm rõ ví dụ:

test1 nằm trong phạm vi với khối, nhưng được đặt bí danh là a.test1. 'Var test1' tạo ra một biến test1 mới trong ngữ cảnh từ vựng phía trên (hàm hoặc toàn cục), trừ khi đó là thuộc tính của a - mà nó là.

Rất tiếc! Hãy cẩn thận khi sử dụng 'với' - giống như var là noop nếu biến đã được xác định trong hàm, nó cũng là noop đối với các tên được nhập từ đối tượng! Một chút hướng lên tên đã được xác định sẽ làm cho điều này an toàn hơn nhiều. Cá nhân tôi sẽ không bao giờ sử dụng với vì điều này.


Bạn có một số sai lầm ở đây, vì một JavaScript có các dạng phạm vi khối.
Benjamin Gruenbaum

Đôi tai (đôi mắt) của tôi đang mở, Benjamin - Những tuyên bố của tôi ở trên là cách tôi đã đối xử với phạm vi Javascript, nhưng chúng không dựa trên việc đọc thông số kỹ thuật. Và tôi hy vọng bạn không đề cập đến câu lệnh with (là một dạng phạm vi đối tượng) hoặc cú pháp 'cho phép' đặc biệt của Mozilla.
Gerard ONeill

Chà, withcâu lệnh một dạng phạm vi khối nhưng catchmệnh đề là một dạng phổ biến hơn nhiều (Thực tế thú vị, v8 thực hiện catchvới a with) - đó là khá nhiều hình thức phạm vi khối duy nhất trong chính JavaScript (Đó là, hàm, toàn cầu, thử / bắt , với và các dẫn xuất của chúng), tuy nhiên, môi trường máy chủ có các khái niệm khác nhau về phạm vi - ví dụ: các sự kiện nội tuyến trong trình duyệt và mô-đun vm của NodeJS.
Benjamin Gruenbaum

Benjamin - từ những gì tôi có thể thấy, cả với và bắt chỉ đưa đối tượng vào phạm vi hiện tại (và do đó là các thuộc tính), nhưng sau khi khối tương ứng kết thúc, các biến được đặt lại. Nhưng ví dụ, một biến mới được giới thiệu trong một lệnh bắt sẽ có phạm vi của hàm / phương thức kèm theo.
Gerard ONeill

2
Đó chính xác là ý nghĩa của phạm vi khối :)
Benjamin Gruenbaum

9

Tôi thấy rằng nhiều người mới sử dụng JavaScript gặp khó khăn khi hiểu rằng tính kế thừa có sẵn theo ngôn ngữ và phạm vi chức năng là phạm vi duy nhất, cho đến nay. Tôi đã cung cấp một phần mở rộng cho một trình làm đẹp mà tôi đã viết vào cuối năm ngoái có tên là JSPretty. Phạm vi chức năng màu sắc tính năng trong mã và luôn liên kết một màu với tất cả các biến được khai báo trong phạm vi đó. Việc đóng được thể hiện một cách trực quan khi một biến có màu từ một phạm vi được sử dụng trong một phạm vi khác.

Hãy thử tính năng tại:

Xem bản demo tại:

Xem mã tại:

Hiện tại tính năng này cung cấp hỗ trợ cho độ sâu của 16 hàm lồng nhau, nhưng hiện tại không tô màu các biến toàn cục.


1
Không hoạt động với tôi với Firefox 26. Tôi dán mã hoặc tải tệp, nhấp vào thực thi và không có gì xảy ra.
mplwork

Phạm vi và sự kế thừa là hai điều khác biệt.
Ben Aston

9

JavaScript chỉ có hai loại phạm vi:

  1. Phạm vi toàn cầu : Toàn cầu không có gì ngoài phạm vi cấp cửa sổ. Đây là biến có trong toàn bộ ứng dụng.
  2. Phạm vi chức năng : Biến được khai báo trong một hàm với vartừ khóa có phạm vi chức năng.

Bất cứ khi nào một hàm được gọi, một đối tượng phạm vi biến được tạo (và được bao gồm trong chuỗi phạm vi) được theo sau bởi các biến trong JavaScript.

        a = "global";
         function outer(){ 
              b = "local";
              console.log(a+b); //"globallocal"
         }
outer();

Chuỗi phạm vi ->

  1. Cấp cửa sổ - aouterchức năng ở cấp cao nhất trong chuỗi phạm vi.
  2. khi hàm ngoài gọi là mới variable scope object(và được bao gồm trong chuỗi phạm vi) được thêm vào với biến bbên trong nó.

Bây giờ khi một biến được ayêu cầu, đầu tiên nó sẽ tìm kiếm phạm vi biến gần nhất và nếu biến không ở đó thì nó sẽ chuyển sang đối tượng tiếp theo của chuỗi phạm vi biến. Trong trường hợp này là mức cửa sổ.


1
Không chắc chắn tại sao đây không phải là câu trả lời được chấp nhận. Thực tế chỉ có phạm vi chức năng (trước ECMA6 không có "phạm vi địa phương") và các ràng buộc toàn cầu
texasbruce 28/2/2015

9

Chỉ cần thêm vào các câu trả lời khác, scope là danh sách tra cứu tất cả các mã định danh (biến) được khai báo và thực thi một bộ quy tắc nghiêm ngặt về cách chúng có thể truy cập được để thực thi mã. Việc tra cứu này có thể nhằm mục đích gán cho biến, đó là tham chiếu LHS (phía bên trái) hoặc có thể dành cho mục đích truy xuất giá trị của nó, là tham chiếu RHS (bên phải). Các tra cứu này là những gì công cụ JavaScript đang thực hiện bên trong khi nó biên dịch và thực thi mã.

Vì vậy, từ quan điểm này, tôi nghĩ rằng một hình ảnh sẽ giúp tôi tìm thấy trong ebook Scopes and Closures của Kyle Simpson:

hình ảnh

Trích dẫn từ ebook của mình:

Tòa nhà đại diện cho quy tắc phạm vi lồng nhau của chương trình của chúng tôi. Tầng đầu tiên của tòa nhà thể hiện phạm vi hiện đang thực hiện của bạn, cho dù bạn ở đâu. Cấp cao nhất của tòa nhà là phạm vi toàn cầu. Bạn giải quyết các tham chiếu LHS và RHS bằng cách nhìn vào tầng hiện tại của bạn và nếu bạn không tìm thấy nó, hãy đi thang máy lên tầng tiếp theo, nhìn vào đó, rồi tiếp theo, v.v. Khi bạn lên tầng trên cùng (phạm vi toàn cầu), bạn có thể tìm thấy những gì bạn đang tìm kiếm hoặc bạn không tìm thấy. Nhưng bạn phải dừng lại bất kể.

Một điều đáng lưu ý là "Tìm kiếm phạm vi dừng lại một khi nó tìm thấy trận đấu đầu tiên".

Ý tưởng về "mức phạm vi" này giải thích tại sao "điều này" có thể được thay đổi với phạm vi mới được tạo, nếu nó được tìm kiếm trong một hàm lồng nhau. Đây là một liên kết đi vào tất cả các chi tiết này, Mọi thứ bạn muốn biết về phạm vi javascript


8

chạy mã. hy vọng điều này sẽ cho một ý tưởng về phạm vi

Name = 'global data';
document.Name = 'current document data';
(function(window,document){
var Name = 'local data';
var myObj = {
    Name: 'object data',
    f: function(){
        alert(this.Name);
    }
};

myObj.newFun = function(){
    alert(this.Name);
}

function testFun(){
    alert("Window Scope : " + window.Name + 
          "\nLocal Scope : " + Name + 
          "\nObject Scope : " + this.Name + 
          "\nCurrent document Scope : " + document.Name
         );
}


testFun.call(myObj);
})(window,document);

8

Phạm vi toàn cầu :

Biến toàn cầu giống hệt như các ngôi sao toàn cầu (Thành Long, Nelson Mandela). Bạn có thể truy cập chúng (nhận hoặc đặt giá trị), từ bất kỳ phần nào trong ứng dụng của bạn. Các chức năng toàn cầu giống như các sự kiện toàn cầu (Năm mới, Giáng sinh). Bạn có thể thực thi (gọi) chúng từ bất kỳ phần nào trong ứng dụng của bạn.

//global variable
var a = 2;

//global function
function b(){
   console.log(a);  //access global variable
}

Phạm vi địa phương:

Nếu bạn ở Mỹ, bạn có thể biết Kim Kardashian, người nổi tiếng khét tiếng (bằng cách nào đó cô ấy xoay sở để làm báo lá cải). Nhưng những người bên ngoài Hoa Kỳ sẽ không nhận ra cô ấy. Cô là một ngôi sao địa phương, ràng buộc với lãnh thổ của mình.

Biến cục bộ giống như các ngôi sao địa phương. Bạn chỉ có thể truy cập chúng (nhận hoặc đặt giá trị) trong phạm vi. Một hàm cục bộ giống như các sự kiện cục bộ - bạn chỉ có thể thực thi (ăn mừng) trong phạm vi đó. Nếu bạn muốn truy cập chúng từ bên ngoài phạm vi, bạn sẽ gặp lỗi tham chiếu

function b(){
   var d = 21; //local variable
   console.log(d);

   function dog(){  console.log(a); }
     dog(); //execute local function
}

 console.log(d); //ReferenceError: dddddd is not defined    

Kiểm tra bài viết này để hiểu sâu về phạm vi


6

Chỉ có hai loại phạm vi JavaScript:

  • phạm vi của mỗi khai báo var được liên kết với hàm kèm theo ngay lập tức nhất
  • nếu không có hàm kèm theo cho khai báo var, thì đó là phạm vi toàn cục

Vì vậy, bất kỳ khối nào ngoài chức năng không tạo ra một phạm vi mới. Điều đó giải thích tại sao các vòng lặp ghi đè lên các biến có phạm vi bên ngoài:

var i = 10, v = 10;
for (var i = 0; i < 5; i++) { var v = 5; }
console.log(i, v);
// output 5 5

Sử dụng các chức năng thay thế:

var i = 10, v = 10;
$.each([0, 1, 2, 3, 4], function(i) { var v = 5; });
console.log(i,v);
// output 10 10

Trong ví dụ đầu tiên, không có phạm vi khối, vì vậy các biến được khai báo ban đầu đã bị ghi đè. Trong ví dụ thứ hai, có một phạm vi mới do hàm, vì vậy các biến được khai báo ban đầu là SHADOWED và không được ghi đè.

Đó gần như là tất cả những gì bạn cần biết về phạm vi JavaScript, ngoại trừ:

Vì vậy, bạn có thể thấy phạm vi JavaScript thực sự cực kỳ đơn giản, mặc dù không phải lúc nào cũng trực quan. Một số điều cần chú ý:

  • khai báo var được nâng lên đầu của phạm vi. Điều này có nghĩa là bất kể việc khai báo var xảy ra ở đâu, đối với trình biên dịch, như thể chính var xảy ra ở trên cùng
  • nhiều khai báo var trong cùng một phạm vi được kết hợp

Mã này:

var i = 1;
function abc() {
  i = 2;
  var i = 3;
}
console.log(i);     // outputs 1

tương đương với:

var i = 1;
function abc() {
  var i;     // var declaration moved to the top of the scope
  i = 2;
  i = 3;     // the assignment stays where it is
}
console.log(i);

Điều này có vẻ phản trực giác, nhưng nó có ý nghĩa từ quan điểm của một nhà thiết kế ngôn ngữ cấp bách.


5

Js hiện đại, ES6 +, ' const' và ' let'

Bạn nên sử dụng phạm vi khối cho mọi biến bạn tạo, giống như hầu hết các ngôn ngữ chính khác. varlỗi thời . Điều này làm cho mã của bạn an toàn hơn và dễ bảo trì hơn.

constnên được sử dụng cho 95% các trường hợp . Nó làm cho nó để tham chiếu biến không thể thay đổi. Các thuộc tính của mảng, đối tượng và nút DOM có thể thay đổi và có thể sẽ như vậy const.

letnên được sử dụng cho bất kỳ biến nào được mong đợi được gán lại. Điều này bao gồm trong một vòng lặp for. Nếu bạn thay đổi giá trị ngoài khởi tạo, hãy sử dụng let.

Phạm vi khối có nghĩa là biến sẽ chỉ khả dụng trong các dấu ngoặc được khai báo. Điều này mở rộng đến phạm vi nội bộ, bao gồm các hàm ẩn danh được tạo trong phạm vi của bạn.


3

Hãy thử ví dụ tò mò này. Trong ví dụ dưới đây nếu a là một số được khởi tạo ở 0, bạn sẽ thấy 0 và sau đó 1. Ngoại trừ a là một đối tượng và javascript sẽ vượt qua F1 một con trỏ của một thay vì một bản sao của nó. Kết quả là bạn nhận được cùng một cảnh báo cả hai lần.

var a = new Date();
function f1(b)
{
    b.setDate(b.getDate()+1);
    alert(b.getDate());
}
f1(a);
alert(a.getDate());

3

Chỉ có phạm vi chức năng trong JS. Không chặn phạm vi! Bạn có thể thấy những gì đang nâng quá.

var global_variable = "global_variable";
var hoisting_variable = "global_hoist";

// Global variables printed
console.log("global_scope: - global_variable: " + global_variable);
console.log("global_scope: - hoisting_variable: " + hoisting_variable);

if (true) {
    // The variable block will be global, on true condition.
    var block = "block";
}
console.log("global_scope: - block: " + block);

function local_function() {
    var local_variable = "local_variable";
    console.log("local_scope: - local_variable: " + local_variable);
    console.log("local_scope: - global_variable: " + global_variable);
    console.log("local_scope: - block: " + block);
    // The hoisting_variable is undefined at the moment.
    console.log("local_scope: - hoisting_variable: " + hoisting_variable);

    var hoisting_variable = "local_hoist";
    // The hoisting_variable is now set as a local one.
    console.log("local_scope: - hoisting_variable: " + hoisting_variable);
}

local_function();

// No variable in a separate function is visible into the global scope.
console.log("global_scope: - local_variable: " + local_variable);

(thời gian dài kể từ khi câu trả lời được đăng) Phạm vi khối; developer.mozilla.org/en/docs/Web/JavaScript/Reference/,
Bob

2

Hiểu biết của tôi là có 3 phạm vi: phạm vi toàn cầu, có sẵn trên toàn cầu; phạm vi cục bộ, có sẵn cho toàn bộ chức năng bất kể khối; và phạm vi khối, chỉ khả dụng đối với khối, câu lệnh hoặc biểu thức mà nó được sử dụng. Phạm vi toàn cầu và cục bộ được biểu thị bằng từ khóa 'var', trong phạm vi chức năng hoặc bên ngoài và phạm vi khối được chỉ định bằng từ khóa 'let'.

Đối với những người tin rằng chỉ có phạm vi toàn cầu và cục bộ, vui lòng giải thích tại sao Mozilla sẽ có toàn bộ trang mô tả các sắc thái của phạm vi khối trong JS.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let


2

Một vấn đề rất phổ biến chưa được mô tả mà các lập trình viên đầu cuối thường gặp phải là phạm vi hiển thị cho một trình xử lý sự kiện nội tuyến trong HTML - ví dụ, với

<button onclick="foo()"></button>

Phạm vi của các biến mà một on*thuộc tính có thể tham chiếu phải là:

  • toàn cầu (xử lý nội tuyến làm việc hầu như luôn luôn tham chiếu các biến toàn cục)
  • một thuộc tính của tài liệu (ví dụ, querySelectornhư một biến độc lập sẽ trỏ đến document.querySelector; hiếm)
  • một thuộc tính của phần tử mà trình xử lý được gắn vào (như trên; hiếm)

Nếu không, bạn sẽ nhận được ReferenceError khi trình xử lý được gọi. Vì vậy, ví dụ, nếu trình xử lý nội tuyến tham chiếu một hàm được xác định bên trong window.onload hoặc $(function() {, tham chiếu sẽ không thành công, vì trình xử lý nội tuyến chỉ có thể tham chiếu các biến trong phạm vi toàn cục và hàm không phải là toàn cục:

Các thuộc tính documentvà thuộc tính của phần tử mà trình xử lý được đính kèm cũng có thể được tham chiếu dưới dạng các biến độc lập bên trong các trình xử lý nội tuyến vì các trình xử lý nội tuyến được gọi bên trong hai withkhối , một cho document, một cho phần tử. Chuỗi phạm vi của các biến trong các trình xử lý này cực kỳ không trực quan và một trình xử lý sự kiện có thể sẽ yêu cầu một chức năng là toàn cầu (và có thể tránh ô nhiễm toàn cầu không cần thiết ).

Vì chuỗi phạm vi bên trong các trình xử lý nội tuyến rất kỳ lạ và vì các trình xử lý nội tuyến yêu cầu ô nhiễm toàn cầu để hoạt động và vì các trình xử lý nội tuyến đôi khi yêu cầu thoát chuỗi xấu xí khi truyền các đối số, nên có thể dễ dàng tránh chúng hơn. Thay vào đó, hãy đính kèm các trình xử lý sự kiện bằng Javascript (như với addEventListener), thay vì đánh dấu HTML.


Trên một lưu ý khác, không giống như <script>các thẻ thông thường chạy ở cấp cao nhất, mã bên trong các mô-đun ES6 chạy trong phạm vi riêng tư của nó. Một biến được xác định ở đầu <script>thẻ thông thường là toàn cục, vì vậy bạn có thể tham chiếu nó trong các <script>thẻ khác , như sau:

Nhưng cấp độ cao nhất của mô-đun ES6 không phải là toàn cầu. Một biến được khai báo ở đầu mô-đun ES6 sẽ chỉ hiển thị bên trong mô-đun đó, trừ khi biến đó được chỉnh sửa rõ ràng exporthoặc trừ khi nó được gán cho một thuộc tính của đối tượng toàn cục.

Cấp cao nhất của mô-đun ES6 tương tự như bên trong IIFE ở cấp cao nhất trong mức bình thường <script>. Mô-đun có thể tham chiếu bất kỳ biến nào là toàn cục và không có gì có thể tham chiếu bất cứ thứ gì bên trong mô-đun trừ khi mô-đun được thiết kế rõ ràng cho nó.


1

Trong JavaScript có hai loại phạm vi:

  • Phạm vi địa phương
  • Phạm vi toàn cầu

Hàm Dưới đây có một biến phạm vi cục bộ carName. Và biến này không thể truy cập từ bên ngoài hàm.

function myFunction() {
    var carName = "Volvo";
    alert(carName);
    // code here can use carName
}

Lớp dưới đây có một biến phạm vi toàn cầu carName. Và biến này có thể truy cập từ mọi nơi trong lớp.

class {

    var carName = " Volvo";

    // code here can use carName

    function myFunction() {
        alert(carName);
        // code here can use carName 
    }
}

1

ES5 và trước đó:

Các biến trong Javascript ban đầu (trước ES6) có chức năng từ vựng. Thuật ngữ phạm vi từ vựng có nghĩa là bạn có thể thấy phạm vi của các biến bằng cách 'nhìn' vào mã.

Mỗi biến được khai báo với vartừ khóa nằm trong phạm vi hàm. Tuy nhiên, nếu hàm khác được khai báo trong hàm đó thì các hàm đó sẽ có quyền truy cập vào các biến của các hàm ngoài. Đây được gọi là chuỗi phạm vi . Nó hoạt động theo cách sau:

  1. Khi một hàm tìm cách giải quyết một giá trị biến, đầu tiên nó nhìn vào phạm vi của chính nó. Đây là phần thân hàm, tức là mọi thứ giữa các dấu ngoặc nhọn {} (ngoại trừ các biến bên trong các hàm khác trong phạm vi này).
  2. Nếu nó không thể tìm thấy biến bên trong thân hàm, nó sẽ leo lên chuỗi và xem xét phạm vi biến trong hàm trong đó hàm được xác định . Đây là những gì có nghĩa với phạm vi từ vựng, chúng ta có thể thấy trong mã nơi hàm này được xác định và do đó có thể xác định chuỗi phạm vi bằng cách chỉ nhìn vào mã.

Thí dụ:

// global scope
var foo = 'global';
var bar = 'global';
var foobar = 'global';

function outerFunc () {
 // outerFunc scope
 var foo = 'outerFunc';
 var foobar = 'outerFunc';
 innerFunc();
 
 function innerFunc(){
 // innerFunc scope
  var foo = 'innerFunc';
  console.log(foo);
  console.log(bar);
  console.log(foobar);
  }
}

outerFunc();

Chuyện gì xảy ra khi chúng ta đang cố gắng đăng nhập các biến foo, barfoobarđể giao diện điều khiển là những điều sau đây:

  1. Chúng tôi cố gắng đăng nhập foo vào bàn điều khiển, foo có thể được tìm thấy bên trong chức năng innerFunc. Do đó, giá trị của foo được phân giải thành chuỗi innerFunc.
  2. Chúng tôi cố gắng đăng nhập thanh vào bàn điều khiển, thanh không thể được tìm thấy bên trong chức năng innerFunc. Do đó, chúng ta cần phải leo lên chuỗi phạm vi . Đầu tiên chúng ta nhìn vào hàm ngoài trong đó hàm innerFuncđược định nghĩa. Đây là chức năng outerFunc. Trong phạm vi của outerFuncchúng ta có thể tìm thấy thanh biến, chứa chuỗi 'outsFunc'.
  3. foobar không thể được tìm thấy trong InternalFunc. . Do đó, chúng ta cần phải leo lên chuỗi phạm vi đến phạm vi bên trong. Nó cũng không thể được tìm thấy ở đây, chúng tôi leo lên một cấp độ khác đến phạm vi toàn cầu (tức là phạm vi ngoài cùng). Chúng tôi tìm thấy foobar biến ở đây chứa chuỗi 'toàn cầu'. Nếu nó không tìm thấy biến sau khi leo lên chuỗi phạm vi, công cụ JS sẽ ném tham chiếuError .

ES6 (ES 2015) trở lên:

Các khái niệm tương tự về phạm vi từ vựng và scopechain vẫn được áp dụng trong ES6. Tuy nhiên, một cách mới để khai báo các biến đã được giới thiệu. Có những điều sau đây:

  • let: tạo một biến phạm vi khối
  • const: tạo một biến phạm vi khối phải được khởi tạo và không thể được chỉ định lại

Sự khác biệt lớn nhất giữa varlet/ constvarlà chức năng scoped trong khi let/ constlà khối scoped. Dưới đây là một ví dụ để minh họa điều này:

let letVar = 'global';
var varVar = 'global';

function foo () {
  
  if (true) {
    // this variable declared with let is scoped to the if block, block scoped
    let letVar = 5;
    // this variable declared with let is scoped to the function block, function scoped
    var varVar = 10;
  }
  
  console.log(letVar);
  console.log(varVar);
}


foo();

Trong ví dụ trên, letVar ghi lại giá trị toàn cục vì các biến được khai báo có letphạm vi khối. Chúng không còn tồn tại bên ngoài khối tương ứng của chúng, vì vậy biến không thể được truy cập bên ngoài khối if.


0

Trong EcmaScript5, chủ yếu có hai phạm vi, phạm vi cục bộphạm vi toàn cầu nhưng trong EcmaScript6 chúng ta chủ yếu có ba phạm vi, phạm vi cục bộ, phạm vi toàn cầu và phạm vi mới được gọi là phạm vi khối .

Ví dụ về phạm vi khối là: -

for ( let i = 0; i < 10; i++)
{
 statement1...
statement2...// inside this scope we can access the value of i, if we want to access the value of i outside for loop it will give undefined.
}

0

ECMAScript 6 đã giới thiệu các từ khóa let và const. Những từ khóa này có thể được sử dụng thay cho từ khóa var. Trái với từ khóa var, từ khóa let và const hỗ trợ khai báo phạm vi cục bộ bên trong các câu lệnh khối.

var x = 10
let y = 10
const z = 10
{
  x = 20
  let y = 20
  const z = 20
  {
    x = 30
    // x is in the global scope because of the 'var' keyword
    let y = 30
    // y is in the local scope because of the 'let' keyword
    const z = 30
    // z is in the local scope because of the 'const' keyword
    console.log(x) // 30
    console.log(y) // 30
    console.log(z) // 30
  }
  console.log(x) // 30
  console.log(y) // 20
  console.log(z) // 20
}

console.log(x) // 30
console.log(y) // 10
console.log(z) // 10

0

Tôi thực sự thích câu trả lời được chấp nhận nhưng tôi muốn thêm câu này:

Phạm vi thu thập và duy trì một danh sách tra cứu tất cả các mã định danh (biến) được khai báo và thực thi một bộ quy tắc nghiêm ngặt về cách những mã này có thể truy cập được đối với mã hiện đang thực thi.

Phạm vi là một bộ quy tắc để tìm kiếm các biến theo tên định danh của chúng.

  • Nếu một biến không thể được tìm thấy trong phạm vi ngay lập tức, Engine sẽ giới thiệu phạm vi chứa bên ngoài tiếp theo, tiếp tục cho đến khi được tìm thấy hoặc cho đến khi đạt được phạm vi ngoài cùng (hay còn gọi là toàn cầu).
  • Là tập hợp các quy tắc xác định vị trí và cách một biến (định danh) có thể được tra cứu. Việc tra cứu này có thể nhằm mục đích gán cho biến, đó là tham chiếu LHS (bên trái) hoặc có thể nhằm mục đích truy xuất giá trị của nó, đó là tham chiếu RHS (bên phải) .
  • Tham chiếu LHS kết quả từ các hoạt động chuyển nhượng. Các phép gán liên quan đến phạm vi có thể xảy ra với toán tử = hoặc bằng cách chuyển các đối số cho các tham số hàm (gán cho).
  • Công cụ JavaScript trước tiên biên dịch mã trước khi thực thi và khi thực hiện, nó sẽ phân tách các câu lệnh như var a = 2; thành hai bước riêng biệt: thứ nhất. Đầu tiên, var a để khai báo nó trong phạm vi đó. Điều này được thực hiện ở đầu, trước khi thực thi mã. lần 2. Sau đó, a = 2 để tra cứu biến (tham chiếu LHS) và gán cho nó nếu tìm thấy.
  • Cả hai tra cứu tham chiếu LHS và RHS đều bắt đầu ở phạm vi hiện đang thực thi và nếu cần (nghĩa là họ không tìm thấy những gì họ đang tìm kiếm ở đó), họ tiến hành phạm vi lồng nhau, một phạm vi (tầng ) tại một thời điểm, tìm kiếm mã định danh, cho đến khi họ đến toàn cầu (tầng trên cùng) và dừng lại, và tìm thấy nó, hoặc không. Các tham chiếu RHS chưa hoàn thành dẫn đến ReferenceError bị ném. Các tham chiếu LHS chưa hoàn thành dẫn đến một tên toàn cầu tự động, được tạo hoàn toàn (nếu không ở Chế độ nghiêm ngặt) hoặc ReferenceError (nếu ở Chế độ nghiêm ngặt).
  • phạm vi bao gồm một loạt các bong bóng trên mạng mà mỗi hành động như một thùng chứa hoặc thùng, trong đó các định danh (biến, hàm) được khai báo. Các bong bóng này làm tổ gọn gàng bên trong nhau và việc làm tổ này được xác định tại thời điểm tác giả.

-3

Có hai loại phạm vi trong JavaScript.

  1. Phạm vi toàn cầu : biến được công bố trong phạm vi toàn cầu có thể được sử dụng ở mọi nơi trong chương trình rất trơn tru. Ví dụ:

    var carName = " BMW";
    
    // code here can use carName
    
    function myFunction() {
         // code here can use carName 
    }
  2. Phạm vi chức năng hoặc Phạm vi cục bộ: biến được khai báo trong phạm vi này chỉ có thể được sử dụng trong chức năng của chính nó. Ví dụ:

    // code here can not use carName
    function myFunction() {
       var carName = "BMW";
       // code here can use carName
    }
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.