壹 ❀ 引

时间一晃,今天已是五一假期最后一天了,没有出门,没有太多惊喜与意外。今天五四青年节,脑子里突然想起鲁迅先生以及悲欢并不相通的话,我的五一经历了什么呢,忍不住想说那大概是,父母教育孩子大声嚷嚷,隔壁装修电钻嗡嗡作响,戴上耳机敲着键盘书写每个白天晚上。

矫情完,那么回归本文正题,我在之前其实已经更新了大部分JS常考手写题,比如手写bind new apply call promise,节流防抖等等,这些知识中有些较为复杂,所以都用了单独的篇幅去介绍,那自然还剩一些好理解一点的手写题,考虑到一个一篇文章浪费篇幅,也没必要,这里就统一整理,我们直接开始。

贰 ❀ 实现trim方法

String.prototype.trim()方法能移除字符串前后的所有空格,这在于我们做表单验证时非常实用,那么现在要求手动实现trim方法应该怎么做?

我们知道,String上有一个replace,用于将指定规则的字符串替换成我们想要的字符,因为空格也是字符,所以本质上只用将空格替换成空即可,但需要注意的是,像 hello echo 这种我们其实只需要将前后的空格去掉,而不能去除两个单词之间的空格。

现在的问题就是怎么匹配前后的空格的问题了,这里就需要借用正则表达式了,先上实现:

const trim = (s) => s.replace(/^\s+|\s+$/g, '');
console.log(trim(' hello echo ')) // 'hello echo'
console.log(trim('echo ')) // 'echo'

因为replace接受一个正则用于表示目标字符的规则,所以假设大家有疑问,肯定就是疑问这段正则是什么意思,这里我给大家解释下:

  • \s表示匹配空格,而+表示量词,等价于{1,}(至少一次或者多个),因此\s+表示一个或者多个空格。
  • 管道符|,你可以理解成JS中的||,表示满足前后条件其一均可
  • 脱字符^和美元符$在这里表示匹配开头和匹配尾部,毕竟我们也说了不要匹配字符串中间的空格
  • 修饰符g表示全局匹配,毕竟一个字符串可能很长,我就是希望全局替换。

因此这段正则意思就是,全局匹配,匹配目标是字符串开头或结尾的1个或者多个空格,用图来表示:

另外,若大家对于^$有疑惑,可以读一读博主JS 正则表达式$详解,脱字符与美元符$同时写表示什么意思?一文。若对于正则感兴趣,可以阅读博主从零开始学正则系列文章,学好正则在某些时刻真的非常非常有帮助。

另外,除了replace之外,其实还有个replaceAll方法,从一个最基本的例子来区分两者的区别:

' ec ho '.replace(' ', '');//'ec ho '  只替换了开头第一个空格
' ec ho '.replaceAll(' ', '');//'echo' 开头结尾中间所有空格全没了,自带全局匹配

叁 ❀ 实现字符串翻转(实现reverse)

本题其实是leetcode 344. 反转字符串,题目要求给定:

输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]

我:return s.reverse()

面试官:滚~

言归正传,本题可以理解为如何实现一个reverse方法,首先可以想到的就是创建一个空数组,倒序遍历塞到新数组,然后返回:

const reverse = (arr) => {
const ans = [];
while (arr.length) {
ans.push(arr.pop())
};
return ans;
}
console.log(reverse(["h", "e", "l", "l", "o"])) // ['o', 'l', 'l', 'e', 'h']

当然本题其实有个限制,题目其实是希望你直接修改原数组,不要新建额外的数组去返回,也不卖关子,其实本题的原意是考核双指针的用法,我们来看两个简单的例子:

比如1,2,3要变成3,2,1,是不是只用交换1,3的位置即可:

而像1,2,3,4变成4,3,2,1,我们其实只用做1,42,3互换即可:

所以我们完全可以定义两个指针,一个从索引0开始,一个从length-1开始,两两互换,然后一个递增一个递减:

