JS怎么使用單個let或者const關鍵字同時給多個變量賦相同的值?
您好!很高興回答您的問題!
一、使用var聲明變量
1、使用方法
通過var關鍵字可以一次聲明一個變量或者多個變量,同時可以為聲明的變量賦初始值。但是變量的聲明和初始值并不是在同一時間執(zhí)行的,在執(zhí)行初始值之前這些聲明的變量的值為undefined。
'use strict';
var x = 12,
y = x;
2、聲明變量與非聲明變量區(qū)別
變量聲明定義的時候無論出現(xiàn)在代碼的什么位置,都會在執(zhí)行代碼之前,將聲明的變量添加到當前執(zhí)行環(huán)境的作用域(上下文)中,該變量與undefined綁定在一起除非到了執(zhí)行變量初始化語句或者除非之前已經(jīng)聲明了這個變量,添加帶作用域中的變量是不可配置的,這就是變量提升。在使用該變量的時候,會解析執(zhí)行表達式,返回值,這個值可能是沒有經(jīng)過執(zhí)行初始化的undefined。返回undefined也很好的提醒程序員該變量沒有初始化。
非變量聲明不會再執(zhí)行之前處理,永遠是在執(zhí)行的時候進行處理。也就是說,當在執(zhí)行代碼的時候,遇到了一個未經(jīng)聲明的標識符,經(jīng)過解析執(zhí)行創(chuàng)建這個標識符,在嚴格模式下會直接返回引用類型錯誤,非嚴格模式下,會在全局對象中添加這個標識符和對應的值這個變量是可以配置的。在使用這個變量的時候,如果在創(chuàng)建之前使用,無論是否在嚴格模式下都會拋出異常。
以下是詳細描述:
(1)聲明變量在執(zhí)行代碼之前解析創(chuàng)建,所以變量提升;非聲明變量在執(zhí)行時候解析創(chuàng)建,不存在變量提升。
console.log(x,y); // x undefined y 拋出異常
var x = 12;
y = 34;
console.log(x,y);// 12 34
因為在執(zhí)行之前進行的變量定義初始化,所以x,但是執(zhí)行的時候由于沒有執(zhí)行x = 12所以x為undefined;但是對于y來說是在執(zhí)行的時候才會創(chuàng)建的,所以在創(chuàng)建之前使用只能拋出異常。當執(zhí)行完x = 12,執(zhí)行y = 34.會發(fā)現(xiàn)y沒有創(chuàng)建,所以會盡可能的去創(chuàng)建它,由于在非嚴格模式下,所以得逞了。之后再使用x,y都會被賦值了。
(2)聲明變量是被創(chuàng)建在當前執(zhí)行環(huán)境的詞法作用域中,非聲明變量在嚴格模式下不會創(chuàng)建拋出異常,在非嚴格模式下會作為屬性創(chuàng)建在全局對象中。
function f(){
f = 12;
var z = 2;
}
f();
z = -1;
console.log(f,z);//12 -1
函數(shù)中f變量是非聲明變量,所以在非嚴格模式下降添加到全局對象中作為一個屬性而存在,但是函數(shù)中的z則添加帶f函數(shù)所創(chuàng)建的執(zhí)行環(huán)境中,所以在函數(shù)外部無法訪問到。值得注意的是,雖然在函數(shù)外部也定義了一個z = -1但是這個z不是通過聲明變量定義的,所以也會添加到全局對象中。
(3)聲明變量所在的詞法環(huán)境是不可配置的,而非聲明變量所在的全局對象是可以配置的。所謂的配置就是指可以刪除該屬性,或者修改該屬性的特性。
var x = 12;
z = -1;
console.log(x,z);//12 -1
delete x;
delete z;
console.log(x,z);//z is not defined證明z刪除了但是x沒有刪除
(4)沒有執(zhí)行初始化的聲明變量typeof為undefined,但是非聲明變量也為undefined
console.log(typeof x);//undefined
console.log(typeof z);//undefined
var x = 12;
z = -1;
3、注意事項
(1)盡可能的使用聲明變量方式進行變量聲明,摒棄非聲明變量的方式,只有這樣才能不會將變量添加到全局中去污染全局變量屬性,以便發(fā)生意想不到的效果,并且還可以提高性能。
(2)通過使用嚴格模式可以限制上述問題之外,還可以提醒程序員使用變量聲明,防止使用未經(jīng)聲明的變量。
(3)不能依賴于變量提升。
(4)應該避免重復聲明相同的變量,最多使用賦值語句對變量值的修改。
(5)由于javascript是一個弱類型的語言,所以在覆蓋變量值得時候盡可能不要更改一個變量的類型。
(6)注意的是typeof檢測一個變量的類型,如果為undefined的話,除了這個變量聲明了但是沒有初始化的意思之外,很可能該變量使用過非聲明變量方式創(chuàng)建的,或者這個變量就是不存在。
所以總結一句就是在嚴格模式下使用var聲明變量,不能依賴于變量提升、js弱類型、typeof檢測undefined、不能重復聲明變量。
可以看出ES5標準中的var存在的問題是不少的,但是最重要的一個問題就是沒有塊作用域這一概念,這完全取決于之前的設計,因為在ES5標準中僅僅說對全局、函數(shù)、eval、catch塊才會創(chuàng)建詞法環(huán)境,對于for等語法塊是不創(chuàng)建的,導致不存在塊級作用域從而使用很耗性能的閉包代替。在ES6中出現(xiàn)了let關鍵字聲明變量,這個可以解決一些問題。
二、使用let聲明變量
1、詳細介紹
在執(zhí)行之前,只要進入一個塊級作用域比如全局、函數(shù)、{}等。不會和var一樣對let進行所謂的變量提升,而是將通過let聲明的變量標識符保存起來,以便于判斷接下來是否含有命名沖突,也服務于‘暫存死區(qū)’,直到開始執(zhí)行代碼。到執(zhí)行到聲明語句的時候,才會正式的創(chuàng)建,如果沒有初始化則賦值為undefined,否則初始化標識符。(對于為什么賦值為undefined而不是null或者其他,前面文章中有介紹)
對于全局變量中的let不會添加到全局對象中去。這是和全局中的var的區(qū)別之一,對于全局中的var,在執(zhí)行代碼之前會將標識符添加到與這個執(zhí)行環(huán)境綁定的詞法環(huán)境中去,但是又因為這個詞法環(huán)境與全局對象是綁定在一起的,所以會將全局的變量聲明都作為全局對象的屬性存在。Let聲明的變量和var不一樣,他并不會去破壞全局對象,而是當執(zhí)行到let語句的時候創(chuàng)建一個塊詞法作用域,而將let聲明的變量添加到這個詞法作用域中去。
var x = 'global';
let y = 'global';
console.log(this.x); // "global"
console.log(this.y); // undefined
在全局中使用let,避免了將全局屬性添加到全局對象中,減少了對全局對象的污染。
在執(zhí)行代碼之前,定義綁定初始化的時候,在處理let聲明表達式的時候,如果該表達式在當前處理的詞法環(huán)境中存在則拋出異常。在E5標準中,如果對同一個變量進行了兩次聲明是不會報錯的只不過后一個聲明僅僅當做賦值表達式在使用,這個會產生一些問題,比如發(fā)生了命名沖突還不知道,導致代碼邏輯錯誤,從而只能依托程序員提高警惕避免重復命名。E6中的let很好的避免了這種重復聲明的問題,只要在let聲明之前無論什么方式創(chuàng)建的變量,只要與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; //與參數(shù)沖突
}
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();
在開始執(zhí)行的時候,在還沒有執(zhí)行到let變量聲明初始化的位置之前對let變量進行引用,引用將會拋出異常,因為這個變量還處于封鎖狀態(tài)也就是‘==暫存死區(qū)==’狀態(tài)。這個概念解決了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
只要一進入{}或者全局,就會單獨創(chuàng)建一個塊級作用域,只不過這個作用域僅僅維護let變量的。在創(chuàng)建的時候除了添加let聲明和初始化值之外,還記錄了父級塊級作用域中l(wèi)et變量執(zhí)行都該位置的時候值得副本(可能這個副本的記錄瀏覽器引擎優(yōu)化了),如果子塊中存在與父塊中相同的變量,則忽略父級塊級作用域中的值。
function letTest() {
let x = 1;
if (true) {
let x = 2; // 不同的變量,忽略父級作用域的x
console.log(x); // 2
}
console.log(x); // 1
}
letTest();
一執(zhí)行函數(shù)進入函數(shù)體{}就會創(chuàng)建一個塊級作用域其中有x賦值為1,然后遇到if{}又創(chuàng)建一個塊級作用域其中有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);
}
這是一個很經(jīng)典的使用let的好處的應用,這個for循環(huán)其實建立了兩個嵌套的塊級作用域,外面的塊中就是為()里面的參數(shù)建立的,
而里面的塊是為循環(huán)體建立的,當每次執(zhí)行循環(huán)體的時候就是創(chuàng)建一個塊級作用域,這循環(huán)體中塊級作用域就會緩存父塊中i的值,
使得相應事件的時候依然有效。下面應用將詳細介紹。
2、引用實例
(1)模仿私有接口
//之前沒有l(wèi)et可能是這么創(chuàng)建一個類的,用閉包形成了一范圍保護類靜態(tài)私有屬性
// Class Class implements Interface
var Class = (function (){
//靜態(tài)私有屬性和方法
var privateProperty = 12;
//靜態(tài)公共方法
//定義構造函數(shù)
var Class = function(property /*optional*/){
};
//extend(subClass,superClass)
//公共實例方法
Class.prototype = {
constructor:Class,
};
return Class;
})();
// 現(xiàn)在可以用let替代閉包了
var Class;
{
//靜態(tài)私有屬性和方法
let privateProperty = 12;
//靜態(tài)公共方法
//定義構造函數(shù)
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);
}
//現(xiàn)在可以使用let了
for (let i = 0; i < 5; i++) {
arr[i] = function () {
alert(i);
}
}
三、使用const聲明變量
合格關鍵字和let一樣也是E6標準提出的,使用const聲明變量和let幾乎差不多,只不過是對于定義的值只能切必須進行一次初始化。一般聲明為const的變量的變量名都要大寫。
console.log(CONST);//和let一樣也是有‘暫存死區(qū)’的存在
const CONST = 100;
//CONST = 12;//不能為定義const的變量修改值
//var CONST = 20;//不能命名重復發(fā)生沖突