一些冷門的js操作

lhyt發表於2018-04-10

本文來源於我的github

0.前言

大家學習的時候,一開始小白,然後接觸到進階的東西的時候,發現一切得心應手,有的人可能開始說精通了。突然有一天,發現了一些基於很基礎的東西的騷操作,就開始懷疑人生:wtf JavaScript? 如果沒有遇到被某些東西打擊到或者見識到新的世界,可能永遠的,就感嘆:jQuery真好用,我精通jQuery,精通js。要不就是,vue?angular?react?我都會,我精通。

然而,我現在只能說我只是熟悉,越學發現越多坑。 對於資料型別轉換和正則的坑,前面有講過: 資料型別 正規表示式

1.陣列

類似一些遍歷型別的api:forEach、map,可能有人就說了:不就是arr.map(x=>x+1),要是涉及到索引,那就再來個index,arr.map((x,index)=>x+index),多簡單是不是 然後,先丟擲一個問到爛的面試題: ['1','2','3'].map(parseInt) 找工作的人,看過面試題的,都知道結果是[1,NaN,NaN],那麼為什麼會這樣呢? 首先,map裡面可以傳兩個引數:map(對每一個元素都呼叫的函式,該函式的this值) 而那個每一個元素都呼叫的函式,他傳入的引數是(current,index,arr)當前元素(必須)、索引、陣列。

1.1對parseInt精通了嗎

在這個例子,對parseInt傳入的就是這三個引數。 而parseInt這個原生的函式,他傳入的引數是(num,radix): radix表示要解析的數字的基數。該值介於 2 ~ 36 之間。 如果省略該引數或其值為 0,則數字將以 10 為基礎來解析。如果它以 “0x” 或 “0X” 開頭,將以 16 為基數。如果該引數小於 2 或者大於 36,則 parseInt() 將返回 NaN

parseInt(10,10)//對10進位制數字10取整,10
parseInt(10,2)//對2進位制數字10取整,2
parseInt(111,2)//對2進位制數字111取整,7
parseInt(111,4)//對4進位制數字111取整,21
parseInt('1',2,[1])//1,傳入第三個陣列是沒有意義的
parseInt('1',2,['1','2','3','4'])//1
parseInt('2',2,['1','2','3','4'])//NaN,2進位制沒有2
parseInt('2',3,['1','2','3','4'])//3
複製程式碼

那就很明顯了,對於為什麼是[1,NaN,NaN],其實就是:

parseInt('1',0,['1','2','3'])//1,radix為 0,則數字將以 10 為基礎來解析
parseInt('2',1,['1','2','3'])//1進位制是啥?反正就是NaN
parseInt('3',2,['1','2','3'])//2進位制沒有3
複製程式碼

另外,parseInt,遇到字串的數字他會盡量解釋,直到不能解釋就停止。

parseInt('123sfd')//123
parseInt('123sfd123')//123
parseInt('sfd213')//NaN
複製程式碼

1.2陣列遍歷的api第二個引數

第二個引數,表示前面那個函式內部的this,一般不填這個引數,嚴格模式this是undefined。而在非嚴格模式下,this指向window: [1,2,3].forEach(function(x){console.log(this)})//window 我們可以強行把他改成其他的看看: [1,2,3].forEach(function(x){console.log(this)},Math)//Math

1.3讓Array成精

我們知道,Array可以直接生成某一個長度元素全是空的陣列: Array(5)//注意了,5空和5個undefined是不同的 不妨apply一下:

Array.apply(null, { length:5 })// [undefined, undefined, undefined, undefined, undefined]
Array.apply(null, { '0':1,length:5 })// [1, undefined, undefined, undefined, undefined]
Array.apply(null, { '2':1,length:5 })// [undefined, undefined, 1, undefined, undefined]
複製程式碼

相當於給新建立的array的屬性直接進行改變

1.4你是不是用迴圈生成一個序列?

生成一個序號陣列:

var arr = [];
for(var i = 0;i<10;i++){
    arr.push(i)
}
複製程式碼

常規操作,沒什麼問題,但是精通jQuery的你會不會用到其他方法呢? 比如:

Array.apply(null, {length:5 }).map(f.call,Number)//[0, 1, 2, 3, 4],f可以是任何函式
Array.apply(null, { '0':1,length:5 }).map(f.call,Number)//[0, 1, 2, 3, 4],不管元素是什麼都一樣
Array.apply(null, {length:5 }).map(f.call,Boolean)//[false, true, true, true, true]
Array.apply(null, {length:5 }).map(f.call,String)//["0", "1", "2", "3", "4"]
Array.apply(null, {length:5 }).map(eval.call,Object)//[Number, Number, Number, Number, Number]
複製程式碼

對於最後一個結果,點開第二個看看

1

map的那個函式,傳入了三個引數,第一個引數是當前元素。可是對於加了call的,第一個引數就是call的this指向了。而map後面的那個引數,就是this,也就是後面那個this已經把current覆蓋,起到主導作用的也是後面那個引數。而map的第一個引數fn,fn裡面的第二個引數是index,也就是當前索引。而對於f.call,第一個引數是this指向,第二個引數以後全是f.call的函式f所使用的引數。最後,就相當於對每一個元素進行,Number(index),Boolean(index),String(index),Object(index)

2.位操作符

基本用法和概念就不說了,自行看文件。

2.1字串轉數字

有全世界都知道的parseInt,但是我們又可以用簡單一點的方法:

//就是讓他進行不改變本身的值的數學運算
+"1"
"1"*1
"1"|0
"1" >> 0
"1" << 0
複製程式碼

2.2更多的操作

