摘要:本文涵蓋了一些新語法可能造成疑惑的地方和一些建議。新接口的迭代器參數的誤調用接口及集合類構造器的參數,可以放入支持迭代器的內容,而不局限于數組兼容。新集合類容器的構造器集合類容器不可以通過非方式來構造。
本文涵蓋了一些ES6新語法可能造成疑惑的地方和一些建議。
1# 箭頭函數箭頭函數看起來像是匿名函數表達式function(){}的簡寫,然而它不是。
這個例子應該很容易看出來會有怎樣的問題:
function Apple(){} Apple.prototype.check = ()=>{ console.log(this instanceof Apple); }; (new Apple()).check() // false
使用apply、call、bind改變箭頭函數的this指向呢?
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確定后無法更改。
在這些場景中不要使用箭頭函數:
當你需要正常使用this binding時,如函數構造器、prototype
當你需要動態改變this的時候
針對工作報酬和代碼量呈反比的程序猿,在需要用到this binding的場景里,可能比較適合的簡寫形式是在新對象字面量語法里提供的:
var obj = { hello() { // 少寫了一個function耶! console.log("world") } };2# Promise 2.1# then
//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與2、3、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 時候的一些反模式》。
2.2# catch在node的一些版本中,采用Promise并忘記給promise鏈增加catch(fn)或then(null, fn),將導致代碼中的異常被吞掉。
這個問題在新的v8中(node 6.6+,chrome最新版)會導致一個UnhandledPromiseRejectionWarning,防止開發遺漏。
node -e "Promise.reject()" # UnhandledPromiseRejectionWarning: Unhandled promise rejection2.3# resolve
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 })2.4# reject / throw
出現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
3# let & const & var看起來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; // SyntaxError4# 邊界
本篇章集結 ES6 給予的不同邊界條件,部分編譯自 You don"t know JS
4.1# 函數默認參數值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有效。
4.2# Object.assignObject.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]4.3# NaN
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); // true4.4# Number
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,前者為精度位允許的最小差異值,考慮的是浮點數的精度位;而后者考慮的是利用到浮點數的所有位置能夠表示的最小正數值。
5# 怪奇錯誤展本節收集了一些奇奇怪怪的錯誤提示,正常寫出的代碼不會導致它們,沒有興趣可以略過。
5.1# 新接口的迭代器參數Array.from(1, 2, 3) // Array.of(1,2,3)的誤調用 // 2 is not a function
Array.from、Promise.all接口及集合類構造器的參數,可以放入支持迭代器的內容,而不局限于數組(node 0.12+兼容)。這里其實嘗試去調用了參數的迭代器Symbol.iterator。
5.2# 新集合類容器的構造器Array(); // [] Set(); // Uncaught TypeError: Constructor Set requires "new"
集合類容器Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array Set不可以通過非new方式來構造。
5.3# Tagged Templatevar x = 30 `abcdefg` // Uncaught TypeError: 30 is not a function
模版語法可能是ES6最為顯然的語法,但它的擴展形式Tagged Template在極端場景可能造成一個奇怪的報錯,算是對不寫分號黨造成的又一個暴擊*。
6# 欺負新來的本篇章集結一些被濫用的特性。
6.1解構特性很棒,它可以在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 );
一個可以嘗試的保持代碼可讀性的方法,是盡量保證解構的層次低。
6.2新對象字面量也很不錯,新的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特性或許是一個可操作的方案。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81332.html
摘要:用來轉換內容,內部調用了方法進行轉換,這里簡單介紹一下的原理將代碼解析成,對進行轉譯,得到新的,新的通過轉換成,核心方法在中的方法,有興趣可以去了解一下。將函數傳入參數和歸并,得到。通常我們是用不上的,估計在某些中可能會使用到。 什么是Loader? 繼上兩篇文章webpack工作原理介紹(上篇、下篇),我們了解到Loader:模塊轉換器,也就是將模塊的內容按照需求裝換成新內容,而且每...
摘要:所以整個過程只涉及三個輸入狀態,中間狀態,輸出狀態關鍵是是如何生成,如何應用修改,如何生成最終的。至此基本把上的模式解析完畢。結束實現還是相當巧妙的,以后可以在狀態管理上使用一下。 開始 在函數式編程中,Immutable這個特性是相當重要的,但是在Javascript中很明顯是沒辦法從語言層面提供支持,但是還有其他庫(例如:Immutable.js)可以提供給開發者用上這樣的特性,所...
摘要:所以整個過程只涉及三個輸入狀態,中間狀態,輸出狀態關鍵是是如何生成,如何應用修改,如何生成最終的。至此基本把上的模式解析完畢。結束實現還是相當巧妙的,以后可以在狀態管理上使用一下。 開始 在函數式編程中,Immutable這個特性是相當重要的,但是在Javascript中很明顯是沒辦法從語言層面提供支持,但是還有其他庫(例如:Immutable.js)可以提供給開發者用上這樣的特性,所...
摘要:前言正則表達式時處理字符串中常用的手法,本文以簡單的方式,快速展示了中正則相關的基礎知識點。文末還提供了幾個簡單的正則相關面試題。接下來是正則部分,注意后面的并不匹配,也就是比如,實際匹配的值是和,在和后面加上,就完成了預期。 前言:正則表達式時處理字符串中常用的手法,本文以簡單的方式,快速展示了JavaScript中正則相關的基礎知識點。文末還提供了幾個簡單的正則相關面試題。個人總結...
閱讀 1123·2021-11-25 09:43
閱讀 1645·2021-09-13 10:25
閱讀 2603·2021-09-09 11:38
閱讀 3410·2021-09-07 10:14
閱讀 1720·2019-08-30 15:52
閱讀 646·2019-08-30 15:44
閱讀 3580·2019-08-29 13:23
閱讀 1980·2019-08-26 13:33