你们学习的时候,一开始小白,而后接触到进阶的东西的时候,发现一切驾轻就熟,有的人可能开始说精通了。忽然有一天,发现了一些基于很基础的东西的骚操做,就开始怀疑人生:wtf JavaScript?
若是没有遇到被某些东西打击到或者见识到新的世界,可能永远的,就感叹:jQuery真好用,我精通jQuery,精通js。要不就是,vue?angular?react?我都会,我精通。javascript
然而,我如今只能说我只是熟悉,越学发现越多坑。
对于数据类型转换和正则的坑,前面有讲过:
数据类型
正则表达式vue
相似一些遍历类型的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)当前元素(必须)、索引、数组。java
在这个例子,对parseInt传入的就是这三个参数。
而parseInt这个原生的函数,他传入的参数是(num,radix):
radix表示要解析的数字的基数。该值介于 2 ~ 36 之间。
若是省略该参数或其值为 0,则数字将以 10 为基础来解析。若是它以 “0x” 或 “0X” 开头,将以 16 为基数。若是该参数小于 2 或者大于 36,则 parseInt() 将返回 NaNreact
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],其实就是:git
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,遇到字符串的数字他会尽可能解释,直到不能解释就中止。github
parseInt('123sfd')//123 parseInt('123sfd123')//123 parseInt('sfd213')//NaN
第二个参数,表示前面那个函数内部的this,通常不填这个参数,this就是undefined。而在非严格模式下,undefined就是window:[1,2,3].forEach(function(x){console.log(this)})//window
咱们能够强行把他改为其余的看看:[1,2,3].forEach(function(x){console.log(this)},Math)//Math
面试
咱们知道,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的属性直接进行改变api
生成一个序号数组:数组
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]
对于最后一个结果,点开第二个看看
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)
基本用法和概念就不说了,自行看文档。
有全世界都知道的parseInt,可是咱们又能够用简单一点的方法:
//就是让他进行不改变自己的值的数学运算 +"1" "1"*1 "1"|0 "1" >> 0 "1" << 0
要是咱们要随意获得一个很大的数,通常就是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交换成功
继续回到前面的例子:
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,结果有
咱们把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
道理都是同样,你们本身能够回去玩一下