深入理解ES6(至第三章-函式)
-
let
-
const
- 預設使用,在某種程度上實現程式碼不可變,減少錯誤發生的機率
- 如果常量是物件,則物件中的值可以修改
- 不能重複宣告,宣告不會提升
二、臨時死區:
JS引擎在掃描程式碼發現變數宣告時,要麼將它們提升至作用域頂部(var
宣告),要麼將宣告放到TDZ
中(let
和const
宣告)。訪問TDZ
中的變數會觸發執行時錯誤。只有執行過變數宣告語句後,變數才會從TDZ
中移出,然後可以正常訪問。
第一種情況:
if(condition) { console.log(typeof value); //引用錯誤! let value = "blue"; //TDZ } 複製程式碼
第二種情況:
console.log(typeof value); //'undefined'-->只有變數在TDZ中才會報錯 if(condition) { let value = "blue"; } 複製程式碼
三、循壞中塊作用域繫結
-
立即呼叫(IIFE)
-
let
和const
之所以可以在運用在for-in 和for-of 迴圈中,是因為每次迭代會建立一個新的繫結 (const在for 迴圈中會報錯)。
四、全域性塊作用域繫結
var
可能會在無意中覆蓋一個已有的全域性屬性let
或const
,會在全域性作用域下建立一個新的繫結,但該繫結不會新增為全域性物件的屬性。換句話說,使用let
或const
不能覆蓋全域性變數,而只能遮蔽它。如果不行為全域性物件建立屬性,使用let
和const
要安全得多。
注:如果希望子安全域性物件下定義變數,仍然可以使用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; //處於臨時死區 複製程式碼
三、不定引數的使用限制
- 每個函式最多隻能宣告一個不定引數,而且移動要放在所有引數的末尾。
-
不定引數不能用於物件字面量
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 屬性
兩個有關函式名稱的特例:
bind(1) Function
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函式不同之處主要有一下一個方面:
-
沒有
this
、super
、arguments
和new.target
繫結; -
不能通過
new
關鍵字呼叫; - 沒有原型;
-
不可以改變
this
的繫結; -
不支援
arguments
物件 - 不支援重複的命名引數
建立一個空函式:
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; //引擎可自動優化 returnfactorial(n-1, result); //不建立新的棧幀,而是消除並重用當前棧幀 } 複製程式碼