let 與 const 命令 以及箭頭函數初步學習

ES6 let 與 const 命令 以及箭頭函數初步學習

let 與 const

let

let 和const用于定義變量。let類似于之前的var。const是定義常量的。

ES6 新增了let命令,用來聲明變量。它的用法類似于var,但是所聲明的變量,只在let命令所在的代碼塊內有效。

<script>
{
    var a = 12;
    let b =10;
}
console.log(a)
console.log(b)
</script>

let使用在for循環中

 //let使用for循環參數里只能在循環內部使用
 for(let i=0;i<=5;i++){
     console.log(i)
 }
 console.log(i) //not  defined

let在for循環添加事件中最典型的使用方式

var btns = document.getElementsByTagName('button');
for(let i=0;i<btns.length;i++){
    btns[i].onclick = function(){
        console.log(i)
    }
}

let 在for循環作為循環參數的時候,其實是倆個作用域,大括號里面的是子作用域,如果里面再次定義與循環參數相同的變量,則不會產生相互影響。

//for其實是倆個作用域,大括號里面是一個子作用域
for(let i=0;i<=3;i++){
    let i = 'a';
    console.log(i);
}

let不存在變量提升,let命令改變了語法行為,它所聲明的變量一定要在聲明后使用,否則報錯。

console.log(a) //not defined
let a = 12;

暫時性死區

ES6 明確規定,如果區塊中存在let和const命令,這個區塊對這些命令聲明的變量,從一開始就形成了封閉作用域。凡是在聲明之前就使用這些變量,就會報錯。

在代碼塊內,使用let命令聲明變量之前,該變量都是不可用的。這在語法上,稱為“暫時性死區“,let不允許重復定義一個變量。

//大括號括起來的稱之為塊作用域,有循環  switch結構 if結構
for(let i =0;i<3;i++){
    console.log(i)
    // let i ='a';
    let i = '123';
}

塊級作用于

  • ES5 規定,函數只能在頂層作用域和函數作用域之中聲明,不能在塊級作用域聲明。

    瀏覽器沒有遵守這個規定,為了兼容以前的舊代碼,還是支持在塊級作用域之中聲明函數,因此上面兩種情況實際都能運行,不會報錯。

if(true){
    function say(){
        console.log(1)
    }
}
say();  //1
  • ES6 引入了塊級作用域,明確允許在塊級作用域之中聲明函數。ES6 規定,塊級作用域之中,函數聲明語句的行為類似于let,在塊級作用域之外不可引用。
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/babel">
    // Your ES6 code
    if(true){
        function say(){
            console.log(1)
        }
    }
    say();
    </script>
WX20200602-113748@2x.png

為了減輕因此產生的不兼容問題,ES6 在附錄 B里面規定,瀏覽器的實現可以不遵守上面的規定,有自己的行為方式。

  1. 允許在塊級作用域內聲明函數。
  2. 函數聲明類似于var,即會提升到全局作用域或函數作用域的頭部。
  3. 同時,函數聲明還會提升到所在的塊級作用域的頭部。

const

  • const聲明一個只讀的常量。一旦聲明,常量的值就不能改變。
const PI = 3.1415;
PI // 3.1415
            
PI = 3;
console.log(PI);//TypeError: invalid assignment to const `PI'

上面代碼表明改變常量的值會報錯。

  • const聲明的變量不得改變值,這意味著,const一旦聲明變量,就必須立即初始化,不能留到以后賦值。
const foo;
// SyntaxError: Missing initializer in const declaration

上面代碼表示,對于const來說,只聲明不賦值,就會報錯。

  • const的作用域與let命令相同:只在聲明所在的塊級作用域內有效。
if (true) {
  const MAX = 5;
}

MAX // Uncaught ReferenceError: MAX is not defined
  • const命令聲明的常量也是不提升,同樣存在暫時性死區,只能在聲明的位置后面使用。
if (true) {
  console.log(MAX); // ReferenceError
  const MAX = 5;
}

上面代碼在常量MAX聲明之前就調用,結果報錯。

  • const聲明的常量,也與let一樣不可重復聲明。
var message = "Hello!";
let age = 25;

// 以下兩行都會報錯
const message = "Goodbye!";
const age = 30;


本質
  • const實際上保證的,并不是變量的值不得改動,而是變量指向的那個內存地址所保存的數據不得改動。對于簡單類型的數據(數值、字符串、布爾值),值就保存在變量指向的那個內存地址,因此等同于常量。但對于復合類型的數據(主要是對象和數組),變量指向的內存地址,保存的只是一個指向實際數據的指針,const只能保證這個指針是固定的(即總是指向另一個固定的地址),至于它指向的數據結構是不是可變的,就完全不能控制了。因此,將一個對象聲明為常量必須非常小心。
const foo = {};
           
// 為 foo 添加一個屬性,可以成功
foo.prop = 123;

