深入理解ES6(至第三章-函式)
第一章 塊級作用域繫結
var
宣告初始化變數, 宣告可以提升,但初始化不可以提升。
一、塊級宣告:
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)
函式建立的函式,其名稱將帶有“bound”字首; - 通過
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函式不同之處主要有一下一個方面:
- 沒有
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;
//引擎可自動優化
return factorial(n-1, result);
//不建立新的棧幀,而是消除並重用當前棧幀
}
複製程式碼