[英]What is the difference between "let" and "var"?
主要區別在於范圍規則。 由var
關鍵字聲明的變量的作用域是直接函數體(因此是函數作用域),而let
變量的作用域是由{ }
表示的直接封閉塊(因此是塊作用域)。
function run() { var foo = "Foo"; let bar = "Bar"; console.log(foo, bar); // Foo Bar { var moo = "Mooo" let baz = "Bazz"; console.log(moo, baz); // Mooo Bazz } console.log(moo); // Mooo console.log(baz); // ReferenceError } run();
將let
關鍵字引入語言的原因是函數范圍令人困惑,並且是 JavaScript 中錯誤的主要來源之一。
從另一個 Stack Overflow 問題看一下這個例子:
var funcs = []; // let's create 3 functions for (var i = 0; i < 3; i++) { // and store them in funcs funcs[i] = function() { // each should log its value. console.log("My value: " + i); }; } for (var j = 0; j < 3; j++) { // and now let's run each one to see funcs[j](); }
My value: 3
每次funcs[j]();
被調用是因為匿名函數綁定到同一個變量。
人們必須創建立即調用的函數來從循環中捕獲正確的值,但這也很麻煩。
雖然使用var
關鍵字聲明的變量被提升(在代碼運行之前使用undefined
初始化),這意味着它們甚至在聲明之前就可以在其封閉范圍內訪問:
function run() { console.log(foo); // undefined var foo = "Foo"; console.log(foo); // Foo } run();
let
變量在它們的定義被評估之前不會被初始化。 在初始化之前訪問它們會導致ReferenceError
。 從塊的開始到處理初始化,該變量被稱為處於“臨時死區”。
function checkHoisting() { console.log(foo); // ReferenceError let foo = "Foo"; console.log(foo); // Foo } checkHoisting();
在頂層, let
與var
不同,不會在全局對象上創建屬性:
var foo = "Foo"; // globally scoped let bar = "Bar"; // not allowed to be globally scoped console.log(window.foo); // Foo console.log(window.bar); // undefined
在嚴格模式下, var
將允許您在同一范圍內重新聲明相同的變量,而let
會引發 SyntaxError。
'use strict'; var foo = "foo1"; var foo = "foo2"; // No problem, 'foo1' is replaced with 'foo2'. let bar = "bar1"; let bar = "bar2"; // SyntaxError: Identifier 'bar' has already been declared
let
也可以用來避免閉包問題。 它綁定新值而不是保留舊引用,如下面的示例所示。
for(var i=1; i<6; i++) { $("#div" + i).click(function () { console.log(i); }); }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <p>Clicking on each number will log to console:</p> <div id="div1">1</div> <div id="div2">2</div> <div id="div3">3</div> <div id="div4">4</div> <div id="div5">5</div>
上面的代碼演示了一個經典的 JavaScript 閉包問題。 對i
變量的引用存儲在 click 處理程序閉包中,而不是i
的實際值。
每個單擊處理程序都將引用同一個對象,因為只有一個計數器對象可以容納 6 個,因此每次單擊都會得到 6 個。
一般的解決方法是將其包裝在匿名函數中並將i
作為參數傳遞。 現在也可以通過使用let
代替var
來避免此類問題,如下面的代碼所示。
(在 Chrome 和 Firefox 50 中測試)
for(let i=1; i<6; i++) { $("#div" + i).click(function () { console.log(i); }); }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <p>Clicking on each number will log to console:</p> <div id="div1">1</div> <div id="div2">2</div> <div id="div3">3</div> <div id="div4">4</div> <div id="div5">5</div>
let
和var
有什么區別?要了解差異,請考慮以下代碼:
// 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
在這里,我們可以看到我們的變量j
只在第一個 for 循環中知道,而在之前和之后都不知道。 然而,我們的變量i
在整個函數中是已知的。
另外,考慮到塊范圍的變量在聲明之前是未知的,因為它們沒有被提升。 您也不允許在同一個塊內重新聲明同一個塊范圍的變量。 這使得塊范圍的變量比全局或功能范圍的變量更不容易出錯,全局或功能范圍的變量被提升並且在多個聲明的情況下不會產生任何錯誤。
let
安全嗎?有些人會爭辯說,將來我們將只使用 let 語句,而 var 語句將變得過時。 JavaScript 大師Kyle Simpson寫了一篇非常詳盡的文章,解釋了為什么他認為情況不會如此。
然而,今天絕對不是這樣。 事實上,我們實際上需要問自己,使用let
語句是否安全。 該問題的答案取決於您的環境:
如果您正在編寫服務器端 JavaScript 代碼 ( Node.js ),則可以安全地使用let
語句。
如果您正在編寫客戶端 JavaScript 代碼並使用基於瀏覽器的轉譯器(如Traceur或babel-standalone ),您可以安全地使用let
語句,但是您的代碼在性能方面可能不是最佳的。
如果您正在編寫客戶端 JavaScript 代碼並使用基於節點的轉譯器(如traceur shell 腳本或Babel ),您可以安全地使用let
語句。 而且因為您的瀏覽器只會知道轉譯的代碼,所以性能缺陷應該是有限的。
如果您正在編寫客戶端 JavaScript 代碼並且不使用轉譯器,則需要考慮瀏覽器支持。
還有一些瀏覽器根本不支持let
:
有關在您閱讀此答案時哪些瀏覽器支持let
語句的最新概述,請參閱Can I Use
page 。
(*) 全局和功能范圍的變量可以在聲明之前初始化和使用,因為 JavaScript 變量是被提升的。 這意味着聲明總是在范圍的頂部。
(**) 塊范圍的變量不被提升
下面是對let
關鍵字的解釋以及一些示例。
let
的工作方式與var
非常相似。 主要區別在於var
變量的作用域是整個封閉函數
Wikipedia 上的此表顯示了哪些瀏覽器支持 Javascript 1.7。
請注意,只有 Mozilla 和 Chrome 瀏覽器支持它。 IE、Safari 和其他可能沒有。
接受的答案缺少一點:
{
let a = 123;
};
console.log(a); // ReferenceError: a is not defined
let
使用let
關鍵字聲明的變量是塊作用域的,這意味着它們僅在聲明它們的塊中可用。
在頂層,使用let
聲明的變量不會在全局對象上創建屬性。
var globalVariable = 42;
let blockScopedVariable = 43;
console.log(globalVariable); // 42
console.log(blockScopedVariable); // 43
console.log(this.globalVariable); // 42
console.log(this.blockScopedVariable); // undefined
在函數內部(但在塊外部), let
與var
具有相同的范圍。
(() => {
var functionScopedVariable = 42;
let blockScopedVariable = 43;
console.log(functionScopedVariable); // 42
console.log(blockScopedVariable); // 43
})();
console.log(functionScopedVariable); // ReferenceError: functionScopedVariable is not defined
console.log(blockScopedVariable); // ReferenceError: blockScopedVariable is not defined
在塊內使用let
聲明的變量不能在該塊外訪問。
{
var globalVariable = 42;
let blockScopedVariable = 43;
console.log(globalVariable); // 42
console.log(blockScopedVariable); // 43
}
console.log(globalVariable); // 42
console.log(blockScopedVariable); // ReferenceError: blockScopedVariable is not defined
使用let
in 循環聲明的變量只能在該循環內引用。
for (var i = 0; i < 3; i++) {
var j = i * 2;
}
console.log(i); // 3
console.log(j); // 4
for (let k = 0; k < 3; k++) {
let l = k * 2;
}
console.log(typeof k); // undefined
console.log(typeof l); // undefined
// Trying to do console.log(k) or console.log(l) here would throw a ReferenceError.
如果在循環中使用let
而不是var
,則每次迭代都會得到一個新變量。 這意味着您可以安全地在循環內使用閉包。
// Logs 3 thrice, not what we meant.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// Logs 0, 1 and 2, as expected.
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 0);
}
由於臨時死區,使用let
聲明的變量在聲明之前無法訪問。 嘗試這樣做會引發錯誤。
console.log(noTDZ); // undefined
var noTDZ = 43;
console.log(hasTDZ); // ReferenceError: hasTDZ is not defined
let hasTDZ = 42;
您不能使用let
多次聲明同一個變量。 您也不能使用let
聲明具有與使用var
聲明的另一個變量相同的標識符的變量。
var a;
var a; // Works fine.
let b;
let b; // SyntaxError: Identifier 'b' has already been declared
var c;
let c; // SyntaxError: Identifier 'c' has already been declared
const
const
與let
非常相似——它是塊范圍的並且有 TDZ。 然而,有兩件事是不同的。
不能重新分配使用const
聲明的變量。
const a = 42;
a = 43; // TypeError: Assignment to constant variable.
請注意,這並不意味着該值是不可變的。 它的屬性仍然可以更改。
const obj = {};
obj.a = 42;
console.log(obj.a); // 42
如果你想擁有一個不可變的對象,你應該使用Object.freeze()
。
const obj = Object.freeze({a: 40});
obj.a = 42;
console.log(obj.a); // 40
console.log(obj.b); // undefined
使用const
聲明變量時,您始終必須指定一個值。
const a; // SyntaxError: Missing initializer in const declaration
這是兩者之間差異的示例(對 chrome 的支持剛剛開始):
如您所見, var j
變量的值仍在 for 循環范圍(塊范圍)之外,但let i
變量在 for 循環范圍之外未定義。
"use strict"; console.log("var:"); for (var j = 0; j < 2; j++) { console.log(j); } console.log(j); console.log("let:"); for (let i = 0; i < 2; i++) { console.log(i); } console.log(i);
主要區別在於作用域的不同,而let只能在它聲明的作用域內使用,例如在 for 循環中,而var可以在循環外訪問。 從MDN中的文檔(也來自 MDN 的示例):
let允許您聲明范圍限制在使用它的塊、語句或表達式的變量。 這與var關鍵字不同,它在全局范圍內定義一個變量,或者在本地定義一個整個函數,而不考慮塊范圍。
let聲明的變量的作用域是定義它們的塊,以及任何包含的子塊。 這樣, let的工作方式與var非常相似。 主要區別在於var變量的作用域是整個封閉函數:
function varTest() {
var x = 1;
if (true) {
var x = 2; // same variable!
console.log(x); // 2
}
console.log(x); // 2
}
function letTest() {
let x = 1;
if (true) {
let x = 2; // different variable
console.log(x); // 2
}
console.log(x); // 1
}`
在程序和函數的頂層, let與var不同,不會在全局對象上創建屬性。 例如:
var x = 'global';
let y = 'global';
console.log(this.x); // "global"
console.log(this.y); // undefined
在塊內使用時,讓變量的范圍限制在該塊內。 注意作用域在聲明它的函數內部的var之間的區別。
var a = 1;
var b = 2;
if (a === 1) {
var a = 11; // the scope is global
let b = 22; // the scope is inside the if-block
console.log(a); // 11
console.log(b); // 22
}
console.log(a); // 11
console.log(b); // 2
也不要忘記它的 ECMA6 功能,所以它還沒有完全支持,所以最好總是使用 Babel 等將它轉換為 ECMA5 ......有關訪問babel 網站的更多信息
有一些細微的差別—— let
作用域的行為更像變量作用域在或多或少的任何其他語言中所做的。
例如,它的范圍是封閉塊,它們在聲明之前不存在,等等。
然而值得注意的是, let
只是較新的 Javascript 實現的一部分,並且具有不同程度的瀏覽器支持。
變量不提升
let
不會提升到它們出現的塊的整個范圍。相比之下, var
可以提升如下。
{ console.log(cc); // undefined. Caused by hoisting var cc = 23; } { console.log(bb); // ReferenceError: bb is not defined let bb = 23; }
實際上,根據@Bergi, var
和let
都被提升了。
垃圾收集
let
的塊作用域與閉包和垃圾收集以回收內存有關。 考慮,
function process(data) { //... } var hugeData = { .. }; process(hugeData); var btn = document.getElementById("mybutton"); btn.addEventListener( "click", function click(evt){ //.... });
click
處理程序回調根本不需要hugeData
變量。 理論上,在process(..)
運行之后,巨大的數據結構hugeData
可能會被垃圾回收。 然而,有可能一些 JS 引擎仍然必須保持這個巨大的結構,因為click
函數在整個范圍內都有一個閉包。
但是,塊作用域可以使這個龐大的數據結構被垃圾回收。
function process(data) { //... } { // anything declared inside this block can be garbage collected let hugeData = { .. }; process(hugeData); } var btn = document.getElementById("mybutton"); btn.addEventListener( "click", function click(evt){ //.... });
let
循環
循環中的let
可以將其重新綁定到循環的每次迭代,確保從上一次循環迭代結束時重新為其分配值。 考慮,
// print '5' 5 times for (var i = 0; i < 5; ++i) { setTimeout(function () { console.log(i); }, 1000); }
但是,將var
替換為let
// print 1, 2, 3, 4, 5. now for (let i = 0; i < 5; ++i) { setTimeout(function () { console.log(i); }, 1000); }
因為let
用這些名稱創建一個新的詞法環境 a) 初始化表達式 b) 每次迭代(之前評估增量表達式),更多細節在這里。
不同之處在於每個聲明的變量的范圍。
在實踐中,范圍差異有許多有用的后果:
let
變量僅在其最近的封閉塊( { ... }
)中可見。let
變量只能在聲明變量之后出現的代碼行中使用(即使它們被提升了!)。let
變量不能被后續的var
或let
重新聲明。let
變量不會添加到全局window
對象中。let
變量很容易與閉包一起使用(它們不會導致競爭條件)。 let
施加的限制降低了變量的可見性,並增加了提前發現意外名稱沖突的可能性。 這使得跟蹤和推理變量變得更容易,包括它們的可達性(幫助回收未使用的內存)。
因此,當在大型程序中使用時,或者當獨立開發的框架以新的和意想不到的方式組合時, let
變量不太可能引起問題。
如果您確定在循環中使用閉包 (#5) 或在代碼中聲明外部可見的全局變量 (#4) 時需要單一綁定效果,則var
可能仍然有用。 如果export
遷移出編譯器空間並遷移到核心語言中,則可能會取代使用var
進行導出。
1. 在最近的封閉塊之外不使用:此代碼塊將引發引用錯誤,因為x
的第二次使用發生在用let
聲明它的塊之外:
{
let x = 1;
}
console.log(`x is ${x}`); // ReferenceError during parsing: "x is not defined".
相反,使用var
的相同示例有效。
2、申報前不得使用:
此代碼塊將在代碼運行之前拋出ReferenceError
,因為x
在聲明之前使用:
{
x = x + 1; // ReferenceError during parsing: "x is not defined".
let x;
console.log(`x is ${x}`); // Never runs.
}
相比之下,使用var
的相同示例在解析和運行時不會拋出任何異常。
3、不重新聲明:下面的代碼演示了用let
聲明的變量以后可能不會重新聲明:
let x = 1;
let x = 2; // SyntaxError: Identifier 'x' has already been declared
4.全局不附加到window
:
var button = "I cause accidents because my name is too common.";
let link = "Though my name is common, I am harder to access from other JS files.";
console.log(link); // OK
console.log(window.link); // undefined (GOOD!)
console.log(window.button); // OK
5. 易於使用閉包:用var
聲明的變量不能很好地與循環內的閉包一起使用。 這是一個簡單的循環,它輸出變量i
在不同時間點的值序列:
for (let i = 0; i < 5; i++) {
console.log(`i is ${i}`), 125/*ms*/);
}
具體來說,這會輸出:
i is 0
i is 1
i is 2
i is 3
i is 4
在 JavaScript 中,我們經常在比創建它們的時間晚得多的時候使用變量。 當我們通過傳遞給setTimeout
的閉包來延遲輸出來證明這一點時:
for (let i = 0; i < 5; i++) {
setTimeout(_ => console.log(`i is ${i}`), 125/*ms*/);
}
...只要我們堅持let
,輸出就保持不變。 相反,如果我們使用var i
代替:
for (var i = 0; i < 5; i++) {
setTimeout(_ => console.log(`i is ${i}`), 125/*ms*/);
}
...循環意外輸出“i is 5”五次:
i is 5
i is 5
i is 5
i is 5
i is 5
這是一個添加到其他人已經編寫的內容的示例。 假設您要創建一個函數數組adderFunctions
,其中每個函數接受一個 Number 參數並返回參數和函數在數組中的索引之和。 嘗試使用var
關鍵字通過循環生成adderFunctions
不會像人們可能天真期望的那樣工作:
// An array of adder functions.
var adderFunctions = [];
for (var i = 0; i < 1000; i++) {
// We want the function at index i to add the index to its argument.
adderFunctions[i] = function(x) {
// What is i bound to here?
return x + i;
};
}
var add12 = adderFunctions[12];
// Uh oh. The function is bound to i in the outer scope, which is currently 1000.
console.log(add12(8) === 20); // => false
console.log(add12(8) === 1008); // => true
console.log(i); // => 1000
// It gets worse.
i = -8;
console.log(add12(8) === 0); // => true
上述過程不會生成所需的函數數組,因為i
的范圍超出了創建每個函數的for
塊的迭代。 相反,在循環結束時,每個函數閉包中的i
指的是adderFunctions
中每個匿名函數在循環結束時的i
值(1000)。 這根本不是我們想要的:我們現在在內存中有一個包含 1000 個不同函數的數組,它們的行為完全相同。 如果我們隨后更新i
的值,突變將影響所有adderFunctions
。
但是,我們可以使用let
關鍵字再試一次:
// Let's try this again.
// NOTE: We're using another ES6 keyword, const, for values that won't
// be reassigned. const and let have similar scoping behavior.
const adderFunctions = [];
for (let i = 0; i < 1000; i++) {
// NOTE: We're using the newer arrow function syntax this time, but
// using the "function(x) { ..." syntax from the previous example
// here would not change the behavior shown.
adderFunctions[i] = x => x + i;
}
const add12 = adderFunctions[12];
// Yay! The behavior is as expected.
console.log(add12(8) === 20); // => true
// i's scope doesn't extend outside the for loop.
console.log(i); // => ReferenceError: i is not defined
這一次, i
在for
循環的每次迭代中都會反彈。 現在,每個函數在創建函數時都保留i
的值,並且adderFunctions
的行為符合預期。
現在,圖像混合這兩種行為,您可能會明白為什么不建議在同一腳本中將較新的let
和const
與較舊的var
混合。 這樣做可能會導致一些非常混亂的代碼。
const doubleAdderFunctions = [];
for (var i = 0; i < 1000; i++) {
const j = i;
doubleAdderFunctions[i] = x => x + i + j;
}
const add18 = doubleAdderFunctions[9];
const add24 = doubleAdderFunctions[12];
// It's not fun debugging situations like this, especially when the
// code is more complex than in this example.
console.log(add18(24) === 42); // => false
console.log(add24(18) === 42); // => false
console.log(add18(24) === add24(18)); // => false
console.log(add18(24) === 2018); // => false
console.log(add24(18) === 2018); // => false
console.log(add18(24) === 1033); // => true
console.log(add24(18) === 1030); // => true
不要讓這種情況發生在你身上。 使用棉絨。
注意:這是一個教學示例,旨在演示循環中的
var
/let
行為以及易於理解的函數閉包。 這將是一種可怕的添加數字的方法。 但是在其他上下文中可能會在現實世界中遇到在匿名函數閉包中捕獲數據的一般技術。 YMMV。
可能以下兩個函數顯示差異:
function varTest() {
var x = 31;
if (true) {
var x = 71; // Same variable!
console.log(x); // 71
}
console.log(x); // 71
}
function letTest() {
let x = 31;
if (true) {
let x = 71; // Different variable
console.log(x); // 71
}
console.log(x); // 31
}
var
和let
的主要區別在於用var
聲明的變量是函數作用域的。 而用let
聲明的函數是塊作用域的。 例如:
function testVar () {
if(true) {
var foo = 'foo';
}
console.log(foo);
}
testVar();
// logs 'foo'
function testLet () {
if(true) {
let bar = 'bar';
}
console.log(bar);
}
testLet();
// reference error
// bar is scoped to the block of the if statement
帶有var
的變量:
當第一個函數testVar
被調用時,用var
聲明的變量 foo 仍然可以在if
語句之外訪問。 這個變量foo
在testVar
函數范圍內的任何地方都可用。
帶有let
的變量:
當第二個函數testLet
被調用時,用let
聲明的變量 bar 只能在if
語句中訪問。 因為用let
聲明的變量是塊范圍的(其中塊是大括號之間的代碼,例如if{}
, for{}
, function{}
)。
let
變量不要被提升: var
和let
之間的另一個區別是用let
聲明的變量不會被提升。 一個例子是說明這種行為的最佳方式:
帶有let
的變量不會被提升:
console.log(letVar);
let letVar = 10;
// referenceError, the variable doesn't get hoisted
帶有var
的變量會被提升:
console.log(varVar);
var varVar = 10;
// logs undefined, the variable gets hoisted
let
沒有附加到window
: 在全局范圍內用let
聲明的變量(即不在函數中的代碼)不會被添加為全局window
對象的屬性。 例如(此代碼在全局范圍內):
var bar = 5;
let foo = 10;
console.log(bar); // logs 5
console.log(foo); // logs 10
console.log(window.bar);
// logs 5, variable added to window object
console.log(window.foo);
// logs undefined, variable not added to window object
let
什么時候應該在var
上使用?
盡可能使用let
over var
,因為它的范圍更具體。 這減少了在處理大量變量時可能發生的潛在命名沖突。 當您希望全局變量顯式位於window
對象上時,可以使用var
(如果確實有必要,請務必仔細考慮)。
看起來,至少在 Visual Studio 2015 和 TypeScript 1.5 中,“var”允許在一個塊中多次聲明相同的變量名,而“let”則不允許。
這不會產生編譯錯誤:
var x = 1;
var x = 2;
這將:
let x = 1;
let x = 2;
let
很有趣,因為它允許我們做這樣的事情:
(() => {
var count = 0;
for (let i = 0; i < 2; ++i) {
for (let i = 0; i < 2; ++i) {
for (let i = 0; i < 2; ++i) {
console.log(count++);
}
}
}
})();
這導致計數 [0, 7]。
然而
(() => {
var count = 0;
for (var i = 0; i < 2; ++i) {
for (var i = 0; i < 2; ++i) {
for (var i = 0; i < 2; ++i) {
console.log(count++);
}
}
}
})();
僅計數 [0, 1]。
這個解釋取自我在Medium上寫的一篇文章:
提升是一種 JavaScript 機制,其中變量和函數聲明由解析器移動到其作用域的頂部,解析器在 JavaScript 解釋器開始實際代碼執行之前將源代碼讀入中間表示。 因此,實際上在哪里聲明變量或函數並不重要,它們將被移動到其作用域的頂部,而不管它們的作用域是全局的還是局部的。 這意味着
console.log (hi); var hi = "say hi";
實際上被解釋為
var hi = undefined; console.log (hi); hi = "say hi";
因此,正如我們剛才所見,
var
變量被提升到其作用域的頂部,並使用 undefined 的值進行初始化,這意味着我們可以在代碼中實際聲明它們之前實際分配它們的值,如下所示:hi = “say hi” console.log (hi); // say hi var hi;
關於函數聲明,我們可以在實際聲明它們之前調用它們,如下所示:
sayHi(); // Hi function sayHi() { console.log('Hi'); };
另一方面,函數表達式沒有被提升,所以我們會得到以下錯誤:
sayHi(); //Output: "TypeError: sayHi is not a function var sayHi = function() { console.log('Hi'); };
ES6 為 JavaScript 開發人員引入了
let
和const
關鍵字。 雖然let
和const
是塊作用域而不是函數作用域為var
,但在討論它們的提升行為時不應該有所作為。 我們將從最后開始,JavaScript 提升let
和const
。console.log(hi); // Output: Cannot access 'hi' before initialization let hi = 'Hi';
正如我們在上面看到的,
let
不允許我們使用未聲明的變量,因此解釋器顯式輸出一個引用錯誤,指示在初始化之前無法訪問hi
變量。 如果我們把上面的let
改成const
也會出現同樣的錯誤console.log(hi); // Output: Cannot access 'hi' before initialization const hi = 'Hi';
因此,最重要的是,JavaScript 解析器搜索變量聲明和函數,並在代碼執行之前將它們提升到其作用域的頂部,並在內存中為它們賦值,以便解釋器在執行代碼時遇到它們,他會識別它們並且將能夠使用分配的值執行代碼。 使用
let
或const
聲明的變量在執行開始時保持未初始化,而使用var
聲明的變量正在使用undefined
值初始化。
var
是全局范圍(可提升)變量。
let
和const
是塊作用域。
測試.js
{ let l = 'let'; const c = 'const'; var v = 'var'; v2 = 'var 2'; } console.log(v, this.v); console.log(v2, this.v2); console.log(l); // ReferenceError: l is not defined console.log(c); // ReferenceError: c is not defined
如果我正確閱讀了規范,那么幸運的是,我們也可以利用let
來避免用於模擬私有成員的自調用函數——一種流行的設計模式,它降低了代碼的可讀性,使調試復雜化,沒有增加真正的代碼保護或其他好處——除了可能滿足有人對語義的渴望,所以停止使用它。 /咆哮
var SomeConstructor;
{
let privateScope = {};
SomeConstructor = function SomeConstructor () {
this.someProperty = "foo";
privateScope.hiddenProperty = "bar";
}
SomeConstructor.prototype.showPublic = function () {
console.log(this.someProperty); // foo
}
SomeConstructor.prototype.showPrivate = function () {
console.log(privateScope.hiddenProperty); // bar
}
}
var myInstance = new SomeConstructor();
myInstance.showPublic();
myInstance.showPrivate();
console.log(privateScope.hiddenProperty); // error
請參閱“ 模擬私有接口”
當使用let
let
關鍵字將變量聲明附加到它包含的任何塊(通常是{ .. }
對)的范圍內。換句話說, let
隱式劫持了任何塊的變量聲明范圍。
let
變量不能在window
對象中被訪問,因為它們不能被全局訪問。
function a(){
{ // this is the Max Scope for let variable
let x = 12;
}
console.log(x);
}
a(); // Uncaught ReferenceError: x is not defined
使用var
時
ES5 中的var
和 variables 在函數中具有作用域,這意味着變量在函數內有效,而不是在函數本身之外。
var
變量可以在window
對象中訪問,因為它們不能被全局訪問。
function a(){ // this is the Max Scope for var variable
{
var x = 12;
}
console.log(x);
}
a(); // 12
如果您想了解更多,請繼續閱讀下面
關於范圍的最著名的面試問題之一也可以滿足let
和var
的確切用法,如下所示;
使用時let
for (let i = 0; i < 10 ; i++) {
setTimeout(
function a() {
console.log(i); //print 0 to 9, that is literally AWW!!!
},
100 * i);
}
這是因為當使用let
時,對於每個循環迭代,變量都是有作用域的並且有自己的副本。
使用var
時
for (var i = 0; i < 10 ; i++) {
setTimeout(
function a() {
console.log(i); //print 10 times 10
},
100 * i);
}
這是因為在使用var
時,對於每個循環迭代,變量都是有作用域的並且具有共享副本。
let
的一些技巧:
1.
let statistics = [16, 170, 10];
let [age, height, grade] = statistics;
console.log(height)
2.
let x = 120,
y = 12;
[x, y] = [y, x];
console.log(`x: ${x} y: ${y}`);
3.
let node = {
type: "Identifier",
name: "foo"
};
let { type, name, value } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value); // undefined
let node = {
type: "Identifier"
};
let { type: localType, name: localName = "bar" } = node;
console.log(localType); // "Identifier"
console.log(localName); // "bar"
let
:let jar = {
numberOfCookies: 10,
get cookies() {
return this.numberOfCookies;
},
set cookies(value) {
this.numberOfCookies = value;
}
};
console.log(jar.cookies)
jar.cookies = 7;
console.log(jar.cookies)
下面顯示了 'let' 和 'var' 在范圍內的不同之處:
let gfoo = 123;
if (true) {
let gfoo = 456;
}
console.log(gfoo); // 123
var hfoo = 123;
if (true) {
var hfoo = 456;
}
console.log(hfoo); // 456
由let
定義的gfoo
最初是在全局范圍內,當我們在if clause
中再次聲明gfoo
時,它的范圍發生了變化,並且當一個新值被分配給該范圍內的變量時,它不會影響全局范圍。
而由var
定義的hfoo
最初是在全局范圍內,但是當我們在if clause
中聲明它時,它考慮了全局范圍 hfoo,盡管 var 再次被用來聲明它。 當我們重新分配它的值時,我們看到全局范圍 hfoo 也受到了影響。 這是主要區別。
我剛剛遇到一個用例,我必須使用var
而不是let
來引入新變量。 這是一個案例:
我想用動態變量名創建一個新變量。
let variableName = 'a';
eval("let " + variableName + '= 10;');
console.log(a); // this doesn't work
var variableName = 'a';
eval("var " + variableName + '= 10;');
console.log(a); // this works
上面的代碼不起作用,因為eval
引入了一個新的代碼塊。 使用var
的聲明將在此代碼塊之外聲明一個變量,因為var
在函數范圍內聲明了一個變量。
另一方面, let
在塊作用域中聲明一個變量。 因此a
變量僅在eval
塊中可見。
let 是 es6 的一部分。 這些功能將以簡單的方式解釋差異。
function varTest() {
var x = 1;
if (true) {
var x = 2; // same variable!
console.log(x); // 2
}
console.log(x); // 2
}
function letTest() {
let x = 1;
if (true) {
let x = 2; // different variable
console.log(x); // 2
}
console.log(x); // 1
}
讓 vs var。 這都是關於范圍的。
var 變量是全局的,基本上可以在任何地方訪問,而let 變量不是全局的,只存在直到右括號殺死它們。
請參閱下面的示例,並注意 Lion (let) 變量在兩個 console.logs 中的行為方式有何不同; 它超出了第二個 console.log 的范圍。
var cat = "cat";
let dog = "dog";
var animals = () => {
var giraffe = "giraffe";
let lion = "lion";
console.log(cat); //will print 'cat'.
console.log(dog); //will print 'dog', because dog was declared outside this function (like var cat).
console.log(giraffe); //will print 'giraffe'.
console.log(lion); //will print 'lion', as lion is within scope.
}
console.log(giraffe); //will print 'giraffe', as giraffe is a global variable (var).
console.log(lion); //will print UNDEFINED, as lion is a 'let' variable and is now out of scope.
“var”是全局范圍(可提升)變量。
“let”和“const”是塊作用域。
正如剛才提到的:
區別在於范圍。
var
的范圍是最近的功能塊,而let
的范圍是最近的封閉塊,它可以小於一個功能塊。 如果在任何塊之外,兩者都是全局的。讓我們看一個例子:
示例 1:
在我的兩個示例中,我都有一個函數myfunc
。 myfunc
包含一個變量myvar
等於 10。在我的第一個示例中,我檢查myvar
是否等於 10 ( myvar==10
)。 如果是,我再次使用var
關鍵字聲明一個變量myvar
(現在我有兩個 myvar 變量)並為其分配一個新值(20)。 在下一行中,我在控制台上打印它的值。 在條件塊之后,我再次在控制台上打印myvar
的值。 如果您查看myfunc
的輸出, myvar
的值等於 20。
示例 2 :在我的第二個示例中,我沒有在條件塊中使用var
關鍵字,而是使用let
關鍵字聲明myvar
。 現在,當我調用myfunc
時,我得到兩個不同的輸出: myvar=20
和myvar=10
。
所以區別很簡單,即它的范圍。
現在我認為使用let
可以更好地為語句塊定義變量范圍:
function printnums()
{
// i is not accessible here
for(let i = 0; i <10; i+=)
{
console.log(i);
}
// i is not accessible here
// j is accessible here
for(var j = 0; j <10; j++)
{
console.log(j);
}
// j is accessible here
}
我認為人們會在這里開始使用 let ,這樣他們在 JavaScript 中就會像其他語言、Java、C# 等一樣擁有類似的作用域。
對 JavaScript 中的作用域沒有清晰理解的人通常會更早地犯錯誤。
不支持使用let
提升。
使用這種方法,JavaScript 中存在的錯誤將被消除。
請參閱ES6 In Depth: let 和 const以更好地理解它。
我想將這些關鍵字鏈接到執行上下文,因為執行上下文在所有這些中都很重要。 執行上下文有兩個階段:創建階段和執行階段。 此外,每個執行上下文都有一個變量環境和外部環境(它的詞法環境)。
在執行上下文的創建階段,var、let 和 const 仍將其變量存儲在內存中,並且在給定執行上下文的變量環境中具有未定義的值。 區別在於執行階段。 如果您在為其賦值之前使用使用 var 定義的變量的引用,它將只是未定義的。 不會引發異常。
但是,在聲明之前,您不能引用使用 let 或 const 聲明的變量。 如果您在聲明之前嘗試使用它,那么在執行上下文的執行階段將引發異常。 現在該變量仍將在內存中,由執行上下文的創建階段提供,但引擎不允許您使用它:
function a(){
b;
let b;
}
a();
> Uncaught ReferenceError: b is not defined
使用 var 定義的變量,如果引擎在當前執行上下文的變量環境中找不到該變量,那么它將沿着作用域鏈(外部環境)檢查該變量的外部環境變量環境。 如果在那里找不到它,它將繼續搜索范圍鏈。 let 和 const 不是這種情況。
let 的第二個特點是它引入了塊作用域。 塊由花括號定義。 示例包括功能塊、if 塊、for 塊等。當您在塊內使用 let 聲明變量時,該變量僅在塊內可用。 實際上,每次運行該塊時,例如在 for 循環中,它都會在內存中創建一個新變量。
ES6 還引入了 const 關鍵字來聲明變量。 const 也是塊作用域。 let 和 const 的區別在於 const 變量需要使用初始化器來聲明,否則會產生錯誤。
最后,當涉及到執行上下文時,用 var 定義的變量將附加到“this”對象。 在全局執行上下文中,這將是瀏覽器中的窗口對象。 這不是 let 或 const 的情況。
由於我目前正在嘗試深入了解 JavaScript,因此我將分享我的簡短研究,其中包含一些已經討論過的優秀作品以及其他一些從不同角度看的細節。
如果我們了解function和block scope之間的區別,理解var和let之間的區別會更容易。
讓我們考慮以下情況:
(function timer() {
for(var i = 0; i <= 5; i++) {
setTimeout(function notime() { console.log(i); }, i * 1000);
}
})();
Stack VariableEnvironment //one VariablEnvironment for timer();
// when the timer is out - the value will be the same value for each call
5. [setTimeout, i] [i=5]
4. [setTimeout, i]
3. [setTimeout, i]
2. [setTimeout, i]
1. [setTimeout, i]
0. [setTimeout, i]
####################
(function timer() {
for (let i = 0; i <= 5; i++) {
setTimeout(function notime() { console.log(i); }, i * 1000);
}
})();
Stack LexicalEnvironment - each iteration has a new lexical environment
5. [setTimeout, i] [i=5]
LexicalEnvironment
4. [setTimeout, i] [i=4]
LexicalEnvironment
3. [setTimeout, i] [i=3]
LexicalEnvironment
2. [setTimeout, i] [i=2]
LexicalEnvironment
1. [setTimeout, i] [i=1]
LexicalEnvironment
0. [setTimeout, i] [i=0]
當timer()
被調用時,將創建一個ExecutionContext ,它將包含變量環境和與每次迭代對應的所有詞匯環境。
還有一個更簡單的例子
功能范圍
function test() {
for(var z = 0; z < 69; z++) {
//todo
}
//z is visible outside the loop
}
塊范圍
function test() {
for(let z = 0; z < 69; z++) {
//todo
}
//z is not defined :(
}
這篇文章明確定義了var、let和const的區別
const
是一個不會重新分配標識符的信號。
let
, 是可以重新分配變量的信號,例如循環中的計數器或算法中的值交換。 它還表明該變量將僅在定義它的塊中使用,這並不總是整個包含函數。
var
現在是在 JavaScript 中定義變量時可用的最弱信號。 變量可能會或可能不會被重新分配,並且變量可能會或可能不會用於整個函數,或者僅用於塊或循環的目的。
https://medium.com/javascript-scene/javascript-es6-var-let-or-const-ba58b8dcde75#.esmkpbg9b
我認為這些術語和大多數示例有點壓倒性,我個人遇到的主要問題是理解什么是“塊”。 在某些時候我意識到,一個塊可以是除IF
語句之外的任何大括號。 函數或循環的左括號{
將定義一個新塊,其中任何用let
定義的內容,在同一事物(函數或循環)的右括號}
之后將不可用; 考慮到這一點,更容易理解:
let msg = "Hello World"; function doWork() { // msg will be available since it was defined above this opening bracket! let friends = 0; console.log(msg); // with VAR though: for (var iCount2 = 0; iCount2 < 5; iCount2++) {} // iCount2 will be available after this closing bracket! console.log(iCount2); for (let iCount1 = 0; iCount1 < 5; iCount1++) {} // iCount1 will not be available behind this closing bracket, it will return undefined console.log(iCount1); } // friends will no be available after this closing bracket! doWork(); console.log(friends);
以前,JavaScript中只有兩個作用域,即功能性和全局性。 現在,使用' let
'關鍵字,JavaScript引入了block-level
變量。
為了全面了解'let'關鍵字, ES6:'let'關鍵字在JavaScript中聲明變量將很有幫助。
var --> Function scope
let --> Block scope
const --> Block scope
變量
在此代碼示例中,變量i
使用var
聲明。 因此,它有一個function scope 。 這意味着您只能從function x
內部訪問i
。 您無法從function x
外部讀取它
function x(){ var i = 100; console.log(i); // 100 } console.log(i); // Error. You can't do this x();
在此示例中,您可以看到i
在if
塊中聲明。 但它是使用var
聲明的。 因此,它得到 function scope。 這意味着您仍然可以在function x
中訪問變量i
。 因為var
總是作用於函數。 即使在if
塊中聲明了變量i
,由於它使用var
,它的范圍也僅限於父function x
。
function x(){ if(true){ var i = 100; } console.log(i); } x();
現在變量i
在function y
中聲明。 因此, i
的范圍為function y
。 您可以在function y
中訪問i
。 但不是來自外部function y
。
function x(){ function y(){ var i = 100; console.log(i); } y(); } x();
function x(){ function y(){ var i = 100; } console.log(i); // ERROR } x();
讓,常數
let 和 const 有塊 scope。
const
和let
行為相同。 但不同的是,當您將值分配給const
時,您無法重新分配。 但是您可以使用let
重新分配值。
在此示例中,變量i
在if
塊中聲明。 所以它只能從if
塊內部訪問。 我們無法從if
塊之外訪問它。 (這里的const
工作與let
相同)
if(true){ let i = 100; console.log(i); // Output: 100 } console.log(i); // Error
function x(){ if(true){ let i = 100; console.log(i); // Output: 100 } console.log(i); // Error } x();
(let, const)
與var
的另一個區別是您可以在聲明之前訪問var
定義的變量。 它會給你undefined
。 但是如果你用let
或const
定義的變量來做這件事,它會給你一個錯誤。
console.log(x); var x = 100;
console.log(x); // ERROR let x = 100;
在MDN中查看此鏈接
let x = 1;
if (x === 1) {
let x = 2;
console.log(x);
// expected output: 2
}
console.log(x);
// expected output: 1
var變量的作用域是函數作用域和全局作用域,而let變量作用域是塊作用域(大括號 {})
const myFun=()=> {
var str1 = "hello";
let str2 = "program";
console.log(str1, str2); // hello program
{
var myvar1 = "hiii"
let myvar2 = "ooo";
console.log(myvar1, myvar2); // hiii ooo
}
console.log(myvar1); // hiii
console.log(myvar2); // ReferenceError
}
console.log(myvar1); // not defined
myFun();
Var
在 ES6 出現之前,var 聲明占主導地位。 但是,使用 var 聲明的變量存在一些問題。 這就是為什么有必要出現新的聲明變量的方法。 首先,在討論這些問題之前,讓我們更多地了解 var。
Scope of var
Scope 本質上意味着這些變量可以在哪里使用。 var 聲明是全局范圍的或函數/本地范圍的。
當 var 變量在 function 之外聲明時,scope 是全局的。 這意味着在 function 塊之外用 var 聲明的任何變量都可用於整個 window。
當在 function 中聲明 var 時,它的作用域是 function。 這意味着它可用並且只能在 function 內訪問。
要進一步了解,請查看下面的示例。
var greeter = "hey hi";
function newFunction() {
var hello = "hello";
}
在這里, greeter 是全局范圍的,因為它存在於 function 之外,而 hello 是 function 范圍的。 所以我們不能在 function 之外訪問變量 hello。 所以如果我們這樣做:
var tester = "hey hi";
function newFunction() {
var hello = "hello";
}
console.log(hello); // error: hello is not defined
我們將收到一個錯誤,這是由於 hello 在 function 之外不可用。
var variables can be re-declared and updated
這意味着我們可以在同一個 scope 中執行此操作,並且不會出錯。
var greeter = "hey hi";
var greeter = "say Hello instead";
這也是
var greeter = "hey hi";
greeter = "say Hello instead";
Hoisting of var
提升是一種 JavaScript 機制,其中變量和 function 聲明在代碼執行之前移動到其 scope 的頂部。 這意味着如果我們這樣做:
console.log (greeter);
var greeter = "say hello"
它被解釋為:
var greeter;
console.log(greeter); // greeter is undefined
greeter = "say hello"
因此 var 變量被提升到其 scope 的頂部,並使用未定義的值進行初始化。
Problem with var
var 有一個弱點。 我將使用下面的例子來解釋:
var greeter = "hey hi";
var times = 4;
if (times > 3) {
var greeter = "say Hello instead";
}
console.log(greeter) // "say Hello instead"
因此,由於 times > 3 返回 true,greeter 被重新定義為“say Hello instead”。 如果您有意要重新定義 greeter,這不是問題,但當您沒有意識到之前已經定義了一個變量 greeter 時,它就會成為問題。
如果你在代碼的其他部分使用了 greeter,你可能會驚訝於你可能得到的 output。 這可能會導致您的代碼中出現很多錯誤。 這就是為什么 let 和 const 是必要的。
Let
let 現在是變量聲明的首選。 這並不奇怪,因為它是對 var 聲明的改進。 它還解決了我們剛剛介紹的 var 問題。 讓我們考慮一下為什么會這樣。
let is block scoped
塊是由 {} 界定的代碼塊。 一個塊存在於花括號中。 花括號內的任何內容都是一個塊。
因此,在帶有 let 的塊中聲明的變量只能在該塊中使用。 讓我用一個例子來解釋一下:
let greeting = "say Hi";
let times = 4;
if (times > 3) {
let hello = "say Hello instead";
console.log(hello);// "say Hello instead"
}
console.log(hello) // hello is not defined
我們看到在它的塊(定義它的花括號)之外使用 hello 會返回一個錯誤。 這是因為 let 變量是塊作用域的。
let can be updated but not re-declared.
就像 var 一樣,用 let 聲明的變量可以在其 scope 中更新。 與 var 不同,let 變量不能在其 scope 中重新聲明。 所以雖然這會起作用:
let greeting = "say Hi";
greeting = "say Hello instead";
這將返回一個錯誤:
let greeting = "say Hi";
let greeting = "say Hello instead"; // error: Identifier 'greeting' has already been declared
但是,如果同一個變量定義在不同的作用域,就不會報錯:
let greeting = "say Hi";
if (true) {
let greeting = "say Hello instead";
console.log(greeting); // "say Hello instead"
}
console.log(greeting); // "say Hi"
為什么沒有錯誤? 這是因為兩個實例都被視為不同的變量,因為它們具有不同的范圍。
這一事實使 let 成為比 var 更好的選擇。 使用 let 時,如果您以前使用過變量名稱,則不必擔心,因為變量僅存在於其 scope 中。
此外,由於不能在 scope 中多次聲明一個變量,因此前面討論的 var 出現的問題不會發生。
Hoisting of let
就像 var 一樣,讓聲明被提升到頂部。 與初始化為 undefined 的 var 不同,let 關鍵字未初始化。 所以如果你在聲明之前嘗試使用 let 變量,你會得到一個引用錯誤。
ECMAScript 6添加了一個關鍵字來聲明除“let”之外的“const”以外的變量。
在“var”之上引入“let”和“const”的主要目標是擁有塊作用域而不是傳統的詞法作用域。 本文非常簡要地解釋了 "var" 和 "let" 之間的區別,並且還涵蓋了關於 "const" 的討論。
const name = 'Max'; let age = 33; var hasHobbies = true; name = 'Maximilian'; age = 34; hasHobbies = false; const summarizeUser = (userName, userAge, userHasHobby) => { return ( 'Name is ' + userName + ', age is ' + userAge + ' and the user has hobbies: ' + userHasHobby ); } console.log(summarizeUser(name, age, hasHobbies));
從運行上面的代碼可以看出,當你嘗試改變const
變量時,你會得到一個錯誤:
試圖覆蓋常量“名稱”。
或者
TypeError:對 const 'name' 的分配無效。
但是看看let
變量。
首先我們聲明let age = 33
,然后分配一些其他值age = 34;
, 可以; 當我們嘗試更改let
變量時,我們沒有任何錯誤
在 2015 年之前,使用var
關鍵字是聲明JavaScript變量的唯一方法。
在ES6 (JavaScript 版本)之后,它允許 2 個新關鍵字let & const 。
let
= 可以重新賦值
const
= 不能重新分配( const 來自常量,短格式 'const' )
例子:
假設,聲明一個國家/地區名稱/您的母親姓名, const
最適合這里。 因為很快或以后更改國家名稱或您母親的名字的機會較小。
您的年齡、體重、薪水、自行車速度等這些類型的數據經常變化或需要重新分配。 那些情況, let
被使用。
僅當您想在腳本中將變量設置為全局變量或想在同一范圍內重新聲明相同的變量時,才使用 var 關鍵字。 隨着 ES2015 的到來,當您想將變量設置為函數作用域、塊作用域、循環作用域或不想在同一作用域內重新聲明變量時,請使用 let 關鍵字。
**var is function scoped
在 function 中聲明的帶有 var 的變量可以在 function 中的任何地方使用。 例如,**
// program to print text
// variable a cannot be used here
function greet() {
// variable a can be used here
var a = 'hello';
console.log(a);
}
// variable a cannot be used here
greet(); // hello
**在上面的程序中,變量a是用var聲明的。 變量 a 可以在 function greet 內部的任何地方使用。
let 是塊作用域 用 let 聲明的變量只能在代碼塊內訪問。 例如,**
// program to print the text
// variable a cannot be used here
function greet() {
let a = 'hello';
// variable b cannot be used here
if(a == 'hello'){
// variable b can be used here
let b = 'world';
console.log(a + ' ' + b);
}
// variable b cannot be used here
console.log(a + ' ' + b); // error
}
// variable a cannot be used here
greet();
**輸出
hello world Uncaught ReferenceError: b is not defined 在上面的程序中,變量 a 是在 function 內部聲明的,它可以在 function 內部的任何地方訪問(a 變為 ZC1C425268E68385D1AB5074C范圍dF1)。
但是,變量 b 是在 if 塊語句中聲明的。 b 將是塊范圍的,並且只能在 if 塊內訪問。
因此,當您嘗試在 if 塊之外訪問 b 時,會發生錯誤(如上面程序中所示)。**
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.