const arr = ["h", "e", "l", "l", "o"];
var reverseString = function (s) {
var i = 0,
j = s.length - 1;
for (; i < j; i++, j--) {
// 解构赋值
[s[i], s[j]] = [s[j], s[i]];
};
console.log(s === arr)
};
reverseString(arr)
console.log(arr); // ['o', 'l', 'l', 'e', 'h']

肆 ❀ 数组去重

老生常谈的问题,这里不做太多赘述,ES5我们可以通过filter + indexOf,实现如下:

const arr = [1, 2, 3, 3, 4, 4];
const unique = (arr) => arr.filter((ele, index) => index === arr.indexOf(ele));
console.log(unique(arr)) // [1,2,3,4];

我们知道filter前两个参数,第一个表示当前元素,第二个是当前元素的索引,而indexOf只能找到一个元素第一次出现的索引,所以我们通过index === arr.indexOf(ele))来找到第一次出现的每个元素。

打个比方,以上面的arr的例子,第一个3出现时index2,而arr.indexOf(3)也是2,符合条件所以把这个3加入到了新数组。

遍历到第二个3index3,而indexOf找出的还是2,不相等,这个3就不要了。

ES6的Set实现就更简单了:

const unique = (arr) => [...new Set(arr)]

这里就不多解释了。

伍 ❀ 实现flat方法

我们知道数组flat方法可用于数组降维,比如:

[[1],[2]].flat(); // [1,2]

同时flat接受一个参数,表示要降维几次,默认是1,像[[[1]]]很明显需要降维2次,因此可以传递2达到效果:

[[[1]]].flat(2); // [1]

那假设我也不清楚这个数组是几维,但是我就是要打瓶怎么办?这里可以传递Infinity,表示无限大,多少维度都给你降了:

[[[[[[[[[[1]]]]]]]]]].flat(Infinity); //[1]

那么我们自己怎么实现false呢?考虑到数组的元素也可能是数组,这里我们就可以考虑递归,只要当前元素是数组,开始递归,直到访问到真正的元素后,再塞入到新数组,递归完成一层层往上返回即可。

我知道,对于递归陌生的同学可能脑子里知道是怎么回事,但就是写不出来,这里我们再回忆下递归的准则:

  • 我们需要定义一个函数,它会自己调用自己
  • 只用想好当前需要做什么,递归会帮你做相同的事情
  • 想好你递归的条件,什么时候跳出,毕竟你不可能一直递归下去
  • 需要做返回值吗?不返回能不能解决

我们假定有个数组[1,2,3],需要你拷贝一份,可以怎么做?不假思索,新建数组,遍历后一一塞进去:

const traverse = (arr) => {
const arr_ = [];
arr.forEach((ele) => {
arr_.push(ele)
})
return arr_;
}

结合上面的例子比递归的特性,我们提炼下信息,注意,你永远只需要考虑当前需要做什么,递归的操作它自然会帮你做相同的操作,我们需要做什么?

递归要做什么操作?很显然是遍历数组,如果当前元素不是数组,直接push,如果是数组,递归一次,到这就不用继续在脑子里递归了,递归自然会帮你做好。

递归跳出条件是什么?上面也说了,只有是数组时才会递归,不需要考虑跳出。

需要考虑返回吗?需要,因为我们每次都新建了一个数组,用于装当前普通元素,如果递归了需要返回给上一层。

直接写代码:

const flat = (arr) => {
const arr_ = [];
arr.forEach((ele) => {
// 是数组吗?
Array.isArray(ele) ?
arr_.push.apply(arr_, flat(ele)) :// 是数组就递归,不断降维
arr_.push(ele);
})
return arr_;
}

或者:

const flat = (arr) => {
const arr_ = [];
arr.forEach((ele) => {
arr_.push.apply(arr_,
Array.isArray(ele) ?
flat(ele) : [ele]
)
})
return arr_;
}

为啥要用apply呢,因为flat递归后返回一个数组,push不能直接压入数组啊,我是希望压入一个个的元素,而apply参数正好是个数组,所以才用了这种写法。

陆 ❀ 实现千位分隔符

