一、使用var宣告變數
1、使用方法
透過var關鍵字可以一次宣告一個變數或者多個變數,同時可以為宣告的變數賦初始值。但是變數的宣告和初始值並不是在同一時間執行的,在執行初始值之前這些宣告的變數的值為undefined。
"use strict";
var x = 12,
y = x;
2、宣告變數與非宣告變數區別
變數宣告定義的時候無論出現在程式碼的什麼位置,都會在執行程式碼之前,將宣告的變數新增到當前執行環境的作用域(上下文)中,該變數與undefined繫結在一起除非到了執行變數初始化語句或者除非之前已經聲明瞭這個變數,新增帶作用域中的變數是不可配置的,這就是變數提升。在使用該變數的時候,會解析執行表示式,返回值,這個值可能是沒有經過執行初始化的undefined。返回undefined也很好的提醒程式設計師該變數沒有初始化。
非變數宣告不會再執行之前處理,永遠是在執行的時候進行處理。也就是說,當在執行程式碼的時候,遇到了一個未經宣告的識別符號,經過解析執行建立這個識別符號,在嚴格模式下會直接返回引用型別錯誤,非嚴格模式下,會在全域性物件中新增這個識別符號和對應的值這個變數是可以配置的。在使用這個變數的時候,如果在建立之前使用,無論是否在嚴格模式下都會丟擲異常。
以下是詳細描述:
(1)宣告變數在執行程式碼之前解析建立,所以變數提升;非宣告變數在執行時候解析建立,不存在變數提升。
console.log(x,y); // x undefined y 丟擲異常
var x = 12;
y = 34;
console.log(x,y);// 12 34
因為在執行之前進行的變數定義初始化,所以x,但是執行的時候由於沒有執行x = 12所以x為undefined;但是對於y來說是在執行的時候才會建立的,所以在建立之前使用只能丟擲異常。當執行完x = 12,執行y = 34.會發現y沒有建立,所以會盡可能的去建立它,由於在非嚴格模式下,所以得逞了。之後再使用x,y都會被賦值了。
(2)宣告變數是被建立在當前執行環境的詞法作用域中,非宣告變數在嚴格模式下不會建立丟擲異常,在非嚴格模式下會作為屬性建立在全域性物件中。
function f(){
f = 12;
var z = 2;
}
f();
z = -1;
console.log(f,z);//12 -1
函式中f變數是非宣告變數,所以在非嚴格模式下降新增到全域性物件中作為一個屬性而存在,但是函式中的z則新增帶f函式所建立的執行環境中,所以在函式外部無法訪問到。值得注意的是,雖然在函式外部也定義了一個z = -1但是這個z不是透過宣告變數定義的,所以也會新增到全域性物件中。
console.log(x,z);//12 -1
delete x;
delete z;
(4)沒有執行初始化的宣告變數typeof為undefined,但是非宣告變數也為undefined
console.log(typeof x);//undefined
console.log(typeof z);//undefined
3、注意事項
(1)儘可能的使用宣告變數方式進行變數宣告,摒棄非宣告變數的方式,只有這樣才能不會將變數新增到全域性中去汙染全域性變數屬性,以便發生意想不到的效果,並且還可以提高效能。
(2)透過使用嚴格模式可以限制上述問題之外,還可以提醒程式設計師使用變數宣告,防止使用未經宣告的變數。
(3)不能依賴於變數提升。
(4)應該避免重複宣告相同的變數,最多使用賦值語句對變數值的修改。
(5)由於javascript是一個弱型別的語言,所以在覆蓋變數值得時候儘可能不要更改一個變數的型別。
(6)注意的是typeof檢測一個變數的型別,如果為undefined的話,除了這個變數聲明瞭但是沒有初始化的意思之外,很可能該變數使用過非宣告變數方式建立的,或者這個變數就是不存在。
所以總結一句就是在嚴格模式下使用var宣告變數,不能依賴於變數提升、js弱型別、typeof檢測undefined、不能重複宣告變數。
可以看出ES5標準中的var存在的問題是不少的,但是最重要的一個問題就是沒有塊作用域這一概念,這完全取決於之前的設計,因為在ES5標準中僅僅說對全域性、函式、eval、catch塊才會建立詞法環境,對於for等語法塊是不建立的,導致不存在塊級作用域從而使用很耗效能的閉包代替。在ES6中出現了let關鍵字宣告變數,這個可以解決一些問題。
二、使用let宣告變數
1、詳細介紹
在執行之前,只要進入一個塊級作用域比如全域性、函式、{}等。不會和var一樣對let進行所謂的變數提升,而是將透過let宣告的變數識別符號儲存起來,以便於判斷接下來是否含有命名衝突,也服務於‘暫存死區’,直到開始執行程式碼。到執行到宣告語句的時候,才會正式的建立,如果沒有初始化則賦值為undefined,否則初始化識別符號。(對於為什麼賦值為undefined而不是null或者其他,前面文章中有介紹)
對於全域性變數中的let不會新增到全域性物件中去。這是和全域性中的var的區別之一,對於全域性中的var,在執行程式碼之前會將識別符號新增到與這個執行環境繫結的詞法環境中去,但是又因為這個詞法環境與全域性物件是繫結在一起的,所以會將全域性的變數宣告都作為全域性物件的屬性存在。Let宣告的變數和var不一樣,他並不會去破壞全域性物件,而是當執行到let語句的時候建立一個塊詞法作用域,而將let宣告的變數新增到這個詞法作用域中去。
var x = "global";
let y = "global";
console.log(this.x); // "global"
console.log(this.y); // undefined
在全域性中使用let,避免了將全域性屬性新增到全域性物件中,減少了對全域性物件的汙染。
在執行程式碼之前,定義繫結初始化的時候,在處理let宣告表示式的時候,如果該表示式在當前處理的詞法環境中存在則丟擲異常。在E5標準中,如果對同一個變數進行了兩次宣告是不會報錯的只不過後一個宣告僅僅當做賦值表示式在使用,這個會產生一些問題,比如發生了命名衝突還不知道,導致程式碼邏輯錯誤,從而只能依託程式設計師提高警惕避免重複命名。E6中的let很好的避免了這種重複宣告的問題,只要在let宣告之前無論什麼方式建立的變數,只要與let宣告語句位於同一個塊中,同名就會丟擲異常。
if (x) {
let foo;
let foo; // 衝突
switch (x) {
case 0:
break;
case 1:
這個switch中所有的case都處於同一個塊作用域中的,這個要注意了。
function f(foo) {
let foo; //與引數衝突
f(12);
let x = -1;//與var宣告的變數衝突
function f() {
if (true) {
let x = -1;//不在同一個塊作用域不衝突
console.log(x);//-1
console.log(x);//12
在開始執行的時候,在還沒有執行到let變數宣告初始化的位置之前對let變數進行引用,引用將會丟擲異常,因為這個變數還處於封鎖狀態也就是‘==暫存死區==’狀態。這個概念解決了E5中使用var導致變數提升的弊端,是的在宣告之前不能提前使用了,一點使用將丟擲異常。
“`
function do_something() {
console.log(foo); // ReferenceError
let foo = 2;
```
var tmp = 123;
tmp = "abc"; // ReferenceError
let tmp;
}
// 不報錯
var x = x;
// 報錯
let x = x;
// ReferenceError: x is not defined
只要一進入{}或者全域性,就會單獨建立一個塊級作用域,只不過這個作用域僅僅維護let變數的。在建立的時候除了新增let宣告和初始化值之外,還記錄了父級塊級作用域中let變數執行都該位置的時候值得副本(可能這個副本的記錄瀏覽器引擎優化了),如果子塊中存在與父塊中相同的變數,則忽略父級塊級作用域中的值。
function letTest() {
let x = 1;
let x = 2; // 不同的變數,忽略父級作用域的x
console.log(x); // 2
console.log(x); // 1
letTest();
一執行函式進入函式體{}就會建立一個塊級作用域其中有x賦值為1,然後遇到if{}又建立一個塊級作用域其中有x賦值為2,但是由於這個子塊中有x所以父塊中x不會進行
var list = document.getElementById("list");
for (let i = 1; i <= 5; i++) {
var item = document.createElement("LI");
item.appendChild(document.createTextNode("Item " + i));
item.onclick = function (ev) {
console.log("Item " +i+ " is clicked.");
};
list.appendChild(item);
這是一個很經典的使用let的好處的應用,這個for迴圈其實建立了兩個巢狀的塊級作用域,外面的塊中就是為()裡面的引數建立的,
而裡面的塊是為迴圈體建立的,當每次執行迴圈體的時候就是建立一個塊級作用域,這迴圈體中塊級作用域就會快取父塊中i的值,
使得相應事件的時候依然有效。下面應用將詳細介紹。
2、引用例項
(1)模仿私有介面
//之前沒有let可能是這麼建立一個類的,用閉包形成了一範圍保護類靜態私有屬性
// Class Class implements Interface
var Class = (function (){
//靜態私有屬性和方法
var privateProperty = 12;
//靜態公共方法
//定義建構函式
var Class = function(property /*optional*/){
//extend(subClass,superClass)
//公共例項方法
Class.prototype = {
constructor:Class,
return Class;
})();
// 現在可以用let替代閉包了
var Class;
{
let privateProperty = 12;
Class = function(property /*optional*/){
(2)替代使用閉包模仿的塊級作用域
var arr = [];
for (var i = 0; i < 5; i ++) {
arr[i] =( function (i){
return function () {
alert(i);
})(i);
//現在可以使用let了
for (let i = 0; i < 5; i++) {
arr[i] = function () {
三、使用const宣告變數
合格關鍵字和let一樣也是E6標準提出的,使用const宣告變數和let幾乎差不多,只不過是對於定義的值只能切必須進行一次初始化。一般宣告為const的變數的變數名都要大寫。
console.log(CONST);//和let一樣也是有‘暫存死區’的存在
const CONST = 100;
//CONST = 12;//不能為定義const的變數修改值
//var CONST = 20;//不能命名重複發生衝突
透過逗號分隔,就可以實現單個let或者const給多個變數賦值,比如
let a=0,b=1,c=2;
const aa=0,bb=1,cc=2;
一、使用var宣告變數
1、使用方法
透過var關鍵字可以一次宣告一個變數或者多個變數,同時可以為宣告的變數賦初始值。但是變數的宣告和初始值並不是在同一時間執行的,在執行初始值之前這些宣告的變數的值為undefined。
"use strict";
var x = 12,
y = x;
2、宣告變數與非宣告變數區別
變數宣告定義的時候無論出現在程式碼的什麼位置,都會在執行程式碼之前,將宣告的變數新增到當前執行環境的作用域(上下文)中,該變數與undefined繫結在一起除非到了執行變數初始化語句或者除非之前已經聲明瞭這個變數,新增帶作用域中的變數是不可配置的,這就是變數提升。在使用該變數的時候,會解析執行表示式,返回值,這個值可能是沒有經過執行初始化的undefined。返回undefined也很好的提醒程式設計師該變數沒有初始化。
非變數宣告不會再執行之前處理,永遠是在執行的時候進行處理。也就是說,當在執行程式碼的時候,遇到了一個未經宣告的識別符號,經過解析執行建立這個識別符號,在嚴格模式下會直接返回引用型別錯誤,非嚴格模式下,會在全域性物件中新增這個識別符號和對應的值這個變數是可以配置的。在使用這個變數的時候,如果在建立之前使用,無論是否在嚴格模式下都會丟擲異常。
以下是詳細描述:
(1)宣告變數在執行程式碼之前解析建立,所以變數提升;非宣告變數在執行時候解析建立,不存在變數提升。
console.log(x,y); // x undefined y 丟擲異常
var x = 12;
y = 34;
console.log(x,y);// 12 34
因為在執行之前進行的變數定義初始化,所以x,但是執行的時候由於沒有執行x = 12所以x為undefined;但是對於y來說是在執行的時候才會建立的,所以在建立之前使用只能丟擲異常。當執行完x = 12,執行y = 34.會發現y沒有建立,所以會盡可能的去建立它,由於在非嚴格模式下,所以得逞了。之後再使用x,y都會被賦值了。
(2)宣告變數是被建立在當前執行環境的詞法作用域中,非宣告變數在嚴格模式下不會建立丟擲異常,在非嚴格模式下會作為屬性建立在全域性物件中。
function f(){
f = 12;
var z = 2;
}
f();
z = -1;
console.log(f,z);//12 -1
函式中f變數是非宣告變數,所以在非嚴格模式下降新增到全域性物件中作為一個屬性而存在,但是函式中的z則新增帶f函式所建立的執行環境中,所以在函式外部無法訪問到。值得注意的是,雖然在函式外部也定義了一個z = -1但是這個z不是透過宣告變數定義的,所以也會新增到全域性物件中。
var x = 12;
z = -1;
console.log(x,z);//12 -1
delete x;
delete z;
(4)沒有執行初始化的宣告變數typeof為undefined,但是非宣告變數也為undefined
console.log(typeof x);//undefined
console.log(typeof z);//undefined
var x = 12;
z = -1;
3、注意事項
(1)儘可能的使用宣告變數方式進行變數宣告,摒棄非宣告變數的方式,只有這樣才能不會將變數新增到全域性中去汙染全域性變數屬性,以便發生意想不到的效果,並且還可以提高效能。
(2)透過使用嚴格模式可以限制上述問題之外,還可以提醒程式設計師使用變數宣告,防止使用未經宣告的變數。
(3)不能依賴於變數提升。
(4)應該避免重複宣告相同的變數,最多使用賦值語句對變數值的修改。
(5)由於javascript是一個弱型別的語言,所以在覆蓋變數值得時候儘可能不要更改一個變數的型別。
(6)注意的是typeof檢測一個變數的型別,如果為undefined的話,除了這個變數聲明瞭但是沒有初始化的意思之外,很可能該變數使用過非宣告變數方式建立的,或者這個變數就是不存在。
所以總結一句就是在嚴格模式下使用var宣告變數,不能依賴於變數提升、js弱型別、typeof檢測undefined、不能重複宣告變數。
可以看出ES5標準中的var存在的問題是不少的,但是最重要的一個問題就是沒有塊作用域這一概念,這完全取決於之前的設計,因為在ES5標準中僅僅說對全域性、函式、eval、catch塊才會建立詞法環境,對於for等語法塊是不建立的,導致不存在塊級作用域從而使用很耗效能的閉包代替。在ES6中出現了let關鍵字宣告變數,這個可以解決一些問題。
二、使用let宣告變數
1、詳細介紹
在執行之前,只要進入一個塊級作用域比如全域性、函式、{}等。不會和var一樣對let進行所謂的變數提升,而是將透過let宣告的變數識別符號儲存起來,以便於判斷接下來是否含有命名衝突,也服務於‘暫存死區’,直到開始執行程式碼。到執行到宣告語句的時候,才會正式的建立,如果沒有初始化則賦值為undefined,否則初始化識別符號。(對於為什麼賦值為undefined而不是null或者其他,前面文章中有介紹)
對於全域性變數中的let不會新增到全域性物件中去。這是和全域性中的var的區別之一,對於全域性中的var,在執行程式碼之前會將識別符號新增到與這個執行環境繫結的詞法環境中去,但是又因為這個詞法環境與全域性物件是繫結在一起的,所以會將全域性的變數宣告都作為全域性物件的屬性存在。Let宣告的變數和var不一樣,他並不會去破壞全域性物件,而是當執行到let語句的時候建立一個塊詞法作用域,而將let宣告的變數新增到這個詞法作用域中去。
var x = "global";
let y = "global";
console.log(this.x); // "global"
console.log(this.y); // undefined
在全域性中使用let,避免了將全域性屬性新增到全域性物件中,減少了對全域性物件的汙染。
在執行程式碼之前,定義繫結初始化的時候,在處理let宣告表示式的時候,如果該表示式在當前處理的詞法環境中存在則丟擲異常。在E5標準中,如果對同一個變數進行了兩次宣告是不會報錯的只不過後一個宣告僅僅當做賦值表示式在使用,這個會產生一些問題,比如發生了命名衝突還不知道,導致程式碼邏輯錯誤,從而只能依託程式設計師提高警惕避免重複命名。E6中的let很好的避免了這種重複宣告的問題,只要在let宣告之前無論什麼方式建立的變數,只要與let宣告語句位於同一個塊中,同名就會丟擲異常。
if (x) {
let foo;
let foo; // 衝突
}
switch (x) {
case 0:
let foo;
break;
case 1:
let foo; // 衝突
break;
}
這個switch中所有的case都處於同一個塊作用域中的,這個要注意了。
function f(foo) {
let foo; //與引數衝突
}
f(12);
var x = 12;
let x = -1;//與var宣告的變數衝突
function f() {
var x = 12;
if (true) {
let x = -1;//不在同一個塊作用域不衝突
console.log(x);//-1
}
console.log(x);//12
}
f();
在開始執行的時候,在還沒有執行到let變數宣告初始化的位置之前對let變數進行引用,引用將會丟擲異常,因為這個變數還處於封鎖狀態也就是‘==暫存死區==’狀態。這個概念解決了E5中使用var導致變數提升的弊端,是的在宣告之前不能提前使用了,一點使用將丟擲異常。
“`
function do_something() {
console.log(foo); // ReferenceError
let foo = 2;
}
```
var tmp = 123;
if (true) {
tmp = "abc"; // ReferenceError
let tmp;
}
// 不報錯
var x = x;
// 報錯
let x = x;
// ReferenceError: x is not defined
只要一進入{}或者全域性,就會單獨建立一個塊級作用域,只不過這個作用域僅僅維護let變數的。在建立的時候除了新增let宣告和初始化值之外,還記錄了父級塊級作用域中let變數執行都該位置的時候值得副本(可能這個副本的記錄瀏覽器引擎優化了),如果子塊中存在與父塊中相同的變數,則忽略父級塊級作用域中的值。
function letTest() {
let x = 1;
if (true) {
let x = 2; // 不同的變數,忽略父級作用域的x
console.log(x); // 2
}
console.log(x); // 1
}
letTest();
一執行函式進入函式體{}就會建立一個塊級作用域其中有x賦值為1,然後遇到if{}又建立一個塊級作用域其中有x賦值為2,但是由於這個子塊中有x所以父塊中x不會進行
var list = document.getElementById("list");
for (let i = 1; i <= 5; i++) {
var item = document.createElement("LI");
item.appendChild(document.createTextNode("Item " + i));
item.onclick = function (ev) {
console.log("Item " +i+ " is clicked.");
};
list.appendChild(item);
}
這是一個很經典的使用let的好處的應用,這個for迴圈其實建立了兩個巢狀的塊級作用域,外面的塊中就是為()裡面的引數建立的,
而裡面的塊是為迴圈體建立的,當每次執行迴圈體的時候就是建立一個塊級作用域,這迴圈體中塊級作用域就會快取父塊中i的值,
使得相應事件的時候依然有效。下面應用將詳細介紹。
2、引用例項
(1)模仿私有介面
//之前沒有let可能是這麼建立一個類的,用閉包形成了一範圍保護類靜態私有屬性
// Class Class implements Interface
var Class = (function (){
//靜態私有屬性和方法
var privateProperty = 12;
//靜態公共方法
//定義建構函式
var Class = function(property /*optional*/){
};
//extend(subClass,superClass)
//公共例項方法
Class.prototype = {
constructor:Class,
};
return Class;
})();
// 現在可以用let替代閉包了
var Class;
{
//靜態私有屬性和方法
let privateProperty = 12;
//靜態公共方法
//定義建構函式
Class = function(property /*optional*/){
};
//extend(subClass,superClass)
//公共例項方法
Class.prototype = {
constructor:Class,
};
}
(2)替代使用閉包模仿的塊級作用域
var arr = [];
for (var i = 0; i < 5; i ++) {
arr[i] =( function (i){
return function () {
alert(i);
}
})(i);
}
//現在可以使用let了
for (let i = 0; i < 5; i++) {
arr[i] = function () {
alert(i);
}
}
三、使用const宣告變數
合格關鍵字和let一樣也是E6標準提出的,使用const宣告變數和let幾乎差不多,只不過是對於定義的值只能切必須進行一次初始化。一般宣告為const的變數的變數名都要大寫。
console.log(CONST);//和let一樣也是有‘暫存死區’的存在
const CONST = 100;
//CONST = 12;//不能為定義const的變數修改值
//var CONST = 20;//不能命名重複發生衝突