本文涵蓋了一些ES6新語法可能形成疑惑的地方和一些建議。html
箭頭函數看起來像是匿名函數表達式function(){}
的簡寫,然而它不是。前端
這個例子應該很容易看出來會有怎樣的問題:node
function Apple(){} Apple.prototype.check = ()=>{ console.log(this instanceof Apple); }; (new Apple()).check() // false
使用apply、call、bind改變箭頭函數的this指向呢?python
var i = 0; var xx = ()=>{ console.log(++i, this) }; var yy = function(){ console.log(++i, this) }; xx(); // 1 window xx.apply([]); // 2 window xx.bind([])(); // 3 window yy(); // 4 window yy.apply([]); // 5 [] yy.bind([])(); // 6 []
顯然apply、call、bind沒法改變箭頭函數的this指向,箭頭函數的this肯定後沒法更改。git
在這些場景中不要使用箭頭函數:es6
當你須要正常使用this binding時,如函數構造器、prototypegithub
當你須要動態改變this的時候chrome
針對工做報酬和代碼量呈反比的程序猿,在須要用到this binding的場景裏,可能比較適合的簡寫形式是在新對象字面量語法裏提供的:segmentfault
var obj = { hello() { // 少寫了一個function耶! console.log('world') } };
//1 fetch(xx, oo).then(handleResultAndReturnsAnPromise(result)); //2 fetch(xx, oo).then(handleResultAndReturnsAnPromise); //3 fetch(xx, oo).then((result) => handleResultAndReturnsAnPromise(result)); //4 fetch(xx, oo).then(function(result) { handleResultAndReturnsAnPromise(result) });
1與二、三、4均不等價:1同步調用了handleResultAndReturnsAnPromise;而2~4均會致使handleResultAndReturnsAnPromise在fetch以後完成數組
2與3/4則是運行時的調用棧有區別,3/4額外建立了一個匿名函數。
3與4除了this binding的區別,4的調用返回值沒有進行返回,這樣將致使promise鏈斷裂。
1中須要注意的是,then(promise)
裏面傳一個 Promise 對象是沒有什麼意義的,它會被當成then(null)
,在下面推薦的文章中,它被稱做「Promise 穿透」
更多的使人混淆的案例,請繼續閱讀《談談使用 promise 時候的一些反模式》。
在node的一些版本中,採用Promise並忘記給promise鏈增長catch(fn)
或then(null, fn)
,將致使代碼中的異常被吞掉。
這個問題在新的v8中(node 6.6+,chrome最新版)會致使一個UnhandledPromiseRejectionWarning
,防止開發遺漏。
node -e 'Promise.reject()' # UnhandledPromiseRejectionWarning: Unhandled promise rejection
Promise接口和jQuery實現的接口不同,resolve
只接受單參數,then
的回調也只能拿到單參數。
在Promise規範中的單參數鏈式調用場景下,能夠利用解構、_.spread
、訪問自由變量等方式來處理多個過程當中獲得的值:
new Promise(function(resolve, reject){ let something = 1, otherstuff = 2; resolve({something, otherstuff}); }).then(function({something, otherstuff}){ // handle something and otherstuff });
Promise.all([ Promise.resolve(40), Promise.resolve(36) ]).then( _.spread(function(first, second){ // first: 40, second: 36 }) );
let someMiddleResult; fetch() .then(function(fetchResult){ someMiddleResult = fetchResult; }) .then(otherHandleFn) .then(function(otherHandleFnResult){ // use both someMiddleResult and otherHandleFnResult now })
出現reject
接口,應該是第一次前端有機會拿異常處理流程作正常流程(好比*)。不要這樣作。
因爲reject(new Error(""))
、throw new Error("")
都能做爲catch
的入口,一些不可預知的錯誤被拋出的時候,這樣的處理方式將會複雜化catch內的代碼。不要用異常處理邏輯來作正常處理流程,這個規則保證了代碼可讀性與可維護性。
throw
和reject
均可以做爲catch
的入口,它們更加詳細的區別以下:
new Promise((resolve, reject) => { setTimeout(function(){ reject(new Error('hello')); }); }).catch(() => console.log('reject')); // reject new Promise((resolve, reject) => { setTimeout(function(){ throw new Error('hello'); }); }).catch(() => console.log('throw')); // Uncaught Error: hello
reject
可以「穿透」回調;而throw
限於函數做用域,沒法「穿透」回調。
建議:
正常流程請選擇在then
的時候if..else
,不要用reject
替代
在須要走異常處理流程的時候封裝Error
拋出,能夠最大化的化簡catch
回調裏面的處理邏輯,相似於e instanceof MyDesignedError
因爲回調函數裏的throw
沒法被自動捕獲到,若是須要在回調中reject
當前 promise,那麼咱們須要用reject
而不是throw
在使用Promise
接口的 polyfill 的場景,應當在reject
後加一個return
看起來let
和const
的組合就像是一個能徹底滅掉var
的新特性,但對舊代碼不能簡單的正則替換掉var
,由於咱們太習慣於濫用它的特性了——主要是聲明提高。
一些情形下會形成語法錯誤:
try { let a = 10; if (a > 2) { throw new Error(); } // ... } catch (err) { console.log(a); // 若爲var聲明,不報錯 // 若爲const、let聲明:Uncaught ReferenceError: a is not defined }
除了try..catch
,隱式造就的塊級做用域在for
和if..else
中也將形成問題:
if(false) { let my = 'bad'; } else { console.log(my); // ReferenceError: my is not defined }
解決方案卻是很簡單,將做用域內的let
放在更靠外層的位置便可。
var
、let
和const
的區別以下(部分參考自stackoverflow*):
做用域:let
和const
將創造一個塊級做用域,在做用域以外此變量不可見,做用域外訪問將致使SyntaxError
;var
遵循函數級做用域
全局影響:全局做用域下的var
使用等同於設置window
/global
之上的內容,但let
和const
不會
提高行爲:var
聲明有提高到當前函數做用域頂部的特性,但const
和let
沒有,在聲明前訪問變量將致使SyntaxError
從新賦值:對const
變量所作的從新賦值將致使TypeError
,而var
和let
不會
從新聲明:var
聲明的變量使用var
再次聲明不會出現SyntaxError
,但const
、let
聲明的變量不能被從新聲明,也不能覆蓋掉以前任何形式的聲明:
var vVar = 1; const vConst = 2; let vLet = 3; var vVar = 4; // success let vVar = 5; // SyntaxError const vVar = 6; // SyntaxError var vConst = 7; // SyntaxError let vConst = 8; // SyntaxError const vConst = 9; // SyntaxError var vLet = 10; // SyntaxError let vLet = 11; // SyntaxError const vLet = 12; // SyntaxError
本篇章集結 ES6 給予的不一樣邊界條件,部分編譯自 You don't know JS
function before(a) { var a = a || 1; console.log(a); } function after(a = 1) { console.log(a); } before(NaN) // 1 after(NaN) // NaN
新的寫法的fallback邏輯只針對undefined
有效。
Object.assign
將賦予全部的可枚舉值,但不包含從原型鏈繼承來的值:
let arr = [1, 2, 3], obj = {}; Object.assign(obj, arr); obj[1] // 2 obj.length // undefined Object.getOwnPropertyDescriptors(arr).length.enumerable // false
此外:Object.assign
僅僅進行淺拷貝:
var orig = { a: [1, 2, 3] }, nObj = {}; Object.assign(nObj, orig); orig.a.push(4); nObj.a // [1, 2, 3, 4]
Number.isNaN
和全局空間中的isNaN
的區別在於不存在隱式轉換:
isNaN('number') // true Number.isNaN('number') // false
Object.is
除了區分正負零這個很是小衆的邊界,這個接口相對===
更大的意義是判斷NaN:
Object.is(NaN, NaN); // true NaN === NaN; // false
Object.is(+0, -0); // false +0 === -0; // true
一樣的,arr.includes(xx)
比arr.lastIndexOf(xx) > -1
好的地方也包括對於NaN的處理:
[1, 2, NaN].includes(NaN); // true
isFinite
和Number.isFinite
的區別也是後者不存在隱式轉換:
isFinite("42"); // true Number.isFinite("42"); // false
Number.isInteger
表示一個數是否是小數,和x === Math.floor(x)
的區別在於對Infinity
的處理
Number.isInteger(Infinity); // false Infinity === Math.floor(Infinity); // true
Number.isSafeInteger
表示傳入的數值有沒有精度損失,它比較的是數字是否在Number.MIN_SAFE_INTEGER
和Number.MAX_SAFE_INTEGER
之間:
Number.isSafeInteger(Math.pow(2, 53) - 1); // true Number.isSafeInteger(Math.pow(2, 53)); // false
我曾整理過Number的數軸(*),也寫過JavaScript中的一些數字內存模型的demo,其中有一部分值沒有直接的量來表示,但如今有了。
從負無窮往正無窮來看,是這樣的:
Number.NEGATIVE_INFINITY
負無窮
-Number.MAX_VALUE
能表示的最小數字,更小被視爲負無窮,等於-(2^53-1)*(2^971)
Number.MIN_SAFE_INTEGER
(新) 沒有精度偏差的最小數,等於-(2^53-1)
0
正負零
Number.EPSILON
(新) IEEE 754規範下的精度位容許的最小差別值,等於2^-52
Number.MIN_VALUE
能表示的最小正整數,這是一個IEEE 754規範下的反規格化值,等於2^-1074
Number.MAX_SAFE_INTEGER
(新) 沒有精度偏差的最大數,,等於2^53-1
Number.MAX_VALUE
能表示的最大數字,更大被視爲正無窮,等於(2^53-1)*(2^971)
Number.INFINITY
正無窮
比較使人混淆的是Number.EPSILON
和Number.MIN_VALUE
,前者爲精度位容許的最小差別值,考慮的是浮點數的精度位;然後者考慮的是利用到浮點數的全部位置可以表示的最小正數值。
本節收集了一些奇奇怪怪的錯誤提示,正常寫出的代碼不會致使它們,沒有興趣能夠略過。
Array.from(1, 2, 3) // Array.of(1,2,3)的誤調用 // 2 is not a function
Array.from
、Promise.all
接口及集合類構造器的參數,能夠放入支持迭代器的內容,而不侷限於數組(node 0.12+兼容)。這裏其實嘗試去調用了參數的迭代器Symbol.iterator
。
Array(); // [] Set(); // Uncaught TypeError: Constructor Set requires 'new'
集合類容器Int8Array
Uint8Array
Uint8ClampedArray
Int16Array
Uint16Array
Int32Array
Uint32Array
Float32Array
Float64Array
Set
不能夠經過非new方式來構造。
var x = 30 `abcdefg` // Uncaught TypeError: 30 is not a function
模版語法多是ES6最爲顯然的語法,但它的擴展形式Tagged Template在極端場景可能形成一個奇怪的報錯,算是對不寫分號黨形成的又一個暴擊*。
本篇章集結一些被濫用的特性。
解構特性很棒,它能夠在promise這樣的單參數鏈式調用場景或是正則匹配場景中大方光芒,更爲經典的是python風格的[y, x] = [x, y]
。
但若是一我的鐵了心要瘋狂解構,新來維護這份代碼的人就要默默流下痛苦的眼淚了:
// 新人:是什麼阻止了你用 a2 = [o1[a], o1[b], o1[c]] …… var o1 = { a: 1, b: 2, c: 3 }, a2 = []; ( { a: a2[0], b: a2[1], c: a2[2] } = o1 );
// 老人:看得爽嗎 var { a: { b: [ c, d ], e: { f } }, g } = obj;
// 主管:寫到一半這個程序猿已經被打死了 var x = 200, y = 300, z = 100; var o1 = { x: { y: 42 }, z: { y: z } }; ( { y: x = { y: y } } = o1 ); ( { z: y = { y: z } } = o1 ); ( { x: z = { y: x } } = o1 );
一個能夠嘗試的保持代碼可讀性的方法,是儘可能保證解構的層次低。
新對象字面量也很不錯,新的rest操做符也很實用,可是若是大家把它們混在一塊兒……下面進一段代碼賞析(*):
export const sharePostStatus = createReducer( {}, { [ PUBLICIZE_SHARE ]: ( state, { siteId, postId } ) => ( { ...state, [ siteId ]: { ...state[ siteId ], [ postId ]: { requesting: true, } } } ), [ PUBLICIZE_SHARE_SUCCESS ]: ( state, { siteId, postId } ) => ( { ...state, [ siteId ]: { ...state[ siteId ], [ postId ]: { requesting: false, success: true, } } } ), [ PUBLICIZE_SHARE_FAILURE ]: ( state, { siteId, postId, error } ) => ( { ...state, [ siteId ]: { ...state[ siteId ], [ postId ]: { requesting: false, success: false, error, } } } ), [ PUBLICIZE_SHARE_DISMISS ]: ( state, { siteId, postId } ) => ( { ...state, [ siteId ]: { ...state[ siteId ], [ postId ]: undefined } } ), } );
儘量的保持代碼的可讀性,一行只用不超過2個ES6特性或許是一個可操做的方案。