要是我們要隨意得到一個很大的數,一般就是9999*9999這樣子吧,而位移操作可以相當於乘上2的n次方:1<<30//1073741824 好像沒什麼用,先丟擲一個需求:隨機生成字串(數字+字母) 我知道,很多人完全不需要思考,直接拿起鍵盤擼:比如生成一個6位隨機字串

var n = 6
var str = 'abcdefghijklmnopqrstuvwxyz0123456789'
var result = ''
for (var i = 0 ;i<n;i++){
   result += str[parseInt(Math.random()*(str.length+1))]
}
複製程式碼

對於位操作,就是短短的程式碼解決:

(~~(Math.random()*(1<<24))).toString(16)
(~~(Math.random()*(1<<30))).toString(36)
複製程式碼

首先生成一個大數,再轉進位制。16進位制就是0-9加上前面幾個字母,36進位制就是0-9加上26個字母,那麼我們可以得到一個穩定的生成n位隨機字串版本:

function f(n){
	if(n==1) return (~~(Math.random()*36)).toString(36)
	return (~~(Math.random()*36)).toString(36) + f(n-1)
}
//尾遞迴優化
function k(n){
	return function f(n,s){
		if(n==1) return s
		return  f(n-1,(~~(Math.random()*36)).toString(36)+s)
	}(n,(~~(Math.random()*36)).toString(36))
}
複製程式碼

另一種方法:(也是基於高進位制) 我們可以從Math.random().toString(36)得到一個0.xxx後面有11位小數的字串,所以我們只要取第2位以後的就可以了Math.random().toString(36).slice(2)

來一段小插曲

對於追求程式碼極其簡短的強迫症,看見上面的if -else,突然想起來平時寫了一大堆if-else實在是不順眼,好的,除了無腦if和簡短的三元表示式,我們還有短路表示式: || && a&&b:a為true,跑到b a||b:a為false,跑b,a為true就取a

//來一個有點智障的例子
function f(a){
if(a==1) console.log(1)
if(a==2) console.log(2)
if(a==3) console.log(3)
}

//一定要記得寫分號
function f(a){
(a==1)&& console.log(1);
(a==2) &&console.log(2);
(a==3) &&console.log(3);
}
複製程式碼

如果在實際應用上面,程式碼將會大大簡潔,但是可能第一次讓別人看難以理解

位操作交換倆整數

不用中間變數,加減法實現交換 a = a+b;b = a-b;a = a-b 用位操作: a ^= b; b ^= a; a ^= b;

具體過程可以這樣子證明:

我們先令a0 = a, b0 = b。a = a ^ b 可以轉化為a = a0 ^ b
第二句:b = b ^ a =  b0 ^ a = b0 ^ (a0 ^ b0) = a0 ^ (b0 ^ b0) = a0 ^ 0 = a0//達到了原始值a0和實際值b交換
第三句一樣:a = a ^ b = a ^ (b0 ^ a) = b0 ^ (a ^ a)= b0 ^ 0 = b0,和原始的b0交換成功
複製程式碼

3. 構造類

繼續回到前面的例子:

Array.apply(null, {length:5 }).map(f.call,Number)//[0, 1, 2, 3, 4],f可以是任何函式
Array.apply(null, { '0':1,length:5 }).map(f.call,Number)//[0, 1, 2, 3, 4],不管元素是什麼都一樣
Array.apply(null, {length:5 }).map(f.call,Boolean)//[false, true, true, true, true]
Array.apply(null, {length:5 }).map(f.call,String)//["0", "1", "2", "3", "4"]
Array.apply(null, {length:5 }).map(eval.call,Object)//[Number, Number, Number, Number, Number]
複製程式碼

map第二個引數ctx是this指向,而第一個引數是一個函式f(任何函式),f的第一個引數已經報廢,因為第一個引數是call的上下文this,但是這個this又被後面的ctx替代了,因此f有效的引數是從第二個開始,最後就相當於ctx(index),即是 :構造類(index)

於是我們又可以看看構造類另一個有意思的地方

var toFixed = 1;
var obj = {
     toFixed:"我只是客串的",
    show:function(){
        return this. toFixed;
    }
}
obj.show.call( toFixed);   //ƒ toFixed() { [native code] }
複製程式碼

也許一眼看上去是1,然而call的第一個引數居然不是null、undefined,效果不一樣了。 我們call的上下文就是toFixed。可以這樣理解,對於js內部,1其實是構造類Number(1)構造出來的,相當於this指向了Number,而我們可以列印一下Number.prototype,結果有

1
我們把toFixed方法列印出來了

對於String也是可以的

var big = '1sdasdsadsdasd';//不是字串的話,其他構造類沒有big方法,返回undefined
var obj = {
    big:"我是客串的",
    show:function(){
        return this.big;
    }
}
obj.show.call(big);  //ƒ big() { [native code] }

//或者說,列印一個length看看
var l = '1sdasdsadsdasd';//變數換成l
var obj = {
    length:"我是客串的",
    show:function(){
        return this.length;//主要是這個,變數是什麼不重要
    }
}
obj.show.call(l); //14
複製程式碼

屬性太多了,可以去控制檯看看String.prototype有什麼。

再或者說,看看函式執行起來會發生什麼事情:

var l = true;//這次試一下boolean型別
var obj = {
    length:"我是客串的",
    show:function(){
        return this.valueOf();//這次我們不列印這個函式了,讓他執行
    }
}
obj.show.call(l,this); //true,直接呼叫型別轉換過程中的那個valueOf
複製程式碼

道理都是一樣,大家自己可以回去玩一下

相關文章