『ES6腳丫系列』let+const+變量+變量作用域+塊作用域+變量聲明提升

圖片.png

『ES6腳丫系列』let+const+變量+變量作用域+塊作用域+變量聲明提升

一、let命令

【01】ES6新加。

【02】let用于聲明變量,let聲明的變量,只在let命令所在的代碼塊內(nèi)有效。

所謂的代碼塊就是指花括號內(nèi)的東西。

{
  let a = 10;
  var b = 1;
}

a // ReferenceError: a is not defined.
b // 1代碼

【03】在for循環(huán)的小括號里,let定義的變量,只在for循環(huán)體內(nèi)有效。其他地方就是未定義了。

例子:

for(let i = 0; i < arr.length; i++){}
console.log(i)//ReferenceError: i is not defined
代碼

例子:

下面的代碼for使用var,最后輸出的是10。因為i在全局作用域內(nèi)有效,。所以每一次循環(huán),新的i值都會覆蓋舊值,導(dǎo)致最后輸出的是最后一輪的i的值。

/*

吃碼小妖解釋:a[i]中的i是每次for循環(huán)的i值,此時function(){console.log(i);})并沒有運行。

a6;運行時,才開始獲取i,此時i是最后for循環(huán)后的值,也就是10。

*/

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10
代碼

如果使用let,聲明的變量僅在塊級作用域內(nèi)有效,最后輸出的是6。

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6 代碼

上面代碼中,變量i是let聲明的,當(dāng)前的i只在本輪循環(huán)有效,所以每一次循環(huán)的i其實都是一個新的變量,所以最后輸出的是6。

【04】不存在變量聲明提升,只能先聲明,再使用。否則報錯。

console.log(foo); // ReferenceError
let foo = 2;代碼

這也意味著typeof不再是一個百分之百安全的操作。

由于typeof運行時,x還沒有聲明,所以會拋出一個ReferenceError。

typeof x; // ReferenceError
let x;
代碼

【05】暫時性死區(qū)(temporal dead zone,簡稱TDZ)

意思就是:如果代碼塊內(nèi)使用let聲明了某個變量x,那么在這個聲明之前,這個變量不能被賦值或使用。否則就會報錯。

打個比方,你的朋友胖虎有個變形金剛,在他說給你玩之前,你不能搶過來玩,更不能賣掉。

其實,就是為了規(guī)范,避免先使用后聲明這種奇葩的行為。

【】例子:

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
} 代碼

【】例子:

if (true) {
  // TDZ開始
  tmp = 'abc'; // ReferenceError
  console.log(tmp); // ReferenceError

  let tmp; // TDZ結(jié)束
  console.log(tmp); // undefined

  tmp = 123;
  console.log(tmp); // 123
}
代碼

【】有些“死區(qū)”比較隱蔽,不太容易發(fā)現(xiàn)。

function bar(x = y, y = 2) {
  return [x, y];
}

bar(); // 報錯
代碼

【】例子:

function bar(x = 2, y = x) {
  return [x, y];
}
bar(); // [2, 2]
代碼

【05】不能重復(fù)聲明

用let聲明后,不可以再用let或var聲明同一個變量。

不能在函數(shù)內(nèi)部用let聲明函數(shù)形參。

// 報錯
function () {
  let a = 10;
  var a = 1;
}

// 報錯
function () {
  let a = 10;
  let a = 1;
}
代碼

[圖片上傳失敗...(image-2e1c90-1561932387592)]

function func(arg) {
  let arg; // 報錯
}

function func(arg) {
  {
    let arg; // 不報錯
  }
}
代碼

~~~~~~~~~~~~~~~~~~~~~ 我是吃碼小妖的分割線 ~~~~~~~~~~~~~~~~~~~~~~~~

二、const命令

【01】聲明的是常量,一旦聲明,不得修改。所以必須聲明的同時并賦值。否則報錯。

const的定義是不可重新賦值的值,與不可變的值(immutable value)不同。重新賦值會報錯。