console.log(foo.prop); // 123
// 將 foo 指向另一個對象,就會報錯
foo = {}; // TypeError: "foo" is read-only
         // "foo" is read-only

上面代碼中,常量foo儲存的是一個地址,這個地址指向一個對象。不可變的只是這個地址,即不能把foo指向另一個地址,但對象本身是可變的,所以依然可以為其添加新屬性。

const a = [];
a.push('Hello'); // 可執行
console.log(a);  // Array ["Hello"]
a.length = 0;    // 可執行
console.log(a);  // Array []
a = ['Dave'];    // 報錯 TypeError: invalid assignment to const `a'

上面代碼中,常量a是一個數組,這個數組本身是可寫的,但是如果將另一個數組賦值給a,就會報錯。

如果真的想將對象凍結,應該使用Object.freeze方法。

const foo = Object.freeze({});
console.log(foo);
// 常規模式時,下面一行不起作用;
// 嚴格模式時,該行會報錯
foo.prop = 123;
        
console.log(foo); // Object {  }

上面代碼中,常量foo指向一個凍結的對象,所以添加新屬性不起作用,嚴格模式時還會報錯。

除了將對象本身凍結,對象的屬性也應該凍結。下面是一個將對象徹底凍結的函數。

var constantize = (obj) => {
  Object.freeze(obj);
  Object.keys(obj).forEach( (key, i) => {
    if ( typeof obj[key] === 'object' ) {
      constantize( obj[key] );
    }
  });
};
//這里用到了箭頭函數

ES6 聲明變量的六種方法

  • ES5 只有兩種聲明變量的方法:var命令和function命令。ES6 除了添加letconst命令,后面章節還會提到,另外兩種聲明變量的方法:import命令和class命令。所以,ES6 一共有 6 種聲明變量的方法。

ES6箭頭函數、箭頭函數與普通函數的區別

一、基本語法

ES6中允許使用箭頭=>來定義箭頭函數,具體語法,我們來看一個簡單的例子:

// 箭頭函數
let fun = (name) => {
    // 函數體
    return `Hello ${name} !`;
};

// 等同于
let fun = function (name) {
    // 函數體
    return `Hello ${name} !`;
};

可以看出,定義箭頭函在數語法上要比普通函數簡潔得多。箭頭函數省去了function關鍵字,采用箭頭=>來定義函數。函數的參數放在=>前面的括號中,函數體跟在=>后的花括號中。

關于箭頭函數的參數:

如果箭頭函數沒有參數,直接寫一個空括號即可。

如果箭頭函數的參數只有一個,也可以省去包裹參數的括號。

如果箭頭函數有多個參數,將參數依次用逗號(,)分隔,包裹在括號中即可。

// 沒有參數
let fun1 = () => {
    console.log(111);
};

// 只有一個參數,可以省去參數括號
let fun2 = name => {
    console.log(`Hello ${name} !`)
};

// 有多個參數
let fun3 = (val1, val2, val3) => {
    return [val1, val2, val3];
};

關于箭頭函數的函數體:

如果箭頭函數的函數體只有一句代碼,就是簡單返回某個變量或者返回一個簡單的JS表達式,可以省去函數體的大括號{ }。

let f = val => val;
// 等同于
let f = function (val) { return val };

let sum = (num1, num2) => num1 + num2;
// 等同于
let sum = function(num1, num2) {
  return num1 + num2;
};

如果箭頭函數的函數體只有一句代碼,就是返回一個對象,可以像下面這樣寫:

// 用小括號包裹要返回的對象,不報錯
let getTempItem = id => ({ id: id, name: "Temp" });

// 但絕不能這樣寫,會報錯。
// 因為對象的大括號會被解釋為函數體的大括號
let getTempItem = id => { id: id, name: "Temp" };

如果箭頭函數的函數體只有一條語句并且不需要返回值(最常見是調用一個函數),可以給這條語句前面加一個void關鍵字

let fn = () => void doesNotReturn();

箭頭函數最常見的用處就是簡化回調函數。

// 例子一
// 正常函數寫法
[1,2,3].map(function (x) {
  return x * x;
});

// 箭頭函數寫法
[1,2,3].map(x => x * x);

// 例子二
// 正常函數寫法
var result = [2, 5, 1, 4, 3].sort(function (a, b) {
  return a - b;
});

// 箭頭函數寫法
var result = [2, 5, 1, 4, 3].sort((a, b) => a - b);

二、箭頭函數與普通函數的區別

1、語法更加簡潔、清晰

從上面的基本語法示例中可以看出,箭頭函數的定義要比普通函數定義簡潔、清晰得多,很快捷。

2、箭頭函數不會創建自己的this(重要!!深入理解!!)

我們先來看看MDN上對箭頭函數this的解釋。

箭頭函數不會創建自己的this,所以它沒有自己的this,它只會從自己的作用域鏈的上一層繼承this。

