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?
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?
Câu trả lời:
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à:
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.
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:
Một số cách định danh có thể được khai báo:
var
, let
vàconst
var
trong chế độ không nghiêm ngặt)import
các câu lệnheval
Một số định danh vị trí có thể được khai báo:
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 eval
chức năng.
Mã định danh được khai báo sử dụng let
và const
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
, const
và var
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 let
và const
khô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 đượ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 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.
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).
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.
Trong các eval
chuỗi, các biến được khai báo sử dụng var
sẽ đượ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.
Sau đây sẽ ném một ReferenceError vì những cái tên x
, y
và z
khô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 y
và z
, nhưng không phải cho x
, vì khả năng hiển thị của x
khô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
, for
và while
, 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, x
có thể nhìn thấy bên ngoài vòng lặp vì var
có 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 var
các vòng lặp. Chỉ có một trường hợp của biến được x
khai 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 5
lần thứ sáu cho console.log
vò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 undefined
vì x
có 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 let
cá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 0
thô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 ReferenceError
vì tầm nhìn của x
không bị hạn chế bởi khối; tuy nhiên, nó sẽ in undefined
vì biến chưa được khởi tạo (vì if
câ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 for
vò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 ReferenceError
vì khả năng hiển thị x
bị 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
, let
hoặ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 var
trong 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
let
và const
trong 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
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 .
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.
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ề ý this
nghĩ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ù .)
Theo truyền thống, JavaScript thực sự chỉ có hai loại phạm vi:
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.
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:
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";
Để 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 j
chỉ đượ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.
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 let
câ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 let
câ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 let
câ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
:
Đối với một cái nhìn tổng quan up-to-date trong đó các trình duyệt hỗ trợ let
tuyê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 Use
trang .
(*) 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.
Đâ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 .
Chìa khóa, theo tôi hiểu, là Javascript có phạm vi cấp độ chức năng so với phạm vi khối C phổ biến hơn.
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 let
câu lệnh :
var a = 4;
let (a = 3) {
alert(a); // 3
}
alert(a); // 4
let
.
Ý 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à window
hoặ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
Á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.
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à đó là 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.
with
câu lệnh là một dạng phạm vi khối nhưng catch
mệnh đề là một dạng phổ biến hơn nhiều (Thực tế thú vị, v8 thực hiện catch
vớ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.
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.
JavaScript chỉ có hai loại phạm vi:
var
từ 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 ->
a
và outer
chức năng ở cấp cao nhất trong chuỗi phạm vi.variable scope object
(và được bao gồm trong chuỗi phạm vi) được thêm vào với biến b
bên trong nó.Bây giờ khi một biến được a
yê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ổ.
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:
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
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);
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
}
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
Chỉ có hai loại phạm vi JavaScript:
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ú ý:
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.
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. var
là lỗ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.
const
nê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
.
let
nê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.
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());
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);
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
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à:
querySelector
như một biến độc lập sẽ trỏ đến document.querySelector
; 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 document
và 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 with
khố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 export
hoặ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ó.
Trong JavaScript có hai loại phạm vi:
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
}
}
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 var
từ 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:
// 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
, bar
và foobar
để giao diện điều khiển là những điều sau đây:
innerFunc
. Do đó, giá trị của foo được phân giải thành chuỗi innerFunc
.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 outerFunc
chúng ta có thể tìm thấy thanh biến, chứa chuỗi 'outsFunc'.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ốiconst
: 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ạiSự khác biệt lớn nhất giữa var
và let
/ const
là var
là chức năng scoped trong khi let
/ const
là 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ó let
phạ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.
Trong EcmaScript5, chủ yếu có hai phạm vi, phạm vi cục bộ và 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.
}
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
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.
Có hai loại phạm vi trong JavaScript.
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
}
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
}