摘要:優(yōu)點(diǎn)簡單粗暴,直接調(diào)用缺點(diǎn)兼容性不太好,不過的話都支持你可能不知道的前端知識點(diǎn)對象和的方法。下面從深層次剖析一下對于開始的兩個(gè)賦值語句,,,相當(dāng)于,而顯然等于。同理可以分析第三個(gè)賦值語句
有些東西很好用,但是你未必知道;有些東西你可能用過,但是你未必知道原理。
實(shí)現(xiàn)一個(gè)目的有多種途徑,俗話說,條條大路通羅馬。很多內(nèi)容來自平時(shí)的一些收集以及過往博客文章底下的精彩評論,收集整理拓展一波,發(fā)散一下大家的思維以及拓展一下知識面。
茴字有四種寫法,233333..., 文末有彩蛋有驚喜。
1、簡短優(yōu)雅地實(shí)現(xiàn) sleep 函數(shù)很多語言都有 sleep 函數(shù),顯然 js 沒有,那么如何能簡短優(yōu)雅地實(shí)現(xiàn)這個(gè)方法?
1.1 普通版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)
優(yōu)點(diǎn):簡單粗暴,通俗易懂。
缺點(diǎn):這是最簡單粗暴的實(shí)現(xiàn),確實(shí) sleep 了,也確實(shí)卡死了,CPU 會飆升,無論你的服務(wù)器 CPU 有多么 Niubility。
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) })
優(yōu)點(diǎn):這種方式實(shí)際上是用了 setTimeout,沒有形成進(jìn)程阻塞,不會造成性能和負(fù)載問題。
缺點(diǎn):雖然不像 callback 套那么多層,但仍不怎么美觀,而且當(dāng)我們需要在某過程中需要停止執(zhí)行(或者在中途返回了錯(cuò)誤的值),還必須得層層判斷后跳出,非常麻煩,而且這種異步并不是那么徹底,還是看起來別扭。
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()) }
優(yōu)點(diǎn):同 Promise 優(yōu)點(diǎn),另外代碼就變得非常簡單干凈,沒有 then 那么生硬和惡心。
缺點(diǎn):但不足也很明顯,就是每次都要執(zhí)行 next() 顯得很麻煩,雖然有 co(第三方包)可以解決,但就多包了一層,不好看,錯(cuò)誤也必須按 co 的邏輯來處理,不爽。
co 之所以這么火并不是沒有原因的,當(dāng)然不是僅僅實(shí)現(xiàn) sleep 這么無聊的事情,而是它活生生的借著generator/yield 實(shí)現(xiàn)了很類似 async/await 的效果!這一點(diǎn)真是讓我三觀盡毀刮目相看。
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) })1.4 Async/Await 版本
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) }()
優(yōu)點(diǎn):同 Promise 和 Generator 優(yōu)點(diǎn)。 Async/Await 可以看做是 Generator 的語法糖,Async 和 Await 相較于 * 和 yield 更加語義,另外各個(gè)函數(shù)都是扁平的,不會產(chǎn)生多余的嵌套,代碼更加清爽易讀。
缺點(diǎn): ES7 語法存在兼容性問題,有 babel 一切兼容性都不是問題
至于 Async/Await 比 Promise 和 Generator 的好處可以參考這兩篇文章:
Async/Await 比 Generator 的四個(gè)改進(jìn)點(diǎn)
關(guān)于Async/Await替代Promise的6個(gè)理由
在 javascript 優(yōu)雅的寫 sleep 等于如何優(yōu)雅的不優(yōu)雅,2333
這里有 C++ 實(shí)現(xiàn)的模塊:https://github.com/ErikDubbel...
const sleep = require("sleep") const t1 = +new Date() sleep.msleep(3000) const t2 = +new Date() console.log(t2 - t1)
優(yōu)點(diǎn):能夠?qū)崿F(xiàn)更加精細(xì)的時(shí)間精確度,而且看起來就是真的 sleep 函數(shù),清晰直白。
缺點(diǎn):缺點(diǎn)需要安裝這個(gè)模塊,^_^,這也許算不上什么缺點(diǎn)。
從一個(gè)間簡簡單單的 sleep 函數(shù)我們就就可以管中窺豹,看見 JavaScript 近幾年來不斷快速的發(fā)展,不單單是異步編程這一塊,還有各種各樣的新技術(shù)和新框架,見證了 JavaScript 的繁榮。
你可能不知道的前端知識點(diǎn):Async/Await是目前前端異步書寫最優(yōu)雅的一種方式
2、獲取時(shí)間戳上面第一個(gè)用多種方式實(shí)現(xiàn) sleep 函數(shù),我們可以發(fā)現(xiàn)代碼有 +new Date()獲取時(shí)間戳的用法,這只是其中的一種,下面就說一下其他兩種以及 +new Date()的原理。
2.1 普通版var timestamp=new Date().getTime()
優(yōu)點(diǎn):具有普遍性,大家都用這個(gè)
缺點(diǎn):目前沒有發(fā)現(xiàn)
var timestamp = (new Date()).valueOf()
valueOf 方法返回對象的原始值(Primitive,"Null","Undefined","String","Boolean","Number"五種基本數(shù)據(jù)類型之一),可能是字符串、數(shù)值或 bool 值等,看具體的對象。
優(yōu)點(diǎn):說明開發(fā)者原始值有一個(gè)具體的認(rèn)知,讓人眼前一亮。
缺點(diǎn): 目前沒有發(fā)現(xiàn)
var timestamp = +new Date()
優(yōu)點(diǎn):對 JavaScript 隱式轉(zhuǎn)換掌握的比較牢固的一個(gè)表現(xiàn)
缺點(diǎn):目前沒有發(fā)現(xiàn)
現(xiàn)在就簡單分析一下為什么 +new Date() 拿到的是時(shí)間戳。
一言以蔽之,這是隱式轉(zhuǎn)換的玄學(xué),實(shí)質(zhì)還是調(diào)用了 valueOf() 的方法。
我們先看看 ECMAScript 規(guī)范對一元運(yùn)算符的規(guī)范:
一元+ 運(yùn)算符
一元 + 運(yùn)算符將其操作數(shù)轉(zhuǎn)換為 Number 類型并反轉(zhuǎn)其正負(fù)。注意負(fù)的 +0 產(chǎn)生 -0,負(fù)的 -0 產(chǎn)生 +0。產(chǎn)生式 UnaryExpression : - UnaryExpression 按照下面的過程執(zhí)行。
令 expr 為解釋執(zhí)行 UnaryExpression 的結(jié)果 .
令 oldValue 為 ToNumber(GetValue(expr)).
如果 oldValue is NaN ,return NaN.
返回 oldValue 取負(fù)(即,算出一個(gè)數(shù)字相同但是符號相反的值)的結(jié)果。
+new Date() 相當(dāng)于 ToNumber(new Date())
我們再來看看 ECMAScript 規(guī)范對 ToNumber 的定義:
我們知道 new Date() 是個(gè)對象,滿足上面的 ToPrimitive(),所以進(jìn)而成了 ToPrimitive(new Date())。
接著我們再來看看 ECMAScript 規(guī)范對 ToPrimitive 的定義,一層一層來,抽絲剝繭。
這個(gè) ToPrimitive 剛開始可能不太好懂,我來大致解釋一下吧:
ToPrimitive(obj,preferredType)
JavaScript 引擎內(nèi)部轉(zhuǎn)換為原始值 ToPrimitive(obj,preferredType) 函數(shù)接受兩個(gè)參數(shù),第一個(gè) obj 為被轉(zhuǎn)換的對象,第二個(gè)preferredType 為希望轉(zhuǎn)換成的類型(默認(rèn)為空,接受的值為 Number 或 String)
在執(zhí)行 ToPrimitive(obj,preferredType) 時(shí)如果第二個(gè)參數(shù)為空并且 obj 為 Date 的實(shí)例時(shí),此時(shí) preferredType 會被設(shè)置為 String,其他情況下 preferredType 都會被設(shè)置為Number 如果 preferredType 為 Number,ToPrimitive 執(zhí)行過程如下:
如果obj為原始值,直接返回;
否則調(diào)用 obj.valueOf(),如果執(zhí)行結(jié)果是原始值,返回之;
否則調(diào)用 obj.toString(),如果執(zhí)行結(jié)果是原始值,返回之;
否則拋異常。
如果 preferredType 為 String,將上面的第2步和第3步調(diào)換,即:
如果obj為原始值,直接返回;
否則調(diào)用 obj.toString(),如果執(zhí)行結(jié)果是原始值,返回之;
否則調(diào)用 obj.valueOf(),如果執(zhí)行結(jié)果是原始值,返回之;
否則拋異常。
首先我們要明白 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 (中國標(biāo)準(zhǔn)時(shí)間)
valueOf 方法返回對象的原始值,可能是字符串、數(shù)值或 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 如代碼所示,三個(gè)不同的對象實(shí)例調(diào)用valueOf返回不同的數(shù)據(jù)
原始值指的是 "Null","Undefined","String","Boolean","Number","Symbol" 6種基本數(shù)據(jù)類型之一,上面已經(jīng)提到過這個(gè)概念,這里再次申明一下。
最后分解一下其中的過程:+new Date():
運(yùn)算符 new 的優(yōu)先級高于一元運(yùn)算符 +,所以過程可以分解為:
var time=new Date();
+time根據(jù)上面提到的規(guī)則相當(dāng)于:ToNumber(time)
time 是個(gè)日期對象,根據(jù) ToNumber 的轉(zhuǎn)換規(guī)則,所以相當(dāng)于:ToNumber(ToPrimitive(time))
根據(jù) ToPrimitive 的轉(zhuǎn)換規(guī)則:ToNumber(time.valueOf()),time.valueOf() 就是 原始值 得到的是個(gè)時(shí)間戳,假設(shè) time.valueOf()=1503479124652
所以 ToNumber(1503479124652) 返回值是 1503479124652 這個(gè)數(shù)字。
分析完畢
你可能不知道的前端知識點(diǎn):隱式轉(zhuǎn)換的妙用
3、數(shù)組去重注:暫不考慮對象字面量,函數(shù)等引用類型的去重,也不考慮 NaN, undefined, null等特殊類型情況。
數(shù)組樣本:[1, 1, "1", "2", 1]
無需思考,我們可以得到 O(n^2) 復(fù)雜度的解法。定義一個(gè)變量數(shù)組 res 保存結(jié)果,遍歷需要去重的數(shù)組,如果該元素已經(jīng)存在在 res 中了,則說明是重復(fù)的元素,如果沒有,則放入 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數(shù)組的item在res已經(jīng)存在,就跳出循環(huán) break } if (j === jlen) //循環(huán)完畢,arr數(shù)組的item在res找不到,就push到res數(shù)組中 res.push(item) } return res } console.log(unique(a)) // [1, 2, "1"]
優(yōu)點(diǎn): 沒有任何兼容性問題,通俗易懂,沒有任何理解成本
缺點(diǎn): 看起來比較臃腫比較繁瑣,時(shí)間復(fù)雜度比較高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//很巧妙,這樣篩選一對一的,過濾掉重復(fù)的 }) } console.log(unique(a)) // [1, 2, "1"]
優(yōu)點(diǎn):很簡潔,思維也比較巧妙,直觀易懂。
缺點(diǎn):不支持 IE9 以下的瀏覽器,時(shí)間復(fù)雜度還是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"]
優(yōu)點(diǎn):hasOwnProperty 是對象的屬性(名稱)存在性檢查方法。對象的屬性可以基于 Hash 表實(shí)現(xiàn),因此對屬性進(jìn)行訪問的時(shí)間復(fù)雜度可以達(dá)到O(1);
filter 是數(shù)組迭代的方法,內(nèi)部還是一個(gè) for 循環(huán),所以時(shí)間復(fù)雜度是 O(n)。
缺點(diǎn):不兼容 IE9 以下瀏覽器,其實(shí)也好解決,把 filter 方法用 for 循環(huán)代替或者自己模擬一個(gè) filter 方法。
以 Set 為例,ES6 提供了新的數(shù)據(jù)結(jié)構(gòu) Set。它類似于數(shù)組,但是成員的值都是唯一的,沒有重復(fù)的值。
const unique = a => [...new Set(a)]
優(yōu)點(diǎn):ES6 語法,簡潔高效,我們可以看到,去重方法從原始的 14 行代碼到 ES6 的 1 行代碼,其實(shí)也說明了 JavaScript 這門語言在不停的進(jìn)步,相信以后的開發(fā)也會越來越高效。
缺點(diǎn):兼容性問題,現(xiàn)代瀏覽器才支持,有 babel 這些都不是問題。
你可能不知道的前端知識點(diǎn):ES6 新的數(shù)據(jù)結(jié)構(gòu) Set 去重
4、數(shù)字格式化 1234567890 --> 1,234,567,890 4.1 普通版function formatNumber(str) { let arr = [], count = str.length while (count >= 3) { arr.unshift(str.slice(count - 3, count)) count -= 3 } // 如果是不是3的倍數(shù)就另外追加到上去 str.length % 3 && arr.unshift(str.slice(0, str.length % 3)) return arr.toString() } console.log(formatNumber("1234567890")) // 1,234,567,890
優(yōu)點(diǎn):自我感覺比網(wǎng)上寫的一堆 for循環(huán) 還有 if-else 判斷的邏輯更加清晰直白。
缺點(diǎn):太普通
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
優(yōu)點(diǎn):把 JS 的 API 玩的了如指掌
缺點(diǎn):可能沒那么好懂,不過讀懂之后就會發(fā)出我怎么沒想到的感覺
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個(gè)或多個(gè)的3個(gè)連續(xù)數(shù)字;
(?!d):第2步中的3個(gè)數(shù)字不允許后面跟著數(shù)字;
(d{3})+(?!d):所以匹配的邊界后面必須跟著3*n(n>=1)的數(shù)字。
最終把匹配到的所有邊界換成,即可達(dá)成目標(biāo)。
優(yōu)點(diǎn):代碼少,濃縮的就是精華
缺點(diǎn):需要對正則表達(dá)式的位置匹配有一個(gè)較深的認(rèn)識,門檻大一點(diǎn)
(123456789).toLocaleString("en-US") // 1,234,567,890
如圖,你可能還不知道 JavaScript 的 toLocaleString 還可以這么玩。
還可以使用?Intl對象 - MDN
Intl 對象是 ECMAScript 國際化 API 的一個(gè)命名空間,它提供了精確的字符串對比,數(shù)字格式化,日期和時(shí)間格式化。Collator,NumberFormat 和 DateTimeFormat 對象的構(gòu)造函數(shù)是 Intl 對象的屬性。
new Intl.NumberFormat().format(1234567890) // 1,234,567,890
優(yōu)點(diǎn):簡單粗暴,直接調(diào)用 API
缺點(diǎn):Intl兼容性不太好,不過 toLocaleString的話 IE6 都支持
你可能不知道的前端知識點(diǎn):Intl對象 和 toLocaleString的方法。
5、交換兩個(gè)整數(shù)let a = 3,b = 4 變成 a = 4, b = 3
5.1 普通版首先最常規(guī)的辦法,引入一個(gè) temp 中間變量
let a = 3,b = 4 let temp = a a = b b = temp console.log(a, b)
優(yōu)點(diǎn):一眼能看懂就是最好的優(yōu)點(diǎn)
缺點(diǎn):硬說缺點(diǎn)就是引入了一個(gè)多余的變量
在不引入中間變量的情況下也能交互兩個(gè)變量
let a = 3,b = 4 a += b b = a - b a -= b console.log(a, b)
優(yōu)點(diǎn):比起樓上那種沒有引入多余的變量,比上面那一種稍微巧妙一點(diǎn)
缺點(diǎn):當(dāng)然缺點(diǎn)也很明顯,整型數(shù)據(jù)溢出,比如說對于32位字符最大表示有符號數(shù)字是2147483647,也就是Math.pow(2,31)-1,如果是2147483645和2147483646交換就失敗了。
利用一個(gè)數(shù)異或本身等于0和異或運(yùn)算符合交換率。
let a = 3,b = 4 a ^= b b ^= a a ^= b console.log(a, b)
下面用豎式進(jìn)行簡單說明:(10進(jìn)制化為二進(jìn)制)
a = 011 (^) b = 100 則 a = 111(a ^ b的結(jié)果賦值給a,a已變成了7) (^) b = 100 則 b = 011(b^a的結(jié)果賦給b,b已經(jīng)變成了3) (^) a = 111 則 a = 100(a^b的結(jié)果賦給a,a已經(jīng)變成了4)
從上面的豎式可以清楚的看到利用異或運(yùn)算實(shí)現(xiàn)兩個(gè)值交換的基本過程。
下面從深層次剖析一下:
1.對于開始的兩個(gè)賦值語句,a = a ^ b,b = b ^ a,相當(dāng)于b = b ^ (a ^ b) = a ^ b ^ b,而b ^ b 顯然等于0。因此b = a ^ 0,顯然結(jié)果為a。
同理可以分析第三個(gè)賦值語句,a = a ^ b = (a ^ b) ^ a = b
注:
^ 即”異或“運(yùn)算符。
它的意思是判斷兩個(gè)相應(yīng)的位值是否為”異“,為”異"(值不同)就取真(1);否則為假(0)。
^ 運(yùn)算符的特點(diǎn)是與0異或,保持原值;與本身異或,結(jié)果為0。
優(yōu)點(diǎn):不存在引入中間變量,不存在整數(shù)溢出
缺點(diǎn):前端對位操作這一塊可能了解不深,不容易理解
熟悉 ES6 語法的人當(dāng)然不會對解構(gòu)陌生
var a = 3,b = 4; [b, a] = [a, b]
其中的解構(gòu)的原理,我暫時(shí)還沒讀過 ES6的規(guī)范,不知道具體的細(xì)則,不過我們可以看看 babel 是自己編譯的,我們可以看出點(diǎn)門路。
哈哈,簡單粗暴,不知道有沒有按照 ES 的規(guī)范,其實(shí)可以扒一扒 v8的源碼,chrome 已經(jīng)實(shí)現(xiàn)這種解構(gòu)用法。
這個(gè)例子和前面的例子編寫風(fēng)格有何不同,你如果細(xì)心的話就會發(fā)現(xiàn)這兩行代碼多了一個(gè)分號,對于我這種編碼不寫分號的潔癖者,為什么加一個(gè)分號在這里,其實(shí)是有原因的,這里就簡單普及一下,建議大家還是寫代碼寫上分號。
5.4 ECMAScript 自動分號;插入(作為補(bǔ)充,防止大家以后踩坑)盡管 JavaScript 有 C 的代碼風(fēng)格,但是它不強(qiáng)制要求在代碼中使用分號,實(shí)際上可以省略它們。
JavaScript 不是一個(gè)沒有分號的語言,恰恰相反上它需要分號來就解析源代碼。 因此 JavaScript 解析器在遇到由于缺少分號導(dǎo)致的解析錯(cuò)誤時(shí),會自動在源代碼中插入分號。
var foo = function() { } // 解析錯(cuò)誤,分號丟失 test()
自動插入分號,解析器重新解析。
var foo = function() { }; // 沒有錯(cuò)誤,解析繼續(xù) 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)
下面是解析器"猜測"的結(jié)果。
(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 表達(dá)式的行為 { // 作為一個(gè)代碼段處理 foo: function() {} }; // <- 插入分號 } window.test = test; // <- 插入分號 // 兩行又被合并了 })(window)(function(window) { window.someLibrary = {}; // <- 插入分號 })(window); //<- 插入分號
解析器顯著改變了上面代碼的行為,在另外一些情況下也會做出錯(cuò)誤的處理。
我們翻到7.9章節(jié),看看其中插入分號的機(jī)制和原理,清楚只寫以后就可以盡量以后少踩坑
必須用分號終止某些 ECMAScript 語句 ( 空語句 , 變量聲明語句 , 表達(dá)式語句 , do-while 語句 , continue 語句 , break 語句 , return 語句 ,throw 語句 )。這些分號總是明確的顯示在源文本里。然而,為了方便起見,某些情況下這些分號可以在源文本里省略。描述這種情況會說:這種情況下給源代碼的 token 流自動插入分號。??
還是比較抽象,看不太懂是不是,不要緊,我們看看實(shí)際例子,總結(jié)出幾個(gè)規(guī)律就行,我們先不看抽象的,看著頭暈,看看具體的總結(jié)說明, 化抽象為具體 。
首先這些規(guī)則是基于兩點(diǎn):
以換行為基礎(chǔ);
解析器會盡量將新行并入當(dāng)前行,當(dāng)且僅當(dāng)符合ASI規(guī)則時(shí)才會將新行視為獨(dú)立的語句。
1. 新行并入當(dāng)前行將構(gòu)成非法語句,自動插入分號。
if(1 < 10) a = 1 console.log(a) // 等價(jià)于 if(1 < 10) a = 1; console.log(a);
2. 在continue,return,break,throw后自動插入分號
return {a: 1} // 等價(jià)于 return; {a: 1};
3. ++、--后綴表達(dá)式作為新行的開始,在行首自動插入分號
a ++ c // 等價(jià)于 a; ++c;
4. 代碼塊的最后一個(gè)語句會自動插入分號
function(){ a = 1 } // 等價(jià)于 function(){ a = 1; }
1. 新行以 ( 開始
var a = 1 var b = a (a+b).toString() // 會被解析為以a+b為入?yún)⒄{(diào)用函數(shù)a,然后調(diào)用函數(shù)返回值的toString函數(shù) var a = 1 var b =a(a+b).toString()
2. 新行以 [ 開始
var a = ["a1", "a2"] var b = a [0,1].slice(1) // 會被解析先獲取a[1],然后調(diào)用a[1].slice(1)。 // 由于逗號位于[]內(nèi),且不被解析為數(shù)組字面量,而被解析為運(yùn)算符,而逗號運(yùn)算符會先執(zhí) 行左側(cè)表達(dá)式,然后執(zhí)行右側(cè)表達(dá)式并且以右側(cè)表達(dá)式的計(jì)算結(jié)果作為返回值 var a = ["a1", "a2"] var b = a[0,1].slice(1)
3. 新行以 / 開始
var a = 1 var b = a /test/.test(b) // /會被解析為整除運(yùn)算符,而不是正則表達(dá)式字面量的起始符號。瀏覽器中會報(bào)test前多了個(gè).號 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)
到這里我們已經(jīng)對ASI的規(guī)則有一定的了解了,另外還有一樣有趣的事情,就是“空語句”。
// 三個(gè)空語句 ;;; // 只有if條件語句,語句塊為空語句。 // 可實(shí)現(xiàn)unless條件語句的效果 if(1>2);else console.log("2 is greater than 1 always!"); // 只有while條件語句,循環(huán)體為空語句。 var a = 1 while(++a < 100);
建議絕對不要省略分號,同時(shí)也提倡將花括號和相應(yīng)的表達(dá)式放在一行, 對于只有一行代碼的 if 或者 else 表達(dá)式,也不應(yīng)該省略花括號。 這些良好的編程習(xí)慣不僅可以提到代碼的一致性,而且可以防止解析器改變代碼行為的錯(cuò)誤處理。
?關(guān)于JavaScript 語句后應(yīng)該加分號么?(點(diǎn)我查看)我們可以看看知乎上大牛們對著個(gè)問題的看法。
你可能不知道的前端知識點(diǎn):原來 JavaScript 還有位操作以及分號的使用細(xì)則
6、將 argruments 對象(類數(shù)組)轉(zhuǎn)換成數(shù)組{0:1,1:2,2:3,length:3}這種形式的就屬于類數(shù)組,就是按照數(shù)組下標(biāo)排序的對象,還有一個(gè) length 屬性,有時(shí)候我們需要這種對象能調(diào)用數(shù)組下的一個(gè)方法,這時(shí)候就需要把把類數(shù)組轉(zhuǎn)化成真正的數(shù)組。
6.1 普通版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]
優(yōu)點(diǎn):通用版本,沒有任何兼容性問題
缺點(diǎn):太普通
var arr = Array.prototype.slice.call(arguments);
這種應(yīng)該是大家用過最常用的方法,至于為什么可以這么用,很多人估計(jì)也是一知半解,反正我看見大家這么用我也這么用,要搞清為什么里面的原因,我們還是從規(guī)范和源碼說起。
照著規(guī)范的流程,自己看看推演一下就明白了:
英文版15.4.4.10 Array.prototype.slice (start, end)
中文版15.4.4.10 Array.prototype.slice (start, end)
如果你想知道 JavaScript 的 sort 排序的機(jī)制,到底是哪種排序好,用的哪種,也可以從規(guī)范看出端倪。
在官方的解釋中,如[mdn]
The slice() method returns a shallow copy of a portion of an array into a new array object.
簡單的說就是根據(jù)參數(shù),返回?cái)?shù)組的一部分的 copy。所以了解其內(nèi)部實(shí)現(xiàn)才能確定它是如何工作的。所以查看 V8 源碼中的 Array.js 可以看到如下的代碼:
方法 ArraySlice,源碼地址,第 660 行,直接添加到 Array.prototype 上的“入口”,內(nèi)部經(jīng)過參數(shù)、類型等等的判斷處理,分支為 SparseSlice 和 SimpleSlice 處理。
slice.call 的作用原理就是,利用 call,將 slice 的方法作用于 arrayLike,slice 的兩個(gè)參數(shù)為空,slice 內(nèi)部解析使得 arguments.lengt 等于0的時(shí)候 相當(dāng)于處理 slice(0) : 即選擇整個(gè)數(shù)組,slice 方法內(nèi)部沒有強(qiáng)制判斷必須是 Array 類型,slice 返回的是新建的數(shù)組(使用循環(huán)取值)”,所以這樣就實(shí)現(xiàn)了類數(shù)組到數(shù)組的轉(zhuǎn)化,call 這個(gè)神奇的方法、slice 的處理缺一不可。
直接看 slice 怎么實(shí)現(xiàn)的吧。其實(shí)就是將 array-like 對象通過下標(biāo)操作放進(jìn)了新的 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; };
優(yōu)點(diǎn):最常用的版本,兼容性較強(qiáng)
缺點(diǎn):ie 低版本,無法處理 dom 集合的 slice call 轉(zhuǎn)數(shù)組。(雖然具有數(shù)值鍵值、length 符合ArrayLike 的定義,卻報(bào)錯(cuò))搜索資料得到 :因?yàn)?ie 下的 dom 對象是以 com 對象的形式實(shí)現(xiàn)的,js 對象與com對象不能進(jìn)行轉(zhuǎn)換 。
使用 Array.from, 值需要對象有 length 屬性, 就可以轉(zhuǎn)換成數(shù)組
var arr = Array.from(arguments);
擴(kuò)展運(yùn)算符
var args = [...arguments];
ES6 中的擴(kuò)展運(yùn)算符...也能將某些數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換成數(shù)組,這種數(shù)據(jù)結(jié)構(gòu)必須有便利器接口。
優(yōu)點(diǎn):直接使用內(nèi)置 API,簡單易維護(hù)
缺點(diǎn):兼容性,使用 babel 的 profill 轉(zhuǎn)化可能使代碼變多,文件包變大
你可能不知道的前端知識點(diǎn):slice 方法的具體原理
7、數(shù)字取整 2.33333 => 2 7.1 普通版const a = parseInt(2.33333)
parseInt() 函數(shù)解析一個(gè)字符串參數(shù),并返回一個(gè)指定基數(shù)的整數(shù) (數(shù)學(xué)系統(tǒng)的基礎(chǔ))。這個(gè)估計(jì)是直接取整最常用的方法了。
更多關(guān)于 parseInt() 函數(shù)可以查看?MDN 文檔
const a = Math.trunc(2.33333)
Math.trunc() 方法會將數(shù)字的小數(shù)部分去掉,只保留整數(shù)部分。
特別要注意的是:Internet Explorer 不支持這個(gè)方法,不過寫個(gè) Polyfill 也很簡單:
Math.trunc = Math.trunc || function(x) { if (isNaN(x)) { return NaN; } if (x > 0) { return Math.floor(x); } return Math.ceil(x); };
數(shù)學(xué)的事情還是用數(shù)學(xué)方法來處理比較好。
7.3 黑科技版雙波浪線 ~~ 操作符也被稱為“雙按位非”操作符。你通??梢允褂盟鳛榇?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
失敗時(shí)返回0,這可能在解決 Math.trunc() 轉(zhuǎn)換錯(cuò)誤返回 NaN 時(shí)是一個(gè)很好的替代。
但是當(dāng)數(shù)字范圍超出 ±2^31?1 即:2147483647 時(shí),異常就出現(xiàn)了:
// 異常情況 console.log(~~2147493647.123) // -> -2147473649 ?
| (按位或) 對每一對比特位執(zhí)行或(OR)操作。
console.log(20.15|0); // -> 20 console.log((-20.15)|0); // -> -20 console.log(3000000000.15|0); // -> -1294967296 ?
^ (按位異或),對每一對比特位執(zhí)行異或(XOR)操作。
console.log(20.15^0); // -> 20 console.log((-20.15)^0); // -> -20 console.log(3000000000.15^0); // -> -1294967296 ?
<< (左移) 操作符會將第一個(gè)操作數(shù)向左移動指定的位數(shù)。向左被移出的位被丟棄,右側(cè)用 0 補(bǔ)充。
console.log(20.15 < < 0); // -> 20 console.log((-20.15) < < 0); //-20 console.log(3000000000.15 << 0); // -> -1294967296 ?
上面這些按位運(yùn)算符方法執(zhí)行很快,當(dāng)你執(zhí)行數(shù)百萬這樣的操作非常適用,速度明顯優(yōu)于其他方法。但是代碼的可讀性比較差。還有一個(gè)特別要注意的地方,處理比較大的數(shù)字時(shí)(當(dāng)數(shù)字范圍超出 ±2^31?1 即:2147483647),會有一些異常情況。使用的時(shí)候明確的檢查輸入數(shù)值的范圍。
8、數(shù)組求和 8.1 普通版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
優(yōu)點(diǎn):通俗易懂,簡單粗暴
缺點(diǎn):沒有亮點(diǎn),太通俗
let arr = [1, 2, 3, 4, 5] function sum(arr) { return arr.reduce((a, b) => a + b) } sum(arr) //15
優(yōu)點(diǎn):簡單明了,數(shù)組迭代器方式清晰直觀
缺點(diǎn):不兼容 IE 9以下瀏覽器
let arr = [1, 2, 3, 4, 5] function sum(arr) { return eval(arr.join("+")) } sum(arr) //15
優(yōu)點(diǎn):讓人一時(shí)看不懂的就是"好方法"。
缺點(diǎn):
eval 不容易調(diào)試。用 chromeDev 等調(diào)試工具無法打斷點(diǎn)調(diào)試,所以麻煩的東西也是不推薦使用的…性能問題,在舊的瀏覽器中如果你使用了eval,性能會下降10倍。在現(xiàn)代瀏覽器中有兩種編譯模式:fast path和slow path。fast path是編譯那些穩(wěn)定和可預(yù)測(stable and predictable)的代碼。而明顯的,eval 不可預(yù)測,所以將會使用 slow path ,所以會慢。
更多關(guān)于 eval 的探討可以關(guān)注這篇文章: JavaScript 為什么不推薦使用 eval?
你可能不知道的前端知識點(diǎn):eval的使用細(xì)則
最后祝大家圣誕快樂?,歡迎補(bǔ)充和交流。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/112925.html
摘要:優(yōu)點(diǎn)簡單粗暴,直接調(diào)用缺點(diǎn)兼容性不太好,不過的話都支持你可能不知道的前端知識點(diǎn)對象和的方法。下面從深層次剖析一下對于開始的兩個(gè)賦值語句,,,相當(dāng)于,而顯然等于。同理可以分析第三個(gè)賦值語句 有些東西很好用,但是你未必知道;有些東西你可能用過,但是你未必知道原理。 實(shí)現(xiàn)一個(gè)目的有多種途徑,俗話說,條條大路通羅馬。很多內(nèi)容來自平時(shí)的一些收集以及過往博客文章底下的精彩評論,收集整理拓展一波,發(fā)...
摘要:優(yōu)點(diǎn)簡單粗暴,直接調(diào)用缺點(diǎn)兼容性不太好,不過的話都支持你可能不知道的前端知識點(diǎn)對象和的方法。下面從深層次剖析一下對于開始的兩個(gè)賦值語句,,,相當(dāng)于,而顯然等于。同理可以分析第三個(gè)賦值語句 有些東西很好用,但是你未必知道;有些東西你可能用過,但是你未必知道原理。 實(shí)現(xiàn)一個(gè)目的有多種途徑,俗話說,條條大路通羅馬。很多內(nèi)容來自平時(shí)的一些收集以及過往博客文章底下的精彩評論,收集整理拓展一波,發(fā)...
摘要:的翻譯文檔由的維護(hù)很多人說,阮老師已經(jīng)有一本關(guān)于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發(fā)過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。 JavaScript Promise 迷你書(中文版) 超詳細(xì)介紹promise的gitbook,看完再不會promise...... 本書的目的是以目前還在制定中的ECMASc...
摘要:實(shí)現(xiàn)一個(gè)目的有多種途徑,俗話說,條條大路通羅馬。因?yàn)槭堑囊粋€(gè)靜態(tài)函數(shù),所以必須以的形式來使用。注意一元運(yùn)算符一元運(yùn)算符將其操作數(shù)轉(zhuǎn)換為類型并反轉(zhuǎn)其正負(fù)。相當(dāng)于用來返回對象的字符串表示。 一、前言 有些東西很好用,但是你未必知道;有些東西你可能用過,但是你未必知道原理。實(shí)現(xiàn)一個(gè)目的有多種途徑,俗話說,條條大路通羅馬。發(fā)散一下大家的思維以及拓展一下知識面。 二、實(shí)現(xiàn)一個(gè)簡短的sleep函數(shù)...
閱讀 2987·2021-11-16 11:45
閱讀 5184·2021-09-22 10:57
閱讀 1773·2021-09-08 09:36
閱讀 1602·2021-09-02 15:40
閱讀 2517·2021-07-26 23:38
閱讀 1202·2019-08-30 15:55
閱讀 929·2019-08-30 15:54
閱讀 1220·2019-08-29 14:06