如果const定義的是引用類型,比如Object,在定義之后仍可以修改屬性。

它的使用場景很廣,包括常量、配置項以及引用的組件、定義的 “大部分” 中間變量等,都應(yīng)該以const做定義。

let多用于在 loop循環(huán)(for,while)和少量必須重賦值的變量上。

const PI = 3.1415;
PI // 3.1415

PI = 3;// TypeError: "PI" is read-only
const foo;// SyntaxError: missing = in const declaration代碼

和let一樣:

【02】只在聲明所在的塊級作用域內(nèi)有效。

if (true) { const MAX = 5;``} MAX // Uncaught ReferenceError: MAX is not defined

【03】const命令聲明的常量也是不提升。

【04】同樣存在暫時性死區(qū),只能在聲明的位置后面使用。

if (true) {
  console.log(MAX); // ReferenceError
  const MAX = 5;} 代碼

【05】const聲明的常量,也與let一樣不可重復(fù)聲明。

var message = "Hello!";let age = 25;
// 以下兩行都會報錯
const message = "Goodbye!";
const age = 30;代碼

【06】對于引用類型的變量,變量名不指向數(shù)據(jù),而是指向數(shù)據(jù)所在的地址。(指針的概念。)

const命令只是保證變量名指向的地址不變,并不保證該地址的數(shù)據(jù)不變,所以將一個對象聲明為常量必須非常小心。

例子:

const foo = {};
foo.prop = 123;

foo.prop// 123

foo = {} // TypeError: "foo" is read-only不起作用
代碼

例子:

const a = [];
a.push("Hello"); // 可執(zhí)行
a.length = 0;    // 可執(zhí)行
a = ["Dave"];    // 報錯代碼

【07】ES5只有兩種聲明變量的方式:var命令和function命令。

ES6除了添加let和const命令,另外兩種聲明變量的方法:import命令和class命令。

所以,ES6一共有6種聲明變量的方法。

【08】ES6規(guī)定,var命令和function命令聲明的全局變量,依舊是全局對象的屬性;

let命令、const命令、class命令聲明的全局變量,不屬于全局對象的屬性。

var a = 1;
// 如果在Node的REPL環(huán)境,可以寫成global.a
// 或者采用通用方法,寫成this.a
window.a // 1
let b = 1;
window.b // undefined
代碼

【09】如果真的想將對象凍結(jié)不再變,應(yīng)該使用Object.freeze()方法。此時添加屬性無效。

const foo = Object.freeze({});
foo.prop = 123; // 不起作用
代碼

除了將對象本身凍結(jié),對象的屬性也應(yīng)該凍結(jié)。

下面是一個將對象徹底凍結(jié)的函數(shù)。

var constantize = (obj) => {
    Object.freeze(obj);
    Object.keys(obj).forEach((key, value) => {
        if (typeof obj[key] === 'object') {
            constantize(obj[key]);
        }
    });
};
代碼

【10】跨模塊常量

const聲明的常量只在當(dāng)前代碼塊有效。

如果想設(shè)置跨模塊的常量,可以將這些變量暴露出去,提供給其他模塊即可。

// constants.js 模塊
export const A = 1;
export const B = 3;
export const C = 4;
// test1.js 模塊
import * as constants from './constants';
console.log(constants.A); // 1
console.log(constants.B); // 3

// test2.js 模塊
import {A, B} from './constants';
console.log(A); // 1
console.log(B); // 3
代碼

【01】變量+標(biāo)識符

06、在函數(shù)內(nèi)部,沒有var聲明的變量,是作為全局變量存在的;

當(dāng)用var聲明一個JS全局變量時,實際上是定義了全局對象window的一個屬性。

~~~~~~~~~~~~~ 我是晚上沒蓋被子的分割線 ~~~~~~~~~~~~~~~~~~~

三、變量+標(biāo)識符

【01】聲明變量

01、變量是用var運算符(variable 的縮寫)加變量名定義的。

例如:

var test = "hi";代碼

02、ES中的變量并不一定要初始化賦值。