千位分隔符在银行账号上非常常见,比如12345678分隔后就是12,345,678,相当于从后往前数,每三位加一个逗号。怎么做呢?一种做法是将数字转为字符串,之后分割成数组,然后倒序遍历,每三个塞一个逗号即可,思路比较简单,直接上代码:

const fn = (num) => {
const s = (num + '').split('');
const ans = [];
let len = s.length - 1;
// 用于统计遍历次数,每三次塞一个逗号
let n = 0;
while (len >= 0) {
ans.unshift(s[len]);
n += 1;
// 考虑123456789的情况
if (n % 3 === 0 && len !== 0) {
ans.unshift(',');
};
len--;
};
return ans.join('');
}; console.log(fn(12345678)); // 12,345,678
console.log(fn(123456789)); // 123,456,789

需要注意塞逗号这里的len !== 0,这是因为不加这个判断123456789会变成,123,456,789,这里就不多说了。

第二种做法当然还是我们的正则,同理,我们从后往前数,每三个位置塞一个逗号,同时过滤开始的位置:

const fn = (num) => (num + '').replace(/(?!^)(?=(\d{3})+$)/g, ',');

关于正则如何实现千位运算符,我在从零开始学正则(三),理解正则的分组与反向引用文章开头有讲,如果摊开讲,又涉及到正则位置概念的普及,反向负向先行断言等,比较复杂,所以我还是建议大家自行学习,有问题可以留言问我,我再做解释。

对了,我突然想到,千位运算符得考虑小数点的情况,这里我说说正则怎么做,毕竟数组的实现本质上还是截取小数点前面的做相同的操作最后再拼接而已,对于正则,我们还是一样先匹配小数点前面的部分,在对这一步做我们上面的千位运算替换即可,实现为:

// 先匹配小数点前面的数字部分,再执行千位运算符替换,两个replace搞定
const fn = (num) => (num + '').replace(/\d+/, (n) => n.replace(/(?!^)(?=(\d{3})+$)/g, ',')); console.log(fn(12345678.123)); // 12,345,678.123
console.log(fn(123456789.123)); // 123,456,789.123

柒 ❀ 总

那么到这里,五道简单的手写编程题搞定,今天是五四,因为想起了鲁迅先生,所以借用他的话做文章的结尾。

愿中国青年都摆脱冷⽓,只是向上⾛,不必听⾃暴⾃弃者流的话。能做事的做事,能发声的发声。有⼀分热,发⼀分光,就令萤⽕⼀般,也可以在⿊暗⾥发⼀点光,不必等候炬⽕。----鲁迅

那么到这里,本文结束。

