挑战看200 个npm模块源码:第1个 目录:
一、介绍
二、手写padLeft
三、源码分析
1、判断基偶数
2、num >>= 1
四、性能比较
字数:大约1500字
最近看到阿里前端技术专家狼叔在 17 年的这篇《迷茫时学习 Node.js 最好的方法》提到:今天小弟过来找我,说迷茫,我告诉他一个密法:一天看 10 个 npm 模块,坚持一年就是 3000+,按正常工作需要,超过 200 个都很厉害了。
所以我也来挑战啦!
一、介绍
今天看的是https://github.com/jonschlinkert/pad-left.git。代码写得是很好的,短短的几十行代码,从中也学到不少好东西。
先了解一下这个npm包
var pad = require('pad-left');
pad( '4', 4, '0') // 0004
pad( '35', 4, '0') // 0035
pad('459', 4, '0') // 0459
看到这里你肯定想说:“艹,这么简单?这代码还需要分析?坑人的吧!”
不要着急,慢慢看。我们先来想一下,如果我们不看他的源码,我们自己会怎么实现这个函数呢?首先,我们会去获取需要填充在左侧的pad的length是多少,然后去循环,将规定的填充字符一个一个的填充在左侧。按照这个思路,我们实现的代码是这样的:
二、手写padLeft
const padLeftFn = (str, len, ch) => {
//将str转成string
str = str + '';
// 获取左侧需要填充部分长度
len = len - str.length;
//如果len为负数则直接返回str
if(len <= 0) return
// 将ch参数转为字符,否则如果是0,则会出现0+0
ch = ch + '';
//如果我们没有给出ch参数,ch默认为一个空格
if(!ch){
ch = ' ';
}
while(len--){
str = ch + str;
}
return str;
}
padLeftFn(0,4,0) // 0000
这样,我们的代码就写出来了,但是为什么我会说我自己写的这个代码和人家写的就差距很大呢,我们看分析一下性能问题就知道了。
我写的这个代码这里的循环时间复杂度是O(n),一次简单的while循环。
三、源码分析
那我们就要看看人家写的源码。
function padLeft(str, num, ch) {
str = str.toString();
if (typeof num === "undefined") {
return str;
}
if (ch === 0) {
ch = "0";
} else if (ch) {
ch = ch.toString();
} else {
ch = " ";
}
return repeat(ch, num - str.length) + str;
}
function repeat(str, num) {
if (typeof str !== "string") {
throw new TypeError("expected a string");
}
// cover common, quick use cases
if (num === 1) return str;
if (num === 2) return str + str;
var max = str.length * num;
if (cache !== str || typeof cache === "undefined") {
cache = str;
res = "";
} else if (res.length >= max) {
return res.substr(0, max);
}
while (max > res.length && num > 1) {
if (num & 1) {
res += str;
}
num >>= 1;
str += str;
}
res += str;
res = res.substr(0, max);
return res;
}
主要是看repeat中的循环部分。
首先,leftPad的作者并没有从一开始就循环添加pad。而是将num === 1 和 num === 2的直接相加返回了。这里考虑到用户在使用时取1、2可能的num比较多,那直接相加返回了,时间复杂度这里就是O(1)。
当数据比较多的时候,源码的实现思路是这样的,每次循环,num除以2,num变成了原来的一半,那么循环次数相应减少到原来的一半,那么str相应增加到原来的两倍的长度str=str+str,这样每次循环都将num减少到原来的一半。这样就将原本O(n)的复杂度减少到了O(log(n))。我们来分析源码。
while (max > res.length && num > 1) {
if (num & 1) {
res += str;
}
num >>= 1;
str += str;
}
res += str;
res = res.substr(0, max);
return res;
1、判断基偶数
首先if (num & 1)是什么意思?,其实就是判断是否是奇数,技术厉害一点这些他们都知道了。我们每一步都给大家讲清楚。看看它是怎么判断基数偶数的。
假如num这个时候为7,7转为二进制后是00000111,这个和00000001按位与。
0 0 0 0 0 1 1 1
0 0 0 0 0 0 0 1
按位&(相同则为1,不相同,则为0)
0 0 0 0 0 0 0 1
得到的二进制再转为10进制就是1。
再来个偶数8
0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 1
按位&(相同则为1,不相同,则为0)
0 0 0 0 0 0 0 0
这里判断num如果是奇数,就先给pad一个str的字符,为什么这里要判断num是否是奇数呢?我们先往下看。
2、num >>= 1
num >>= 1;这又是什么意思,它的意思是,num的二进制向右移动一位,并且把移动一位之后的值重新赋值给num,也就是num = num二进制向右移动一位。
要知道右移一位数并赋值是用来做什么的,最简单的方法就是把移位后的得到的数字转为10进制看一下,以len=10为例。
2^3 2^1
0 0 0 0 1 0 1 0
// 10
------->移动一位
2^2 2^0
0 0 0 0 0 1 0 1 0
// 5
可以看出右移一位之后,其实就是相对原来的数除以了2。10向右移一位之后得到的是5。但是需要注意,与一般js里的/符号不同的是,移位的这个方法,会直接保留整数部分,截取掉小数部分。回过来看上一句,当len为奇数的时候,后面除以2就会把小数截取掉,所以先添加一个pad。num除以2之后,str就要变成原来的两倍长度的字符串。直到num最后小于2的时候跳出循环。最后返回pad+str。
四、性能比较
看看性能比较:
这个性能区别就很大了。100多毫秒:0.几毫秒。