如果未在var聲明語句中給變量指定初始值,那么它的初始值就是undefined。var test;代碼

03、可以用一個 var 語句定義兩個或多個變量:

var test1 = "hi", test2 = "hello";代碼

04、變量可以是不同類型。

var test = "hi", age = 25;代碼

05、由于由于ES是弱類型的,所以變量聲明時不用聲明類型。

變量可以存放不同類型的值。這是弱類型變量的優(yōu)勢。

變量是弱類型的?;蛘哒f是“松散類型”。就是可以用來保存任何類型的數(shù)據(jù)。

可以隨時改變變量保存的數(shù)據(jù)的類型。

例如,可以把變量初始化為字符串類型的值,之后把它設(shè)置為數(shù)字值,如下所示:

var test = "hi";
alert(test);
test = 55;
alert(test);代碼

06、在函數(shù)內(nèi)部,沒有var聲明的變量,是作為全局變量存在的;

【02】變量的生命周期

JS變量的生命期從它們被聲明的時間開始。

局部變量會在函數(shù)運行以后被刪除。

全局變量會在頁面關(guān)閉后被刪除。

【03】當(dāng)用var聲明一個JS全局變量時,實際上是定義了全局對象window的一個屬性。

【04】當(dāng)使用var聲明一個變量時,這個變量無法通過delete運算符刪除。

非嚴(yán)格模式,給一個未聲明的變量賦值,JS會自動創(chuàng)建一個全局變量,可以用delete刪除。

例子:

var truevar = 1;        // 聲明一個不可刪除的全局變量
fakevar = 2;            // 創(chuàng)建全局對象的一個可刪除的屬性
this.fakevar2 = 3;      // 同上
delete truevar          // => false: 變量并沒有被刪除
delete fakevar          // => true: 變量被刪除
delete this.fakevar2    // => true: 變量被刪除
代碼

【05】聲明、初始化、賦值之間的區(qū)別

聲明: 變量將會對一個包含相應(yīng)作用域范圍(比如: 在一個函數(shù)里面)的名字進(jìn)行注冊。

初始化: 當(dāng)你聲明一個變量的時候會自動的進(jìn)行初始化,也就是說由 JavaScript 解釋引擎為變量分配了一塊內(nèi)存。

賦值: 把一個指定的值指派(賦)給一個變量。

使用:使用變量的值 例如alert(myValue)

~~~~~~~~~~~~~ 我是被蚊子咬的分割線 ~~~~~~~~~~~~~~~~~~~

四、變量作用域

【01】全局變量和局部變量

全局變量

在函數(shù)外聲明的變量是全局變量,網(wǎng)頁上的所有腳本和函數(shù)都能訪問它。

局部變量

在JS函數(shù)內(nèi)部聲明的變量(使用 var)是局部變量,只能在函數(shù)內(nèi)部訪問它。(該變量的作用域是局部的)。 可以在不同的函數(shù)中使用名稱相同的局部變量,因為只有聲明過該變量的函數(shù)才能識別出該變量。 只要函數(shù)運行完畢,局部變量就會被刪除。

function a (){ var b=3; }

console.log(b);//b is not defined

【02】一個變量的作用域(scope)是代碼中定義這個變量的區(qū)域。

全局變量擁有全局作用域,在JS代碼中的任何地方都是有定義的。

然而在函數(shù)內(nèi)聲明的變量只在函數(shù)體內(nèi)有定義。

它們是局部變量,作用域是局部性的。

函數(shù)參數(shù)也是局部變量,它們只在函數(shù)體內(nèi)有定義。

【03】在ES5中,JS通過函數(shù)管理作用域。ES6中,有塊作用域。

【04】全局變量會在下列情況下出現(xiàn):

  • 在任何地方不使用 var ,let,const聲明變量。
  • 直接向未聲明的變量賦值。
  • 在函數(shù)外部使用 var 聲明的變量。
  • 以 window. variable 形式聲明的變量。

【05】全局變量的問題