五四青年节,今天要学习。汇总5道难度不高但可能遇到的JS手写编程题的更多相关文章

  1. 常见的JS手写函数汇总(代码注释、持续更新)

    最近在复习面试中常见的JS手写函数,顺便进行代码注释和总结,方便自己回顾也加深记,内容也会陆陆续续进行补充和改善. 一.手写深拷贝 <script> const obj1 = { name ...

  2. 50道经典的JAVA编程题(汇总)

    这是一次不可思议的编程历程.从2013年的最后一天开始做这份题,中间连续好几天的考试,包括java考试(今天考试的JAVA编程题),直到今天完成了.挺有成就感的...废话不多说了,来电实质性的吧. 全 ...

  3. 50道经典的JAVA编程题 (6-10)

    50道经典的JAVA编程题 (6-10),今晚做了10道了,累死了...感觉难度不是很大,就是不知道是不是最好的实现方法啊!希望大神们能给指点哈... [程序6]GCDAndLCM.java 题目:输 ...

  4. 识别手写数字增强版100% - pytorch从入门到入道(一)

    手写数字识别,神经网络领域的“hello world”例子,通过pytorch一步步构建,通过训练与调整,达到“100%”准确率 1.快速开始 1.1 定义神经网络类,继承torch.nn.Modul ...

  5. 汤姆大叔的6道javascript编程题题解

    看汤姆大叔的博文,其中有篇(猛戳这里)的最后有6道编程题,于是我也试试,大家都可以先试试. 1.找出数字数组中最大的元素(使用Math.max函数) var a = [1, 2, 3, 6, 5, 4 ...

  6. 50道经典的JAVA编程题(46-50)

    50道经典的JAVA编程题(46-50),最后五道题了,这是一个美妙的过程,编程真的能让我忘掉一切投入其中,感觉很棒.今天下午考完微机原理了,大三上学期就这样度过了,这学期算是解放了,可是感觉我还是没 ...

  7. 50道经典的JAVA编程题(41-45)

    50道经典的JAVA编程题(41-45),苦逼的程序猿,晚上睡不着了编程吧~今天坚持做10道题!发现编程能是我快乐...O(∩_∩)O哈哈~能平静我烦乱的心,剩下5道题留到考试完了再做吧!该睡觉了.. ...

  8. 50道经典的JAVA编程题(36-40)

    50道经典的JAVA编程题(36-40),今天晚上心情压抑,不爽,继续做题,管它明天考试,我继续我的java,一个周末都在看微机原理看得的很头疼啊~明天该挂科就挂吧,不在乎了~~~ [程序36] Ar ...

  9. 50道经典的JAVA编程题(31-35)

    50道经典的JAVA编程题(31-35),今天考完了java,在前篇博客里面贴出了题了,见:<今天考试的JAVA编程题>.考完了也轻松了,下个星期一还考微机原理呢,啥都不会,估计今天就做到 ...

随机推荐

  1. jvm-learning-运行时数据区-整体

    在jdk8之后之前的方法区有叫做元数据. 每个JVM只有一个Runtime实例,即为运行时环境,相当于内存结构种的运行时数据区 线程 线程是一个程序里的运行单元,JVM允许一个应用有多个线程并行的执行 ...

  2. Mybatis框架基础入门(四)--SqlMapConfig.xml配置文件简介

    SqlMapConfig.xml中配置的内容和顺序如下: properties(属性) settings(全局配置参数) typeAliases(类型别名) typeHandlers(类型处理器) o ...

  3. 学习 MongoDB(一)

    1.介绍 MongoDB是C++语言编写,是一个基于分布式文件存储的开源数据库系统,MongoDB将数据存储为一个文档, 数据结构由键值对(key=>value)组成,MongoDB文档类似于 ...

  4. TIME_WAIT 优化

    ·[场景描述] HTTP1.1之后,HTTP协议支持持久连接,也就是长连接,优点在于在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟. 如果我们使用了nginx去作为 ...

  5. 使用Visual Studio查看C++类内存分布

    书上类继承相关章节到这里就结束了,这里不妨说下C++内存分布结构,我们来看看编译器是怎么处理类成员内存分布的,特别是在继承.虚函数存在的情况下. 工欲善其事,必先利其器,我们先用好Visual Stu ...

  6. JavaScript 小技巧 数组去重

    const array = [1, 2, 3, 3, 5, 5, 1]; const uniqueArray = [...new Set(array)]; console.log(uniqueArra ...

  7. .NET程序设计实验四

    实验四  文件操作 一.实验目的 1. 掌握窗口控件的使用方法: 2. 掌握文件系统的操作方法.File 类和 Directory类的使用. 二.实验要求 根据要求,编写 C#程序,并将程序代码和运行 ...

  8. Java中 i++和++i 的区别

    学习目标: 理解i++和++i的区别 学习内容: 1.i++ / i- - i++/i- -:遇到 i++或 i- -,i先参与运算,然后 i 再自加或自减1 代码如下: int a = 1; int ...

  9. 将base64转成File文件对象

    function dataURLtoFile(dataurl, filename) { //将base64转换为文件        var arr = dataurl.split(','),      ...

  10. route -n 讲解

    我们经常会出现临时添加路由,或者是路由重启路由丢失等导致网络不通的情况,上网查发现很多介绍或者没有实验跟进导致理解的时候很费劲的情况,可能人家认为是比较简单的事情了,但是很多不尽然,老手也不一定能很快 ...