1. 程式人生 > >深入理解ES6(至第三章-函式)

深入理解ES6(至第三章-函式)

第一章 塊級作用域繫結

var 宣告初始化變數, 宣告可以提升,但初始化不可以提升。

一、塊級宣告:

  1. let
  2. const
    • 預設使用,在某種程度上實現程式碼不可變,減少錯誤發生的機率
    • 如果常量是物件,則物件中的值可以修改
  3. 不能重複宣告,宣告不會提升

二、臨時死區:

JS引擎在掃描程式碼發現變數宣告時,要麼將它們提升至作用域頂部(var宣告),要麼將宣告放到TDZ中(letconst宣告)。訪問TDZ中的變數會觸發執行時錯誤。只有執行過變數宣告語句後,變數才會從TDZ中移出,然後可以正常訪問。

第一種情況:

if(condition) {
    console.log(typeof value); //引用錯誤!
    let
value = "blue"; //TDZ } 複製程式碼

第二種情況:

console.log(typeof value); //'undefined'-->只有變數在TDZ中才會報錯
if(condition) {
    let value = "blue";
}
複製程式碼

三、循壞中塊作用域繫結

  • 立即呼叫(IIFE)

  • letconst之所以可以在運用在for-infor-of迴圈中,是因為每次迭代會建立一個新的繫結(const在for迴圈中會報錯)。

四、全域性塊作用域繫結

var 可能會在無意中覆蓋一個已有的全域性屬性 letconst,會在全域性作用域下建立一個新的繫結,但該繫結不會新增為全域性物件的屬性。換句話說,使用let

const不能覆蓋全域性變數,而只能遮蔽它。如果不行為全域性物件建立屬性,使用letconst要安全得多。

注:如果希望子安全域性物件下定義變數,仍然可以使用var。這種情況常見於在瀏覽器中跨frame或跨window訪問程式碼

第二章 字串和正則表示式

一、UTF-8碼位

名詞解釋:

  • 碼位: 每一個字元的“全球唯一的識別符號,從0開始的數值”
  • 字元編碼:表示某個字元的數值或碼位即為該字元的字元編碼。
  • 基本多文種平面(BMP,Basic Multilingual Plane)

    在UTF-16中,前2^16個碼位均以16位的編碼單元表示,這個範圍被稱作基本多文種平面

二、codePointAt()、 String.fromCodePoint() 和 normalize()

  • 兩個方法對應於charCodeAt()fromCharCode()
  • normalize(): 規範的統一,適用於比較排序,國際化。

三、正則表示式 u 和 y 修飾符、正則表示式的複製、flag屬性

  • u: 編碼單元 ---> 字元模式

這個方法儘管有效,但是當統計長字串中的碼位數量時,運動效率很低。因此,你也可以使用字串迭代器解決效率低的問題,總體而言,只要有可能就嘗試著減小碼位計算的開銷。

檢測u修飾符支援:

function hasRegExpU() {
    try {
        var pattern = new RegExp('.', 'u');
        return true;
    } catch (ex) {
        return false;
    }
}
複製程式碼
  • y: 第一次匹配不到就終止匹配

當執行操作時, y修飾符會把上次匹配後面一個字元的索引儲存到lastIndexOf中;如果該操作匹配的結果為空,則lastIndexOf會被重置為0。g修飾符的行為類似。

1. 只有呼叫exec()和test()這些正則表示式物件的方法時才會涉及lastIndex屬性;
2. 呼叫字串的方法,例如match(),則不會觸發粘滯行為。
複製程式碼
  • 正則表示式的複製
var re1 = /ab/i;
re2 = new RegExp(re1); //沒有修飾符複製
re3 = new RegExp(re1, "g"); //有修飾符(ES6)
複製程式碼
  • flag屬性 --- 獲取正則表示式的修飾符

es5方法獲取正則表示式的修飾符:

function getFlags(re) {
    var text = re.toString();
    return text.substring(text.lastIndexOf('/' + 1, text.length);
}
複製程式碼

模板字面量

多行字串

基本的字串格式化(字串佔位符)

HTML轉義

  • 標籤模板
function passthru(literals, ...substitutions) {
    //返回一個字串
    let result = "";
    //根據substitutions的數量來確定迴圈的執行次數
    for(let i=0; i<substitutions.length; i++){
        result += literals;
        result += substitutions[i]
    }
    
    //合併最後一個literal
    result += literals[literals.length - 1];
    return result;
}

let count = 10;
price = 0.25;
message = passthru`${count} items cost $${(count * price).toFixed(2)}`;

console.log(message)
複製程式碼
  • String.raw
String.raw`assda\\naadasd`

//程式碼模擬(略)
複製程式碼

第三章 函式

一、預設引數值

ES5預設引數值

下面函式存在什麼問題 ???

function makeRequest(url, timeout, callback) {
    
    timeout = timeout || 2000;
    callback = callback || function() {};
    
}
複製程式碼

假如timeout傳入值0,這個值是合法的,但是也會被視為一個假值,並最終將timeout賦值為2000。在這種情況下,更安全的選擇是通過typeof檢查引數型別,如下:

function makeRequest(url, timeout, callback) {
    
    timeout = (typeof timeout !== 'undefined') ? timeout :2000;
    callback = (typeof callback !== 'undefined') ? callback : function() {};
    
}
複製程式碼

ES5預設引數值

function makeRequest(url, timeout = 2000, callback) {
    
    //函式的其餘部分
    
}

//特別注意:此時 null 是一個合法值,所以不會使用 timeout 預設值,即 timeout = null
makeRequest('/foo', null, function(body){
    doSomething(body);
})
複製程式碼

二、預設引數值對arguments的影響**

  • ES5:

非嚴格模式:引數變化,arguments物件隨之改變;

嚴格模式:無論引數如何變化,arguments物件不再隨之改變;

  • ES6

非嚴格模式/嚴格模式:無論引數如何變化,arguments物件不再隨之改變;

注: 在引用引數預設值的時候,只允許引用前面引數的值,即先定義的引數不能訪問後定義的引數。這可以用預設引數的臨時死區來解釋。如下:

function add(first = second, second) {
    return first + second;
}

console.log(add(1, 1)); //2
console.log(add(undefined, 1)) //丟擲錯誤

//解釋原理:
//add(1, 1)
let first = 1;
let second =1;
//add(undefined, 1)
let first = second;
let second = 1; //處於臨時死區
複製程式碼

三、不定引數的使用限制

  1. 每個函式最多隻能宣告一個不定引數,而且移動要放在所有引數的末尾。
  2. 不定引數不能用於物件字面量setter之中(因為物件字面量setter的引數有且只有一個,而在不定引數的定義中,引數的數量可以無限多

無論是否使用不定引數,arguments物件總是包含所有傳入函式的引數。

四、展開運算子

let value = [25, 50, 75, 100];
//es5
console.log(Math.max.apply(Math, values); //100
//es6
console.log(Math.max(...values)); //100
複製程式碼

五、name 屬性

兩個有關函式名稱的特例:

  1. 通過bind(1)函式建立的函式,其名稱將帶有“bound”字首;
  2. 通過Function建構函式建立的函式,其名稱將是“anonymous”.
var doSomething = function() {
    //空函式
}

console.log(doSomething.bind().name); //'bound doSomething'
console.log((new Function()).name); //'anonymous'
複製程式碼

切記: 函式name屬性的值不一定引用同名變數,它只是協助除錯用的額外資訊,所以不能使用name屬性的值來獲取對於函式的引用。

六、明確函式的多重用途

JS函式有兩個不同的內部方法:[[call]][[Construct]]

  • 當通過new關鍵字呼叫函式是,執行的是 [[Construct]] 函式,它負責建立一個通常被稱為例項的新物件,然後再執行函式體,將this繫結到例項上(具有 [[Construct]] 方法的函式被統稱為建構函式,箭頭函式沒有 [[Construct]] 方法 );
  • 如果不通過 new 關鍵字呼叫函式,則執行 [[call]] 函式,從而直接執行程式碼中的函式體;

七、元屬性(Metaproperty)new.target

為了解決判斷函式是否通過new關鍵字呼叫的問題,new.target橫空出世 (instance of ---> new.target)

在函式外使用new.target是一個語法錯誤。

八、塊級函式

  • ES5嚴格模式下,程式碼塊中宣告函式會報錯;
  • ES6嚴格模式下, 可以在定義該函式的程式碼塊中訪問和呼叫它 (塊級函式提升,let變數不提升);
  • ES6非嚴格模式下,函式不再提升至程式碼塊的頂部,而是提升至外圍函式或全域性作用域的頂部。

九、箭頭函式

箭頭函式與傳統的JS函式不同之處主要有一下一個方面:

  1. 沒有thissuperargumentsnew.target繫結;
  2. 不能通過new關鍵字呼叫;
  3. 沒有原型;
  4. 不可以改變this的繫結;
  5. 不支援arguments物件
  6. 不支援重複的命名引數

建立一個空函式

let doNothing = () => {};
複製程式碼

返回一個物件字面量

let getTempItem = id => ({ id: id, name: "Temp"});
複製程式碼

建立立即執行的函式

let person = ((name) => {
    
    return {
        getName: function() {
            return name;
        }
    }
    
})("xszi")

console.log(person.getName()); //xszi
複製程式碼

箭頭函式沒有this繫結

let PageHandler = {
    
    id: '123456',
    init: function() {
        document.addEventListener("click", function(event){
            this.doSomething(event.type); //丟擲錯誤
        }, false)
    },
    
    doSomething: function(type) {
        console.log("handling " + type + "for" + this.id)
    }
    
}
複製程式碼

使用bind()方法將函式的this繫結到PageHandler,修正報錯:

let PageHandler = {
    
    id: '123456',
    init: function() {
        document.addEventListener("click", (function(event){
            this.doSomething(event.type); //丟擲錯誤
        }).bind(this), false)
    },
    
    doSomething: function(type) {
        console.log("handling " + type + "for" + this.id)
    }
    
}
複製程式碼

使用箭頭函式修正:

let PageHandler = {
    
    id: '123456',
    init: function() {
        document.addEventListener("click", 
            event => this.doSomething(event.type), false);
    },
    
    doSomething: function(type) {
        console.log("handling " + type + "for" + this.id)
    }
    
}
複製程式碼
  • 箭頭函式沒有 prototype屬性,它的設計初衷是 即用即棄, 不能用來定義新的型別。
  • 箭頭函式的中this取決於該函式外部非箭頭函式的this值,不能通過call(), apply()bind()方法來改變this的值。

箭頭函式沒有arguments繫結

始終訪問外圍函式的arguments物件

十、尾呼叫優化

  • ES5中,迴圈呼叫情況下,每一個未完成的棧幀都會儲存在記憶體中,當呼叫棧變的過大時會造成程式問題。
  • ES6中尾呼叫優化,需要滿足一下三個條件:
    • 尾呼叫不訪問當前棧幀的變數(也就是說函式不是一個閉包);
    • 在函式內部,尾呼叫是最後一條語句;
    • 尾呼叫的結果作為函式值返回;

如何利用尾呼叫優化

function factorial(n) {
    if ( n<=1 ) {
        return 1;
    }
}else{
    
    //引擎無法自動優化,必須在返回後執行乘法操作 
    return n * factorial(n-1);
    //隨呼叫棧尺寸的增大,存在棧溢位的風險
}
複製程式碼
function factorial(n, p = 1) {
    if ( n<=1 ) {
        return 1 * p;
    }
}else{
    let result = n * p;
    //引擎可自動優化
    return  factorial(n-1, result);
    //不建立新的棧幀,而是消除並重用當前棧幀
}
複製程式碼