所有的JS文件共享這些全局變量,它們在同一個全局命名空間,很容易命名沖突。

比如:

  • 第三方的功能庫文件。
  • 同事的代碼。
  • 第三方的用戶分析代碼。
function sum(x, y) {
   // 不推薦寫法: 隱式全局變量
   result = x + y;
   return result;
}
代碼

a是本地變量但是b確實全局變量。賦值表達(dá)式是從右向左進(jìn)行的。

// 反例,勿使用
function foo() {
   var a = b = 0;
   // ...
}
代碼

【07】避免全局變量的方式:

方式1:使用ES6的let和const聲明變量。

方式2:使用立即自執(zhí)行函數(shù)。

(val=>val+1)(3); //4

【08】在函數(shù)體內(nèi),局部變量的優(yōu)先級高于同名的全局變量。

如果在函數(shù)內(nèi)聲明的一個局部變量或者函數(shù)參數(shù)中帶有的變量和全局變量重名,那么全局變量就被局部變量所覆蓋。

var scope = "global";           // 聲明一個全局變量
function checkscope() {
        var scope = "local";    // 聲明一個同名的局部變量
        return scope;           // 返回局部變量的值,而不是全局變量的值
}
checkscope()                    // => "local"
代碼

【09】函數(shù)定義是可以嵌套的。由于每個函數(shù)都有它自己的作用域,因此會出現(xiàn)幾個局部作用域嵌套的情況,例如:

var scope = "global scope";     // 全局變量
function checkscope() {
    var scope = "local scope";      //局部變量 
    function nested() {
        var scope = "nested scope";     // 嵌套作用域內(nèi)的局部變量
        return scope;                   // 返回當(dāng)前作用域內(nèi)的值
    }

    return nested();
}

checkscope()                    // => "嵌套作用域"代碼
var a = 2;
function test(){
    console.log(a);
    var a = 10;
}
test();//undefined;
代碼

01、在程序設(shè)計語言中,變量可分為自由變量與約束變量兩種。

簡單來說,局部變量和參數(shù)都被認(rèn)為是約束變量;

而不是約束變量的則是自由變量。

02、在馮·諾依曼計算機體系結(jié)構(gòu)的內(nèi)存中,變量的屬性可以視為一個六元組:(名字,地址,值,類型,生命期,作用域)。

地址屬性具有明顯的馮·諾依曼體系結(jié)構(gòu)的色彩,代表變量所關(guān)聯(lián)的存儲器地址(內(nèi)存)。

類型規(guī)定了變量的取值范圍和可能的操作。(比如數(shù)值有取值范圍)

生命期表示變量與某個存儲區(qū)地址綁定的過程。

根據(jù)生命期的不同,變量可以被分為四類:靜態(tài)、棧動態(tài)、顯式堆動態(tài)和隱式堆動態(tài)。

作用域限制了變量在語句中的適用范圍,分為詞法作用域和動態(tài)作用域兩種。

在詞法作用域的環(huán)境中,變量的作用域與其在代碼中所處的位置有關(guān)。

由于代碼可以靜態(tài)決定(運行前就可以決定),所以變量的作用域也可以被靜態(tài)決定,因此也將該作用域稱為靜態(tài)作用域。

在動態(tài)作用域的環(huán)境中,變量的作用域與代碼的執(zhí)行順序有關(guān)。

JavaScript和C都是詞法作用域語言。

~~~~~~~~~~~~~ 我是晚上沒蓋被子的分割線 ~~~~~~~~~~~~~~~~~~~

五、塊級作用域

【01】為什么要有塊級作用域?

ES5只有全局作用域和函數(shù)作用域,沒有塊級作用域,這帶來很多不合理的場景。

var tmp = new Date();
function f(){
  console.log(tmp);
  if (false){
    var tmp = "hello world";
  }
}
f() // undefined
代碼
var s = 'hello';
for (var i = 0; i < s.length; i++){
  console.log(s[i]);
}
console.log(i); // 5
代碼

【02】ES6的塊級作用域

