爲了更好的對它們的區別進行理解,我們必須要先理解幾個概念
一 作用域
從ES6開始,js作用域有三種:全局作用域、函數作用域(也叫局部作用域)和塊級作用域。其中塊級作用域是自從ES6引入了let和const之後纔有的作用域,即塊級作用域在ES5和ES5之前是沒有的
1 全局作用域
- 在script腳本中定義的最外層函數和變量都屬於全局作用域
- 通過script引入的js文件中最外層定義的函數和變量都屬於全局作用域
- 同一個html文件中無論是script標籤裏面的腳本還是引入的js文件腳本,都屬於同一個全局作用域
- 全局作用域在html頁面打開時創建,頁面關閉時銷燬
- 在全局作用域中有一個全局對象 window(代表的是一個瀏覽器的窗口,由瀏覽器創建,所以如果在node中執行腳本,則沒有這個對象)
- 按照script標籤在html中引入的順序,後面定義的變量或函數在之前的script中無法訪問
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<script type="text/javascript">
var a = 1;
let b = 2;
console.log(c) // 報錯:c is not defined
</script>
<script type="text/javascript">
var c = 3;
console.log(a) // 1
console.log(b) // 2
</script>
<html>
2 函數作用域
- 在函數內部就是局部作用域,這個代碼的名字只在函數的內部起作用
- 調用函數時創建函數作用域,函數執行完畢之後,函數作用域銷燬
- 每調用一次函數就會創建一個新的函數作用域,它們之間是相互獨立的
var a = 10;
function test(){
var a = 20;
console.log('內a:' + a); // 內a:20
}
test();
console.log('外a:' + a); // 外a:10
3 塊級作用域
- 從ES6開始引入了const和let,即通過const和let引入了塊級作用域的概念
- 塊級作用域只對const和let有效,對var無效
- 可以理解成一個{ }括起來的範圍就是一個塊級作用域
- 塊級作用域裏面的let和const聲明的變量只在當前塊級作用域有效
{let a = "hello"}
console.log(a); // 報錯:a is not defined
二 變量提升
瀏覽器在運行js代碼之前會進行預解析,首先解析函數的聲明和定義的變量,把它們初始化爲undefined,解析完之後再對函數、變量進行運行、賦值等。 不論var聲明的變量處於當前作用域的第幾行,都會提升到作用域的頂部
注意:
- 這個只對var聲明的變量或者函數有效
- 如果函數未通過var、let或者const聲明,而是直接定義,會將該函數整個進行提升
- 如果函數通過let或者const聲明也不會進行變量提升
- 提升到作用域頂部的作用域,指的是聲明變量的作用域(全局作用域或者函數作用域)
- 同一個html文件引入的不同script腳本中的變量進行提升的時候,不會提升到整個全局作用域的頂部,而是提升到當前script腳本文件的最頂部
console.log(foo); // undefined
var foo = 2;
// 相當於
var foo; // 聲明且初始化爲undefined
console.log(foo);
foo = 2;
// let定義的變量不會進行變量提升
console.log(a) // 報錯:a is not defined
let a = function (){}
// const定義的變量不會進行變量提升
console.log(b) // 報錯:b is not defined
const b = 1
// let定義的變量不會進行變量提升
console.log(a) // undefined
var a = function (){}
// 整個函數體進行了提升
console.log(a) // [Function: a]
function a(){}
// 相當於
var a = function(){}
console.log(a)
// b函數內的a函數進行了提升,所以a=10是對函數作用域內的a進行了賦值,沒有影響全局作用域中的a
// a函數行提升的時候,因爲它是局部作用域內的函數,所以並不是對全局的a進行了重新賦值
var a = 1;
function b() {
a = 10;
return;
function a(){}
}
b();
console.log(a) // 1
<!--雖然兩個script腳本屬於同一個全局作用域,但是第二個script腳本中的a函數仍然無法在第一個script腳本中訪問 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<script type="text/javascript">
console.log(a) // 報錯:a is not defined
</script>
<script type="text/javascript">
console.log(a) // [Function: a]
function a (){}
</script>
<html>
三 區別
有了以上的基礎知識,我麼再理解三者的區別就更加容易了
1 掛載window的區別
如果在瀏覽器中執行js腳本,有window對象的話,var聲明的變量會掛載在window上,而let和const聲明的變量不會
var a = 100;
console.log(a, window.a); // 100 100
let b = 10;
console.log(b, window.b); // 10 undefined
const c = 1;
console.log(c, window.c); // 1 undefined
2 變量提升的區別
在上面的變量提升章節中講過,只有var聲明的變量或者函數存在變量提升,let和const聲明的變量或者函數是不存在變量提升的。這裏就不再贅述了,如果還有不理解的可以返回變量提升章節查看
3 作用域的區別
let和const聲明形成塊級作用域,而var聲明不會。只要塊級作用域內存在let/const聲明,它所聲明的變量就“綁定”這個塊級作用域內,不再受外部的影響。而且,在塊級作用域內,使用let/cosnt命令聲明變量之前,該變量都是未聲明的狀態,儘管代碼塊外可能已經聲明瞭同名全局變量(該現象的產生是因爲:暫存死區,本篇文章未做展開講解,有興趣的可查閱其它資料)
{
var a = 100;
let b = 10;
}
console.log(a) // 100
console.log(b) // 報錯:b is not defined
// 暫存死區
var tmp = 123
if (true) {
tmp = 'abc' // 報錯:tmp is not defined
let tmp
}
4 重複聲明同一變量的區別
同一作用域下let和const不能聲明同名變量,而var可以
var a = 100;
console.log(a); // 100
var a = 10;
console.log(a); // 10
let a = 100
let a = 10 // 報錯:Identifier 'a' has already been declared
let a = 100
const a = 10 // 報錯:Identifier 'a' has already been declared
5 let 和 const 區別
從上面看來,好像let和const好像沒啥區別,那當然不會,那麼它倆有什麼區別呢?一下就是const和let不同的一些地方:
- const用來聲明常量,一旦聲明,其值不能改變
- 一旦聲明必須賦值,可以賦值null,undefined
- 如果聲明的是複合類型數據,const聲明的常量指向其內存中地址,const命令只是保證指向的地址不變,而不保證該地址內的數據不變
const a = undefined
const b = null
const c = 2
console.log(a) // undefined
console.log(b) // null
console.log(c) // 2
const a = [1]
a.push(2)
console.log(a) // [1,2]
const b = {
name: 'huzhenv5'
}
b.name = '三千'
b.id = 1
console.log(b) // { name: '三千', id: 1 }