暫時性死區以及函式作用域

兔子先森發表於2023-02-22

暫時性死區

暫時性死區也就是變數宣告到宣告完成的區塊,這個區塊是一個封閉的作用域,直到宣告完成。
如果在變數宣告之前使用該變數,那麼該變數是不可用的,也就被稱為暫時性死區。

  • var 沒有暫時性死區,因為var存在變數提升
  • let、const有塊級作用域,沒有變數提升,存在暫時性死區
console.log(a); // 報錯 Cannot access 'a' before initialization
let a = '東方不敗' 
console.log(b); // 報錯 Cannot access 'b' before initialization
const b = '東方不敗'
console.log(c);   // undefined 因為var存在變數提升
var c = '東方求敗'  

ES6規定,如果程式碼塊中存在letconst命令宣告的變數,這個區塊對這些變數從一開始就形成了封閉作用域,直到宣告語句完成,這些變數才能被訪問(獲取或設定),否則會報錯ReferenceError。這在語法上稱為“暫時性死區”(英temporal dead zone,簡 TDZ),既程式碼塊開始到變數生命語句完成之前的區域。


函式作用域

案例一
一旦設定了引數的預設值,函式進行生命初始化時,引數就會形成一個單獨的作用域,等初始化結束,這個作用域就會消失,這種語法在不設定引數預設值時不會出現。

var x = 1
function f(x,y = x){
     console.log(y);
    }
f(2)  // 2

上面這個例子中,函式引數這裡(x,y = x),這個區域就是單獨的作用域,y預設的x變數指向第一個引數x,而不是全域性變數x,這裡呼叫f函式,向x傳遞數值2y = x 那麼 y = 2,列印結果為2

案例二

let x2 = 1
function f2(y2 = x2){
         let x2 = 2
         console.log(y2);
     }
f2()  // 1

呼叫f2函式,由於未給f2函式任何引數,並且 y2 = x2 形成一個單獨的作用域,在這個作用域裡x2並未定義,所以x2指向的是外層全域性變數x2y2 = x2 也就是y2 = 1,在這裡,函式內部的x2並未起到任何作用。

函式執行的時候會先執行引數,再執行函式體。
// 報錯
function f2(y2 = x2){
         let x2 = 2
         console.log(y2);
     }
f2()  // 報錯

上面的例子中,如果去掉全域性變數x2則會報錯,因為變數為宣告,給y2賦值了一個未宣告的變數,報錯。

var xx = 1
function fxx(xx = xx){
    console.log(xx);
    }
fxx()

上面這個寫法也會報錯,由於函式的引數存在單獨的作用域,在這個引數作用域內,執行結果為 let xx = xx,給xx賦值一個未宣告的變數xx報錯。(暫時性死區)


如果函式的預設引數是函式,該函式的作用域也要遵循這個規則。

let foo = 'out'
function bar(func = () => foo){
      let foo = 'come'
      console.log(func());
  }
bar()  // out

這個例子中,函式的引數是func預設值是一個匿名函式,返回值為變數foo,由於函式引數這裡形成一個單獨的作用域,在這個作用域裡面並沒有定義變數foo,所以foo會指向外層全域性變數foo。如果去掉全域性變數foo='out'報錯,賦值了一個未宣告的變數。


應用
可以利用這個特性寫一個引數預設值錯誤丟擲,如果引數並未傳參則丟擲一個錯誤。

function throwErr(){
    throw new Error('引數不得省略')
    }
    
function omits(mustfn = throwErr()){
    return mustfn
    }
omits(); // 未傳參丟擲錯誤 : 引數不得省略

呼叫omits函式未傳引數,該函式就會預設呼叫throwErr()函式並丟擲錯誤。

如果將引數預設值設定為 undefined 則表示該引數是可以省略的。


rest引數

arguments
arguments可以獲得函式的引數值以及函式資訊(name、length)等

    function au(arr){
        console.log('arguments:',arguments);
    }
    au(2,1,4,3)

在這裡插入圖片描述

可以透過陣列方法對函式引數進行操作,例如排序。
arguments物件不是陣列,而是一個類似陣列的物件,為了使用陣列的方法,必須使用Array.from先將其轉為陣列。

function au(arr){
     // 透過陣列方法對函式引數進行排序
     return Array.from(arguments).sort();
   }
console.log(au(2,1,4,3))  // [1,2,3,4]

在這裡插入圖片描述


rest引數
ES6提供了rest引數,語法:(...變數名),其實就是剩餘運算子,透過rest引數就可以很容易的對函式引數進行操作,並且rest的引數是一個真正的陣列。

// resy引數(剩餘運算子)
function residue(...val){
      console.log(val);  // [1,2,3]
    }
residue(1,2,3)

rest引數(剩餘運算子)只能放到最後一位,否則報錯

// function residue2(...val,b){}  // 剩餘運算子不是最後一位,報錯
function residue3(c,...val){
  console.log(c,val);  // 1 [2,3,4,5] 
 }
residue3(1,2,3,4,5)

上面arguments完成的引數排序,使用rest可以很輕鬆的做到,並且語義更強,更方便閱讀。

let au2 = (...val) => val.sort()
console.log(au2(2,1,4,3));  // [1, 2, 3, 4]


嚴格模式

ES5開始,函式內部可以設定為嚴格模式: function s(){ 'use strict'// 嚴格模式 }

// es5嚴格模式
function s(){
   'use strict'   // 嚴格模式
   // 程式碼.....
}

ES6做了修改,規定只要函式引數使用了預設值、解構賦值、擴充套件運算子,那麼函式內部就不能顯示設定為嚴格模式,否則報錯。

// es6嚴格模式  報錯,因為設定了函式預設值
function s2(a,b = a){
   'use strict'
   // 程式碼.....
}
// 報錯,使用瞭解構賦值
const s3 = function({a,b}){
    'use strict'
}
// 報錯,使用了剩餘運算子
const s4 = (...a) => {
    'use strict'
}

es6這樣設定的原因是,函式內部的嚴格模式應該同樣適用於函式體和函式引數,但是,函式執行的時候會先執行引數,再執行函式體,這樣就有一些不嚴謹的情況,只有函式體中才能知道引數是否應該以嚴格模式執行,但是函式的引數確是先執行,所以es6修改了函式引數關於嚴格模式的行為。

function s5(val = 070){
    'use strict'
    return val
}
s5()  // 報錯

這一段,函式的預設值是八進位制070,嚴格模式下不能使用字首0表示八進位制,所以報錯。
實際上是因為函式設定了預設引數的原因,函式先執行引數,再進函式體,由於es6限制,報錯。


有兩種方法可以規避這種限制
第一種:設定全域性嚴格模式

'use strict'
function s6(val = 100){
  console.log(val);
}
s6()  // 100

第二種方法:把函式巢狀在一個無引數的立即執行函式里

const s7 = () => {
     'use strict'
     let a;
     return (function(val = 200){ return val })()
}
console.log(s7());
匿名函式的呼叫方法:在上述例子(function(val = 200){return val})()中,將整個return的函式用()套起來,尾部加一個()呼叫即可,()在函式中代表呼叫。


案例原始碼:https://gitee.com/wang_fan_w/es6-science-institute

如果覺得這篇文章對你有幫助,歡迎點亮一下star喲

相關文章