let和const為JavaScript新增了塊級作用域。

function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
}
代碼

【03】內(nèi)層作用域可以和外層作用域定義同名的變量。

{{{{let insane = 'Hello World';{let insane = 'Hello World';}}}}};
代碼

【04】立即執(zhí)行匿名函數(shù)(IIFE)不再必要了。

// IIFE寫法
(function () {var tmp = ...;...}());
// 塊級作用域?qū)懛?{let tmp = ...;...}
代碼

【05】函數(shù)的作用域,在其定義的父級代碼塊作用域之內(nèi)。

function f() {
    console.log('I am outside!');
}
(function () {
    if (false) { // 重復(fù)聲明一次函數(shù)f
        function f() {
            console.log('I am inside!');
        }
    }
    f();
}());
代碼

上面代碼在ES5中運行,會得到“I am inside!”,但是在ES6中運行,會得到“I am outside!”。

這是因為ES5存在函數(shù)提升,不管會不會進(jìn)入 if代碼塊,函數(shù)聲明都會提升到當(dāng)前作用域的頂部,得到執(zhí)行;

而ES6支持塊級作用域,不管會不會進(jìn)入if代碼塊,其內(nèi)部聲明的函數(shù)皆不會影響到作用域的外部。

【06】塊級作用域外部,無法調(diào)用塊級作用域內(nèi)部定義的函數(shù)。如果確實需要調(diào)用,就要像下面這樣處理。

{
  let a = 'secret';
  function f() {
    return a;
  }
}
f() // 報錯
代碼

【】例子:

let f;{let a = 'secret';
  f = function () {return a;}}f(); // "secret"
代碼

【07】在嚴(yán)格模式下,函數(shù)只能在頂層作用域和函數(shù)內(nèi)聲明,其他情況(比如if代碼塊、循環(huán)代碼塊)的聲明都會報錯。

【08】ES6允許塊級作用域的任意嵌套。

{{{{{let insane = 'Hello World'}}}}};
代碼

上面代碼使用了一個五層的塊級作用域。外層作用域無法讀取內(nèi)層作用域的變量。

{{{{{let insane = 'Hello World'}
  console.log(insane); // 報錯
}}}};代碼

~~~~~~~~~~~~~ 我是晚上沒蓋被子的分割線 ~~~~~~~~~~~~~~~~~~~

六、變量和函數(shù)聲明提升

【01】變量提升(hoisting)是 JavaScript 編譯器的行為,將所有的變量和函數(shù)聲明移至當(dāng)前作用域的最高處。

然而,只有聲明被提升了。任何賦值行為都被留在它們所在的地方。

如果在聲明之前訪問該變量,它的值是undefined。

例子:

(function() {
  var foo = 1;
  var bar = 2;
  var baz = 3;

  console.log(foo + " " + bar + " " + baz);//"1 2 3"
})();
代碼

【】例子:

(function() {
  var foo = 1;
  console.log(foo + " " + bar + " " + baz);// "1 undefined undefined"
  var bar = 2;
  var baz = 3;
})();
代碼

【】例子:

(function() {
  var foo = 1;
  console.log(foo + " " + bar + " " + baz);// ReferenceError baz未定義。
  var bar = 2;
代碼

【02】函數(shù)變量提升(hoisting)

函數(shù)聲明也是可以被提升的。

函數(shù)聲明表達(dá)式不會聲明提升。

【】例子:

foo();//Hello! 成功。

function foo() {
  console.log("Hello!");
}
代碼

【】例子:

foo();//失敗。函數(shù)表達(dá)式聲明不能函數(shù)提升。

var foo = function() {
  console.log("Hello!");
};
代碼

【03】聲明命令和變量聲明提升

用var 聲明的變量存在變量聲明提升。

let和const聲明的變量不會變量聲明提升,存在暫時性死區(qū)。

class 聲明的類不會聲明提升。

function double(num) {
  console.log(myVariable); // => undefined
  var myVariable;
  return num * 2;
}
double(3); // => 6
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容