箭頭函數沒有自己的this,它會捕獲自己在定義時(注意,是定義時,不是調用時)所處的外層執行環境的this,并繼承這個this值。所以,箭頭函數中this的指向在它被定義的時候就已經確定了,之后永遠不會改變。

來看個例子:

var id = 'Global';

function fun1() {
    // setTimeout中使用普通函數
    setTimeout(function(){
        console.log(this.id);
    }, 2000);
}

function fun2() {
    // setTimeout中使用箭頭函數
    setTimeout(() => {
        console.log(this.id);
    }, 2000)
}

fun1.call({id: 'Obj'});     // 'Global'

fun2.call({id: 'Obj'});     // 'Obj'

上面這個例子,函數fun1中的setTimeout中使用普通函數,2秒后函數執行時,這時函數其實是在全局作用域執行的,所以this指向Window對象,this.id就指向全局變量id,所以輸出'Global'
但是函數fun2中的setTimeout中使用的是箭頭函數,這個箭頭函數的this在定義時就確定了,它繼承了它外層fun2的執行環境中的this,而fun2調用時thiscall方法改變到了對象{id: 'Obj'}中,所以輸出'Obj'

再來看另一個例子:

var id = 'GLOBAL';
var obj = {
  id: 'OBJ',
  a: function(){
    console.log(this.id);
  },
  b: () => {
    console.log(this.id);
  }
};

obj.a();    // 'OBJ'
obj.b();    // 'GLOBAL'

上面這個例子,對象obj的方法a使用普通函數定義的,普通函數作為對象的方法調用時,this指向它所屬的對象。所以,this.id就是obj.id,所以輸出'OBJ'
但是方法b是使用箭頭函數定義的,箭頭函數中的this實際是繼承的它定義時所處的全局執行環境中的this,所以指向Window對象,所以輸出'GLOBAL'。(這里要注意,定義對象的大括號{}是無法形成一個單獨的執行環境的,它依舊是處于全局執行環境中!!

三、箭頭函數繼承而來的this指向永遠不變(重要!!深入理解!!)

上面的例子,就完全可以說明箭頭函數繼承而來的this指向永遠不變。對象obj的方法b是使用箭頭函數定義的,這個函數中的this永遠指向它定義時所處的全局執行環境中的this,即便這個函數是作為對象obj的方法調用,this依舊指向Window對象。

四、.call()/.apply()/.bind()無法改變箭頭函數中this的指向

.call()/.apply()/.bind()方法可以用來動態修改函數執行時this的指向,但由于箭頭函數的this定義時就已經確定且永遠不會改變。所以使用這些方法永遠也改變不了箭頭函數this的指向,雖然這么做代碼不會報錯。

var id = 'Global';
// 箭頭函數定義在全局作用域
let fun1 = () => {
    console.log(this.id)
};

fun1();     // 'Global'
// this的指向不會改變,永遠指向Window對象
fun1.call({id: 'Obj'});     // 'Global'
fun1.apply({id: 'Obj'});    // 'Global'
fun1.bind({id: 'Obj'})();   // 'Global'

五、箭頭函數不能作為構造函數使用

我們先了解一下構造函數的new都做了些什么?簡單來說,分為四步:

① JS內部首先會先生成一個對象;

② 再把函數中的this指向該對象;

③ 然后執行構造函數中的語句;

④ 最終返回該對象實例。

但是!!因為箭頭函數沒有自己的this,它的this其實是繼承了外層執行環境中的this,且this指向永遠不會隨在哪里調用、被誰調用而改變,所以箭頭函數不能作為構造函數使用,或者說構造函數不能定義成箭頭函數,否則用new調用時會報錯!

let Fun = (name, age) => {
    this.name = name;
    this.age = age;
};

// 報錯
let p = new Fun('cao', 24);

六、箭頭函數沒有自己的arguments

箭頭函數沒有自己的arguments對象。在箭頭函數中訪問arguments實際上獲得的是外層局部(函數)執行環境中的值。

// 例子一
let fun = (val) => {
    console.log(val);   // 111
    // 下面一行會報錯
    // Uncaught ReferenceError: arguments is not defined
    // 因為外層全局環境沒有arguments對象
    console.log(arguments); 
};
fun(111);

// 例子二
function outer(val1, val2) {
    let argOut = arguments;
    console.log(argOut);    // ①
    let fun = () => {
        let argIn = arguments;
        console.log(argIn);     // ②
        console.log(argOut === argIn);  // ③
    };
    fun();
}
outer(111, 222);

上面例子二,①②③處的輸出結果如下:

img

很明顯,普通函數outer內部的箭頭函數fun中的arguments對象,其實是沿作用域鏈向上訪問的外層outer函數的arguments對象。

可以在箭頭函數中使用rest參數代替arguments對象,來訪問箭頭函數的參數列表!!

七、箭頭函數沒有原型prototype

let sayHi = () => {
    console.log('Hello World !')
};
console.log(sayHi.prototype); // undefined

八、箭頭函數不能用作Generator函數,不能使用yeild關鍵字

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379