有些東西很好用,可是你未必知道;有些東西你可能用過,可是你未必知道原理。javascript
實現一個目的有多種途徑,俗話說,條條大路通羅馬。不少內容來自平時的一些收集以及過往博客文章底下的精彩評論,收集整理拓展一波,發散一下你們的思惟以及拓展一下知識面。前端
茴字有四種寫法,233333..., 文末有彩蛋有驚喜。java
不少語言都有 sleep
函數,顯然 js
沒有,那麼如何能簡短優雅地實現這個方法?node
function sleep(sleepTime) { for(var start = +new Date; +new Date - start <= sleepTime;) {} } var t1 = +new Date() sleep(3000) var t2 = +new Date() console.log(t2 - t1)
優勢:簡單粗暴,通俗易懂。
缺點:這是最簡單粗暴的實現,確實 sleep 了,也確實卡死了,CPU 會飆升,不管你的服務器 CPU 有多麼 Niubility。git
function sleep(time) { return new Promise(resolve => setTimeout(resolve, time)) } const t1 = +new Date() sleep(3000).then(() => { const t2 = +new Date() console.log(t2 - t1) })
優勢:這種方式其實是用了 setTimeout,沒有造成進程阻塞,不會形成性能和負載問題。
缺點:雖然不像 callback 套那麼多層,但仍不怎麼美觀,並且當咱們須要在某過程當中須要中止執行(或者在中途返回了錯誤的值),還必須得層層判斷後跳出,很是麻煩,並且這種異步並非那麼完全,仍是看起來彆扭。github
function sleep(delay) { return function(cb) { setTimeout(cb.bind(this), delay) }; } function* genSleep() { const t1 = +new Date() yield sleep(3000) const t2 = +new Date() console.log(t2 - t1) } async(genSleep) function async(gen) { const iter = gen() function nextStep(it) { if (it.done) return if (typeof it.value === "function") { it.value(function(ret) { nextStep(iter.next(ret)) }) } else { nextStep(iter.next(it.value)) } } nextStep(iter.next()) }
優勢:同 Promise 優勢,另外代碼就變得很是簡單幹淨,沒有 then 那麼生硬和噁心。
缺點:但不足也很明顯,就是每次都要執行 next() 顯得很麻煩,雖然有 co(第三方包)能夠解決,但就多包了一層,很差看,錯誤也必須按 co 的邏輯來處理,不爽。正則表達式
co 之因此這麼火併非沒有緣由的,固然不是僅僅實現 sleep
這麼無聊的事情,而是它活生生的藉着generator/yield
實現了很相似 async/await
的效果!這一點真是讓我三觀盡毀另眼相看。chrome
const co = require("co") function sleep(delay) { return function(cb) { setTimeout(cb.bind(this), delay) } } co(function*() { const t1 = +new Date() yield sleep(3000) const t2 = +new Date() console.log(t2 - t1) })
function sleep(delay) { return new Promise(reslove => { setTimeout(reslove, delay) }) } !async function test() { const t1 = +new Date() await sleep(3000) const t2 = +new Date() console.log(t2 - t1) }()
優勢:同 Promise 和 Generator 優勢。 Async/Await 能夠看作是 Generator 的語法糖,Async 和 Await 相較於 * 和 yield 更加語義,另外各個函數都是扁平的,不會產生多餘的嵌套,代碼更加清爽易讀。
缺點: ES7 語法存在兼容性問題,有 babel 一切兼容性都不是問題編程
至於 Async/Await
比 Promise
和 Generator
的好處能夠參考這兩篇文章:
Async/Await 比 Generator 的四個改進點
關於Async/Await替代Promise的6個理由數組
在 javascript 優雅的寫 sleep 等於如何優雅的不優雅,2333
這裏有 C++ 實現的模塊:https://github.com/ErikDubbelboer/node-sleep
const sleep = require("sleep") const t1 = +new Date() sleep.msleep(3000) const t2 = +new Date() console.log(t2 - t1)
優勢:可以實現更加精細的時間精確度,並且看起來就是真的 sleep 函數,清晰直白。
缺點:缺點須要安裝這個模塊,^_^
,這也許算不上什麼缺點。
從一個間簡簡單單的 sleep 函數咱們就就能夠管中窺豹,看見 JavaScript 近幾年來不斷快速的發展,不僅僅是異步編程這一塊,還有各類各樣的新技術和新框架,見證了 JavaScript 的繁榮。
你可能不知道的前端知識點:Async/Await是目前前端異步書寫最優雅的一種方式
上面第一個用多種方式實現 sleep
函數,咱們能夠發現代碼有 +new Date()
獲取時間戳的用法,這只是其中的一種,下面就說一下其餘兩種以及 +new Date()
的原理。
var timestamp=new Date().getTime()
優勢:具備廣泛性,你們都用這個
缺點:目前沒有發現
var timestamp = (new Date()).valueOf()
valueOf 方法返回對象的原始值(Primitive,'Null','Undefined','String','Boolean','Number'五種基本數據類型之一),多是字符串、數值或 bool 值等,看具體的對象。
優勢:說明開發者原始值有一個具體的認知,讓人眼前一亮。
缺點: 目前沒有發現
var timestamp = +new Date()
優勢:對 JavaScript 隱式轉換掌握的比較牢固的一個表現
缺點:目前沒有發現
如今就簡單分析一下爲何 +new Date()
拿到的是時間戳。
一言以蔽之,這是隱式轉換的玄學,實質仍是調用了 valueOf() 的方法。
咱們先看看 ECMAScript
規範對一元運算符的規範:
一元+ 運算符
一元 +
運算符將其操做數轉換爲 Number
類型並反轉其正負。注意負的 +0
產生 -0
,負的 -0
產生 +0
。產生式 UnaryExpression : - UnaryExpression
按照下面的過程執行。
- 令 expr 爲解釋執行 UnaryExpression 的結果 .
- 令 oldValue 爲 ToNumber(GetValue(expr)).
- 若是 oldValue is NaN ,return NaN.
- 返回 oldValue 取負(即,算出一個數字相同可是符號相反的值)的結果。
+new Date() 至關於 ToNumber(new Date())
咱們再來看看 ECMAScript
規範對 ToNumber
的定義:
咱們知道 new Date()
是個對象,知足上面的 ToPrimitive()
,因此進而成了 ToPrimitive(new Date())
。
接着咱們再來看看 ECMAScript
規範對 ToPrimitive
的定義,一層一層來,抽絲剝繭。
這個 ToPrimitive
剛開始可能不太好懂,我來大體解釋一下吧:
ToPrimitive(obj,preferredType)
JavaScript
引擎內部轉換爲原始值 ToPrimitive(obj,preferredType)
函數接受兩個參數,第一個 obj
爲被轉換的對象,第二個preferredType
爲但願轉換成的類型(默認爲空,接受的值爲 Number
或 String
)
在執行 ToPrimitive(obj,preferredType)
時若是第二個參數爲空而且 obj
爲 Date
的實例時,此時 preferredType
會被設置爲 String
,其餘狀況下 preferredType
都會被設置爲Number
若是 preferredType
爲 Number
,ToPrimitive
執行過程以下:
- 若是obj爲原始值,直接返回;
- 不然調用 obj.valueOf(),若是執行結果是原始值,返回之;
- 不然調用 obj.toString(),若是執行結果是原始值,返回之;
- 不然拋異常。
若是 preferredType
爲 String
,將上面的第2步和第3步調換,即:
- 若是obj爲原始值,直接返回;
- 不然調用 obj.toString(),若是執行結果是原始值,返回之;
- 不然調用 obj.valueOf(),若是執行結果是原始值,返回之;
- 不然拋異常。
首先咱們要明白 obj.valueOf()
和 obj.toString()
還有原始值分別是什麼意思,這是弄懂上面描述的前提之一:
toString
用來返回對象的字符串表示。
var obj = {}; console.log(obj.toString());//[object Object] var arr2 = []; console.log(arr2.toString());//""空字符串 var date = new Date(); console.log(date.toString());//Sun Feb 28 2016 13:40:36 GMT+0800 (中國標準時間)
valueOf
方法返回對象的原始值,多是字符串、數值或 bool
值等,看具體的對象。
var obj = { name: "obj" } console.log(obj.valueOf()) //Object {name: "obj"} var arr1 = [1] console.log(arr1.valueOf()) //[1] var date = new Date() console.log(date.valueOf())//1456638436303 如代碼所示,三個不一樣的對象實例調用valueOf返回不一樣的數據
原始值指的是 'Null','Undefined','String','Boolean','Number','Symbol'
6種基本數據類型之一,上面已經提到過這個概念,這裏再次申明一下。
最後分解一下其中的過程:+new Date():
- 運算符
new
的優先級高於一元運算符+
,因此過程能夠分解爲:
var time=new Date();
+time- 根據上面提到的規則至關於:
ToNumber(time)
time
是個日期對象,根據ToNumber
的轉換規則,因此至關於:ToNumber(ToPrimitive(time))
- 根據
ToPrimitive
的轉換規則:ToNumber(time.valueOf())
,time.valueOf()
就是 原始值 獲得的是個時間戳,假設time.valueOf()=1503479124652
- 因此
ToNumber(1503479124652)
返回值是1503479124652
這個數字。- 分析完畢
你可能不知道的前端知識點:隱式轉換的妙用
注:暫不考慮對象字面
量,函數
等引用類型的去重,也不考慮 NaN
, undefined
, null
等特殊類型狀況。
數組樣本:[1, 1, '1', '2', 1]
無需思考,咱們能夠獲得 O(n^2) 複雜度的解法。定義一個變量數組 res 保存結果,遍歷須要去重的數組,若是該元素已經存在在 res 中了,則說明是重複的元素,若是沒有,則放入 res 中。
var a = [1, 1, '1', '2', 1] function unique(arr) { var res = [] for (var i = 0, len = arr.length; i < len; i++) { var item = arr[i] for (var j = 0, len = res.length; j < jlen; j++) { if (item === res[j]) //arr數組的item在res已經存在,就跳出循環 break } if (j === jlen) //循環完畢,arr數組的item在res找不到,就push到res數組中 res.push(item) } return res } console.log(unique(a)) // [1, 2, "1"]
優勢: 沒有任何兼容性問題,通俗易懂,沒有任何理解成本
缺點: 看起來比較臃腫比較繁瑣,時間複雜度比較高O(n^2)
var a = [1, 1, '1', '2', 1] function unique(arr) { return arr.filter(function(ele,index,array){ return array.indexOf(ele) === index//很巧妙,這樣篩選一對一的,過濾掉重複的 }) } console.log(unique(a)) // [1, 2, "1"]
優勢:很簡潔,思惟也比較巧妙,直觀易懂。
缺點:不支持 IE9
如下的瀏覽器,時間複雜度仍是O(n^2)
var a = [1, 1, '1', '2', 1] function unique(arr) { var obj = {} return arr.filter(function(item, index, array){ return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true) }) } console.log(unique(a)) // [1, 2, "1"]
優勢:hasOwnProperty
是對象的屬性(名稱)存在性檢查方法。對象的屬性能夠基於 Hash
表實現,所以對屬性進行訪問的時間複雜度能夠達到O(1)
;filter
是數組迭代的方法,內部仍是一個 for
循環,因此時間複雜度是 O(n)
。
缺點:不兼容 IE9
如下瀏覽器,其實也好解決,把 filter
方法用 for
循環代替或者本身模擬一個 filter 方法。
以 Set 爲例,ES6 提供了新的數據結構 Set。它相似於數組,可是成員的值都是惟一的,沒有重複的值。
const unique = a => [...new Set(a)]
優勢:ES6
語法,簡潔高效,咱們能夠看到,去重方法從原始的 14
行代碼到 ES6
的 1
行代碼,其實也說明了 JavaScript
這門語言在不停的進步,相信之後的開發也會愈來愈高效。
缺點:兼容性問題,現代瀏覽器才支持,有 babel
這些都不是問題。
你可能不知道的前端知識點:ES6 新的數據結構 Set 去重
function formatNumber(str) { let arr = [], count = str.length while (count >= 3) { arr.unshift(str.slice(count - 3, count)) count -= 3 } // 若是是否是3的倍數就另外追加到上去 str.length % 3 && arr.unshift(str.slice(0, str.length % 3)) return arr.toString() } console.log(formatNumber("1234567890")) // 1,234,567,890
優勢:自我感受比網上寫的一堆 for循環 還有 if-else 判斷的邏輯更加清晰直白。
缺點:太普通
function formatNumber(str) { // ["0", "9", "8", "7", "6", "5", "4", "3", "2", "1"] return str.split("").reverse().reduce((prev, next, index) => { return ((index % 3) ? next : (next + ',')) + prev }) } console.log(formatNumber("1234567890")) // 1,234,567,890
優勢:把 JS 的 API 玩的瞭如指掌
缺點:可能沒那麼好懂,不過讀懂以後就會發出我怎麼沒想到的感受
function formatNumber(str) { return str.replace(/\B(?=(\d{3})+(?!\d))/g, ',') } console.log(formatNumber("123456789")) // 1,234,567,890
下面簡單分析下正則/\B(?=(\d{3})+(?!\d))/g
:
/\B(?=(\d{3})+(?!\d))/g
:正則匹配邊界\B
,邊界後面必須跟着(\d{3})+(?!\d)
;(\d{3})+
:必須是1個或多個的3個連續數字;(?!\d)
:第2步中的3個數字不容許後面跟着數字;(\d{3})+(?!\d)
:因此匹配的邊界後面必須跟着3*n
(n>=1)的數字。
最終把匹配到的全部邊界換成,
便可達成目標。
優勢:代碼少,濃縮的就是精華
缺點:須要對正則表達式的位置匹配有一個較深的認識,門檻大一點
(123456789).toLocaleString('en-US') // 1,234,567,890
如圖,你可能還不知道 JavaScript
的 toLocaleString
還能夠這麼玩。
還可使用 Intl對象 - MDN
Intl 對象是 ECMAScript 國際化 API 的一個命名空間,它提供了精確的字符串對比,數字格式化,日期和時間格式化。Collator,NumberFormat 和 DateTimeFormat 對象的構造函數是 Intl 對象的屬性。
new Intl.NumberFormat().format(1234567890) // 1,234,567,890
優勢:簡單粗暴,直接調用 API
缺點:Intl兼容性不太好,不過 toLocaleString的話 IE6 都支持
你可能不知道的前端知識點:Intl對象 和 toLocaleString的方法。
let a = 3,b = 4 變成 a = 4, b = 3
首先最常規的辦法,引入一個 temp 中間變量
let a = 3,b = 4 let temp = a a = b b = temp console.log(a, b)
優勢:一眼能看懂就是最好的優勢
缺點:硬說缺點就是引入了一個多餘的變量
在不引入中間變量的狀況下也能交互兩個變量
let a = 3,b = 4 a += b b = a - b a -= b console.log(a, b)
優勢:比起樓上那種沒有引入多餘的變量,比上面那一種稍微巧妙一點
缺點:固然缺點也很明顯,整型數據溢出,好比說對於32位字符最大表示有符號數字是2147483647,也就是Math.pow(2,31)-1,若是是2147483645和2147483646交換就失敗了。
利用一個數異或自己等於0和異或運算符合交換率。
let a = 3,b = 4 a ^= b b ^= a a ^= b console.log(a, b)
下面用豎式進行簡單說明:(10進制化爲二進制)
a = 011 (^) b = 100 則 a = 111(a ^ b的結果賦值給a,a已變成了7) (^) b = 100 則 b = 011(b^a的結果賦給b,b已經變成了3) (^) a = 111 則 a = 100(a^b的結果賦給a,a已經變成了4)
從上面的豎式能夠清楚的看到利用異或運算實現兩個值交換的基本過程。
下面從深層次剖析一下:
1.對於開始的兩個賦值語句,a = a ^ b,b = b ^ a,至關於b = b ^ (a ^ b) = a ^ b ^ b,而b ^ b 顯然等於0。所以b = a ^ 0,顯然結果爲a。
2. 同理能夠分析第三個賦值語句,a = a ^ b = (a ^ b) ^ a = b
注:
它的意思是判斷兩個相應的位值是否爲」異「,爲」異"(值不一樣)就取真(1);不然爲假(0)。
優勢:不存在引入中間變量,不存在整數溢出
缺點:前端對位操做這一塊可能瞭解不深,不容易理解
熟悉 ES6
語法的人固然不會對解構陌生
var a = 3,b = 4; [b, a] = [a, b]
其中的解構的原理,我暫時還沒讀過 ES6的規範,不知道具體的細則,不過咱們能夠看看 babel
是本身編譯的,咱們能夠看出點門路。
哈哈,簡單粗暴,不知道有沒有按照 ES 的規範,其實能夠扒一扒 v8的源碼,chrome 已經實現這種解構用法。
這個例子和前面的例子編寫風格有何不一樣,你若是細心的話就會發現這兩行代碼多了一個分號,對於我這種編碼不寫分號的潔癖者,爲何加一個分號在這裏,實際上是有緣由的,這裏就簡單普及一下,建議你們仍是寫代碼寫上分號。
儘管 JavaScript 有 C 的代碼風格,可是它不強制要求在代碼中使用分號,實際上能夠省略它們。
JavaScript 不是一個沒有分號的語言,偏偏相反上它須要分號來就解析源代碼。 所以 JavaScript 解析器在遇到因爲缺乏分號致使的解析錯誤時,會自動在源代碼中插入分號。
var foo = function() { } // 解析錯誤,分號丟失 test()
自動插入分號,解析器從新解析。
var foo = function() { }; // 沒有錯誤,解析繼續 test()
下面的代碼沒有分號,所以解析器須要本身判斷須要在哪些地方插入分號。
(function(window, undefined) { function test(options) { log('testing!') (options.list || []).forEach(function(i) { }) options.value.test( 'long string to pass here', 'and another long string to pass' ) return { foo: function() {} } } window.test = test })(window) (function(window) { window.someLibrary = {} })(window)
下面是解析器"猜想"的結果。
(function(window, undefined) { function test(options) { // 沒有插入分號,兩行被合併爲一行 log('testing!')(options.list || []).forEach(function(i) { }); // <- 插入分號 options.value.test( 'long string to pass here', 'and another long string to pass' ); // <- 插入分號 return; // <- 插入分號, 改變了 return 表達式的行爲 { // 做爲一個代碼段處理 foo: function() {} }; // <- 插入分號 } window.test = test; // <- 插入分號 // 兩行又被合併了 })(window)(function(window) { window.someLibrary = {}; // <- 插入分號 })(window); //<- 插入分號
解析器顯著改變了上面代碼的行爲,在另一些狀況下也會作出錯誤的處理。
咱們翻到7.9章節,看看其中插入分號的機制和原理,清楚只寫之後就能夠儘可能之後少踩坑
**必須用分號終止某些 ECMAScript 語句 ( 空語句 , 變量聲明語句 , 表達式語句 , do-while 語句 , continue 語句 , break 語句 , return 語句 ,throw 語句 )。這些分號老是明確的顯示在源文本里。然而,爲了方便起見,某些狀況下這些分號能夠在源文本里省略。描述這種狀況會說:這種狀況下給源代碼的 token 流自動插入分號。**
仍是比較抽象,看不太懂是否是,沒關係,咱們看看實際例子,總結出幾個規律就行,咱們先不看抽象的,看着頭暈,看看具體的總結說明, 化抽象爲具體 。
首先這些規則是基於兩點:
1. 新行併入當前行將構成非法語句,自動插入分號。
if(1 < 10) a = 1 console.log(a) // 等價於 if(1 < 10) a = 1; console.log(a);
2. 在continue,return,break,throw後自動插入分號
return
{a: 1} // 等價於 return; {a: 1};
3. ++、--後綴表達式做爲新行的開始,在行首自動插入分號
a
++
c
// 等價於 a; ++c;
4. 代碼塊的最後一個語句會自動插入分號
function(){ a = 1 } // 等價於 function(){ a = 1; }
1. 新行以 ( 開始
var a = 1 var b = a (a+b).toString() // 會被解析爲以a+b爲入參調用函數a,而後調用函數返回值的toString函數 var a = 1 var b =a(a+b).toString()
2. 新行以 [ 開始
var a = ['a1', 'a2'] var b = a [0,1].slice(1) // 會被解析先獲取a[1],而後調用a[1].slice(1)。 // 因爲逗號位於[]內,且不被解析爲數組字面量,而被解析爲運算符,而逗號運算符會先執 行左側表達式,而後執行右側表達式而且以右側表達式的計算結果做爲返回值 var a = ['a1', 'a2'] var b = a[0,1].slice(1)
3. 新行以 / 開始
var a = 1 var b = a /test/.test(b) // /會被解析爲整除運算符,而不是正則表達式字面量的起始符號。瀏覽器中會報test前多了個.號 var a = 1 var b = a / test / .test(b)
4. 新行以 + 、 - 、 % 和 * 開始
var a = 2 var b = a +a // 會解析以下格式 var a = 2 var b = a + a
5. 新行以 , 或 . 開始
var a = 2 var b = a .toString() console.log(typeof b) // 會解析爲 var a = 2 var b = a.toString() console.log(typeof b)
到這裏咱們已經對ASI的規則有必定的瞭解了,另外還有同樣有趣的事情,就是「空語句」。
// 三個空語句 ;;; // 只有if條件語句,語句塊爲空語句。 // 可實現unless條件語句的效果 if(1>2);else console.log('2 is greater than 1 always!'); // 只有while條件語句,循環體爲空語句。 var a = 1 while(++a < 100);
建議絕對不要省略分號,同時也提倡將花括號和相應的表達式放在一行, 對於只有一行代碼的 if 或者 else 表達式,也不該該省略花括號。 這些良好的編程習慣不只能夠提到代碼的一致性,並且能夠防止解析器改變代碼行爲的錯誤處理。
關於JavaScript 語句後應該加分號麼?(點我查看)咱們能夠看看知乎上大牛們對着個問題的見解。
你可能不知道的前端知識點:原來 JavaScript 還有位操做以及分號的使用細則
{0:1,1:2,2:3,length:3}
這種形式的就屬於類數組,就是按照數組下標排序的對象,還有一個 length
屬性,有時候咱們須要這種對象能調用數組下的一個方法,這時候就須要把把類數組轉化成真正的數組。
var makeArray = function(array) { var ret = [] if (array != null) { var i = array.length if (i == null || typeof array === "string") ret[0] = array else while (i) ret[--i] = array[i]; } return ret } makeArray({0:1,1:2,2:3,length:3}) //[1,2,3]
優勢:通用版本,沒有任何兼容性問題
缺點:太普通
var arr = Array.prototype.slice.call(arguments);
這種應該是你們用過最經常使用的方法,至於爲何能夠這麼用,不少人估計也是隻知其一;不知其二,反正我看見你們這麼用我也這麼用,要搞清爲何裏面的緣由,咱們仍是從規範和源碼提及。
照着規範的流程,本身看看推演一下就明白了:
英文版15.4.4.10 Array.prototype.slice (start, end)
中文版15.4.4.10 Array.prototype.slice (start, end)
若是你想知道 JavaScript
的 sort
排序的機制,究竟是哪一種排序好,用的哪一種,也能夠從規範看出端倪。
在官方的解釋中,如[mdn]
The slice() method returns a shallow copy of a portion of an array into a new array object.
簡單的說就是根據參數,返回數組的一部分的 copy
。因此瞭解其內部實現才能肯定它是如何工做的。因此查看 V8
源碼中的 Array.js
能夠看到以下的代碼:
方法 ArraySlice
,源碼地址,第 660
行,直接添加到 Array.prototype
上的「入口」,內部通過參數、類型等等的判斷處理,分支爲 SparseSlice
和 SimpleSlice
處理。
slice.call
的做用原理就是,利用 call
,將 slice
的方法做用於 arrayLike
,slice
的兩個參數爲空,slice
內部解析使得 arguments.lengt
等於0的時候 至關於處理 slice(0)
: 即選擇整個數組,slice
方法內部沒有強制判斷必須是 Array
類型,slice
返回的是新建的數組(使用循環取值)」,因此這樣就實現了類數組到數組的轉化,call
這個神奇的方法、slice
的處理缺一不可。
直接看 slice
怎麼實現的吧。其實就是將 array-like
對象經過下標操做放進了新的 Array
裏面:
// This will work for genuine arrays, array-like objects, // NamedNodeMap (attributes, entities, notations), // NodeList (e.g., getElementsByTagName), HTMLCollection (e.g., childNodes), // and will not fail on other DOM objects (as do DOM elements in IE < 9) Array.prototype.slice = function(begin, end) { // IE < 9 gets unhappy with an undefined end argument end = (typeof end !== 'undefined') ? end : this.length; // For native Array objects, we use the native slice function if (Object.prototype.toString.call(this) === '[object Array]'){ return _slice.call(this, begin, end); } // For array like object we handle it ourselves. var i, cloned = [], size, len = this.length; // Handle negative value for "begin" var start = begin || 0; start = (start >= 0) ? start : Math.max(0, len + start); // Handle negative value for "end" var upTo = (typeof end == 'number') ? Math.min(end, len) : len; if (end < 0) { upTo = len + end; } // Actual expected size of the slice size = upTo - start; if (size > 0) { cloned = new Array(size); if (this.charAt) { for (i = 0; i < size; i++) { cloned[i] = this.charAt(start + i); } } else { for (i = 0; i < size; i++) { cloned[i] = this[start + i]; } } } return cloned; };
優勢:最經常使用的版本,兼容性較強
缺點:ie 低版本,沒法處理 dom 集合的 slice call 轉數組。(雖然具備數值鍵值、length 符合ArrayLike 的定義,卻報錯)搜索資料獲得 :由於 ie 下的 dom 對象是以 com 對象的形式實現的,js 對象與com對象不能進行轉換 。
使用 Array.from
, 值須要對象有 length
屬性, 就能夠轉換成數組
var arr = Array.from(arguments);
擴展運算符
var args = [...arguments];
ES6
中的擴展運算符...也能將某些數據結構轉換成數組,這種數據結構必須有便利器接口。
優勢:直接使用內置 API,簡單易維護
缺點:兼容性,使用 babel 的 profill 轉化可能使代碼變多,文件包變大
你可能不知道的前端知識點:slice 方法的具體原理
const a = parseInt(2.33333)
parseInt()
函數解析一個字符串參數,並返回一個指定基數的整數 (數學系統的基礎)。這個估計是直接取整最經常使用的方法了。
更多關於 parseInt()
函數能夠查看 MDN 文檔
const a = Math.trunc(2.33333)
Math.trunc()
方法會將數字的小數部分去掉,只保留整數部分。
特別要注意的是:Internet Explorer
不支持這個方法,不過寫個 Polyfill
也很簡單:
Math.trunc = Math.trunc || function(x) { if (isNaN(x)) { return NaN; } if (x > 0) { return Math.floor(x); } return Math.ceil(x); };
數學的事情仍是用數學方法來處理比較好。
雙波浪線 ~~ 操做符也被稱爲「雙按位非」操做符。你一般可使用它做爲代替 Math.trunc() 的更快的方法。
console.log(~~47.11) // -> 47 console.log(~~1.9999) // -> 1 console.log(~~3) // -> 3 console.log(~~[]) // -> 0 console.log(~~NaN) // -> 0 console.log(~~null) // -> 0
失敗時返回0,這可能在解決 Math.trunc() 轉換錯誤返回 NaN 時是一個很好的替代。
可是當數字範圍超出 ±2^31−1 即:2147483647 時,異常就出現了:
// 異常狀況 console.log(~~2147493647.123) // -> -2147473649 🙁
| (按位或) 對每一對比特位執行或(OR)操做。
console.log(20.15|0); // -> 20 console.log((-20.15)|0); // -> -20 console.log(3000000000.15|0); // -> -1294967296 🙁
^ (按位異或),對每一對比特位執行異或(XOR)操做。
console.log(20.15^0); // -> 20 console.log((-20.15)^0); // -> -20 console.log(3000000000.15^0); // -> -1294967296 🙁
<< (左移) 操做符會將第一個操做數向左移動指定的位數。向左被移出的位被丟棄,右側用 0 補充。
console.log(20.15 < < 0); // -> 20 console.log((-20.15) < < 0); //-20 console.log(3000000000.15 << 0); // -> -1294967296 🙁
上面這些按位運算符方法執行很快,當你執行數百萬這樣的操做很是適用,速度明顯優於其餘方法。可是代碼的可讀性比較差。還有一個特別要注意的地方,處理比較大的數字時(當數字範圍超出 ±2^31−1 即:2147483647),會有一些異常狀況。使用的時候明確的檢查輸入數值的範圍。
let arr = [1, 2, 3, 4, 5] function sum(arr){ let x = 0 for(let i = 0; i < arr.length; i++){ x += arr[i] } return x } sum(arr) // 15
優勢:通俗易懂,簡單粗暴
缺點:沒有亮點,太通俗
let arr = [1, 2, 3, 4, 5] function sum(arr) { return arr.reduce((a, b) => a + b) } sum(arr) //15
優勢:簡單明瞭,數組迭代器方式清晰直觀
缺點:不兼容 IE 9如下瀏覽器
let arr = [1, 2, 3, 4, 5] function sum(arr) { return eval(arr.join("+")) } sum(arr) //15
優勢:讓人一時看不懂的就是"好方法"。
缺點:
eval 不容易調試。用 chromeDev 等調試工具沒法打斷點調試,因此麻煩的東西也是不推薦使用的…
性能問題,在舊的瀏覽器中若是你使用了eval,性能會降低10倍。在現代瀏覽器中有兩種編譯模式:fast path和slow path。fast path是編譯那些穩定和可預測(stable and predictable)的代碼。而明顯的,eval 不可預測,因此將會使用 slow path ,因此會慢。
更多關於 eval
的探討能夠關注這篇文章: JavaScript 爲何不推薦使用 eval?
你可能不知道的前端知識點:eval的使用細則